2009-01-27 12:30:02 +01:00
|
|
|
|
/* app-geldkarte.c - The German Geldkarte application
|
|
|
|
|
* Copyright (C) 2004 g10 Code GmbH
|
|
|
|
|
* Copyright (C) 2009 Free Software Foundation, Inc.
|
|
|
|
|
*
|
|
|
|
|
* This file is part of GnuPG.
|
|
|
|
|
*
|
|
|
|
|
* GnuPG is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* GnuPG is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2016-11-05 12:02:19 +01:00
|
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2009-01-27 12:30:02 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* This is a read-only application to quickly dump information of a
|
2009-01-27 17:38:33 +01:00
|
|
|
|
German Geldkarte (debit card for small amounts). We only support
|
|
|
|
|
newer Geldkarte (with the AID DF_BOERSE_NEU) issued since 2000 or
|
|
|
|
|
even earlier.
|
2009-01-27 12:30:02 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
|
|
#include "scdaemon.h"
|
|
|
|
|
|
2017-03-07 12:21:23 +01:00
|
|
|
|
#include "../common/i18n.h"
|
2009-01-27 12:30:02 +01:00
|
|
|
|
#include "iso7816.h"
|
|
|
|
|
#include "app-common.h"
|
2017-03-07 12:21:23 +01:00
|
|
|
|
#include "../common/tlv.h"
|
2009-01-27 12:30:02 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Object with application (i.e. Geldkarte) specific data. */
|
|
|
|
|
struct app_local_s
|
|
|
|
|
{
|
|
|
|
|
char kblz[2+1+4+1];
|
|
|
|
|
const char *banktype;
|
|
|
|
|
char *cardno;
|
|
|
|
|
char expires[7+1];
|
|
|
|
|
char validfrom[10+1];
|
|
|
|
|
char *country;
|
|
|
|
|
char currency[3+1];
|
|
|
|
|
unsigned int currency_mult100;
|
|
|
|
|
unsigned char chipid;
|
|
|
|
|
unsigned char osvers;
|
2009-01-27 17:38:33 +01:00
|
|
|
|
int balance;
|
|
|
|
|
int maxamount;
|
|
|
|
|
int maxamount1;
|
2009-01-27 12:30:02 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Deconstructor. */
|
|
|
|
|
static void
|
|
|
|
|
do_deinit (app_t app)
|
|
|
|
|
{
|
|
|
|
|
if (app && app->app_local)
|
|
|
|
|
{
|
|
|
|
|
xfree (app->app_local->cardno);
|
|
|
|
|
xfree (app->app_local->country);
|
|
|
|
|
xfree (app->app_local);
|
|
|
|
|
app->app_local = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
send_one_string (ctrl_t ctrl, const char *name, const char *string)
|
|
|
|
|
{
|
|
|
|
|
if (!name || !string)
|
|
|
|
|
return 0;
|
|
|
|
|
send_status_info (ctrl, name, string, strlen (string), NULL, 0);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement the GETATTR command. This is similar to the LEARN
|
|
|
|
|
command but returns just one value via the status interface. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static gpg_error_t
|
2009-01-27 12:30:02 +01:00
|
|
|
|
do_getattr (app_t app, ctrl_t ctrl, const char *name)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
struct app_local_s *ld = app->app_local;
|
|
|
|
|
char numbuf[100];
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
if (!strcmp (name, "X-KBLZ"))
|
|
|
|
|
err = send_one_string (ctrl, name, ld->kblz);
|
|
|
|
|
else if (!strcmp (name, "X-BANKINFO"))
|
|
|
|
|
err = send_one_string (ctrl, name, ld->banktype);
|
|
|
|
|
else if (!strcmp (name, "X-CARDNO"))
|
|
|
|
|
err = send_one_string (ctrl, name, ld->cardno);
|
|
|
|
|
else if (!strcmp (name, "X-EXPIRES"))
|
|
|
|
|
err = send_one_string (ctrl, name, ld->expires);
|
|
|
|
|
else if (!strcmp (name, "X-VALIDFROM"))
|
|
|
|
|
err = send_one_string (ctrl, name, ld->validfrom);
|
|
|
|
|
else if (!strcmp (name, "X-COUNTRY"))
|
|
|
|
|
err = send_one_string (ctrl, name, ld->country);
|
|
|
|
|
else if (!strcmp (name, "X-CURRENCY"))
|
|
|
|
|
err = send_one_string (ctrl, name, ld->currency);
|
|
|
|
|
else if (!strcmp (name, "X-ZKACHIPID"))
|
|
|
|
|
{
|
|
|
|
|
snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid);
|
|
|
|
|
err = send_one_string (ctrl, name, numbuf);
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (name, "X-OSVERSION"))
|
|
|
|
|
{
|
|
|
|
|
snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers);
|
|
|
|
|
err = send_one_string (ctrl, name, numbuf);
|
|
|
|
|
}
|
2009-01-27 17:38:33 +01:00
|
|
|
|
else if (!strcmp (name, "X-BALANCE"))
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
snprintf (numbuf, sizeof numbuf, "%.2f",
|
2009-01-27 17:38:33 +01:00
|
|
|
|
(double)ld->balance / 100 * ld->currency_mult100);
|
|
|
|
|
err = send_one_string (ctrl, name, numbuf);
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (name, "X-MAXAMOUNT"))
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
snprintf (numbuf, sizeof numbuf, "%.2f",
|
2009-01-27 17:38:33 +01:00
|
|
|
|
(double)ld->maxamount / 100 * ld->currency_mult100);
|
|
|
|
|
err = send_one_string (ctrl, name, numbuf);
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (name, "X-MAXAMOUNT1"))
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
snprintf (numbuf, sizeof numbuf, "%.2f",
|
2009-01-27 17:38:33 +01:00
|
|
|
|
(double)ld->maxamount1 / 100 * ld->currency_mult100);
|
|
|
|
|
err = send_one_string (ctrl, name, numbuf);
|
|
|
|
|
}
|
2009-01-27 12:30:02 +01:00
|
|
|
|
else
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = gpg_error (GPG_ERR_INV_NAME);
|
2009-01-27 12:30:02 +01:00
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static gpg_error_t
|
2009-03-18 12:18:56 +01:00
|
|
|
|
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
|
2009-01-27 12:30:02 +01:00
|
|
|
|
{
|
|
|
|
|
static const char *names[] = {
|
|
|
|
|
"X-KBLZ",
|
|
|
|
|
"X-BANKINFO",
|
|
|
|
|
"X-CARDNO",
|
|
|
|
|
"X-EXPIRES",
|
|
|
|
|
"X-VALIDFROM",
|
|
|
|
|
"X-COUNTRY",
|
|
|
|
|
"X-CURRENCY",
|
|
|
|
|
"X-ZKACHIPID",
|
|
|
|
|
"X-OSVERSION",
|
2009-01-27 17:38:33 +01:00
|
|
|
|
"X-BALANCE",
|
|
|
|
|
"X-MAXAMOUNT",
|
|
|
|
|
"X-MAXAMOUNT1",
|
2009-01-27 12:30:02 +01:00
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
int idx;
|
|
|
|
|
|
2009-03-18 12:18:56 +01:00
|
|
|
|
(void)flags;
|
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
for (idx=0; names[idx] && !err; idx++)
|
|
|
|
|
err = do_getattr (app, ctrl, names[idx]);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char *
|
|
|
|
|
copy_bcd (const unsigned char *string, size_t length)
|
|
|
|
|
{
|
|
|
|
|
const unsigned char *s;
|
|
|
|
|
size_t n;
|
|
|
|
|
size_t needed;
|
|
|
|
|
char *buffer, *dst;
|
|
|
|
|
|
|
|
|
|
if (!length)
|
|
|
|
|
return xtrystrdup ("");
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
/* Skip leading zeroes. */
|
|
|
|
|
for (; length && !*string; length--, string++)
|
|
|
|
|
;
|
|
|
|
|
s = string;
|
|
|
|
|
n = length;
|
|
|
|
|
needed = 0;
|
|
|
|
|
for (; n ; n--, s++)
|
|
|
|
|
{
|
|
|
|
|
if (!needed && !(*s & 0xf0))
|
|
|
|
|
; /* Skip the leading zero in the first nibble. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
else
|
2009-01-27 12:30:02 +01:00
|
|
|
|
{
|
|
|
|
|
if ( ((*s >> 4) & 0x0f) > 9 )
|
|
|
|
|
{
|
|
|
|
|
errno = EINVAL;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
needed++;
|
|
|
|
|
}
|
|
|
|
|
if ( n == 1 && (*s & 0x0f) > 9 )
|
|
|
|
|
; /* Ignore the last digit if it has the sign. */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
needed++;
|
|
|
|
|
if ( (*s & 0x0f) > 9 )
|
|
|
|
|
{
|
|
|
|
|
errno = EINVAL;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
}
|
|
|
|
|
if (!needed) /* If it is all zero, print a "0". */
|
|
|
|
|
needed++;
|
|
|
|
|
|
|
|
|
|
buffer = dst = xtrymalloc (needed+1);
|
|
|
|
|
if (!buffer)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
s = string;
|
|
|
|
|
n = length;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
needed = 0;
|
2009-01-27 12:30:02 +01:00
|
|
|
|
for (; n ; n--, s++)
|
|
|
|
|
{
|
|
|
|
|
if (!needed && !(*s & 0xf0))
|
|
|
|
|
; /* Skip the leading zero in the first nibble. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
else
|
2009-01-27 12:30:02 +01:00
|
|
|
|
{
|
|
|
|
|
*dst++ = '0' + ((*s >> 4) & 0x0f);
|
|
|
|
|
needed++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( n == 1 && (*s & 0x0f) > 9 )
|
|
|
|
|
; /* Ignore the last digit if it has the sign. */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*dst++ = '0' + (*s & 0x0f);
|
|
|
|
|
needed++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!needed)
|
2011-02-04 12:57:53 +01:00
|
|
|
|
*dst++ = '0';
|
2009-01-27 12:30:02 +01:00
|
|
|
|
*dst = 0;
|
|
|
|
|
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-01-27 17:38:33 +01:00
|
|
|
|
/* Convert the BCD number at STING of LENGTH into an integer and store
|
|
|
|
|
that at RESULT. Return 0 on success. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
bcd_to_int (const unsigned char *string, size_t length, int *result)
|
|
|
|
|
{
|
|
|
|
|
char *tmp;
|
|
|
|
|
|
|
|
|
|
tmp = copy_bcd (string, length);
|
|
|
|
|
if (!tmp)
|
|
|
|
|
return gpg_error (GPG_ERR_BAD_DATA);
|
|
|
|
|
*result = strtol (tmp, NULL, 10);
|
|
|
|
|
xfree (tmp);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2009-01-27 12:30:02 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Select the Geldkarte application. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
app_select_geldkarte (app_t app)
|
|
|
|
|
{
|
2009-09-03 12:57:23 +02:00
|
|
|
|
static char const aid[] =
|
2009-01-27 17:38:33 +01:00
|
|
|
|
{ 0xD2, 0x76, 0x00, 0x00, 0x25, 0x45, 0x50, 0x02, 0x00 };
|
2009-01-27 12:30:02 +01:00
|
|
|
|
gpg_error_t err;
|
|
|
|
|
int slot = app->slot;
|
|
|
|
|
unsigned char *result = NULL;
|
|
|
|
|
size_t resultlen;
|
|
|
|
|
struct app_local_s *ld;
|
|
|
|
|
const char *banktype;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-01-27 17:38:33 +01:00
|
|
|
|
err = iso7816_select_application (slot, aid, sizeof aid, 0);
|
2009-01-27 12:30:02 +01:00
|
|
|
|
if (err)
|
2011-02-04 12:57:53 +01:00
|
|
|
|
goto leave;
|
|
|
|
|
|
2009-01-27 17:38:33 +01:00
|
|
|
|
/* Read the first record of EF_ID (SFI=0x17). We require this
|
2017-02-20 22:19:50 +01:00
|
|
|
|
record to be at least 24 bytes with the first byte 0x67 and a
|
2009-01-27 17:38:33 +01:00
|
|
|
|
correct filler byte. */
|
|
|
|
|
err = iso7816_read_record (slot, 1, 1, ((0x17 << 3)|4), &result, &resultlen);
|
2009-01-27 12:30:02 +01:00
|
|
|
|
if (err)
|
2009-01-27 17:38:33 +01:00
|
|
|
|
goto leave; /* Oops - not a Geldkarte. */
|
2009-01-27 12:30:02 +01:00
|
|
|
|
if (resultlen < 24 || *result != 0x67 || result[22])
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
/* The short Bankleitzahl consists of 3 bytes at offset 1. */
|
|
|
|
|
switch (result[1])
|
|
|
|
|
{
|
|
|
|
|
case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break;
|
|
|
|
|
case 0x22: banktype = "Privat- oder Geschaeftsbank"; break;
|
|
|
|
|
case 0x25: banktype = "Sparkasse"; break;
|
|
|
|
|
case 0x26:
|
|
|
|
|
case 0x29: banktype = "Genossenschaftsbank"; break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
default:
|
2009-01-27 12:30:02 +01:00
|
|
|
|
err = gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
goto leave; /* Probably not a Geldkarte. */
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
app->apptype = "GELDKARTE";
|
|
|
|
|
app->fnc.deinit = do_deinit;
|
|
|
|
|
|
2009-01-27 17:38:33 +01:00
|
|
|
|
/* If we don't have a serialno yet construct it from the EF_ID. */
|
|
|
|
|
if (!app->serialno)
|
|
|
|
|
{
|
|
|
|
|
app->serialno = xtrymalloc (10);
|
|
|
|
|
if (!app->serialno)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
memcpy (app->serialno, result, 10);
|
|
|
|
|
app->serialnolen = 10;
|
|
|
|
|
err = app_munge_serialno (app);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
app->app_local = ld = xtrycalloc (1, sizeof *app->app_local);
|
|
|
|
|
if (!app->app_local)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_err_code_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
snprintf (ld->kblz, sizeof ld->kblz, "%02X-%02X%02X",
|
2009-01-27 12:30:02 +01:00
|
|
|
|
result[1], result[2], result[3]);
|
|
|
|
|
ld->banktype = banktype;
|
|
|
|
|
ld->cardno = copy_bcd (result+4, 5);
|
|
|
|
|
if (!ld->cardno)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_err_code_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
|
|
|
|
snprintf (ld->expires, sizeof ld->expires, "20%02X-%02X",
|
2009-01-27 12:30:02 +01:00
|
|
|
|
result[10], result[11]);
|
|
|
|
|
snprintf (ld->validfrom, sizeof ld->validfrom, "20%02X-%02X-%02X",
|
|
|
|
|
result[12], result[13], result[14]);
|
|
|
|
|
|
|
|
|
|
ld->country = copy_bcd (result+15, 2);
|
|
|
|
|
if (!ld->country)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_err_code_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snprintf (ld->currency, sizeof ld->currency, "%c%c%c",
|
|
|
|
|
isascii (result[17])? result[17]:' ',
|
|
|
|
|
isascii (result[18])? result[18]:' ',
|
|
|
|
|
isascii (result[19])? result[19]:' ');
|
|
|
|
|
|
|
|
|
|
ld->currency_mult100 = (result[20] == 0x01? 1:
|
|
|
|
|
result[20] == 0x02? 10:
|
|
|
|
|
result[20] == 0x04? 100:
|
|
|
|
|
result[20] == 0x08? 1000:
|
|
|
|
|
result[20] == 0x10? 10000:
|
|
|
|
|
result[20] == 0x20? 100000:0);
|
|
|
|
|
|
|
|
|
|
ld->chipid = result[21];
|
|
|
|
|
ld->osvers = result[23];
|
|
|
|
|
|
2009-01-27 17:38:33 +01:00
|
|
|
|
/* Read the first record of EF_BETRAG (SFI=0x18). */
|
|
|
|
|
xfree (result);
|
|
|
|
|
err = iso7816_read_record (slot, 1, 1, ((0x18 << 3)|4), &result, &resultlen);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave; /* It does not make sense to continue. */
|
|
|
|
|
if (resultlen < 12)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = bcd_to_int (result+0, 3, &ld->balance);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = bcd_to_int (result+3, 3, &ld->maxamount);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = bcd_to_int (result+6, 3, &ld->maxamount1);
|
|
|
|
|
/* The next 3 bytes are the maximum amount chargable without using a
|
|
|
|
|
MAC. This is usually 0. */
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2009-01-27 12:30:02 +01:00
|
|
|
|
/* Setup the rest of the methods. */
|
|
|
|
|
app->fnc.learn_status = do_learn_status;
|
|
|
|
|
app->fnc.getattr = do_getattr;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (result);
|
|
|
|
|
if (err)
|
|
|
|
|
do_deinit (app);
|
|
|
|
|
return err;
|
|
|
|
|
}
|