2003-08-05 19:11:04 +02:00
|
|
|
|
/* 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>
|
2004-01-27 17:40:42 +01:00
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_READLINE_READLINE_H
|
|
|
|
|
#include <readline/readline.h>
|
|
|
|
|
#include <readline/history.h>
|
|
|
|
|
#endif
|
2003-08-05 19:11:04 +02:00
|
|
|
|
|
|
|
|
|
#define JNLIB_NEED_LOG_LOGV
|
|
|
|
|
#include "scdaemon.h"
|
|
|
|
|
#include <gcrypt.h>
|
|
|
|
|
|
|
|
|
|
#include "apdu.h" /* for open_reader */
|
|
|
|
|
#include "atr.h"
|
|
|
|
|
#include "app-common.h"
|
2004-01-27 17:40:42 +01:00
|
|
|
|
#include "iso7816.h"
|
2003-08-05 19:11:04 +02:00
|
|
|
|
|
|
|
|
|
#define _(a) (a)
|
|
|
|
|
|
2004-01-27 17:40:42 +01:00
|
|
|
|
#define CONTROL_D ('D' - 'A' + 1)
|
|
|
|
|
|
2003-08-05 19:11:04 +02:00
|
|
|
|
|
|
|
|
|
enum cmd_and_opt_values
|
2004-01-27 17:40:42 +01:00
|
|
|
|
{
|
|
|
|
|
oInteractive = 'i',
|
|
|
|
|
oVerbose = 'v',
|
2004-03-16 11:49:37 +01:00
|
|
|
|
oQuiet = 'q',
|
2003-08-05 19:11:04 +02:00
|
|
|
|
oReaderPort = 500,
|
2003-08-05 19:11:04 +02:00
|
|
|
|
octapiDriver,
|
2004-03-16 11:49:37 +01:00
|
|
|
|
|
2003-08-05 19:11:04 +02:00
|
|
|
|
oDebug,
|
|
|
|
|
oDebugAll,
|
|
|
|
|
|
2004-01-27 17:40:42 +01:00
|
|
|
|
oDisableCCID,
|
|
|
|
|
|
|
|
|
|
|
2003-08-05 19:11:04 +02:00
|
|
|
|
oGenRandom,
|
|
|
|
|
|
|
|
|
|
aTest };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static ARGPARSE_OPTS opts[] = {
|
|
|
|
|
|
|
|
|
|
{ 301, NULL, 0, "@Options:\n " },
|
|
|
|
|
|
2004-01-27 17:40:42 +01:00
|
|
|
|
{ oInteractive, "interactive", 0, "start in interactive explorer mode"},
|
2004-03-16 11:49:37 +01:00
|
|
|
|
{ oQuiet, "quiet", 0, "quiet" },
|
2003-08-05 19:11:04 +02:00
|
|
|
|
{ oVerbose, "verbose", 0, "verbose" },
|
2003-08-05 19:11:04 +02:00
|
|
|
|
{ oReaderPort, "reader-port", 2, "|N|connect to reader at port N"},
|
|
|
|
|
{ octapiDriver, "ctapi-driver", 2, "NAME|use NAME as ctAPI driver"},
|
2004-01-27 17:40:42 +01:00
|
|
|
|
{ oDisableCCID, "disable-ccid", 0,
|
|
|
|
|
#ifdef HAVE_LIBUSB
|
|
|
|
|
"do not use the internal CCID driver"
|
|
|
|
|
#else
|
|
|
|
|
"@"
|
|
|
|
|
#endif
|
|
|
|
|
},
|
2003-08-05 19:11:04 +02:00
|
|
|
|
{ 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}
|
|
|
|
|
};
|
|
|
|
|
|
2004-01-27 17:40:42 +01:00
|
|
|
|
|
|
|
|
|
static void interactive_shell (int slot);
|
2004-03-16 11:49:37 +01:00
|
|
|
|
static void dump_other_cards (int slot);
|
2004-01-27 17:40:42 +01:00
|
|
|
|
|
2003-08-05 19:11:04 +02:00
|
|
|
|
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;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
const char *reader_port = NULL;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
unsigned long gen_random = 0;
|
2004-01-27 17:40:42 +01:00
|
|
|
|
int interactive = 0;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
|
|
|
|
|
set_strusage (my_strusage);
|
|
|
|
|
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
|
|
|
|
|
log_set_prefix ("sc-investigate", 1);
|
|
|
|
|
|
2004-02-18 18:00:11 +01:00
|
|
|
|
/* Try to auto set the character set. */
|
|
|
|
|
set_native_charset (NULL);
|
|
|
|
|
|
2003-08-05 19:11:04 +02:00
|
|
|
|
/* 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) );
|
|
|
|
|
}
|
|
|
|
|
|
2004-02-18 18:00:11 +01:00
|
|
|
|
|
2003-08-05 19:11:04 +02:00
|
|
|
|
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;
|
2004-03-16 11:49:37 +01:00
|
|
|
|
case oQuiet: opt.quiet++; break;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
case oDebug: opt.debug |= pargs.r.ret_ulong; break;
|
|
|
|
|
case oDebugAll: opt.debug = ~0; break;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
case oReaderPort: reader_port = pargs.r.ret_str; break;
|
|
|
|
|
case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
|
2004-01-27 17:40:42 +01:00
|
|
|
|
case oDisableCCID: opt.disable_ccid = 1; break;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
case oGenRandom: gen_random = pargs.r.ret_ulong; break;
|
2004-01-27 17:40:42 +01:00
|
|
|
|
case oInteractive: interactive = 1; break;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
default : pargs.err = 2; break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (log_get_errorcount(0))
|
|
|
|
|
exit(2);
|
|
|
|
|
|
|
|
|
|
if (opt.verbose < 2)
|
2004-01-27 17:40:42 +01:00
|
|
|
|
opt.verbose = 2; /* Hack to let select_openpgp print some info. */
|
2003-08-05 19:11:04 +02:00
|
|
|
|
|
|
|
|
|
if (argc)
|
|
|
|
|
usage (1);
|
|
|
|
|
|
|
|
|
|
slot = apdu_open_reader (reader_port);
|
|
|
|
|
if (slot == -1)
|
|
|
|
|
exit (1);
|
|
|
|
|
|
2004-03-16 11:49:37 +01:00
|
|
|
|
if (!gen_random && !opt.quiet)
|
2003-08-05 19:11:04 +02:00
|
|
|
|
{
|
|
|
|
|
rc = atr_dump (slot, stdout);
|
|
|
|
|
if (rc)
|
|
|
|
|
log_error ("can't dump ATR: %s\n", gpg_strerror (rc));
|
|
|
|
|
}
|
|
|
|
|
|
2004-01-27 17:40:42 +01:00
|
|
|
|
if (interactive)
|
|
|
|
|
interactive_shell (slot);
|
2003-08-05 19:11:04 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
2004-01-27 17:40:42 +01:00
|
|
|
|
struct app_ctx_s appbuf;
|
2003-08-05 19:11:04 +02:00
|
|
|
|
|
2004-01-27 17:40:42 +01:00
|
|
|
|
/* Fixme: We better use app.c directly. */
|
|
|
|
|
memset (&appbuf, 0, sizeof appbuf);
|
|
|
|
|
appbuf.slot = slot;
|
|
|
|
|
rc = app_select_openpgp (&appbuf);
|
|
|
|
|
if (rc)
|
2003-08-05 19:11:04 +02:00
|
|
|
|
{
|
2004-03-16 11:49:37 +01:00
|
|
|
|
if (!opt.quiet)
|
|
|
|
|
log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
|
2004-01-27 17:40:42 +01:00
|
|
|
|
memset (&appbuf, 0, sizeof appbuf);
|
|
|
|
|
appbuf.slot = slot;
|
|
|
|
|
rc = app_select_dinsig (&appbuf);
|
|
|
|
|
if (rc)
|
2004-03-16 11:49:37 +01:00
|
|
|
|
{
|
|
|
|
|
if (!opt.quiet)
|
|
|
|
|
log_info ("selecting dinsig failed: %s\n", gpg_strerror (rc));
|
|
|
|
|
dump_other_cards (slot);
|
|
|
|
|
}
|
2004-01-27 17:40:42 +01:00
|
|
|
|
else
|
2003-08-05 19:11:04 +02:00
|
|
|
|
{
|
2004-01-27 17:40:42 +01:00
|
|
|
|
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
|
2003-08-05 19:11:04 +02:00
|
|
|
|
{
|
2004-01-27 17:40:42 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2003-08-05 19:11:04 +02:00
|
|
|
|
}
|
2004-01-27 17:40:42 +01:00
|
|
|
|
while (gen_random && !log_get_errorcount (0));
|
|
|
|
|
xfree (buffer);
|
2003-08-05 19:11:04 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2004-01-27 17:40:42 +01:00
|
|
|
|
|
2003-08-05 19:11:04 +02:00
|
|
|
|
return log_get_errorcount (0)? 2:0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
send_status_info (CTRL ctrl, const char *keyword, ...)
|
|
|
|
|
{
|
|
|
|
|
/* DUMMY */
|
|
|
|
|
}
|
2004-01-27 17:40:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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 *
|
|
|
|
|
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,
|
2004-03-16 11:49:37 +01:00
|
|
|
|
cmdREADSHORTREC,
|
2004-01-27 17:40:42 +01:00
|
|
|
|
cmdDEBUG,
|
|
|
|
|
cmdVERIFY,
|
|
|
|
|
cmdCHANGEREF,
|
|
|
|
|
|
|
|
|
|
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 },
|
2004-03-16 11:49:37 +01:00
|
|
|
|
{ "rsr" , cmdREADSHORTREC, "readshortrec RECNO SHORT_EF" },
|
2004-01-27 17:40:42 +01:00
|
|
|
|
{ "verify" , cmdVERIFY, "verify CHVNO PIN" },
|
|
|
|
|
{ "ver" , cmdVERIFY, NULL },
|
|
|
|
|
{ "changeref", cmdCHANGEREF, "change reference data" },
|
|
|
|
|
{ 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 = 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;
|
2004-03-16 11:49:37 +01:00
|
|
|
|
err = iso7816_read_record (slot, i, 1, 0,
|
|
|
|
|
&result, &resultlen);
|
2004-01-27 17:40:42 +01:00
|
|
|
|
if (!err)
|
|
|
|
|
dump_buffer (result, resultlen);
|
|
|
|
|
}
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
|
|
|
|
|
err = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2004-03-16 11:49:37 +01:00
|
|
|
|
err = iso7816_read_record (slot, arg_number, 1, 0,
|
2004-01-27 17:40:42 +01:00
|
|
|
|
&result, &resultlen);
|
|
|
|
|
if (!err)
|
|
|
|
|
dump_or_store_buffer (arg_string, result, resultlen);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2004-03-16 11:49:37 +01:00
|
|
|
|
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;
|
|
|
|
|
|
2004-01-27 17:40:42 +01:00
|
|
|
|
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 cmdINVCMD:
|
|
|
|
|
default:
|
|
|
|
|
printf ("\n");
|
|
|
|
|
printf ("Invalid command (try \"help\")\n");
|
|
|
|
|
break;
|
|
|
|
|
} /* End command switch. */
|
|
|
|
|
} /* End of main menu loop. */
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-16 11:49:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|