Add a Geldkarte gadget application.
[gnupg.git] / scd / app-geldkarte.c
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 <http://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).
24
25    Because this application does no use an AID it is best to test for
26    it after the test for other applications.
27 */
28
29
30 #include <config.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <assert.h>
36 #include <time.h>
37 #include <ctype.h>
38
39 #include "scdaemon.h"
40
41 #include "i18n.h"
42 #include "iso7816.h"
43 #include "app-common.h"
44 #include "tlv.h"
45
46
47
48 /* Object with application (i.e. Geldkarte) specific data.  */
49 struct app_local_s
50 {
51   char kblz[2+1+4+1];
52   const char *banktype;
53   char *cardno;
54   char expires[7+1];
55   char validfrom[10+1];
56   char *country;
57   char currency[3+1];
58   unsigned int currency_mult100;
59   unsigned char chipid;
60   unsigned char osvers;
61 };
62
63
64
65 \f
66 /* Deconstructor. */
67 static void
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
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 
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-CRNCMULT"))
113     {
114       snprintf (numbuf, sizeof numbuf, "%u", ld->currency_mult100);
115       err = send_one_string (ctrl, name, numbuf);
116     }
117   else if (!strcmp (name, "X-ZKACHIPID"))
118     {
119       snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid);
120       err = send_one_string (ctrl, name, numbuf);
121     }
122   else if (!strcmp (name, "X-OSVERSION"))
123     {
124       snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers);
125       err = send_one_string (ctrl, name, numbuf);
126     }
127   else
128     err = gpg_error (GPG_ERR_INV_NAME); 
129
130   return err;
131 }
132
133
134 static gpg_error_t
135 do_learn_status (app_t app, ctrl_t ctrl)
136 {
137   static const char *names[] = {
138     "X-KBLZ",
139     "X-BANKINFO",
140     "X-CARDNO",
141     "X-EXPIRES",
142     "X-VALIDFROM",
143     "X-COUNTRY",
144     "X-CURRENCY",
145     "X-CRNCMULT",
146     "X-ZKACHIPID",
147     "X-OSVERSION",
148     NULL
149   };
150   gpg_error_t err = 0;
151   int idx;
152
153   for (idx=0; names[idx] && !err; idx++)
154     err = do_getattr (app, ctrl, names[idx]);
155   return err;
156 }
157
158
159 static char *
160 copy_bcd (const unsigned char *string, size_t length)
161 {
162   const unsigned char *s;
163   size_t n;
164   size_t needed;
165   char *buffer, *dst;
166
167   if (!length)
168     return xtrystrdup ("");
169       
170   /* Skip leading zeroes. */
171   for (; length && !*string; length--, string++)
172     ;
173   s = string;
174   n = length;
175   needed = 0;
176   for (; n ; n--, s++)
177     {
178       if (!needed && !(*s & 0xf0))
179         ; /* Skip the leading zero in the first nibble.  */
180       else 
181         {
182           if ( ((*s >> 4) & 0x0f) > 9 )
183             {
184               errno = EINVAL;
185               return NULL;
186             }
187           needed++;
188         }
189       if ( n == 1 && (*s & 0x0f) > 9 )
190         ; /* Ignore the last digit if it has the sign.  */
191       else
192         {
193           needed++;
194           if ( (*s & 0x0f) > 9 )
195             {
196               errno = EINVAL;
197               return NULL;
198             }
199         }
200       
201     }
202   if (!needed) /* If it is all zero, print a "0". */
203     needed++;
204
205   buffer = dst = xtrymalloc (needed+1);
206   if (!buffer)
207     return NULL;
208
209   s = string;
210   n = length;
211   needed = 0;  
212   for (; n ; n--, s++)
213     {
214       if (!needed && !(*s & 0xf0))
215         ; /* Skip the leading zero in the first nibble.  */
216       else 
217         {
218           *dst++ = '0' + ((*s >> 4) & 0x0f);
219           needed++;
220         }
221
222       if ( n == 1 && (*s & 0x0f) > 9 )
223         ; /* Ignore the last digit if it has the sign.  */
224       else
225         {
226           *dst++ = '0' + (*s & 0x0f);
227           needed++;
228         }
229     }
230   if (!needed)
231     *dst++ = '0';  
232   *dst = 0;
233
234   return buffer;
235 }
236
237
238
239
240 /* Select the Geldkarte application.  */
241 gpg_error_t
242 app_select_geldkarte (app_t app)
243 {
244   gpg_error_t err;
245   int slot = app->slot;
246   unsigned char *result = NULL;
247   size_t resultlen;
248   struct app_local_s *ld;
249   const char *banktype;
250   
251   err = iso7816_select_file (slot, 0x3f00, 1, NULL, NULL);
252   if (err)
253     goto leave;  /* Oops.  */
254
255   /* Read short EF 0xbc.  We require this record to be at least 24
256      bytes with the the first byte 0x67 and a correct the filler
257      byte. */
258   err = iso7816_read_record (slot, 1, 1, 0xbc, &result, &resultlen);
259   if (err)
260     goto leave;  /* No such record or other error - not a Geldkarte.  */
261   if (resultlen < 24 || *result != 0x67 || result[22])
262     {
263       err = gpg_error (GPG_ERR_NOT_FOUND);
264       goto leave;
265     }
266   
267   /* The short Bankleitzahl consists of 3 bytes at offset 1.  */
268   switch (result[1])
269     {
270     case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break;
271     case 0x22: banktype = "Privat- oder Geschaeftsbank"; break;
272     case 0x25: banktype = "Sparkasse"; break;
273     case 0x26:
274     case 0x29: banktype = "Genossenschaftsbank"; break;
275     default: 
276       err = gpg_error (GPG_ERR_NOT_FOUND);
277       goto leave; /* Probably not a Geldkarte. */
278     }
279   
280   app->apptype = "GELDKARTE";
281   app->fnc.deinit = do_deinit;
282
283   app->app_local = ld = xtrycalloc (1, sizeof *app->app_local);
284   if (!app->app_local)
285     {
286       err = gpg_err_code_from_syserror ();
287       goto leave;
288     }
289
290   snprintf (ld->kblz, sizeof ld->kblz, "%02X-%02X%02X", 
291             result[1], result[2], result[3]);
292   ld->banktype = banktype;
293   ld->cardno = copy_bcd (result+4, 5);
294   if (!ld->cardno)
295     {
296       err = gpg_err_code_from_syserror ();
297       goto leave;
298     }
299   
300   snprintf (ld->expires, sizeof ld->expires, "20%02X-%02X", 
301             result[10], result[11]);
302   snprintf (ld->validfrom, sizeof ld->validfrom, "20%02X-%02X-%02X",
303             result[12], result[13], result[14]);
304
305   ld->country = copy_bcd (result+15, 2);
306   if (!ld->country)
307     {
308       err = gpg_err_code_from_syserror ();
309       goto leave;
310     }
311
312   snprintf (ld->currency, sizeof ld->currency, "%c%c%c",
313             isascii (result[17])? result[17]:' ',
314             isascii (result[18])? result[18]:' ',
315             isascii (result[19])? result[19]:' ');
316
317   ld->currency_mult100 = (result[20] == 0x01? 1:
318                           result[20] == 0x02? 10:
319                           result[20] == 0x04? 100:
320                           result[20] == 0x08? 1000:
321                           result[20] == 0x10? 10000:
322                           result[20] == 0x20? 100000:0);
323
324   ld->chipid = result[21];
325   ld->osvers = result[23];
326
327   /* Setup the rest of the methods.  */
328   app->fnc.learn_status = do_learn_status;
329   app->fnc.getattr = do_getattr;
330
331
332  leave:
333   xfree (result);
334   if (err)
335     do_deinit (app);
336   return err;
337 }