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 <http://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 unsigned 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
;