1 /* app-geldkarte.c - The German Geldkarte application
2 * Copyright (C) 2004 g10 Code GmbH
3 * Copyright (C) 2009 Free Software Foundation, Inc.
5 * This file is part of GnuPG.
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.
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.
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/>.
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
42 #include "app-common.h"
47 /* Object with application (i.e. Geldkarte) specific data. */
57 unsigned int currency_mult100;
72 if (app && app->app_local)
74 xfree (app->app_local->cardno);
75 xfree (app->app_local->country);
76 xfree (app->app_local);
77 app->app_local = NULL;
83 send_one_string (ctrl_t ctrl, const char *name, const char *string)
87 send_status_info (ctrl, name, string, strlen (string), NULL, 0);
91 /* Implement the GETATTR command. This is similar to the LEARN
92 command but returns just one value via the status interface. */
94 do_getattr (app_t app, ctrl_t ctrl, const char *name)
97 struct app_local_s *ld = app->app_local;
100 if (!strcmp (name, "X-KBLZ"))
101 err = send_one_string (ctrl, name, ld->kblz);
102 else if (!strcmp (name, "X-BANKINFO"))
103 err = send_one_string (ctrl, name, ld->banktype);
104 else if (!strcmp (name, "X-CARDNO"))
105 err = send_one_string (ctrl, name, ld->cardno);
106 else if (!strcmp (name, "X-EXPIRES"))
107 err = send_one_string (ctrl, name, ld->expires);
108 else if (!strcmp (name, "X-VALIDFROM"))
109 err = send_one_string (ctrl, name, ld->validfrom);
110 else if (!strcmp (name, "X-COUNTRY"))
111 err = send_one_string (ctrl, name, ld->country);
112 else if (!strcmp (name, "X-CURRENCY"))
113 err = send_one_string (ctrl, name, ld->currency);
114 else if (!strcmp (name, "X-ZKACHIPID"))
116 snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid);
117 err = send_one_string (ctrl, name, numbuf);
119 else if (!strcmp (name, "X-OSVERSION"))
121 snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers);
122 err = send_one_string (ctrl, name, numbuf);
124 else if (!strcmp (name, "X-BALANCE"))
126 snprintf (numbuf, sizeof numbuf, "%.2f",
127 (double)ld->balance / 100 * ld->currency_mult100);
128 err = send_one_string (ctrl, name, numbuf);
130 else if (!strcmp (name, "X-MAXAMOUNT"))
132 snprintf (numbuf, sizeof numbuf, "%.2f",
133 (double)ld->maxamount / 100 * ld->currency_mult100);
134 err = send_one_string (ctrl, name, numbuf);
136 else if (!strcmp (name, "X-MAXAMOUNT1"))
138 snprintf (numbuf, sizeof numbuf, "%.2f",
139 (double)ld->maxamount1 / 100 * ld->currency_mult100);
140 err = send_one_string (ctrl, name, numbuf);
143 err = gpg_error (GPG_ERR_INV_NAME);
150 do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
152 static const char *names[] = {
172 for (idx=0; names[idx] && !err; idx++)
173 err = do_getattr (app, ctrl, names[idx]);
179 copy_bcd (const unsigned char *string, size_t length)
181 const unsigned char *s;
187 return xtrystrdup ("");
189 /* Skip leading zeroes. */
190 for (; length && !*string; length--, string++)
197 if (!needed && !(*s & 0xf0))
198 ; /* Skip the leading zero in the first nibble. */
201 if ( ((*s >> 4) & 0x0f) > 9 )
208 if ( n == 1 && (*s & 0x0f) > 9 )
209 ; /* Ignore the last digit if it has the sign. */
213 if ( (*s & 0x0f) > 9 )
221 if (!needed) /* If it is all zero, print a "0". */
224 buffer = dst = xtrymalloc (needed+1);
233 if (!needed && !(*s & 0xf0))
234 ; /* Skip the leading zero in the first nibble. */
237 *dst++ = '0' + ((*s >> 4) & 0x0f);
241 if ( n == 1 && (*s & 0x0f) > 9 )
242 ; /* Ignore the last digit if it has the sign. */
245 *dst++ = '0' + (*s & 0x0f);
257 /* Convert the BCD number at STING of LENGTH into an integer and store
258 that at RESULT. Return 0 on success. */
260 bcd_to_int (const unsigned char *string, size_t length, int *result)
264 tmp = copy_bcd (string, length);
266 return gpg_error (GPG_ERR_BAD_DATA);
267 *result = strtol (tmp, NULL, 10);
273 /* Select the Geldkarte application. */
275 app_select_geldkarte (app_t app)
277 static char const aid[] =
278 { 0xD2, 0x76, 0x00, 0x00, 0x25, 0x45, 0x50, 0x02, 0x00 };
280 int slot = app->slot;
281 unsigned char *result = NULL;
283 struct app_local_s *ld;
284 const char *banktype;
286 err = iso7816_select_application (slot, aid, sizeof aid, 0);
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 err = iso7816_read_record (slot, 1, 1, ((0x17 << 3)|4), &result, &resultlen);
295 goto leave; /* Oops - not a Geldkarte. */
296 if (resultlen < 24 || *result != 0x67 || result[22])
298 err = gpg_error (GPG_ERR_NOT_FOUND);
302 /* The short Bankleitzahl consists of 3 bytes at offset 1. */
305 case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break;
306 case 0x22: banktype = "Privat- oder Geschaeftsbank"; break;
307 case 0x25: banktype = "Sparkasse"; break;
309 case 0x29: banktype = "Genossenschaftsbank"; break;
311 err = gpg_error (GPG_ERR_NOT_FOUND);
312 goto leave; /* Probably not a Geldkarte. */
315 app->apptype = "GELDKARTE";
316 app->fnc.deinit = do_deinit;
318 /* If we don't have a serialno yet construct it from the EF_ID. */
321 app->serialno = xtrymalloc (10);
324 err = gpg_error_from_syserror ();
327 memcpy (app->serialno, result, 10);
328 app->serialnolen = 10;
329 err = app_munge_serialno (app);
335 app->app_local = ld = xtrycalloc (1, sizeof *app->app_local);
338 err = gpg_err_code_from_syserror ();
342 snprintf (ld->kblz, sizeof ld->kblz, "%02X-%02X%02X",
343 result[1], result[2], result[3]);
344 ld->banktype = banktype;
345 ld->cardno = copy_bcd (result+4, 5);
348 err = gpg_err_code_from_syserror ();
352 snprintf (ld->expires, sizeof ld->expires, "20%02X-%02X",
353 result[10], result[11]);
354 snprintf (ld->validfrom, sizeof ld->validfrom, "20%02X-%02X-%02X",
355 result[12], result[13], result[14]);
357 ld->country = copy_bcd (result+15, 2);
360 err = gpg_err_code_from_syserror ();
364 snprintf (ld->currency, sizeof ld->currency, "%c%c%c",
365 isascii (result[17])? result[17]:' ',
366 isascii (result[18])? result[18]:' ',
367 isascii (result[19])? result[19]:' ');
369 ld->currency_mult100 = (result[20] == 0x01? 1:
370 result[20] == 0x02? 10:
371 result[20] == 0x04? 100:
372 result[20] == 0x08? 1000:
373 result[20] == 0x10? 10000:
374 result[20] == 0x20? 100000:0);
376 ld->chipid = result[21];
377 ld->osvers = result[23];
379 /* Read the first record of EF_BETRAG (SFI=0x18). */
381 err = iso7816_read_record (slot, 1, 1, ((0x18 << 3)|4), &result, &resultlen);
383 goto leave; /* It does not make sense to continue. */
386 err = gpg_error (GPG_ERR_NOT_FOUND);
389 err = bcd_to_int (result+0, 3, &ld->balance);
391 err = bcd_to_int (result+3, 3, &ld->maxamount);
393 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. */
399 /* Setup the rest of the methods. */
400 app->fnc.learn_status = do_learn_status;
401 app->fnc.getattr = do_getattr;