gnupg/scd/sc-investigate.c

813 lines
22 KiB
C
Raw Blame History

/* sc-investigate.c - A tool to look around on smartcards.
* Copyright (C) 2003 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 2 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
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#ifdef USE_GNU_PTH
# include <pth.h>
#endif
#ifdef HAVE_READLINE_READLINE_H
#include <readline/readline.h>
#include <readline/history.h>
#endif
#define JNLIB_NEED_LOG_LOGV
#include "scdaemon.h"
#include <gcrypt.h>
#include "apdu.h" /* for open_reader */
#include "atr.h"
#include "app-common.h"
#include "iso7816.h"
#define _(a) (a)
#define CONTROL_D ('D' - 'A' + 1)
enum cmd_and_opt_values
{
oInteractive = 'i',
oVerbose = 'v',
oQuiet = 'q',
oReaderPort = 500,
octapiDriver,
oDebug,
oDebugAll,
oDisableCCID,
oGenRandom,
aTest };
static ARGPARSE_OPTS opts[] = {
{ 301, NULL, 0, "@Options:\n " },
{ oInteractive, "interactive", 0, "start in interactive explorer mode"},
{ oQuiet, "quiet", 0, "quiet" },
{ oVerbose, "verbose", 0, "verbose" },
{ oReaderPort, "reader-port", 2, "|N|connect to reader at port N"},
{ octapiDriver, "ctapi-driver", 2, "|NAME|use NAME as ctAPI driver"},
{ oDisableCCID, "disable-ccid", 0,
#ifdef HAVE_LIBUSB
"do not use the internal CCID driver"
#else
"@"
#endif
},
{ oDebug, "debug" ,4|16, "set debugging flags"},
{ oDebugAll, "debug-all" ,0, "enable full debugging"},
{ oGenRandom, "gen-random", 4, "|N|generate N bytes of random"},
{0}
};
#ifndef HAVE_OPENSC
#ifdef USE_GNU_PTH
/* Pth wrapper function definitions. */
GCRY_THREAD_OPTION_PTH_IMPL;
#endif /*USE_GNU_PTH*/
#endif /*!HAVE_OPENSC*/
static void interactive_shell (int slot);
static void dump_other_cards (int slot);
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 11: p = "sc-investigate (GnuPG)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
break;
case 1:
case 40: p = _("Usage: sc-investigate [options] (-h for help)\n");
break;
case 41: p = _("Syntax: sc-investigate [options] [args]]\n"
"Have a look at smartcards\n");
break;
default: p = NULL;
}
return p;
}
/* Used by gcry for logging */
static void
my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
{
/* translate the log levels */
switch (level)
{
case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break;
case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
default: level = JNLIB_LOG_ERROR; break;
}
log_logv (level, fmt, arg_ptr);
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int slot, rc;
const char *reader_port = NULL;
unsigned long gen_random = 0;
int interactive = 0;
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix ("sc-investigate", 1);
/* Try to auto set the character set. */
set_native_charset (NULL);
/* Libgcrypt requires us to register the threading model first. We
can't use pth at all if we are using OpenSC becuase OpenSC uses
ptreads. Note that this will also do the pth_init. */
#ifndef HAVE_OPENSC
#ifdef USE_GNU_PTH
rc = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth);
if (rc)
{
log_fatal ("can't register GNU Pth with Libgcrypt: %s\n",
gpg_strerror (rc));
}
#endif /*USE_GNU_PTH*/
#endif /*!HAVE_OPENSC*/
/* Check that the libraries are suitable. Do it here because
the option parsing may need services of the library */
if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
{
log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
}
gcry_set_log_handler (my_gcry_logger, NULL);
/* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oQuiet: opt.quiet++; break;
case oDebug: opt.debug |= pargs.r.ret_ulong; break;
case oDebugAll: opt.debug = ~0; break;
case oReaderPort: reader_port = pargs.r.ret_str; break;
case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
case oDisableCCID: opt.disable_ccid = 1; break;
case oGenRandom: gen_random = pargs.r.ret_ulong; break;
case oInteractive: interactive = 1; break;
default : pargs.err = 2; break;
}
}
if (log_get_errorcount(0))
exit(2);
if (opt.verbose < 2)
opt.verbose = 2; /* Hack to let select_openpgp print some info. */
if (argc)
usage (1);
slot = apdu_open_reader (reader_port);
if (slot == -1)
exit (1);
if (!gen_random && !opt.quiet)
{
rc = atr_dump (slot, stdout);
if (rc)
log_error ("can't dump ATR: %s\n", gpg_strerror (rc));
}
if (interactive)
interactive_shell (slot);
else
{
struct app_ctx_s appbuf;
/* Fixme: We better use app.c directly. */
memset (&appbuf, 0, sizeof appbuf);
appbuf.slot = slot;
rc = app_select_openpgp (&appbuf);
if (rc)
{
if (!opt.quiet)
log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
memset (&appbuf, 0, sizeof appbuf);
appbuf.slot = slot;
rc = app_select_dinsig (&appbuf);
if (rc)
{
if (!opt.quiet)
log_info ("selecting dinsig failed: %s\n", gpg_strerror (rc));
dump_other_cards (slot);
}
else
{
appbuf.initialized = 1;
log_info ("dinsig application selected\n");
}
}
else
{
appbuf.initialized = 1;
log_info ("openpgp application selected\n");
if (gen_random)
{
size_t nbytes;
unsigned char *buffer;
buffer = xmalloc (4096);
do
{
nbytes = gen_random > 4096? 4096 : gen_random;
rc = app_get_challenge (&appbuf, nbytes, buffer);
if (rc)
log_error ("app_get_challenge failed: %s\n",gpg_strerror (rc));
else
{
if (fwrite (buffer, nbytes, 1, stdout) != 1)
log_error ("writing to stdout failed: %s\n",
strerror (errno));
gen_random -= nbytes;
}
}
while (gen_random && !log_get_errorcount (0));
xfree (buffer);
}
}
}
return log_get_errorcount (0)? 2:0;
}
void
send_status_info (CTRL ctrl, const char *keyword, ...)
{
/* DUMMY */
}
/* Dump BUFFER of length NBYTES in a nicely human readable format. */
static void
dump_buffer (const unsigned char *buffer, size_t nbytes)
{
int i;
while (nbytes)
{
for (i=0; i < 16 && i < nbytes; i++)
printf ("%02X%s ", buffer[i], i==8? " ":"");
for (; i < 16; i++)
printf (" %s ", i==8? " ":"");
putchar (' ');
putchar (' ');
for (i=0; i < 16 && i < nbytes; i++)
if (isprint (buffer[i]))
putchar (buffer[i]);
else
putchar ('.');
nbytes -= i;
buffer += i;
for (; i < 16; i++)
putchar (' ');
putchar ('\n');
}
}
static void
dump_or_store_buffer (const char *arg,
const unsigned char *buffer, size_t nbytes)
{
const char *s = strchr (arg, '>');
int append;
FILE *fp;
if (!s)
{
dump_buffer (buffer, nbytes);
return;
}
if ((append = (*++s == '>')))
s++;
fp = fopen (s, append? "ab":"wb");
if (!fp)
{
log_error ("failed to create `%s': %s\n", s, strerror (errno));
return;
}
if (nbytes && fwrite (buffer, nbytes, 1, fp) != 1)
log_error ("failed to write to `%s': %s\n", s, strerror (errno));
if (fclose (fp))
log_error ("failed to close `%s': %s\n", s, strerror (errno));
}
/* Convert STRING into a a newly allocated buffer and return the
length of the buffer in R_LENGTH. Detect xx:xx:xx... sequence and
unhexify that one. */
static unsigned char *
pin_to_buffer (const char *string, size_t *r_length)
{
unsigned char *buffer = xmalloc (strlen (string)+1);
const char *s;
size_t n;
for (s=string, n=0; *s; s += 3)
{
if (hexdigitp (s) && hexdigitp (s+1) && (s[2]==':'||!s[2]))
{
buffer[n++] = xtoi_2 (s);
if (!s[2])
break;
}
else
{
memcpy (buffer, string, strlen (string));
*r_length = strlen (string);
return buffer;
}
}
*r_length = n;
return buffer;
}
static char *
my_read_line (int use_readline, char *prompt)
{
static char buf[256];
#ifdef HAVE_READLINE
if (use_readline)
{
char *line = readline (prompt);
if (line)
trim_spaces (line);
if (line && strlen (line) > 2 )
add_history (line);
return line;
}
#endif
/* Either we don't have readline or we are not running
interactively */
#ifndef HAVE_READLINE
printf ("%s", prompt );
#endif
fflush(stdout);
if (!fgets(buf, sizeof(buf), stdin))
return NULL;
if (!strlen(buf))
return NULL;
if (buf[strlen (buf)-1] == '\n')
buf[strlen (buf)-1] = 0;
trim_spaces (buf);
return buf;
}
/* Run a shell for interactive exploration of the card. */
static void
interactive_shell (int slot)
{
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdHELP,
cmdSELECT,
cmdCHDIR,
cmdLS,
cmdAPP,
cmdREAD,
cmdREADREC,
cmdREADSHORTREC,
cmdDEBUG,
cmdVERIFY,
cmdCHANGEREF,
cmdREADPK,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
const char *desc;
} cmds[] = {
{ "quit" , cmdQUIT , "quit this menu" },
{ "q" , cmdQUIT , NULL },
{ "help" , cmdHELP , "show this help" },
{ "?" , cmdHELP , NULL },
{ "debug" , cmdDEBUG, "set debugging flags" },
{ "select" , cmdSELECT, "select file (EF)" },
{ "s" , cmdSELECT, NULL },
{ "chdir" , cmdCHDIR, "change directory (select DF)"},
{ "cd" , cmdCHDIR, NULL },
{ "ls" , cmdLS, "list directory (some cards only)"},
{ "app" , cmdAPP, "select application"},
{ "read" , cmdREAD, "read binary" },
{ "rb" , cmdREAD, NULL },
{ "readrec", cmdREADREC, "read record(s)" },
{ "rr" , cmdREADREC, NULL },
{ "rsr" , cmdREADSHORTREC, "readshortrec RECNO SHORT_EF" },
{ "verify" , cmdVERIFY, "verify CHVNO PIN" },
{ "ver" , cmdVERIFY, NULL },
{ "changeref", cmdCHANGEREF, "change reference data" },
{ "readpk", cmdREADPK, "read a public key" },
{ NULL, cmdINVCMD }
};
enum cmdids cmd = cmdNOP;
int use_readline = isatty (fileno(stdin));
char *line;
gpg_error_t err = 0;
unsigned char *result = NULL;
size_t resultlen;
#ifdef HAVE_READLINE
if (use_readline)
using_history ();
#endif
for (;;)
{
int arg_number;
const char *arg_string = "";
const char *arg_next = "";
char *p;
int i;
if (err)
printf ("command failed: %s\n", gpg_strerror (err));
err = 0;
xfree (result);
result = NULL;
printf ("\n");
do
{
line = my_read_line (use_readline, "cmd> ");
}
while ( line && *line == '#' );
arg_number = 0;
if (!line || *line == CONTROL_D)
cmd = cmdQUIT;
else if (!*line)
cmd = cmdNOP;
else {
if ((p=strchr (line,' ')))
{
char *endp;
*p++ = 0;
trim_spaces (line);
trim_spaces (p);
arg_number = strtol (p, &endp, 0);
arg_string = p;
if (endp != p)
{
arg_next = endp;
while ( spacep (arg_next) )
arg_next++;
}
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (line, cmds[i].name ))
break;
cmd = cmds[i].id;
}
switch (cmd)
{
case cmdHELP:
for (i=0; cmds[i].name; i++ )
if (cmds[i].desc)
printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) );
break;
case cmdQUIT:
goto leave;
case cmdNOP:
break;
case cmdDEBUG:
if (!*arg_string)
opt.debug = opt.debug? 0 : 2048;
else
opt.debug = arg_number;
break;
case cmdSELECT:
err = iso7816_select_file (slot, arg_number, 0, NULL, NULL);
break;
case cmdCHDIR:
err = iso7816_select_file (slot, arg_number, 1, NULL, NULL);
break;
case cmdLS:
err = iso7816_list_directory (slot, 1, &result, &resultlen);
if (!err || gpg_err_code (err) == GPG_ERR_ENOENT)
err = iso7816_list_directory (slot, 0, &result, &resultlen);
/* FIXME: Do something with RESULT. */
break;
case cmdAPP:
{
app_t app;
app = select_application (NULL, slot, *arg_string? arg_string:NULL);
if (app)
{
char *sn;
app_get_serial_and_stamp (app, &sn, NULL);
log_info ("application `%s' ready; sn=%s\n",
app->apptype?app->apptype:"?", sn? sn:"[none]");
release_application (app);
}
}
break;
case cmdREAD:
err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
if (!err)
dump_or_store_buffer (arg_string, result, resultlen);
break;
case cmdREADREC:
if (*arg_string == '*' && (!arg_string[1] || arg_string[1] == ' '))
{
/* Fixme: Can't write to a file yet. */
for (i=1, err=0; !err; i++)
{
xfree (result); result = NULL;
err = iso7816_read_record (slot, i, 1, 0,
&result, &resultlen);
if (!err)
dump_buffer (result, resultlen);
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
}
else
{
err = iso7816_read_record (slot, arg_number, 1, 0,
&result, &resultlen);
if (!err)
dump_or_store_buffer (arg_string, result, resultlen);
}
break;
case cmdREADSHORTREC:
{
int short_ef;
short_ef = strtol (arg_next, NULL, 0);
if (short_ef < 1 || short_ef > 254)
printf ("error: short EF must be between 1 and 254\n");
else
{
err = iso7816_read_record (slot, arg_number, 1, short_ef,
&result, &resultlen);
if (!err)
dump_or_store_buffer (arg_string, result, resultlen);
}
}
break;
case cmdVERIFY:
if (arg_number < 0 || arg_number > 255 || (arg_number & 127) > 31)
printf ("error: invalid CHVNO\n");
else
{
unsigned char *pin;
size_t pinlen;
pin = pin_to_buffer (arg_next, &pinlen);
err = iso7816_verify (slot, arg_number, pin, pinlen);
xfree (pin);
}
break;
case cmdCHANGEREF:
{
const char *newpin = arg_next;
while ( *newpin && !spacep (newpin) )
newpin++;
while ( spacep (newpin) )
newpin++;
if (arg_number < 0 || arg_number > 255 || (arg_number & 127) > 31)
printf ("error: invalid CHVNO\n");
else if (!*arg_next || !*newpin || newpin == arg_next)
printf ("usage: changeref CHVNO OLDPIN NEWPIN\n");
else
{
char *oldpin = xstrdup (arg_next);
unsigned char *oldpin_buf, *newpin_buf;
size_t oldpin_len, newpin_len;
for (p=oldpin; *p && !spacep (p); p++ )
;
*p = 0;
oldpin_buf = pin_to_buffer (oldpin, &oldpin_len);
newpin_buf = pin_to_buffer (newpin, &newpin_len);
err = iso7816_change_reference_data (slot, arg_number,
oldpin_buf, oldpin_len,
newpin_buf, newpin_len);
xfree (newpin_buf);
xfree (oldpin_buf);
xfree (oldpin);
}
}
break;
case cmdREADPK:
if (arg_number < 1 || arg_number > 255)
printf ("usage: readpk CRTBYTE1\n");
else
{
unsigned char crt[2];
crt[0] = arg_number;
crt[1] = 0;
err = iso7816_read_public_key(slot, crt, 2,
&result, &resultlen);
if (!err)
dump_or_store_buffer (arg_string, result, resultlen);
}
break;
case cmdINVCMD:
default:
printf ("\n");
printf ("Invalid command (try \"help\")\n");
break;
} /* End command switch. */
} /* End of main menu loop. */
leave:
;
}
/* Figure out whether the current card is a German Geldkarte and print
what we know about it. */
static int
dump_geldkarte (int slot)
{
unsigned char *r = NULL;
size_t rlen;
const char *t;
if (iso7816_read_record (slot, 1, 1, 0xbc, &r, &rlen))
return -1;
/* We require that the record is at least 24 bytes, the first byte
is 0x67 and the filler byte is correct. */
if (rlen < 24 || *r != 0x67 || r[22])
return -1;
/* The short Bankleitzahl consists of 3 bytes at offset 1. */
switch (r[1])
{
case 0x21: t = "Oeffentlich-rechtliche oder private Bank"; break;
case 0x22: t = "Privat- oder Gesch<63>ftsbank"; break;
case 0x25: t = "Sparkasse"; break;
case 0x26:
case 0x29: t = "Genossenschaftsbank"; break;
default:
xfree (r);
return -1; /* Probably not a Geldkarte. */
}
printf ("KBLZ .....: %02X-%02X%02X (%s)\n", r[1], r[2], r[3], t);
printf ("Card-No ..: %02X%02X%02X%02X%02X\n", r[4], r[5], r[6], r[7], r[8]);
/* Byte 10 enth<74>lt im linken Halbbyte eine Pr<50>fziffer, die nach dem */
/* Verfahren 'Luhn formula for computing modulus 10' <20>ber die Ziffern der */
/* ersten 9 Byte berechnet ist. */
/* Das rechte Halbbyte wird zu 'D' gesetzt. */
/* F<>r die Berechnung der Luhn-Pr<50>fziffer sind die folgenden Schritte */
/* durchzuf<75>hren: */
/* Schritt 1: Mit der rechtesten Ziffer beginnend ist einschlie<69>lich dieser */
/* Ziffer jede <20>bern<72>chste Ziffer zu verdoppeln (mit 2 multiplizieren). */
/* Schritt 2: Die einzelnen Ziffern der Produkte aus Schritt 1 und die bei */
/* diesen Multiplikationen unber<65>hrt gebliebenen Ziffern sind zu addieren. */
/* Schritt 3: Das Ergebnis der Addition aus Schritt 2 ist von dem auf die */
/* n<>chst h<>here Zahl mit der Einerstelle 0 aufgerundeten Ergebnis der */
/* Addition aus Schritt 2 abzuziehen. Wenn das Ergebnis der Addition aus */
/* Schritt 2 bereits eine Zahl mit der Einerstelle 0 ergibt (z.B. 30, 40, */
/* usw.), ist die Pr<50>fziffer 0. */
/* Beispiel: Kartennummer ohne Pr<50>fziffer: 992 839 871 */
/* 9 9 2 8 3 9 8 7 1 */
/* x 2 x 2 x 2 x 2 x 2 Schritt 1 */
/* 18 4 6 16 2 */
/* 1+8 +9 +4 +8 +6 +9 +1+6 +7 +2 = 61 Schritt 2 */
/* 70-61 = 9 Schritt 3 */
/* Pr<50>fziffer zu 992 839 871 = 9 */
printf ("Expires at: %02X/%02X\n", r[11], r[10] );
printf ("Valid from: %02X.%02X.%02X\n", r[14], r[13], r[12]);
printf ("Country ..: %02X%02X\n", r[15], r[16]);
printf ("Currency .: %c%c%c\n", isascii (r[17])? r[17]:' ',
isascii (r[18])? r[18]:' ', isascii (r[19])? r[19]:' ');
printf ("Cur.-Mult : %s\n",
r[20] == 0x01? "0.01":
r[20] == 0x02? "0.1":
r[20] == 0x04? "1":
r[20] == 0x08? "10":
r[20] == 0x10? "100":
r[20] == 0x20? "1000": "?");
printf ("ZKA ChipID: %02X\n", r[21]);
printf ("OS version: %02X\n", r[23]);
xfree (r);
return 0;
}
/* Try to figure out the type of teh card and dump its contents. */
static void
dump_other_cards (int slot)
{
if (!dump_geldkarte (slot))
return;
}