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 <time.h>
35 #include <ctype.h>
36
37 #include "scdaemon.h"
38
39 #include "../common/i18n.h"
40 #include "iso7816.h"
41 #include "../common/tlv.h"
42
43
44
45 /* Object with application (i.e. Geldkarte) specific data. */
46 struct app_local_s
47 {
48 char kblz[2+1+4+1];
49 const char *banktype;
50 char *cardno;
51 char expires[7+1];
52 char validfrom[10+1];
53 char *country;
54 char currency[3+1];
55 unsigned int currency_mult100;
56 unsigned char chipid;
57 unsigned char osvers;
58 int balance;
59 int maxamount;
60 int maxamount1;
61 };
62
63
64
65
66 /* Deconstructor. */
67 static void
do_deinit(app_t app)68 do_deinit (app_t app)
69 {
70 if (app && app->app_local)
71 {
72 xfree (app->app_local->cardno);
73 xfree (app->app_local->country);
74 xfree (app->app_local);
75 app->app_local = NULL;
76 }
77 }
78
79
80 static gpg_error_t
send_one_string(ctrl_t ctrl,const char * name,const char * string)81 send_one_string (ctrl_t ctrl, const char *name, const char *string)
82 {
83 if (!name || !string)
84 return 0;
85 send_status_info (ctrl, name, string, strlen (string), NULL, 0);
86 return 0;
87 }
88
89 /* Implement the GETATTR command. This is similar to the LEARN
90 command but returns just one value via the status interface. */
91 static gpg_error_t
do_getattr(app_t app,ctrl_t ctrl,const char * name)92 do_getattr (app_t app, ctrl_t ctrl, const char *name)
93 {
94 gpg_error_t err;
95 struct app_local_s *ld = app->app_local;
96 char numbuf[100];
97
98 if (!strcmp (name, "X-KBLZ"))
99 err = send_one_string (ctrl, name, ld->kblz);
100 else if (!strcmp (name, "X-BANKINFO"))
101 err = send_one_string (ctrl, name, ld->banktype);
102 else if (!strcmp (name, "X-CARDNO"))
103 err = send_one_string (ctrl, name, ld->cardno);
104 else if (!strcmp (name, "X-EXPIRES"))
105 err = send_one_string (ctrl, name, ld->expires);
106 else if (!strcmp (name, "X-VALIDFROM"))
107 err = send_one_string (ctrl, name, ld->validfrom);
108 else if (!strcmp (name, "X-COUNTRY"))
109 err = send_one_string (ctrl, name, ld->country);
110 else if (!strcmp (name, "X-CURRENCY"))
111 err = send_one_string (ctrl, name, ld->currency);
112 else if (!strcmp (name, "X-ZKACHIPID"))
113 {
114 snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid);
115 err = send_one_string (ctrl, name, numbuf);
116 }
117 else if (!strcmp (name, "X-OSVERSION"))
118 {
119 snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers);
120 err = send_one_string (ctrl, name, numbuf);
121 }
122 else if (!strcmp (name, "X-BALANCE"))
123 {
124 snprintf (numbuf, sizeof numbuf, "%.2f",
125 (double)ld->balance / 100 * ld->currency_mult100);
126 err = send_one_string (ctrl, name, numbuf);
127 }
128 else if (!strcmp (name, "X-MAXAMOUNT"))
129 {
130 snprintf (numbuf, sizeof numbuf, "%.2f",
131 (double)ld->maxamount / 100 * ld->currency_mult100);
132 err = send_one_string (ctrl, name, numbuf);
133 }
134 else if (!strcmp (name, "X-MAXAMOUNT1"))
135 {
136 snprintf (numbuf, sizeof numbuf, "%.2f",
137 (double)ld->maxamount1 / 100 * ld->currency_mult100);
138 err = send_one_string (ctrl, name, numbuf);
139 }
140 else
141 err = gpg_error (GPG_ERR_INV_NAME);
142
143 return err;
144 }
145
146
147 static gpg_error_t
do_learn_status(app_t app,ctrl_t ctrl,unsigned int flags)148 do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
149 {
150 static const char *names[] = {
151 "X-KBLZ",
152 "X-BANKINFO",
153 "X-CARDNO",
154 "X-EXPIRES",
155 "X-VALIDFROM",
156 "X-COUNTRY",
157 "X-CURRENCY",
158 "X-ZKACHIPID",
159 "X-OSVERSION",
160 "X-BALANCE",
161 "X-MAXAMOUNT",
162 "X-MAXAMOUNT1",
163 NULL
164 };
165 gpg_error_t err = 0;
166 int idx;
167
168 (void)flags;
169
170 for (idx=0; names[idx] && !err; idx++)
171 err = do_getattr (app, ctrl, names[idx]);
172 return err;
173 }
174
175
176 static char *
copy_bcd(const unsigned char * string,size_t length)177 copy_bcd (const unsigned char *string, size_t length)
178 {
179 const unsigned char *s;
180 size_t n;
181 size_t needed;
182 char *buffer, *dst;
183
184 if (!length)
185 return xtrystrdup ("");
186
187 /* Skip leading zeroes. */
188 for (; length && !*string; length--, string++)
189 ;
190 s = string;
191 n = length;
192 needed = 0;
193 for (; n ; n--, s++)
194 {
195 if (!needed && !(*s & 0xf0))
196 ; /* Skip the leading zero in the first nibble. */
197 else
198 {
199 if ( ((*s >> 4) & 0x0f) > 9 )
200 {
201 errno = EINVAL;
202 return NULL;
203 }
204 needed++;
205 }
206 if ( n == 1 && (*s & 0x0f) > 9 )
207 ; /* Ignore the last digit if it has the sign. */
208 else
209 {
210 needed++;
211 if ( (*s & 0x0f) > 9 )
212 {
213 errno = EINVAL;
214 return NULL;
215 }
216 }
217
218 }
219 if (!needed) /* If it is all zero, print a "0". */
220 needed++;
221
222 buffer = dst = xtrymalloc (needed+1);
223 if (!buffer)
224 return NULL;
225
226 s = string;
227 n = length;
228 needed = 0;
229 for (; n ; n--, s++)
230 {
231 if (!needed && !(*s & 0xf0))
232 ; /* Skip the leading zero in the first nibble. */
233 else
234 {
235 *dst++ = '0' + ((*s >> 4) & 0x0f);
236 needed++;
237 }
238
239 if ( n == 1 && (*s & 0x0f) > 9 )
240 ; /* Ignore the last digit if it has the sign. */
241 else
242 {
243 *dst++ = '0' + (*s & 0x0f);
244 needed++;
245 }
246 }
247 if (!needed)
248 *dst++ = '0';
249 *dst = 0;
250
251 return buffer;
252 }
253
254
255 /* Convert the BCD number at STRING of LENGTH into an integer and store
256 that at RESULT. Return 0 on success. */
257 static gpg_error_t
bcd_to_int(const unsigned char * string,size_t length,int * result)258 bcd_to_int (const unsigned char *string, size_t length, int *result)
259 {
260 char *tmp;
261
262 tmp = copy_bcd (string, length);
263 if (!tmp)
264 return gpg_error (GPG_ERR_BAD_DATA);
265 *result = strtol (tmp, NULL, 10);
266 xfree (tmp);
267 return 0;
268 }
269
270
271 /* Select the Geldkarte application. */
272 gpg_error_t
app_select_geldkarte(app_t app)273 app_select_geldkarte (app_t app)
274 {
275 static char const aid[] =
276 { 0xD2, 0x76, 0x00, 0x00, 0x25, 0x45, 0x50, 0x02, 0x00 };
277 gpg_error_t err;
278 int slot = app_get_slot (app);
279 unsigned char *result = NULL;
280 size_t resultlen;
281 struct app_local_s *ld;
282 const char *banktype;
283
284 err = iso7816_select_application (slot, aid, sizeof aid, 0);
285 if (err)
286 goto leave;
287
288 /* Read the first record of EF_ID (SFI=0x17). We require this
289 record to be at least 24 bytes with the first byte 0x67 and a
290 correct filler byte. */
291 err = iso7816_read_record (slot, 1, 1, ((0x17 << 3)|4), &result, &resultlen);
292 if (err)
293 goto leave; /* Oops - not a Geldkarte. */
294 if (resultlen < 24 || *result != 0x67 || result[22])
295 {
296 err = gpg_error (GPG_ERR_NOT_FOUND);
297 goto leave;
298 }
299
300 /* The short Bankleitzahl consists of 3 bytes at offset 1. */
301 switch (result[1])
302 {
303 case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break;
304 case 0x22: banktype = "Privat- oder Geschaeftsbank"; break;
305 case 0x25: banktype = "Sparkasse"; break;
306 case 0x26:
307 case 0x29: banktype = "Genossenschaftsbank"; break;
308 default:
309 err = gpg_error (GPG_ERR_NOT_FOUND);
310 goto leave; /* Probably not a Geldkarte. */
311 }
312
313 app->apptype = APPTYPE_GELDKARTE;
314 app->fnc.deinit = do_deinit;
315 app->fnc.prep_reselect = NULL;
316 app->fnc.reselect = NULL;
317
318 /* If we don't have a serialno yet construct it from the EF_ID. */
319 if (!app->card->serialno)
320 {
321 app->card->serialno = xtrymalloc (10);
322 if (!app->card->serialno)
323 {
324 err = gpg_error_from_syserror ();
325 goto leave;
326 }
327 memcpy (app->card->serialno, result, 10);
328 app->card->serialnolen = 10;
329 err = app_munge_serialno (app->card);
330 if (err)
331 goto leave;
332 }
333
334
335 app->app_local = ld = xtrycalloc (1, sizeof *app->app_local);
336 if (!app->app_local)
337 {
338 err = gpg_err_code_from_syserror ();
339 goto leave;
340 }
341
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);
346 if (!ld->cardno)
347 {
348 err = gpg_err_code_from_syserror ();
349 goto leave;
350 }
351
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]);
356
357 ld->country = copy_bcd (result+15, 2);
358 if (!ld->country)
359 {
360 err = gpg_err_code_from_syserror ();
361 goto leave;
362 }
363
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]:' ');
368
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);
375
376 ld->chipid = result[21];
377 ld->osvers = result[23];
378
379 /* Read the first record of EF_BETRAG (SFI=0x18). */
380 xfree (result);
381 err = iso7816_read_record (slot, 1, 1, ((0x18 << 3)|4), &result, &resultlen);
382 if (err)
383 goto leave; /* It does not make sense to continue. */
384 if (resultlen < 12)
385 {
386 err = gpg_error (GPG_ERR_NOT_FOUND);
387 goto leave;
388 }
389 err = bcd_to_int (result+0, 3, &ld->balance);
390 if (!err)
391 err = bcd_to_int (result+3, 3, &ld->maxamount);
392 if (!err)
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. */
396 if (err)
397 goto leave;
398
399 /* Setup the rest of the methods. */
400 app->fnc.learn_status = do_learn_status;
401 app->fnc.getattr = do_getattr;
402
403
404 leave:
405 xfree (result);
406 if (err)
407 do_deinit (app);
408 return err;
409 }
410