/* 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 #include #include #include #include #include #include #ifdef HAVE_READLINE_READLINE_H #include #include #endif #define JNLIB_NEED_LOG_LOGV #include "scdaemon.h" #include #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', 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"}, { 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} }; static void interactive_shell (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); /* 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 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) { 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) { 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) log_info ("selecting dinsig failed: %s\n", gpg_strerror (rc)); 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 * 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, 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 }, { "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; err = iso7816_read_record (slot, i, 1, &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, &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 cmdINVCMD: default: printf ("\n"); printf ("Invalid command (try \"help\")\n"); break; } /* End command switch. */ } /* End of main menu loop. */ leave: ; }