Line data Source code
1 : /* app-geldkarte.c - The German Geldkarte application
2 : * Copyright (C) 2004 g10 Code GmbH
3 : * Copyright (C) 2009 Free Software Foundation, Inc.
4 : *
5 : * This file is part of GnuPG.
6 : *
7 : * GnuPG is free software; you can redistribute it and/or modify
8 : * it under the terms of the GNU General Public License as published by
9 : * the Free Software Foundation; either version 3 of the License, or
10 : * (at your option) any later version.
11 : *
12 : * GnuPG is distributed in the hope that it will be useful,
13 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : * GNU General Public License for more details.
16 : *
17 : * You should have received a copy of the GNU General Public License
18 : * along with this program; if not, see <https://www.gnu.org/licenses/>.
19 : */
20 :
21 :
22 : /* This is a read-only application to quickly dump information of a
23 : German Geldkarte (debit card for small amounts). We only support
24 : newer Geldkarte (with the AID DF_BOERSE_NEU) issued since 2000 or
25 : even earlier.
26 : */
27 :
28 :
29 : #include <config.h>
30 : #include <errno.h>
31 : #include <stdio.h>
32 : #include <stdlib.h>
33 : #include <string.h>
34 : #include <assert.h>
35 : #include <time.h>
36 : #include <ctype.h>
37 :
38 : #include "scdaemon.h"
39 :
40 : #include "i18n.h"
41 : #include "iso7816.h"
42 : #include "app-common.h"
43 : #include "tlv.h"
44 :
45 :
46 :
47 : /* Object with application (i.e. Geldkarte) specific data. */
48 : struct app_local_s
49 : {
50 : char kblz[2+1+4+1];
51 : const char *banktype;
52 : char *cardno;
53 : char expires[7+1];
54 : char validfrom[10+1];
55 : char *country;
56 : char currency[3+1];
57 : unsigned int currency_mult100;
58 : unsigned char chipid;
59 : unsigned char osvers;
60 : int balance;
61 : int maxamount;
62 : int maxamount1;
63 : };
64 :
65 :
66 :
67 :
68 : /* Deconstructor. */
69 : static void
70 0 : do_deinit (app_t app)
71 : {
72 0 : if (app && app->app_local)
73 : {
74 0 : xfree (app->app_local->cardno);
75 0 : xfree (app->app_local->country);
76 0 : xfree (app->app_local);
77 0 : app->app_local = NULL;
78 : }
79 0 : }
80 :
81 :
82 : static gpg_error_t
83 0 : send_one_string (ctrl_t ctrl, const char *name, const char *string)
84 : {
85 0 : if (!name || !string)
86 0 : return 0;
87 0 : send_status_info (ctrl, name, string, strlen (string), NULL, 0);
88 0 : return 0;
89 : }
90 :
91 : /* Implement the GETATTR command. This is similar to the LEARN
92 : command but returns just one value via the status interface. */
93 : static gpg_error_t
94 0 : do_getattr (app_t app, ctrl_t ctrl, const char *name)
95 : {
96 : gpg_error_t err;
97 0 : struct app_local_s *ld = app->app_local;
98 : char numbuf[100];
99 :
100 0 : if (!strcmp (name, "X-KBLZ"))
101 0 : err = send_one_string (ctrl, name, ld->kblz);
102 0 : else if (!strcmp (name, "X-BANKINFO"))
103 0 : err = send_one_string (ctrl, name, ld->banktype);
104 0 : else if (!strcmp (name, "X-CARDNO"))
105 0 : err = send_one_string (ctrl, name, ld->cardno);
106 0 : else if (!strcmp (name, "X-EXPIRES"))
107 0 : err = send_one_string (ctrl, name, ld->expires);
108 0 : else if (!strcmp (name, "X-VALIDFROM"))
109 0 : err = send_one_string (ctrl, name, ld->validfrom);
110 0 : else if (!strcmp (name, "X-COUNTRY"))
111 0 : err = send_one_string (ctrl, name, ld->country);
112 0 : else if (!strcmp (name, "X-CURRENCY"))
113 0 : err = send_one_string (ctrl, name, ld->currency);
114 0 : else if (!strcmp (name, "X-ZKACHIPID"))
115 : {
116 0 : snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid);
117 0 : err = send_one_string (ctrl, name, numbuf);
118 : }
119 0 : else if (!strcmp (name, "X-OSVERSION"))
120 : {
121 0 : snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers);
122 0 : err = send_one_string (ctrl, name, numbuf);
123 : }
124 0 : else if (!strcmp (name, "X-BALANCE"))
125 : {
126 0 : snprintf (numbuf, sizeof numbuf, "%.2f",
127 0 : (double)ld->balance / 100 * ld->currency_mult100);
128 0 : err = send_one_string (ctrl, name, numbuf);
129 : }
130 0 : else if (!strcmp (name, "X-MAXAMOUNT"))
131 : {
132 0 : snprintf (numbuf, sizeof numbuf, "%.2f",
133 0 : (double)ld->maxamount / 100 * ld->currency_mult100);
134 0 : err = send_one_string (ctrl, name, numbuf);
135 : }
136 0 : else if (!strcmp (name, "X-MAXAMOUNT1"))
137 : {
138 0 : snprintf (numbuf, sizeof numbuf, "%.2f",
139 0 : (double)ld->maxamount1 / 100 * ld->currency_mult100);
140 0 : err = send_one_string (ctrl, name, numbuf);
141 : }
142 : else
143 0 : err = gpg_error (GPG_ERR_INV_NAME);
144 :
145 0 : return err;
146 : }
147 :
148 :
149 : static gpg_error_t
150 0 : do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
151 : {
152 : static const char *names[] = {
153 : "X-KBLZ",
154 : "X-BANKINFO",
155 : "X-CARDNO",
156 : "X-EXPIRES",
157 : "X-VALIDFROM",
158 : "X-COUNTRY",
159 : "X-CURRENCY",
160 : "X-ZKACHIPID",
161 : "X-OSVERSION",
162 : "X-BALANCE",
163 : "X-MAXAMOUNT",
164 : "X-MAXAMOUNT1",
165 : NULL
166 : };
167 0 : gpg_error_t err = 0;
168 : int idx;
169 :
170 : (void)flags;
171 :
172 0 : for (idx=0; names[idx] && !err; idx++)
173 0 : err = do_getattr (app, ctrl, names[idx]);
174 0 : return err;
175 : }
176 :
177 :
178 : static char *
179 0 : copy_bcd (const unsigned char *string, size_t length)
180 : {
181 : const unsigned char *s;
182 : size_t n;
183 : size_t needed;
184 : char *buffer, *dst;
185 :
186 0 : if (!length)
187 0 : return xtrystrdup ("");
188 :
189 : /* Skip leading zeroes. */
190 0 : for (; length && !*string; length--, string++)
191 : ;
192 0 : s = string;
193 0 : n = length;
194 0 : needed = 0;
195 0 : for (; n ; n--, s++)
196 : {
197 0 : if (!needed && !(*s & 0xf0))
198 : ; /* Skip the leading zero in the first nibble. */
199 : else
200 : {
201 0 : if ( ((*s >> 4) & 0x0f) > 9 )
202 : {
203 0 : errno = EINVAL;
204 0 : return NULL;
205 : }
206 0 : needed++;
207 : }
208 0 : if ( n == 1 && (*s & 0x0f) > 9 )
209 : ; /* Ignore the last digit if it has the sign. */
210 : else
211 : {
212 0 : needed++;
213 0 : if ( (*s & 0x0f) > 9 )
214 : {
215 0 : errno = EINVAL;
216 0 : return NULL;
217 : }
218 : }
219 :
220 : }
221 0 : if (!needed) /* If it is all zero, print a "0". */
222 0 : needed++;
223 :
224 0 : buffer = dst = xtrymalloc (needed+1);
225 0 : if (!buffer)
226 0 : return NULL;
227 :
228 0 : s = string;
229 0 : n = length;
230 0 : needed = 0;
231 0 : for (; n ; n--, s++)
232 : {
233 0 : if (!needed && !(*s & 0xf0))
234 : ; /* Skip the leading zero in the first nibble. */
235 : else
236 : {
237 0 : *dst++ = '0' + ((*s >> 4) & 0x0f);
238 0 : needed++;
239 : }
240 :
241 0 : if ( n == 1 && (*s & 0x0f) > 9 )
242 : ; /* Ignore the last digit if it has the sign. */
243 : else
244 : {
245 0 : *dst++ = '0' + (*s & 0x0f);
246 0 : needed++;
247 : }
248 : }
249 0 : if (!needed)
250 0 : *dst++ = '0';
251 0 : *dst = 0;
252 :
253 0 : return buffer;
254 : }
255 :
256 :
257 : /* Convert the BCD number at STING of LENGTH into an integer and store
258 : that at RESULT. Return 0 on success. */
259 : static gpg_error_t
260 0 : bcd_to_int (const unsigned char *string, size_t length, int *result)
261 : {
262 : char *tmp;
263 :
264 0 : tmp = copy_bcd (string, length);
265 0 : if (!tmp)
266 0 : return gpg_error (GPG_ERR_BAD_DATA);
267 0 : *result = strtol (tmp, NULL, 10);
268 0 : xfree (tmp);
269 0 : return 0;
270 : }
271 :
272 :
273 : /* Select the Geldkarte application. */
274 : gpg_error_t
275 0 : app_select_geldkarte (app_t app)
276 : {
277 : static char const aid[] =
278 : { 0xD2, 0x76, 0x00, 0x00, 0x25, 0x45, 0x50, 0x02, 0x00 };
279 : gpg_error_t err;
280 0 : int slot = app->slot;
281 0 : unsigned char *result = NULL;
282 : size_t resultlen;
283 : struct app_local_s *ld;
284 : const char *banktype;
285 :
286 0 : err = iso7816_select_application (slot, aid, sizeof aid, 0);
287 0 : if (err)
288 0 : goto leave;
289 :
290 : /* Read the first record of EF_ID (SFI=0x17). We require this
291 : record to be at least 24 bytes with the the first byte 0x67 and a
292 : correct filler byte. */
293 0 : err = iso7816_read_record (slot, 1, 1, ((0x17 << 3)|4), &result, &resultlen);
294 0 : if (err)
295 0 : goto leave; /* Oops - not a Geldkarte. */
296 0 : if (resultlen < 24 || *result != 0x67 || result[22])
297 : {
298 0 : err = gpg_error (GPG_ERR_NOT_FOUND);
299 0 : goto leave;
300 : }
301 :
302 : /* The short Bankleitzahl consists of 3 bytes at offset 1. */
303 0 : switch (result[1])
304 : {
305 0 : case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break;
306 0 : case 0x22: banktype = "Privat- oder Geschaeftsbank"; break;
307 0 : case 0x25: banktype = "Sparkasse"; break;
308 : case 0x26:
309 0 : case 0x29: banktype = "Genossenschaftsbank"; break;
310 : default:
311 0 : err = gpg_error (GPG_ERR_NOT_FOUND);
312 0 : goto leave; /* Probably not a Geldkarte. */
313 : }
314 :
315 0 : app->apptype = "GELDKARTE";
316 0 : app->fnc.deinit = do_deinit;
317 :
318 : /* If we don't have a serialno yet construct it from the EF_ID. */
319 0 : if (!app->serialno)
320 : {
321 0 : app->serialno = xtrymalloc (10);
322 0 : if (!app->serialno)
323 : {
324 0 : err = gpg_error_from_syserror ();
325 0 : goto leave;
326 : }
327 0 : memcpy (app->serialno, result, 10);
328 0 : app->serialnolen = 10;
329 0 : err = app_munge_serialno (app);
330 0 : if (err)
331 0 : goto leave;
332 : }
333 :
334 :
335 0 : app->app_local = ld = xtrycalloc (1, sizeof *app->app_local);
336 0 : if (!app->app_local)
337 : {
338 0 : err = gpg_err_code_from_syserror ();
339 0 : goto leave;
340 : }
341 :
342 0 : snprintf (ld->kblz, sizeof ld->kblz, "%02X-%02X%02X",
343 0 : result[1], result[2], result[3]);
344 0 : ld->banktype = banktype;
345 0 : ld->cardno = copy_bcd (result+4, 5);
346 0 : if (!ld->cardno)
347 : {
348 0 : err = gpg_err_code_from_syserror ();
349 0 : goto leave;
350 : }
351 :
352 0 : snprintf (ld->expires, sizeof ld->expires, "20%02X-%02X",
353 0 : result[10], result[11]);
354 0 : snprintf (ld->validfrom, sizeof ld->validfrom, "20%02X-%02X-%02X",
355 0 : result[12], result[13], result[14]);
356 :
357 0 : ld->country = copy_bcd (result+15, 2);
358 0 : if (!ld->country)
359 : {
360 0 : err = gpg_err_code_from_syserror ();
361 0 : goto leave;
362 : }
363 :
364 0 : snprintf (ld->currency, sizeof ld->currency, "%c%c%c",
365 0 : isascii (result[17])? result[17]:' ',
366 0 : isascii (result[18])? result[18]:' ',
367 0 : isascii (result[19])? result[19]:' ');
368 :
369 0 : ld->currency_mult100 = (result[20] == 0x01? 1:
370 0 : result[20] == 0x02? 10:
371 0 : result[20] == 0x04? 100:
372 0 : result[20] == 0x08? 1000:
373 0 : result[20] == 0x10? 10000:
374 0 : result[20] == 0x20? 100000:0);
375 :
376 0 : ld->chipid = result[21];
377 0 : ld->osvers = result[23];
378 :
379 : /* Read the first record of EF_BETRAG (SFI=0x18). */
380 0 : xfree (result);
381 0 : err = iso7816_read_record (slot, 1, 1, ((0x18 << 3)|4), &result, &resultlen);
382 0 : if (err)
383 0 : goto leave; /* It does not make sense to continue. */
384 0 : if (resultlen < 12)
385 : {
386 0 : err = gpg_error (GPG_ERR_NOT_FOUND);
387 0 : goto leave;
388 : }
389 0 : err = bcd_to_int (result+0, 3, &ld->balance);
390 0 : if (!err)
391 0 : err = bcd_to_int (result+3, 3, &ld->maxamount);
392 0 : if (!err)
393 0 : err = bcd_to_int (result+6, 3, &ld->maxamount1);
394 : /* The next 3 bytes are the maximum amount chargable without using a
395 : MAC. This is usually 0. */
396 0 : if (err)
397 0 : goto leave;
398 :
399 : /* Setup the rest of the methods. */
400 0 : app->fnc.learn_status = do_learn_status;
401 0 : app->fnc.getattr = do_getattr;
402 :
403 :
404 : leave:
405 0 : xfree (result);
406 0 : if (err)
407 0 : do_deinit (app);
408 0 : return err;
409 : }
|