diff --git a/common/util.h b/common/util.h index a6f86069a..94878bc36 100644 --- a/common/util.h +++ b/common/util.h @@ -38,7 +38,8 @@ /* These error codes are used but not defined in the required libgpg-error version. Define them here. */ #if GPG_ERROR_VERSION_NUMBER < 0x011200 /* 1.18 */ -# define GPG_ERR_FORBIDDEN 251 +# define GPG_ERR_OBJ_TERM_STATE 225 +# define GPG_ERR_FORBIDDEN 251 #endif diff --git a/doc/DETAILS b/doc/DETAILS index 9ad616c2a..ba2725fdb 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -765,6 +765,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: - 4 :: No card available - 5 :: No card reader available - 6 :: No card support available + - 7 :: Card is in termination state *** SC_OP_FAILURE [] An operation on a smartcard definitely failed. Currently there is diff --git a/g10/call-agent.c b/g10/call-agent.c index 43a5c4e12..0450b8165 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -343,6 +343,9 @@ start_agent (ctrl_t ctrl, int for_card) case GPG_ERR_NO_SCDAEMON: write_status_text (STATUS_CARDCTRL, "6"); break; + case GPG_ERR_OBJ_TERM_STATE: + write_status_text (STATUS_CARDCTRL, "7"); + break; default: write_status_text (STATUS_CARDCTRL, "4"); log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc)); @@ -586,6 +589,8 @@ learn_status_cb (void *opaque, const char *line) parm->extcap.ki = abool; else if (!strcmp (p, "aac")) parm->extcap.aac = abool; + else if (!strcmp (p, "si")) + parm->status_indicator = strtoul (p2, NULL, 10); } } xfree (buf); @@ -657,6 +662,9 @@ agent_scd_learn (struct agent_card_info_s *info) struct default_inq_parm_s parm; struct agent_card_info_s dummyinfo; + if (!info) + info = &dummyinfo; + memset (info, 0, sizeof *info); memset (&parm, 0, sizeof parm); rc = start_agent (NULL, 1); @@ -675,11 +683,7 @@ agent_scd_learn (struct agent_card_info_s *info) if (rc) return rc; - if (!info) - info = &dummyinfo; - parm.ctx = agent_ctx; - memset (info, 0, sizeof *info); rc = assuan_transact (agent_ctx, "LEARN --sendinfo", dummy_data_cb, NULL, default_inq_cb, &parm, learn_status_cb, info); @@ -694,6 +698,63 @@ agent_scd_learn (struct agent_card_info_s *info) } +/* Send an APDU to the current card. On success the status word is + stored at R_SW. With HEXAPDU being NULL only a RESET command is + send to scd. With HEXAPDU being the string "undefined" the command + "SERIALNO undefined" is send to scd. */ +gpg_error_t +agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) +{ + gpg_error_t err; + + /* Start the agent but not with the card flag so that we do not + autoselect the openpgp application. */ + err = start_agent (NULL, 0); + if (err) + return err; + + if (!hexapdu) + { + err = assuan_transact (agent_ctx, "SCD RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + + } + else if (!strcmp (hexapdu, "undefined")) + { + err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + { + char line[ASSUAN_LINELENGTH]; + membuf_t mb; + unsigned char *data; + size_t datalen; + + init_membuf (&mb, 256); + + snprintf (line, DIM(line)-1, "SCD APDU %s", hexapdu); + err = assuan_transact (agent_ctx, line, + membuf_data_cb, &mb, NULL, NULL, NULL, NULL); + if (!err) + { + data = get_membuf (&mb, &datalen); + if (!data) + err = gpg_error_from_syserror (); + else if (datalen < 2) /* Ooops */ + err = gpg_error (GPG_ERR_CARD); + else + { + *r_sw = (data[datalen-2] << 8) | data[datalen-1]; + } + xfree (data); + } + } + + return err; +} + + int agent_keytocard (const char *hexgrip, int keyno, int force, const char *serialno, const char *timestamp) diff --git a/g10/call-agent.h b/g10/call-agent.h index a24941e4d..bcb5ae9f5 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -61,6 +61,7 @@ struct agent_card_info_s unsigned int ki:1; /* Key import available. */ unsigned int aac:1; /* Algorithm attributes are changeable. */ } extcap; + unsigned int status_indicator; }; struct agent_card_genkey_s { @@ -78,6 +79,9 @@ void agent_release_card_info (struct agent_card_info_s *info); /* Return card info. */ int agent_scd_learn (struct agent_card_info_s *info); +/* Send an APDU to the card. */ +gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw); + /* Update INFO with the attribute NAME. */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info); diff --git a/g10/card-util.c b/g10/card-util.c index 0535c1dab..b030fadb3 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -1635,6 +1635,169 @@ card_store_subkey (KBNODE node, int use) } + +/* Direct sending of an hex encoded APDU with error printing. */ +static gpg_error_t +send_apdu (const char *hexapdu, const char *desc, unsigned int ignore) +{ + gpg_error_t err; + unsigned int sw; + + err = agent_scd_apdu (hexapdu, &sw); + if (err) + tty_printf ("sending card command %s failed: %s\n", desc, + gpg_strerror (err)); + else if (!hexapdu || !strcmp (hexapdu, "undefined")) + ; + else if (ignore == 0xffff) + ; /* Ignore all status words. */ + else if (sw != 0x9000) + { + switch (sw) + { + case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; + case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; + case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; + default: err = gpg_error (GPG_ERR_CARD); + } + if (!(ignore && ignore == sw)) + tty_printf ("card command %s failed: %s (0x%04x)\n", desc, + gpg_strerror (err), sw); + } + return err; +} + + +/* Do a factory reset after confirmation. */ +static void +factory_reset (void) +{ + struct agent_card_info_s info; + gpg_error_t err; + char *answer = NULL; + int termstate = 0; + int i; + + /* The code below basically does the same what this + gpg-connect-agent script does: + + scd reset + scd serialno undefined + scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + scd apdu 00 e6 00 00 + scd reset + scd serialno undefined + scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 + scd apdu 00 44 00 00 + /echo Card has been reset to factory defaults + + but tries to find out something about the card first. + */ + + err = agent_scd_learn (&info); + if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE + && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) + termstate = 1; + else if (err) + { + log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); + return; + } + + if (!termstate) + { + log_info (_("OpenPGP card no. %s detected\n"), + info.serialno? info.serialno : "[none]"); + if (!(info.status_indicator == 3 || info.status_indicator == 5)) + { + /* Note: We won't see status-indicator 3 here because it is not + possible to select a card application in termination state. */ + log_error (_("This command is not supported by this card\n")); + goto leave; + } + + tty_printf ("\n"); + log_info (_("Note: This command destroys all keys stored on the card!\n")); + tty_printf ("\n"); + if (!cpr_get_answer_is_yes ("cardedit.factory-reset.proceed", + _("Continue? (y/N) "))) + goto leave; + + + answer = cpr_get ("cardedit.factory-reset.really", + _("Really do a factory reset? (enter \"yes\") ")); + cpr_kill_prompt (); + trim_spaces (answer); + if (strcmp (answer, "yes")) + goto leave; + + /* We need to select a card application before we can send APDUs + to the card without scdaemon doing anything on its own. */ + err = send_apdu (NULL, "RESET", 0); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0); + if (err) + goto leave; + + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); + if (err) + goto leave; + + /* Do some dummy verifies with wrong PINs to set the retry + counter to zero. We can't easily use the card version 2.1 + feature of presenting the admin PIN to allow the terminate + command because there is no machinery in scdaemon to catch + the verify command and ask for the PIN when the "APDU" + command is used. */ + for (i=0; i < 4; i++) + send_apdu ("00200081084040404040404040", "VERIFY", 0xffff); + for (i=0; i < 4; i++) + send_apdu ("00200083084040404040404040", "VERIFY", 0xffff); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + if (err) + goto leave; + } + + /* The card is in termination state - reset and select again. */ + err = send_apdu (NULL, "RESET", 0); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select", 0); + if (err) + goto leave; + + /* Select the OpenPGP application. (no error checking here). */ + send_apdu ("00A4040006D27600012401", "SELECT AID", 0xffff); + + /* Send activate datafile command. This is used without + confirmation if the card is already in termination state. */ + err = send_apdu ("00440000", "ACTIVATE DF", 0); + if (err) + goto leave; + + /* Finally we reset the card reader once more. */ + err = send_apdu (NULL, "RESET", 0); + if (err) + goto leave; + + leave: + xfree (answer); + agent_release_card_info (&info); +} + + /* Data used by the command parser. This needs to be outside of the function scope to allow readline based command completion. */ @@ -1644,7 +1807,7 @@ enum cmdids cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, - cmdREADCERT, cmdUNBLOCK, + cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdINVCMD }; @@ -1676,6 +1839,7 @@ static struct { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code") }, + { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, /* Note, that we do not announce these command yet. */ { "privatedo", cmdPRIVATEDO, 0, NULL }, { "readcert", cmdREADCERT, 0, NULL }, @@ -1848,7 +2012,7 @@ card_edit (ctrl_t ctrl, strlist_t commands) for (i=0; cmds[i].name; i++ ) if(cmds[i].desc && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin))) - tty_printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) ); + tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); break; case cmdADMIN: @@ -1953,6 +2117,10 @@ card_edit (ctrl_t ctrl, strlist_t commands) change_pin (1, allow_admin); break; + case cmdFACTORYRESET: + factory_reset (); + break; + case cmdQUIT: goto leave; diff --git a/scd/apdu.h b/scd/apdu.h index 2e518b1b6..7e30f761b 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -53,7 +53,7 @@ enum { SW_CLA_NOT_SUP = 0x6e00, SW_SUCCESS = 0x9000, - /* The follwoing statuswords are no real ones but used to map host + /* The following statuswords are no real ones but used to map host OS errors into status words. A status word is 16 bit so that those values can't be issued by a card. */ SW_HOST_OUT_OF_CORE = 0x10001, /* No way yet to differentiate diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index ac290c9ac..daf0310e8 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1073,10 +1073,10 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) } if (table[idx].special == -2) { - char tmp[100]; + char tmp[110]; snprintf (tmp, sizeof tmp, - "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d sm=%d", + "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d sm=%d si=%u", app->app_local->extcap.get_challenge, app->app_local->extcap.key_import, app->app_local->extcap.change_force_chv, @@ -1085,7 +1085,8 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) app->app_local->extcap.algo_attr_change, (app->app_local->extcap.sm_supported ? (app->app_local->extcap.sm_aes128? 7 : 2) - : 0)); + : 0), + app->app_local->status_indicator); send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); return 0; } diff --git a/scd/app.c b/scd/app.c index 1694ea1c4..5fa06b095 100644 --- a/scd/app.c +++ b/scd/app.c @@ -389,7 +389,7 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) err = app_select_dinsig (app); if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm"))) err = app_select_sc_hsm (app); - if (err && name) + if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE) err = gpg_error (GPG_ERR_NOT_SUPPORTED); leave: diff --git a/scd/iso7816.c b/scd/iso7816.c index f1dbcffe4..3c43a4c81 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -64,7 +64,7 @@ map_sw (int sw) switch (sw) { case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; - case SW_TERM_STATE: ec = GPG_ERR_CARD; break; + case SW_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break; case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 763ce2d90..7c786c2c4 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -344,7 +344,7 @@ set_debug (const char *level) gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) - log_info ("enabled debug flags:%s%s%s%s%s%s%s%s%s\n", + log_info ("enabled debug flags:%s%s%s%s%s%s%s%s%s%s\n", (opt.debug & DBG_COMMAND_VALUE)? " command":"", (opt.debug & DBG_MPI_VALUE )? " mpi":"", (opt.debug & DBG_CRYPTO_VALUE )? " crypto":"", @@ -353,7 +353,8 @@ set_debug (const char *level) (opt.debug & DBG_MEMSTAT_VALUE)? " memstat":"", (opt.debug & DBG_HASHING_VALUE)? " hashing":"", (opt.debug & DBG_ASSUAN_VALUE )? " assuan":"", - (opt.debug & DBG_CARD_IO_VALUE)? " cardio":""); + (opt.debug & DBG_CARD_IO_VALUE)? " cardio":"", + (opt.debug & DBG_READER_VALUE )? " reader":""); }