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