diff options
Diffstat (limited to 'scd')
-rw-r--r-- | scd/Makefile.am | 3 | ||||
-rw-r--r-- | scd/apdu.c | 66 | ||||
-rw-r--r-- | scd/apdu.h | 4 | ||||
-rw-r--r-- | scd/app-common.h | 38 | ||||
-rw-r--r-- | scd/app-dinsig.c | 2 | ||||
-rw-r--r-- | scd/app-geldkarte.c | 2 | ||||
-rw-r--r-- | scd/app-help.c | 45 | ||||
-rw-r--r-- | scd/app-nks.c | 15 | ||||
-rw-r--r-- | scd/app-openpgp.c | 240 | ||||
-rw-r--r-- | scd/app-piv.c | 3350 | ||||
-rw-r--r-- | scd/app.c | 139 | ||||
-rw-r--r-- | scd/ccid-driver.c | 53 | ||||
-rw-r--r-- | scd/ccid-driver.h | 2 | ||||
-rw-r--r-- | scd/command.c | 251 | ||||
-rw-r--r-- | scd/iso7816.c | 195 | ||||
-rw-r--r-- | scd/iso7816.h | 29 | ||||
-rw-r--r-- | scd/scdaemon.c | 2 | ||||
-rw-r--r-- | scd/scdaemon.h | 4 |
18 files changed, 4179 insertions, 261 deletions
diff --git a/scd/Makefile.am b/scd/Makefile.am index cbd1f9f0f..0cc50dca8 100644 --- a/scd/Makefile.am +++ b/scd/Makefile.am @@ -33,7 +33,8 @@ AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) -card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c +card_apps = app-openpgp.c app-piv.c app-nks.c app-dinsig.c app-p15.c \ + app-geldkarte.c app-sc-hsm.c scdaemon_SOURCES = \ scdaemon.c scdaemon.h \ diff --git a/scd/apdu.c b/scd/apdu.c index ce7f41f61..816938ac5 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -105,6 +105,7 @@ struct reader_table_s { int (*check_pinpad)(int, int, pininfo_t *); void (*dump_status_reader)(int); int (*set_progress_cb)(int, gcry_handler_progress_t, void*); + int (*set_prompt_cb)(int, void (*) (void *, int), void*); int (*pinpad_verify)(int, int, int, int, int, pininfo_t *); int (*pinpad_modify)(int, int, int, int, int, pininfo_t *); @@ -444,6 +445,7 @@ new_reader_slot (void) reader_table[reader].check_pinpad = check_pcsc_pinpad; reader_table[reader].dump_status_reader = NULL; reader_table[reader].set_progress_cb = NULL; + reader_table[reader].set_prompt_cb = NULL; reader_table[reader].pinpad_verify = pcsc_pinpad_verify; reader_table[reader].pinpad_modify = pcsc_pinpad_modify; @@ -1404,6 +1406,14 @@ set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg) return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg); } +static int +set_prompt_cb_ccid_reader (int slot, void (*cb) (void *, int ), void *cb_arg) +{ + reader_table_t slotp = reader_table + slot; + + return ccid_set_prompt_cb (slotp->ccid.handle, cb, cb_arg); +} + static int get_status_ccid (int slot, unsigned int *status, int on_wire) @@ -1543,6 +1553,7 @@ open_ccid_reader (struct dev_list *dl) reader_table[slot].check_pinpad = check_ccid_pinpad; reader_table[slot].dump_status_reader = dump_ccid_reader_status; reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader; + reader_table[slot].set_prompt_cb = set_prompt_cb_ccid_reader; reader_table[slot].pinpad_verify = ccid_pinpad_operation; reader_table[slot].pinpad_modify = ccid_pinpad_operation; /* Our CCID reader code does not support T=0 at all, thus reset the @@ -2382,6 +2393,29 @@ apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg) } +int +apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].set_prompt_cb) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].set_prompt_cb (slot, cb, cb_arg); + unlock_slot (slot); + } + } + else + sw = 0; + return sw; +} + + /* Do a reset for the card in reader at SLOT. */ int apdu_reset (int slot) @@ -2612,7 +2646,7 @@ send_apdu (int slot, unsigned char *apdu, size_t apdulen, } -/* Core APDU tranceiver function. Parameters are described at +/* Core APDU transceiver function. Parameters are described at apdu_send_le with the exception of PININFO which indicates pinpad related operations if not NULL. If EXTENDED_MODE is not 0 command chaining or extended length will be used according to these @@ -3029,19 +3063,25 @@ apdu_send_simple (int slot, int extended_mode, /* This is a more generic version of the apdu sending routine. It - takes an already formatted APDU in APDUDATA or length APDUDATALEN - and returns with an APDU including the status word. With - HANDLE_MORE set to true this function will handle the MORE DATA - status and return all APDUs concatenated with one status word at - the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed - with a max. result data length of EXTENDED_LENGTH bytes. The - function does not return a regular status word but 0 on success. - If the slot is locked, the function returns immediately with an - error. */ + * takes an already formatted APDU in APDUDATA or length APDUDATALEN + * and returns with an APDU including the status word. With + * HANDLE_MORE set to true this function will handle the MORE DATA + * status and return all APDUs concatenated with one status word at + * the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed + * with a max. result data length of EXTENDED_LENGTH bytes. The + * function does not return a regular status word but 0 on success. + * If the slot is locked, the function returns immediately with an + * error. + * + * Out of historical reasons the function returns 0 on success and + * outs the status word at the end of the result to be able to get the + * status word in the case of a not provided RETBUF, R_SW can be used + * to store the SW. But note that R_SW qill only be set if the + * function returns 0. */ int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **retbuf, size_t *retbuflen) { #define SHORT_RESULT_BUFFER_SIZE 258 @@ -3248,9 +3288,13 @@ apdu_send_direct (int slot, size_t extended_length, (*retbuf)[(*retbuflen)++] = sw; } + if (r_sw) + *r_sw = sw; + if (DBG_CARD_IO && retbuf) log_printhex (*retbuf, *retbuflen, " dump: "); + return 0; } diff --git a/scd/apdu.h b/scd/apdu.h index 8a0d4bda8..89df45cb8 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -31,6 +31,7 @@ enum { SW_EOF_REACHED = 0x6282, SW_TERM_STATE = 0x6285, /* Selected file is in termination state. */ SW_EEPROM_FAILURE = 0x6581, + SW_ACK_TIMEOUT = 0x6600, /* OpenPGPcard: Ack timeout. */ SW_WRONG_LENGTH = 0x6700, SW_SM_NOT_SUP = 0x6882, /* Secure Messaging is not supported. */ SW_CC_NOT_SUP = 0x6884, /* Command Chaining is not supported. */ @@ -117,6 +118,7 @@ int apdu_connect (int slot); int apdu_disconnect (int slot); int apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg); +int apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg); int apdu_reset (int slot); int apdu_get_status (int slot, int hang, unsigned int *status); @@ -137,7 +139,7 @@ int apdu_send_le (int slot, int extended_mode, unsigned char **retbuf, size_t *retbuflen); int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **retbuf, size_t *retbuflen); const char *apdu_get_reader_name (int slot); diff --git a/scd/app-common.h b/scd/app-common.h index 38e6cc609..3df896228 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -25,9 +25,16 @@ #include <npth.h> #include <ksba.h> +/* Flags used with app_change_pin. */ +#define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */ +#define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */ +#define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */ -#define APP_CHANGE_FLAG_RESET 1 -#define APP_CHANGE_FLAG_NULLPIN 2 +/* Flags used with app_genkey. */ +#define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ + +/* Flags used with app_writekey. */ +#define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ /* Bit flags set by the decipher function into R_INFO. */ #define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ @@ -51,8 +58,10 @@ struct app_ctx_s { unsigned char *serialno; /* Serialnumber in raw form, allocated. */ size_t serialnolen; /* Length in octets of serialnumber. */ + const char *cardtype; /* NULL or string with the token's type. */ const char *apptype; - unsigned int card_version; + unsigned int cardversion;/* Firmware version of the token or 0. */ + unsigned int appversion; /* Version of the application or 0. */ unsigned int card_status; unsigned int reset_requested:1; unsigned int periodical_check_needed:1; @@ -66,7 +75,7 @@ struct app_ctx_s { gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); - gpg_error_t (*readkey) (app_t app, int advanced, const char *certid, + gpg_error_t (*readkey) (app_t app, const char *certid, unsigned char **pk, size_t *pklen); gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name); gpg_error_t (*setattr) (app_t app, const char *name, @@ -101,8 +110,8 @@ struct app_ctx_s { void *pincb_arg, const unsigned char *pk, size_t pklen); gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - time_t createtime, + const char *keyref, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, @@ -118,6 +127,8 @@ struct app_ctx_s { /*-- app-help.c --*/ unsigned int app_help_count_bits (const unsigned char *a, size_t len); gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip); +gpg_error_t app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen); size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); @@ -139,7 +150,7 @@ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid, unsigned char **cert, size_t *certlen); -gpg_error_t app_readkey (app_t app, ctrl_t ctrl, int advanced, +gpg_error_t app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen); gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name); gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name, @@ -173,16 +184,16 @@ gpg_error_t app_writekey (app_t app, ctrl_t ctrl, void *pincb_arg, const unsigned char *keydata, size_t keydatalen); gpg_error_t app_genkey (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - time_t createtime, + const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer); gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, - const char *chvnostr, int reset_mode, - gpg_error_t (*pincb)(void*, const char *, char **), - void *pincb_arg); + const char *chvnostr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); @@ -206,5 +217,8 @@ gpg_error_t app_select_geldkarte (app_t app); /*-- app-sc-hsm.c --*/ gpg_error_t app_select_sc_hsm (app_t app); +/*-- app-piv.c --*/ +gpg_error_t app_select_piv (app_t app); + #endif /*GNUPG_SCD_APP_COMMON_H*/ diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c index bea285687..983bed6e1 100644 --- a/scd/app-dinsig.c +++ b/scd/app-dinsig.c @@ -415,7 +415,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, && indatalen != (15+20) && indatalen != (19+32)) return gpg_error (GPG_ERR_INV_VALUE); - /* Check that the provided ID is vaid. This is not really needed + /* Check that the provided ID is valid. This is not really needed but we do it to enforce correct usage by the caller. */ if (strncmp (keyidstr, "DINSIG.", 7) ) return gpg_error (GPG_ERR_INV_ID); diff --git a/scd/app-geldkarte.c b/scd/app-geldkarte.c index 510beb550..85bcedc4f 100644 --- a/scd/app-geldkarte.c +++ b/scd/app-geldkarte.c @@ -254,7 +254,7 @@ copy_bcd (const unsigned char *string, size_t length) } -/* Convert the BCD number at STING of LENGTH into an integer and store +/* Convert the BCD number at STRING 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) diff --git a/scd/app-help.c b/scd/app-help.c index 842a73d5a..f0f551c55 100644 --- a/scd/app-help.c +++ b/scd/app-help.c @@ -29,9 +29,9 @@ #include "../common/tlv.h" -/* Count the number of bits, assuming the A represents an unsigned big - integer of length LEN bytes. If A is NULL a length of 0 is - returned. */ +/* Count the number of bits, assuming that A represents an unsigned + * big integer of length LEN bytes. If A is NULL a length of 0 is + * returned. */ unsigned int app_help_count_bits (const unsigned char *a, size_t len) { @@ -87,6 +87,45 @@ app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip) } +gpg_error_t +app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen) +{ + gpg_error_t err; + ksba_cert_t kc; + unsigned char *pk; + size_t pklen; + + *r_pk = NULL; + *r_pklen = 0; + + err = ksba_cert_new (&kc); + if (err) + return err; + + err = ksba_cert_init_from_mem (kc, cert, certlen); + if (err) + goto leave; + + pk = ksba_cert_get_public_key (kc); + if (!pk) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + pklen = gcry_sexp_canon_len (pk, 0, NULL, &err); + + leave: + if (!err) + { + *r_pk = pk; + *r_pklen = pklen; + } + else + ksba_free (pk); + ksba_cert_release (kc); + return err; +} /* Given the SLOT and the File ID FID, return the length of the certificate contained in that file. Returns 0 if the file does not diff --git a/scd/app-nks.c b/scd/app-nks.c index 9e720f0b0..40c941616 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -273,7 +273,7 @@ get_chv_status (app_t app, int sigg, int pwid) command[3] = pwid; if (apdu_send_direct (app->slot, 0, (unsigned char *)command, - 4, 0, &result, &resultlen)) + 4, 0, NULL, &result, &resultlen)) rc = -1; /* Error. */ else if (resultlen < 2) rc = -1; /* Error. */ @@ -618,17 +618,13 @@ do_readcert (app_t app, const char *certid, certificate parsing code in commands.c:cmd_readkey. For internal use PK and PKLEN may be NULL to just check for an existing key. */ static gpg_error_t -do_readkey (app_t app, int advanced, const char *keyid, - unsigned char **pk, size_t *pklen) +do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; unsigned char *buffer[2]; size_t buflen[2]; unsigned short path[1] = { 0x4500 }; - if (advanced) - return GPG_ERR_NOT_SUPPORTED; - /* We use a generic name to retrieve PK.AUT.IFD-SPK. */ if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3) ; @@ -702,7 +698,7 @@ do_writekey (app_t app, ctrl_t ctrl, else return gpg_error (GPG_ERR_INV_ID); - if (!force && !do_readkey (app, 0, keyid, NULL, NULL)) + if (!force && !do_readkey (app, keyid, NULL, NULL)) return gpg_error (GPG_ERR_EEXIST); /* Parse the S-expression. */ @@ -1169,6 +1165,9 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, if (!newdesc) return gpg_error (GPG_ERR_INV_ID); + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = switch_application (app, is_sigg); if (err) return err; @@ -1300,7 +1299,7 @@ get_nks_version (int slot) int type; if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0, - &result, &resultlen)) + NULL, &result, &resultlen)) return 2; /* NKS 2 does not support this command. */ /* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00 diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index c17452555..1e904b578 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -119,8 +119,11 @@ static struct { { 0x0104, 0, 0, 0, 0, 0, 0, 2, "Private DO 4"}, { 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"}, /* V3.0 */ - { 0x7F74, 0, 0, 1, 0, 0, 0, 0, "General Feature Management"}, + { 0x7F74, 0, 0x6E, 1, 0, 0, 0, 0, "General Feature Management"}, { 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"}, + { 0x00D6, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Signature"}, + { 0x00D7, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Decryption"}, + { 0x00D8, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Authentication"}, { 0x00F9, 0, 0, 1, 0, 0, 0, 0, "KDF data object"}, { 0 } }; @@ -473,7 +476,7 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) ; - if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11) + if (app->appversion > 0x0100 && data_objects[i].get_immediate_in_v11) { exmode = 0; rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen); @@ -640,7 +643,7 @@ count_bits (const unsigned char *a, size_t len) Where FLAGS is a plain hexadecimal number representing flag values. The lsb is here the rightmost bit. Defined flags bits are: - Bit 0 = CHV1 and CHV2 are not syncronized + Bit 0 = CHV1 and CHV2 are not synchronized Bit 1 = CHV2 has been set to the default PIN of "123456" (this implies that bit 0 is also set). @@ -813,7 +816,7 @@ store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, xfree (buffer); - tag = (app->card_version > 0x0007? 0xC7 : 0xC6) + keynumber; + tag = (app->appversion > 0x0007? 0xC7 : 0xC6) + keynumber; flush_cache_item (app, 0xC5); tag2 = 0xCE + keynumber; flush_cache_item (app, 0xCD); @@ -822,7 +825,7 @@ store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, if (rc) log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); - if (!rc && app->card_version > 0x0100) + if (!rc && app->appversion > 0x0100) { unsigned char buf[4]; @@ -985,6 +988,9 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) { "PRIVATE-DO-4", 0x0104 }, { "$AUTHKEYID", 0x0000, -3 }, { "$DISPSERIALNO",0x0000, -4 }, + { "UIF-1", 0x00D6, 0 }, + { "UIF-2", 0x00D7, 0 }, + { "UIF-3", 0x00D8, 0 }, { "KDF", 0x00F9 }, { NULL, 0 } }; @@ -1442,13 +1448,13 @@ ecdh_params (const char *curve) /* See RFC-6637 for those constants. 0x03: Number of bytes 0x01: Version for this parameter format - KDF algo - KEK algo + KEK digest algorithm + KEK cipher algorithm */ if (nbits <= 256) return (const unsigned char*)"\x03\x01\x08\x07"; else if (nbits <= 384) - return (const unsigned char*)"\x03\x01\x09\x08"; + return (const unsigned char*)"\x03\x01\x09\x09"; else return (const unsigned char*)"\x03\x01\x0a\x09"; } @@ -1649,7 +1655,7 @@ get_public_key (app_t app, int keyno) m = e = NULL; /* (avoid cc warning) */ - if (app->card_version > 0x0100) + if (app->appversion > 0x0100) { int exmode, le_value; @@ -1779,6 +1785,7 @@ send_keypair_info (app_t app, ctrl_t ctrl, int key) unsigned char grip[20]; char gripstr[41]; char idbuf[50]; + const char *usage; err = get_public_key (app, keyno); if (err) @@ -1796,10 +1803,19 @@ send_keypair_info (app_t app, ctrl_t ctrl, int key) bin2hex (grip, 20, gripstr); + switch (keyno) + { + case 0: usage = "sc"; break; + case 1: usage = "e"; break; + case 2: usage = "sa"; break; + default: usage = ""; break; + } + sprintf (idbuf, "OPENPGP.%d", keyno+1); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, idbuf, strlen (idbuf), + usage, strlen (usage), NULL, (size_t)0); leave: @@ -1822,11 +1838,19 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) do_getattr (app, ctrl, "PUBKEY-URL"); do_getattr (app, ctrl, "LOGIN-DATA"); do_getattr (app, ctrl, "KEY-FPR"); - if (app->card_version > 0x0100) + if (app->appversion > 0x0100) do_getattr (app, ctrl, "KEY-TIME"); do_getattr (app, ctrl, "CA-FPR"); do_getattr (app, ctrl, "CHV-STATUS"); do_getattr (app, ctrl, "SIG-COUNTER"); + if (app->app_local->extcap.kdf_do) + do_getattr (app, ctrl, "KDF"); + if (app->app_local->extcap.has_button) + { + do_getattr (app, ctrl, "UIF-1"); + do_getattr (app, ctrl, "UIF-2"); + do_getattr (app, ctrl, "UIF-3"); + } if (app->app_local->extcap.private_dos) { do_getattr (app, ctrl, "PRIVATE-DO-1"); @@ -1851,10 +1875,8 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) buffer. On error PK and PKLEN are not changed and an error code is returned. */ static gpg_error_t -do_readkey (app_t app, int advanced, const char *keyid, - unsigned char **pk, size_t *pklen) +do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { -#if GNUPG_MAJOR_VERSION > 1 gpg_error_t err; int keyno; unsigned char *buf; @@ -1876,45 +1898,17 @@ do_readkey (app_t app, int advanced, const char *keyid, if (!buf) return gpg_error (GPG_ERR_NO_PUBKEY); - if (advanced) - { - gcry_sexp_t s_key; - - err = gcry_sexp_new (&s_key, buf, app->app_local->pk[keyno].keylen, 0); - if (err) - return err; - - *pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); - *pk = xtrymalloc (*pklen); - if (!*pk) - { - err = gpg_error_from_syserror (); - *pklen = 0; - return err; - } - - gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, *pk, *pklen); - gcry_sexp_release (s_key); - /* Decrement for trailing '\0' */ - *pklen = *pklen - 1; - } - else + *pklen = app->app_local->pk[keyno].keylen; + *pk = xtrymalloc (*pklen); + if (!*pk) { - *pklen = app->app_local->pk[keyno].keylen; - *pk = xtrymalloc (*pklen); - if (!*pk) - { - err = gpg_error_from_syserror (); - *pklen = 0; - return err; - } - memcpy (*pk, buf, *pklen); + err = gpg_error_from_syserror (); + *pklen = 0; + return err; } + memcpy (*pk, buf, *pklen); return 0; -#else - return gpg_error (GPG_ERR_NOT_IMPLEMENTED); -#endif } /* Read the standard certificate of an OpenPGP v2 card. It is @@ -2437,29 +2431,33 @@ do_setattr (app_t app, const char *name, static struct { const char *name; int tag; + int flush_tag; /* The tag which needs to be flushed or 0. */ int need_chv; int special; unsigned int need_v2:1; } table[] = { - { "DISP-NAME", 0x005B, 3 }, - { "LOGIN-DATA", 0x005E, 3, 2 }, - { "DISP-LANG", 0x5F2D, 3 }, - { "DISP-SEX", 0x5F35, 3 }, - { "PUBKEY-URL", 0x5F50, 3 }, - { "CHV-STATUS-1", 0x00C4, 3, 1 }, - { "CA-FPR-1", 0x00CA, 3 }, - { "CA-FPR-2", 0x00CB, 3 }, - { "CA-FPR-3", 0x00CC, 3 }, - { "PRIVATE-DO-1", 0x0101, 2 }, - { "PRIVATE-DO-2", 0x0102, 3 }, - { "PRIVATE-DO-3", 0x0103, 2 }, - { "PRIVATE-DO-4", 0x0104, 3 }, - { "CERT-3", 0x7F21, 3, 0, 1 }, - { "SM-KEY-ENC", 0x00D1, 3, 0, 1 }, - { "SM-KEY-MAC", 0x00D2, 3, 0, 1 }, - { "KEY-ATTR", 0, 0, 3, 1 }, - { "AESKEY", 0x00D5, 3, 0, 1 }, - { "KDF", 0x00F9, 3, 4, 1 }, + { "DISP-NAME", 0x005B, 0, 3 }, + { "LOGIN-DATA", 0x005E, 0, 3, 2 }, + { "DISP-LANG", 0x5F2D, 0, 3 }, + { "DISP-SEX", 0x5F35, 0, 3 }, + { "PUBKEY-URL", 0x5F50, 0, 3 }, + { "CHV-STATUS-1", 0x00C4, 0, 3, 1 }, + { "CA-FPR-1", 0x00CA, 0x00C6, 3 }, + { "CA-FPR-2", 0x00CB, 0x00C6, 3 }, + { "CA-FPR-3", 0x00CC, 0x00C6, 3 }, + { "PRIVATE-DO-1", 0x0101, 0, 2 }, + { "PRIVATE-DO-2", 0x0102, 0, 3 }, + { "PRIVATE-DO-3", 0x0103, 0, 2 }, + { "PRIVATE-DO-4", 0x0104, 0, 3 }, + { "CERT-3", 0x7F21, 0, 3, 0, 1 }, + { "SM-KEY-ENC", 0x00D1, 0, 3, 0, 1 }, + { "SM-KEY-MAC", 0x00D2, 0, 3, 0, 1 }, + { "KEY-ATTR", 0, 0, 0, 3, 1 }, + { "AESKEY", 0x00D5, 0, 3, 0, 1 }, + { "UIF-1", 0x00D6, 0, 3, 5, 1 }, + { "UIF-2", 0x00D7, 0, 3, 5, 1 }, + { "UIF-3", 0x00D8, 0, 3, 5, 1 }, + { "KDF", 0x00F9, 0, 3, 4, 1 }, { NULL, 0 } }; int exmode; @@ -2471,6 +2469,9 @@ do_setattr (app_t app, const char *name, if (table[idx].need_v2 && !app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */ + if (table[idx].special == 5 && app->app_local->extcap.has_button == 0) + return gpg_error (GPG_ERR_INV_OBJ); + if (table[idx].special == 3) return change_keyattr_from_string (app, pincb, pincb_arg, value, valuelen); @@ -2491,7 +2492,8 @@ do_setattr (app_t app, const char *name, /* Flush the cache before writing it, so that the next get operation will reread the data from the card and thus get synced in case of errors (e.g. data truncated by the card). */ - flush_cache_item (app, table[idx].tag); + flush_cache_item (app, table[idx].flush_tag? table[idx].flush_tag + /* */ : table[idx].tag); if (app->app_local->cardcap.ext_lc_le && valuelen > 254) exmode = 1; /* Use extended length w/o a limit. */ @@ -2518,10 +2520,10 @@ do_setattr (app_t app, const char *name, } -/* Handle the WRITECERT command for OpenPGP. This rites the standard - certifciate to the card; CERTID needs to be set to "OPENPGP.3". - PINCB and PINCB_ARG are the usual arguments for the pinentry - callback. */ +/* Handle the WRITECERT command for OpenPGP. This writes the standard + * certificate to the card; CERTID needs to be set to "OPENPGP.3". + * PINCB and PINCB_ARG are the usual arguments for the pinentry + * callback. */ static gpg_error_t do_writecert (app_t app, ctrl_t ctrl, const char *certidstr, @@ -2546,6 +2548,42 @@ do_writecert (app_t app, ctrl_t ctrl, } +static gpg_error_t +clear_chv_status (app_t app, int chvno) +{ + unsigned char apdu[4]; + gpg_error_t err; + + if (!app->app_local->extcap.is_v2) + return GPG_ERR_UNSUPPORTED_OPERATION; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0xff; + apdu[3] = 0x80+chvno; + + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_INV_VALUE) + err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + return err; + } + + if (chvno == 1) + { + apdu[3]++; + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + app->did_chv1 = app->did_chv2 = 0; + } + else if (chvno == 2) + app->did_chv2 = 0; + else if (chvno == 3) + app->did_chv3 = 0; + + return err; +} + /* Handle the PASSWD command. The following combinations are possible: @@ -2561,6 +2599,8 @@ do_writecert (app_t app, ctrl_t ctrl, - 2 1 Verify CHV2 and set a new CHV1 and CHV2. - 2 2 Verify Reset Code and set a new PW1. - 3 any Verify CHV3/PW3 and set a new CHV3/PW3. + + The CHVNO can be prefixed with "OPENPGP.". */ static gpg_error_t do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, @@ -2569,7 +2609,7 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, void *pincb_arg) { int rc = 0; - int chvno = atoi (chvnostr); + int chvno; char *resetcode = NULL; char *oldpinvalue = NULL; char *pinvalue = NULL; @@ -2582,10 +2622,25 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int pinlen = 0; (void)ctrl; + + if (digitp (chvnostr)) + chvno = atoi (chvnostr); + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.1")) + chvno = 1; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.2")) + chvno = 2; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.3")) + chvno = 3; + else + return gpg_error (GPG_ERR_INV_ID); + memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = minlen; + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return clear_chv_status (app, chvno); + if (reset_mode && chvno == 3) { rc = gpg_error (GPG_ERR_INV_ID); @@ -2839,10 +2894,10 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ } else - { + { rc = pin2hash_if_kdf (app, chvno, oldpinvalue, &pinlen0); if (!rc) - rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen); + rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen); if (!rc) rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, oldpinvalue, pinlen0, @@ -3644,7 +3699,7 @@ rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), /* Store the key. */ err = iso7816_put_data (app->slot, 0, - (app->card_version > 0x0007? 0xE0:0xE9)+keyno, + (app->appversion > 0x0007? 0xE0:0xE9)+keyno, template, template_len); } if (err) @@ -4016,8 +4071,8 @@ do_writekey (app_t app, ctrl_t ctrl, /* Handle the GENKEY command. */ static gpg_error_t -do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - time_t createtime, +do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -4033,6 +4088,8 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, int exmode = 0; int le_value = 256; /* Use legacy value. */ + (void)keytype; /* Ignored for OpenPGP cards. */ + if (keyno < 0 || keyno > 2) return gpg_error (GPG_ERR_INV_ID); @@ -4081,7 +4138,7 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); - err = iso7816_generate_keypair (app->slot, exmode, + err = iso7816_generate_keypair (app->slot, exmode, 0x80, 0, (keyno == 0? "\xB6" : keyno == 1? "\xB8" : "\xA4"), 2, le_value, &buffer, &buflen); @@ -4381,7 +4438,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, log_info (_("signatures created so far: %lu\n"), sigcount); /* Check CHV if needed. */ - if (!app->did_chv1 || app->force_chv1 ) + if (!app->did_chv1 || app->force_chv1) { char *pinvalue; int pinlen; @@ -4429,6 +4486,11 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, } rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value, outdata, outdatalen); + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); + else if (!rc && app->force_chv1) + app->did_chv1 = 0; + return rc; } @@ -4535,6 +4597,8 @@ do_auth (app_t app, const char *keyidstr, rc = iso7816_internal_authenticate (app->slot, exmode, indata, indatalen, le_value, outdata, outdatalen); + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); } return rc; } @@ -4758,7 +4822,7 @@ do_decipher (app_t app, const char *keyidstr, indata, indatalen, le_value, padind, outdata, outdatalen); xfree (fixbuf); - if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) + if (!rc && app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) { unsigned char prefix = 0; @@ -4782,10 +4846,12 @@ do_decipher (app_t app, const char *keyidstr, *outdatalen = *outdatalen + 1; } } + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */ && app->app_local->manufacturer == 5 - && app->card_version == 0x0200) + && app->appversion == 0x0200) log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)" " do not work with encryption keys > 2048 bits\n"); @@ -5144,8 +5210,8 @@ app_select_openpgp (app_t app) log_printhex (buffer, buflen, ""); } - app->card_version = buffer[6] << 8; - app->card_version |= buffer[7]; + app->appversion = buffer[6] << 8; + app->appversion |= buffer[7]; manufacturer = (buffer[8]<<8 | buffer[9]); xfree (app->serialno); @@ -5161,10 +5227,10 @@ app_select_openpgp (app_t app) app->app_local->manufacturer = manufacturer; - if (app->card_version >= 0x0200) + if (app->appversion >= 0x0200) app->app_local->extcap.is_v2 = 1; - if (app->card_version >= 0x0300) + if (app->appversion >= 0x0300) app->app_local->extcap.extcap_v3 = 1; /* Read the historical bytes. */ @@ -5231,7 +5297,7 @@ app_select_openpgp (app_t app) /* Some of the first cards accidentally don't set the CHANGE_FORCE_CHV bit but allow it anyway. */ - if (app->card_version <= 0x0100 && manufacturer == 1) + if (app->appversion <= 0x0100 && manufacturer == 1) app->app_local->extcap.change_force_chv = 1; /* Check optional DO of "General Feature Management" for button. */ diff --git a/scd/app-piv.c b/scd/app-piv.c new file mode 100644 index 000000000..5748c70b8 --- /dev/null +++ b/scd/app-piv.c @@ -0,0 +1,3350 @@ +/* app-piv.c - The OpenPGP card application. + * Copyright (C) 2019 g10 Code GmbH + * + * 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 + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* Some notes: + * - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4 + * + * - Access control matrix: + * | Action | 9B | PIN | PUK | | + * |--------------+-----+-----+-----+------------------------------| + * | Generate key | yes | | | | + * | Change 9B | yes | | | | + * | Change retry | yes | yes | | Yubikey only | + * | Import key | yes | | | | + * | Import cert | yes | | | | + * | Change CHUID | yes | | | | + * | Reset card | | | | PIN and PUK in blocked state | + * | Verify PIN | | yes | | | + * | Sign data | | yes | | | + * | Decrypt data | | yes | | | + * | Change PIN | | yes | | | + * | Change PUK | | | yes | | + * | Unblock PIN | | | yes | New PIN required | + * |---------------------------------------------------------------| + * (9B indicates the 24 byte PIV Card Application Administration Key) + * + * - When generating a key we store the created public key in the + * corresponding data object, so that gpg and gpgsm are able to get + * the public key, create a certificate and store that then in that + * data object. That is not standard compliant but due to the use + * of other tags, it should not harm. See do_genkey for the actual + * used tag structure. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> + +#include "scdaemon.h" + +#include "../common/util.h" +#include "../common/i18n.h" +#include "iso7816.h" +#include "app-common.h" +#include "../common/tlv.h" +#include "../common/host2net.h" +#include "apdu.h" /* We use apdu_send_direct. */ + +#define PIV_ALGORITHM_3DES_ECB_0 0x00 +#define PIV_ALGORITHM_2DES_ECB 0x01 +#define PIV_ALGORITHM_2DES_CBC 0x02 +#define PIV_ALGORITHM_3DES_ECB 0x03 +#define PIV_ALGORITHM_3DES_CBC 0x04 +#define PIV_ALGORITHM_RSA 0x07 +#define PIV_ALGORITHM_AES128_ECB 0x08 +#define PIV_ALGORITHM_AES128_CBC 0x09 +#define PIV_ALGORITHM_AES192_ECB 0x0A +#define PIV_ALGORITHM_AES192_CBC 0x0B +#define PIV_ALGORITHM_AES256_ECB 0x0C +#define PIV_ALGORITHM_AES256_CBC 0x0D +#define PIV_ALGORITHM_ECC_P256 0x11 +#define PIV_ALGORITHM_ECC_P384 0x14 + + + +/* A table describing the DOs of a PIV card. */ +struct data_object_s +{ + unsigned int tag; + unsigned int mandatory:1; + unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */ + unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN, + 3=VCIand(PINorOCC) */ + unsigned int dont_cache:1; /* Data item will not be cached. */ + unsigned int flush_on_error:1; /* Flush cached item on error. */ + unsigned int keypair:1; /* Has a public key for a keypair. */ + const char keyref[3]; /* The key reference. */ + const char *oidsuffix; /* Suffix of the OID. */ + const char *usage; /* Usage string for a keypair or NULL. */ + const char *desc; /* Description of the DO. */ +}; +typedef struct data_object_s *data_object_t; +static struct data_object_s data_objects[] = { + { 0x5FC107, 1, 0,1, 0,0, 0, "", "1.219.0", NULL, + "Card Capability Container"}, + { 0x5FC102, 1, 0,0, 0,0, 0, "", "2.48.0", NULL, + "Cardholder Unique Id" }, + { 0x5FC105, 1, 0,1, 0,0, 1, "9A", "2.1.1", "a", + "Cert PIV Authentication" }, + { 0x5FC103, 1, 2,2, 0,0, 0, "", "2.96.16", NULL, + "Cardholder Fingerprints" }, + { 0x5FC106, 1, 0,1, 0,0, 0, "", "2.144.0", NULL, + "Security Object" }, + { 0x5FC108, 1, 2,2, 0,0, 0, "", "2.96.48", NULL, + "Cardholder Facial Image" }, + { 0x5FC101, 1, 0,0, 0,0, 1, "9E", "2.5.0", "a", + "Cert Card Authentication"}, + { 0x5FC10A, 0, 0,1, 0,0, 1, "9C", "2.1.0", "sc", + "Cert Digital Signature" }, + { 0x5FC10B, 0, 0,1, 0,0, 1, "9D", "2.1.2", "e", + "Cert Key Management" }, + { 0x5FC109, 0, 3,3, 0,0, 0, "", "2.48.1", NULL, + "Printed Information" }, + { 0x7E, 0, 0,0, 0,0, 0, "", "2.96.80", NULL, + "Discovery Object" }, + { 0x5FC10C, 0, 0,1, 0,0, 0, "", "2.96.96", NULL, + "Key History Object" }, + { 0x5FC10D, 0, 0,1, 0,0, 0, "82", "2.16.1", "e", + "Retired Cert Key Mgm 1" }, + { 0x5FC10E, 0, 0,1, 0,0, 0, "83", "2.16.2", "e", + "Retired Cert Key Mgm 2" }, + { 0x5FC10F, 0, 0,1, 0,0, 0, "84", "2.16.3", "e", + "Retired Cert Key Mgm 3" }, + { 0x5FC110, 0, 0,1, 0,0, 0, "85", "2.16.4", "e", + "Retired Cert Key Mgm 4" }, + { 0x5FC111, 0, 0,1, 0,0, 0, "86", "2.16.5", "e", + "Retired Cert Key Mgm 5" }, + { 0x5FC112, 0, 0,1, 0,0, 0, "87", "2.16.6", "e", + "Retired Cert Key Mgm 6" }, + { 0x5FC113, 0, 0,1, 0,0, 0, "88", "2.16.7", "e", + "Retired Cert Key Mgm 7" }, + { 0x5FC114, 0, 0,1, 0,0, 0, "89", "2.16.8", "e", + "Retired Cert Key Mgm 8" }, + { 0x5FC115, 0, 0,1, 0,0, 0, "8A", "2.16.9", "e", + "Retired Cert Key Mgm 9" }, + { 0x5FC116, 0, 0,1, 0,0, 0, "8B", "2.16.10", "e", + "Retired Cert Key Mgm 10" }, + { 0x5FC117, 0, 0,1, 0,0, 0, "8C", "2.16.11", "e", + "Retired Cert Key Mgm 11" }, + { 0x5FC118, 0, 0,1, 0,0, 0, "8D", "2.16.12", "e", + "Retired Cert Key Mgm 12" }, + { 0x5FC119, 0, 0,1, 0,0, 0, "8E", "2.16.13", "e", + "Retired Cert Key Mgm 13" }, + { 0x5FC11A, 0, 0,1, 0,0, 0, "8F", "2.16.14", "e", + "Retired Cert Key Mgm 14" }, + { 0x5FC11B, 0, 0,1, 0,0, 0, "90", "2.16.15", "e", + "Retired Cert Key Mgm 15" }, + { 0x5FC11C, 0, 0,1, 0,0, 0, "91", "2.16.16", "e", + "Retired Cert Key Mgm 16" }, + { 0x5FC11D, 0, 0,1, 0,0, 0, "92", "2.16.17", "e", + "Retired Cert Key Mgm 17" }, + { 0x5FC11E, 0, 0,1, 0,0, 0, "93", "2.16.18", "e", + "Retired Cert Key Mgm 18" }, + { 0x5FC11F, 0, 0,1, 0,0, 0, "94", "2.16.19", "e", + "Retired Cert Key Mgm 19" }, + { 0x5FC120, 0, 0,1, 0,0, 0, "95", "2.16.20", "e", + "Retired Cert Key Mgm 20" }, + { 0x5FC121, 0, 2,2, 0,0, 0, "", "2.16.21", NULL, + "Cardholder Iris Images" }, + { 0x7F61, 0, 0,0, 0,0, 0, "", "2.16.22", NULL, + "BIT Group Template" }, + { 0x5FC122, 0, 0,0, 0,0, 0, "", "2.16.23", NULL, + "SM Cert Signer" }, + { 0x5FC123, 0, 3,3, 0,0, 0, "", "2.16.24", NULL, + "Pairing Code Ref Data" }, + { 0 } + /* Other key reference values without a data object: + * "00" Global PIN (not cleared by application switching) + * "04" PIV Secure Messaging Key + * "80" PIV Application PIN + * "81" PIN Unblocking Key + * "96" Primary Finger OCC + * "97" Secondary Finger OCC + * "98" Pairing Code + * "9B" PIV Card Application Administration Key + * + * Yubikey specific data objects: + * "F9" Attestation key (preloaded can be replaced) + */ +}; + + +/* One cache item for DOs. */ +struct cache_s { + struct cache_s *next; + int tag; + size_t length; + unsigned char data[1]; +}; + + +/* Object with application specific data. */ +struct app_local_s { + /* A linked list with cached DOs. */ + struct cache_s *cache; + + /* Various flags. */ + struct + { + unsigned int yubikey:1; /* This is on a Yubikey. */ + } flags; + +}; + + +/***** Local prototypes *****/ +static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag, + char **r_keygripstr, int *got_cert); +static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen, + gcry_sexp_t *r_sexp); +static gpg_error_t genkey_parse_ecc (const unsigned char *data, size_t datalen, + int mechanism, gcry_sexp_t *r_sexp); + + + + + +/* Deconstructor. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + struct cache_s *c, *c2; + + for (c = app->app_local->cache; c; c = c2) + { + c2 = c->next; + xfree (c); + } + + xfree (app->app_local); + app->app_local = NULL; + } +} + + +/* Wrapper around iso7816_get_data which first tries to get the data + * from the cache. With GET_IMMEDIATE passed as true, the cache is + * bypassed. The tag-53 container is also removed. */ +static gpg_error_t +get_cached_data (app_t app, int tag, + unsigned char **result, size_t *resultlen, + int get_immediate) +{ + gpg_error_t err; + int i; + unsigned char *p; + const unsigned char *s; + size_t len, n; + struct cache_s *c; + + *result = NULL; + *resultlen = 0; + + if (!get_immediate) + { + for (c=app->app_local->cache; c; c = c->next) + if (c->tag == tag) + { + if(c->length) + { + p = xtrymalloc (c->length); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, c->data, c->length); + *result = p; + } + + *resultlen = c->length; + + return 0; + } + } + + err = iso7816_get_data_odd (app->slot, 0, tag, &p, &len); + if (err) + return err; + + /* Unless the Discovery Object or the BIT Group Template is + * requested, remove the outer container. + * (SP800-73.4 Part 2, section 3.1.2) */ + if (tag == 0x7E || tag == 0x7F61) + ; + else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n))) + { + memmove (p, s, n); + len = n; + } + + if (len) + *result = p; + *resultlen = len; + + /* Check whether we should cache this object. */ + if (get_immediate) + return 0; + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].tag == tag) + { + if (data_objects[i].dont_cache) + return 0; + break; + } + + /* Okay, cache it. */ + for (c=app->app_local->cache; c; c = c->next) + log_assert (c->tag != tag); + + c = xtrymalloc (sizeof *c + len); + if (c) + { + if (len) + memcpy (c->data, p, len); + else + xfree (p); + c->length = len; + c->tag = tag; + c->next = app->app_local->cache; + app->app_local->cache = c; + } + + return 0; +} + + +/* Remove data object described by TAG from the cache. If TAG is 0 + * all cache iterms are flushed. */ +static void +flush_cached_data (app_t app, int tag) +{ + struct cache_s *c, *cprev; + + for (c=app->app_local->cache, cprev=NULL; c; cprev=c, c = c->next) + if (c->tag == tag || !tag) + { + if (cprev) + cprev->next = c->next; + else + app->app_local->cache = c->next; + xfree (c); + + for (c=app->app_local->cache; c ; c = c->next) + { + log_assert (c->tag != tag); /* Oops: duplicated entry. */ + } + return; + } +} + + +/* Get the DO identified by TAG from the card in SLOT and return a + * buffer with its content in RESULT and NBYTES. The return value is + * NULL if not found or a pointer which must be used to release the + * buffer holding value. */ +static void * +get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, + int *r_err) +{ + gpg_error_t err; + int i; + unsigned char *buffer; + size_t buflen; + unsigned char *value; + size_t valuelen; + gpg_error_t dummyerr; + + if (!r_err) + r_err = &dummyerr; + + *result = NULL; + *nbytes = 0; + *r_err = 0; + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + + value = NULL; + err = gpg_error (GPG_ERR_ENOENT); + + if (!value) /* Not in a constructed DO, try simple. */ + { + err = get_cached_data (app, tag, &buffer, &buflen, + data_objects[i].dont_cache); + if (!err) + { + value = buffer; + valuelen = buflen; + } + } + + if (!err) + { + *nbytes = valuelen; + *result = value; + return buffer; + } + + *r_err = err; + return NULL; +} + + +static void +dump_all_do (int slot) +{ + gpg_error_t err; + int i; + unsigned char *buffer; + size_t buflen; + + for (i=0; data_objects[i].tag; i++) + { + /* We don't try extended length APDU because such large DO would + be pretty useless in a log file. */ + err = iso7816_get_data_odd (slot, 0, data_objects[i].tag, + &buffer, &buflen); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_ENOENT + && !data_objects[i].mandatory) + ; + else + log_info ("DO '%s' not available: %s\n", + data_objects[i].desc, gpg_strerror (err)); + } + else + { + if (data_objects[i].tag == 0x5FC109) + log_info ("DO '%s': '%.*s'\n", data_objects[i].desc, + (int)buflen, buffer); + else + { + log_info ("DO '%s': ", data_objects[i].desc); + if (buflen > 16 && opt.verbose < 2) + { + log_printhex (buffer, 16, NULL); + log_printf ("[...]\n"); + } + else + log_printhex (buffer, buflen, ""); + } + + } + xfree (buffer); buffer = NULL; + } +} + + +/* Create a TLV tag and value and store it at BUFFER. Return the + * length of tag and length. A LENGTH greater than 65535 is + * truncated. TAG must be less or equal to 2^16. If BUFFER is NULL, + * only the required length is computed. */ +static size_t +add_tlv (unsigned char *buffer, unsigned int tag, size_t length) +{ + if (length > 0xffff) + length = 0xffff; + + if (buffer) + { + unsigned char *p = buffer; + + if (tag > 0xff) + *p++ = tag >> 8; + *p++ = tag; + if (length < 128) + *p++ = length; + else if (length < 256) + { + *p++ = 0x81; + *p++ = length; + } + else + { + *p++ = 0x82; + *p++ = length >> 8; + *p++ = length; + } + + return p - buffer; + } + else + { + size_t n = 0; + + if (tag > 0xff) + n++; + n++; + if (length < 128) + n++; + else if (length < 256) + n += 2; + else + n += 3; + return n; + } +} + + +/* Function to build a list of TLV and return the result in a mallcoed + * buffer. The varargs are tuples of (int,size_t,void) each with the + * tag, the length and the actual data. A (0,0,NULL) tuple terminates + * the list. Up to 10 tuples are supported. If SECMEM is true the + * returned buffer is allocated in secure memory. */ +static gpg_error_t +concat_tlv_list (int secure, unsigned char **r_result, size_t *r_resultlen, ...) +{ + gpg_error_t err; + va_list arg_ptr; + struct { + int tag; + unsigned int len; + unsigned int contlen; + const void *data; + } argv[10]; + int i, j, argc; + unsigned char *data = NULL; + size_t datalen; + unsigned char *p; + size_t n; + + *r_result = NULL; + *r_resultlen = 0; + + /* Collect all args. Check that length is <= 2^16 to match the + * behaviour of add_tlv. */ + va_start (arg_ptr, r_resultlen); + argc = 0; + while (((argv[argc].tag = va_arg (arg_ptr, int)))) + { + argv[argc].len = va_arg (arg_ptr, size_t); + argv[argc].contlen = 0; + argv[argc].data = va_arg (arg_ptr, const void *); + if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff) + { + va_end (arg_ptr); + err = gpg_error (GPG_ERR_EINVAL); + goto leave; + } + argc++; + } + va_end (arg_ptr); + + /* Compute the required buffer length and allocate the buffer. */ + datalen = 0; + for (i=0; i < argc; i++) + { + if (!argv[i].len && !argv[i].data) + { + /* Constructed tag. Compute its length. Note that we + * currently allow only one constructed tag in the list. */ + for (n=0, j = i + 1; j < argc; j++) + { + log_assert (!(!argv[j].len && !argv[j].data)); + n += add_tlv (NULL, argv[j].tag, argv[j].len); + n += argv[j].len; + } + argv[i].contlen = n; + datalen += add_tlv (NULL, argv[i].tag, n); + } + else + { + datalen += add_tlv (NULL, argv[i].tag, argv[i].len); + datalen += argv[i].len; + } + } + data = secure? xtrymalloc_secure (datalen) : xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Copy that data to the buffer. */ + p = data; + for (i=0; i < argc; i++) + { + if (!argv[i].len && !argv[i].data) + { + /* Constructed tag. */ + p += add_tlv (p, argv[i].tag, argv[i].contlen); + } + else + { + p += add_tlv (p, argv[i].tag, argv[i].len); + memcpy (p, argv[i].data, argv[i].len); + p += argv[i].len; + } + } + log_assert ( data + datalen == p ); + *r_result = data; + data = NULL; + *r_resultlen = datalen; + err = 0; + + leave: + xfree (data); + return err; +} + + +/* Wrapper around iso7816_put_data_odd which also sets the tag into + * the '5C' data object. The varargs are tuples of (int,size_t,void) + * with the tag, the length and the actual data. A (0,0,NULL) tuple + * terminates the list. Up to 10 tuples are supported. */ +static gpg_error_t +put_data (int slot, unsigned int tag, ...) +{ + gpg_error_t err; + va_list arg_ptr; + struct { + int tag; + size_t len; + const void *data; + } argv[10]; + int i, argc; + unsigned char data5c[5]; + size_t data5clen; + unsigned char *data = NULL; + size_t datalen; + unsigned char *p; + size_t n; + + /* Collect all args. Check that length is <= 2^16 to match the + * behaviour of add_tlv. */ + va_start (arg_ptr, tag); + argc = 0; + while (((argv[argc].tag = va_arg (arg_ptr, int)))) + { + argv[argc].len = va_arg (arg_ptr, size_t); + argv[argc].data = va_arg (arg_ptr, const void *); + if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff) + { + va_end (arg_ptr); + return GPG_ERR_EINVAL; + } + argc++; + } + va_end (arg_ptr); + + /* Build the TLV with the tag to be updated. */ + data5c[0] = 0x5c; /* Tag list */ + if (tag <= 0xff) + { + data5c[1] = 1; + data5c[2] = tag; + data5clen = 3; + } + else if (tag <= 0xffff) + { + data5c[1] = 2; + data5c[2] = (tag >> 8); + data5c[3] = tag; + data5clen = 4; + } + else + { + data5c[1] = 3; + data5c[2] = (tag >> 16); + data5c[3] = (tag >> 8); + data5c[4] = tag; + data5clen = 5; + } + + /* Compute the required buffer length and allocate the buffer. */ + n = 0; + for (i=0; i < argc; i++) + { + n += add_tlv (NULL, argv[i].tag, argv[i].len); + n += argv[i].len; + } + datalen = data5clen + add_tlv (NULL, 0x53, n) + n; + data = xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Copy that data to the buffer. */ + p = data; + memcpy (p, data5c, data5clen); + p += data5clen; + p += add_tlv (p, 0x53, n); + for (i=0; i < argc; i++) + { + p += add_tlv (p, argv[i].tag, argv[i].len); + memcpy (p, argv[i].data, argv[i].len); + p += argv[i].len; + } + log_assert ( data + datalen == p ); + err = iso7816_put_data_odd (slot, -1 /* use command chaining */, + 0x3fff, data, datalen); + + leave: + xfree (data); + return err; +} + + +/* Parse the key reference KEYREFSTR which is expected to hold a key + * reference for a CHV object. Return the one octet keyref or -1 for + * an invalid reference. */ +static int +parse_chv_keyref (const char *keyrefstr) +{ + if (!keyrefstr) + return -1; + else if (!ascii_strcasecmp (keyrefstr, "PIV.00")) + return 0x00; + else if (!ascii_strcasecmp (keyrefstr, "PIV.80")) + return 0x80; + else if (!ascii_strcasecmp (keyrefstr, "PIV.81")) + return 0x81; + else + return -1; +} + + +/* Return an allocated string with the serial number in a format to be + * show to the user. With FAILMODE is true return NULL if such an + * abbreviated S/N is not available, else return the full serial + * number as a hex string. May return NULL on malloc problem. */ +static char * +get_dispserialno (app_t app, int failmode) +{ + char *result; + + if (app->serialno && app->serialnolen == 3+1+4 + && !memcmp (app->serialno, "\xff\x02\x00", 3)) + { + /* This is a 4 byte S/N of a Yubikey which seems to be printed + * on the token in decimal. Maybe they will print larger S/N + * also in decimal but we can't be sure, thus do it only for + * these 32 bit numbers. */ + unsigned long sn; + sn = app->serialno[4] * 16777216; + sn += app->serialno[5] * 65536; + sn += app->serialno[6] * 256; + sn += app->serialno[7]; + result = xtryasprintf ("yk-%lu", sn); + } + else if (failmode) + result = NULL; /* No Abbreviated S/N. */ + else + result = app_get_serialno (app); + + return result; +} + + +/* The verify command can be used to retrieve the security status of + * the card. Given the PIN name (e.g. "PIV.80" for thge application + * pin, a status is returned: + * + * -1 = Error retrieving the data, + * -2 = No such PIN, + * -3 = PIN blocked, + * -5 = Verify still valid, + * n >= 0 = Number of verification attempts left. + */ +static int +get_chv_status (app_t app, const char *keyrefstr) +{ + unsigned char apdu[4]; + unsigned int sw; + int result; + int keyref; + + keyref = parse_chv_keyref (keyrefstr); + if (!keyrefstr) + return -1; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + result = -5; /* No need to verification. */ + else if (sw == 0x6a88 || sw == 0x6a80) + result = -2; /* No such PIN. */ + else if (sw == 0x6983) + result = -3; /* PIN is blocked. */ + else if ((sw & 0xfff0) == 0x63C0) + result = (sw & 0x000f); + else + result = -1; /* Error. */ + + return result; +} + + +/* Implementation of the GETATTR command. This is similar to the + * LEARN command but returns only one value via status lines. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + static struct { + const char *name; + int tag; + int special; + } table[] = { + { "SERIALNO", 0x0000, -1 }, + { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */ + { "$DISPSERIALNO",0x0000, -3 }, + { "CHV-STATUS", 0x0000, -4 }, + { "CHV-USAGE", 0x007E, -5 } + }; + gpg_error_t err = 0; + int idx; + void *relptr; + unsigned char *value; + size_t valuelen; + const unsigned char *s; + size_t n; + + for (idx=0; (idx < DIM (table) + && ascii_strcasecmp (table[idx].name, name)); idx++) + ; + if (!(idx < DIM (table))) + err = gpg_error (GPG_ERR_INV_NAME); + else if (table[idx].special == -1) + { + char *serial = app_get_serialno (app); + + if (serial) + { + send_status_direct (ctrl, "SERIALNO", serial); + xfree (serial); + } + } + else if (table[idx].special == -2) + { + char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate. */ + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + } + else if (table[idx].special == -3) + { + char *tmp = get_dispserialno (app, 1); + + if (tmp) + { + send_status_info (ctrl, table[idx].name, + tmp, strlen (tmp), + NULL, (size_t)0); + xfree (tmp); + } + else + err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */ + } + else if (table[idx].special == -4) /* CHV-STATUS */ + { + int tmp[4]; + + tmp[0] = get_chv_status (app, "PIV.00"); + tmp[1] = get_chv_status (app, "PIV.80"); + tmp[2] = get_chv_status (app, "PIV.81"); + err = send_status_printf (ctrl, table[idx].name, "%d %d %d", + tmp[0], tmp[1], tmp[2]); + } + else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */ + { + /* We return 2 hex bytes or nothing in case the discovery object + * is not supported. */ + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); + if (relptr) + { + s = find_tlv (value, valuelen, 0x7E, &n); + if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 ) + err = send_status_printf (ctrl, table[idx].name, "%02X %02X", + s[0], s[1]); + xfree (relptr); + } + } + else + { + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); + if (relptr) + { + send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); + xfree (relptr); + } + } + + return err; +} + + +/* Authenticate the card using the Card Application Administration + * Key. (VALUE,VALUELEN) has that 24 byte key. */ +static gpg_error_t +auth_adm_key (app_t app, const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + unsigned char tmpl[4+24]; + size_t tmpllen; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + char witness[8]; + size_t n; + gcry_cipher_hd_t cipher = NULL; + + /* Prepare decryption. */ + err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipher, value, valuelen); + if (err) + goto leave; + + /* Request a witness. */ + tmpl[0] = 0x7c; + tmpl[1] = 0x02; + tmpl[2] = 0x80; + tmpl[3] = 0; /* (Empty witness requests a witness.) */ + tmpllen = 4; + err = iso7816_general_authenticate (app->slot, 0, + PIV_ALGORITHM_3DES_ECB_0, 0x9B, + tmpl, tmpllen, 0, + &outdata, &outdatalen); + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_BAD_AUTH); + if (err) + goto leave; + if (!(outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x80, &n)) + && n == 8)) + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: improper witness received\n"); + goto leave; + } + err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); + if (err) + goto leave; + + /* Return decrypted witness and send our challenge. */ + tmpl[0] = 0x7c; + tmpl[1] = 22; + tmpl[2] = 0x80; + tmpl[3] = 8; + memcpy (tmpl+4, witness, 8); + tmpl[12] = 0x81; + tmpl[13] = 8; + gcry_create_nonce (tmpl+14, 8); + tmpl[22] = 0x80; + tmpl[23] = 0; + tmpllen = 24; + xfree (outdata); + err = iso7816_general_authenticate (app->slot, 0, + PIV_ALGORITHM_3DES_ECB_0, 0x9B, + tmpl, tmpllen, 0, + &outdata, &outdatalen); + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_BAD_AUTH); + if (err) + goto leave; + if (!(outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n)) + && n == 8)) + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: improper challenge received\n"); + goto leave; + } + /* (We reuse the witness buffer.) */ + err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); + if (err) + goto leave; + if (memcmp (witness, tmpl+14, 8)) + { + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + } + + leave: + xfree (outdata); + gcry_cipher_close (cipher); + return err; +} + + +/* Set a new admin key. */ +static gpg_error_t +set_adm_key (app_t app, const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + unsigned char apdu[8+24]; + unsigned int sw; + + /* Check whether it is a weak key and that it is of proper length. */ + { + gcry_cipher_hd_t cipher; + + err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); + if (!err) + { + err = gcry_cipher_setkey (cipher, value, valuelen); + gcry_cipher_close (cipher); + } + if (err) + goto leave; + } + + if (app->app_local->flags.yubikey) + { + /* This is a Yubikey. */ + if (valuelen != 24) + { + err = gpg_error (GPG_ERR_INV_LENGTH); + goto leave; + } + + /* We use a proprietary Yubikey command. */ + apdu[0] = 0; + apdu[1] = 0xff; + apdu[2] = 0xff; + apdu[3] = 0xff; /* touch policy: 0xff=never, 0xfe = always. */ + apdu[4] = 3 + 24; + apdu[5] = PIV_ALGORITHM_3DES_ECB; + apdu[6] = 0x9b; + apdu[7] = 24; + memcpy (apdu+8, value, 24); + err = iso7816_apdu_direct (app->slot, apdu, 8+24, 0, &sw, NULL, NULL); + wipememory (apdu+8, 24); + if (err) + log_error ("piv: setting admin key failed; sw=%04x\n", sw); + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + } + else + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + + leave: + return err; +} + +/* Handle the SETATTR operation. All arguments are already basically + * checked. */ +static gpg_error_t +do_setattr (app_t app, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + static struct { + const char *name; + unsigned short tag; + unsigned short flush_tag; /* The tag which needs to be flushed or 0. */ + int special; /* Special mode to use for thus NAME. */ + } table[] = { + /* Authenticate using the PIV Card Application Administration Key + * (0x0B). Note that Yubico calls this key the "management key" + * which we don't do because that term is too similar to "Cert + * Management Key" (0x9D). */ + { "AUTH-ADM-KEY", 0x0000, 0x0000, 1 }, + { "SET-ADM-KEY", 0x0000, 0x0000, 2 } + }; + int idx; + + (void)pincb; + (void)pincb_arg; + + for (idx=0; (idx < DIM (table) + && ascii_strcasecmp (table[idx].name, name)); idx++) + ; + if (!(idx < DIM (table))) + return gpg_error (GPG_ERR_INV_NAME); + + /* Flush the cache before writing it, so that the next get operation + * will reread the data from the card and thus get synced in case of + * errors (e.g. data truncated by the card). */ + if (table[idx].tag) + flush_cached_data (app, table[idx].flush_tag? table[idx].flush_tag + /* */ : table[idx].tag); + + switch (table[idx].special) + { + case 1: + err = auth_adm_key (app, value, valuelen); + break; + + case 2: + err = set_adm_key (app, value, valuelen); + break; + + default: + err = gpg_error (GPG_ERR_BUG); + break; + } + + return err; +} + + +/* Send the KEYPAIRINFO back. DOBJ describes the data object carrying + * the key. This is used by the LEARN command. */ +static gpg_error_t +send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, + int only_keypair) +{ + gpg_error_t err = 0; + char *keygripstr = NULL; + int got_cert; + char idbuf[50]; + const char *usage; + + err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert); + if (err) + goto leave; + + usage = dobj->usage? dobj->usage : ""; + + snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref); + send_status_info (ctrl, "KEYPAIRINFO", + keygripstr, strlen (keygripstr), + idbuf, strlen (idbuf), + usage, strlen (usage), + NULL, (size_t)0); + if (!only_keypair && got_cert) + { + /* All certificates are of type 100 (Regular X.509 Cert). */ + send_status_info (ctrl, "CERTINFO", + "100", 3, + idbuf, strlen (idbuf), + NULL, (size_t)0); + } + + leave: + xfree (keygripstr); + return err; +} + + +/* Handle the LEARN command. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + int i; + + (void)flags; + + do_getattr (app, ctrl, "CHV-USAGE"); + do_getattr (app, ctrl, "CHV-STATUS"); + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].keypair) + send_keypair_and_cert_info (app, ctrl, data_objects + i, !!(flags & 1)); + + + return 0; +} + + +/* Core of do_readcert which fetches the certificate based on the + * given tag and returns it in a freshly allocated buffer stored at + * R_CERT and the length of the certificate stored at R_CERTLEN. If + * on success a non-zero value is stored at R_MECHANISM, the returned + * data is not a certificate but a public key (in the format used by the + * container '7f49'. */ +static gpg_error_t +readcert_by_tag (app_t app, unsigned int tag, + unsigned char **r_cert, size_t *r_certlen, int *r_mechanism) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + void *relptr; + const unsigned char *s, *s2; + size_t n, n2; + + *r_cert = NULL; + *r_certlen = 0; + *r_mechanism = 0; + + relptr = get_one_do (app, tag, &buffer, &buflen, NULL); + if (!relptr || !buflen) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + s = find_tlv (buffer, buflen, 0x71, &n); + if (!s) + { + /* No certificate; check whether a public key has been stored + * using our own scheme. */ + s = find_tlv (buffer, buflen, 0x7f49, &n); + if (!s || !n) + { + log_error ("piv: No public key in 0x%X\n", tag); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + s2 = find_tlv (buffer, buflen, 0x80, &n2); + if (!s2 || n2 != 1 || !*s2) + { + log_error ("piv: No mechanism for public key in 0x%X\n", tag); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + *r_mechanism = *s2; + } + else + { + if (n != 1) + { + log_error ("piv: invalid CertInfo in 0x%X\n", tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + if (*s == 0x01) + { + log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", + tag); + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto leave; + } + if (*s) + { + log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + + /* Note: We don't check that the LRC octet has a length of zero + * as required by the specs. */ + + /* Get the cert from the container. */ + s = find_tlv (buffer, buflen, 0x70, &n); + if (!s || !n) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + /* The next is common for certificate and public key. */ + if (!(*r_cert = xtrymalloc (n))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + memcpy (*r_cert, s, n); + *r_certlen = n; + err = 0; + + leave: + xfree (relptr); + return err; +} + + +/* Get the keygrip in hex format of a key from the certificate stored + * at TAG. Caller must free the string at R_KEYGRIPSTR. */ +static gpg_error_t +get_keygrip_by_tag (app_t app, unsigned int tag, + char **r_keygripstr, int *r_got_cert) +{ + gpg_error_t err; + unsigned char *certbuf = NULL; + size_t certbuflen; + int mechanism; + gcry_sexp_t s_pkey = NULL; + ksba_cert_t cert = NULL; + unsigned char grip[KEYGRIP_LEN]; + + *r_got_cert = 0; + *r_keygripstr = xtrymalloc (2*KEYGRIP_LEN+1); + if (!r_keygripstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* We need to get the public key from the certificate. */ + err = readcert_by_tag (app, tag, &certbuf, &certbuflen, &mechanism); + if (err) + goto leave; + if (mechanism) /* Compute keygrip from public key. */ + { + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey); + else if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + err = genkey_parse_ecc (certbuf, certbuflen, mechanism, &s_pkey); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + if (err) + goto leave; + + if (!gcry_pk_get_keygrip (s_pkey, grip)) + { + log_error ("piv: error computing keygrip\n"); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + bin2hex (grip, sizeof grip, *r_keygripstr); + } + else /* Compute keygrip from certificate. */ + { + *r_got_cert = 0; + err = ksba_cert_new (&cert); + if (err) + goto leave; + err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); + if (err) + goto leave; + err = app_help_get_keygrip_string (cert, *r_keygripstr); + } + + leave: + gcry_sexp_release (s_pkey); + ksba_cert_release (cert); + xfree (certbuf); + if (err) + { + xfree (*r_keygripstr); + *r_keygripstr = NULL; + } + return err; +} + + +/* Locate the data object from the given KEYREF. The KEYREF may also + * be the corresponding OID of the key object. Returns the data + * object or NULL if not found. */ +static data_object_t +find_dobj_by_keyref (app_t app, const char *keyref) +{ + int i; + + (void)app; + + if (!ascii_strncasecmp (keyref, "PIV.", 4)) + { + keyref += 4; + for (i=0; data_objects[i].tag; i++) + if (*data_objects[i].keyref + && !ascii_strcasecmp (keyref, data_objects[i].keyref)) + { + return data_objects + i; + } + } + else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19)) + { + keyref += 19; + for (i=0; data_objects[i].tag; i++) + if (*data_objects[i].keyref + && !strcmp (keyref, data_objects[i].oidsuffix)) + { + return data_objects + i; + } + } + + return NULL; +} + + +/* Return the keyref from DOBJ as an integer. If it does not exist, + * return -1. */ +static int +keyref_from_dobj (data_object_t dobj) +{ + if (!dobj || !hexdigitp (dobj->keyref) || !hexdigitp (dobj->keyref+1)) + return -1; + return xtoi_2 (dobj->keyref); +} + + +/* Read a certificate from the card and returned in a freshly + * allocated buffer stored at R_CERT and the length of the certificate + * stored at R_CERTLEN. CERTID is either the OID of the cert's + * container or of the form "PIV.<two_hexdigit_keyref>" */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + data_object_t dobj; + int mechanism; + + *r_cert = NULL; + *r_certlen = 0; + + /* Hack to read a Yubikey attestation certificate. */ + if (app->app_local->flags.yubikey + && strlen (certid) == 11 + && !ascii_strncasecmp (certid, "PIV.ATST.", 9) + && hexdigitp (certid+9) && hexdigitp (certid+10)) + { + unsigned char apdu[4]; + unsigned char *result; + size_t resultlen; + + apdu[0] = 0; + apdu[1] = 0xf9; /* Yubikey: Get attestation cert. */ + apdu[2] = xtoi_2 (certid+9); + apdu[3] = 0; + err = iso7816_apdu_direct (app->slot, apdu, 4, 1, + NULL, &result, &resultlen); + if (!err) + { + *r_cert = result; + *r_certlen = resultlen; + } + return err; + } + + dobj = find_dobj_by_keyref (app, certid); + if (!dobj) + return gpg_error (GPG_ERR_INV_ID); + + err = readcert_by_tag (app, dobj->tag, r_cert, r_certlen, &mechanism); + if (!err && mechanism) + { + /* Well, no certificate but a public key - we don't want it. */ + xfree (*r_cert); + *r_cert = NULL; + *r_certlen = 0; + err = gpg_error (GPG_ERR_NOT_FOUND); + } + return err; +} + + +/* Return a public key in a freshly allocated buffer. This will only + * work for a freshly generated key as long as no reset of the + * application has been performed. This is because we return a cached + * result from key generation. If no cached result is available, the + * error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher + * layer can then get the key by reading the matching certificate. + * On success a canonical encoded s-expression with the public key is + * stored at (R_PK,R_PKLEN); the caller must release that buffer. On + * error R_PK and R_PKLEN are not changed and an error code is + * returned. + */ +static gpg_error_t +do_readkey (app_t app, const char *keyrefstr, + unsigned char **r_pk, size_t *r_pklen) +{ + gpg_error_t err; + data_object_t dobj; + int keyref; + unsigned char *cert = NULL; + size_t certlen; + int mechanism; + gcry_sexp_t s_pkey = NULL; + unsigned char *pk = NULL; + size_t pklen; + + dobj = find_dobj_by_keyref (app, keyrefstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + err = readcert_by_tag (app, dobj->tag, &cert, &certlen, &mechanism); + if (err) + goto leave; + if (!mechanism) + { + /* We got a certificate. Extract the pubkey from it. */ + err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen); + if (err) + { + log_error ("failed to parse the certificate: %s\n", + gpg_strerror (err)); + goto leave; + } + } + else + { + /* Convert the public key into the expected s-expression. */ + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (cert, certlen, &s_pkey); + else if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + if (err) + goto leave; + + err = make_canon_sexp (s_pkey, &pk, &pklen); + if (err) + goto leave; + } + + *r_pk = pk; + pk = NULL; + *r_pklen = pklen; + + leave: + gcry_sexp_release (s_pkey); + xfree (pk); + xfree (cert); + return err; +} + + +/* Given a data object DOBJ return the corresponding PIV algorithm and + * store it at R_ALGO. The algorithm is taken from the corresponding + * certificate or from a cache. */ +static gpg_error_t +get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism) +{ + gpg_error_t err; + unsigned char *certbuf = NULL; + size_t certbuflen; + int mechanism; + ksba_cert_t cert = NULL; + ksba_sexp_t k_pkey = NULL; + gcry_sexp_t s_pkey = NULL; + gcry_sexp_t l1 = NULL; + char *algoname = NULL; + int algo; + size_t n; + const char *curve_name; + + *r_mechanism = 0; + + err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism); + if (err) + goto leave; + if (mechanism) + { + /* A public key was found. That makes it easy. */ + switch (mechanism) + { + case PIV_ALGORITHM_RSA: + case PIV_ALGORITHM_ECC_P256: + case PIV_ALGORITHM_ECC_P384: + *r_mechanism = mechanism; + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_error ("piv: unknown mechanism %d in public key at %s\n", + mechanism, dobj->keyref); + break; + } + goto leave; + } + + err = ksba_cert_new (&cert); + if (err) + goto leave; + + err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); + if (err) + { + log_error ("piv: failed to parse the certificate %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + xfree (certbuf); + certbuf = NULL; + + k_pkey = ksba_cert_get_public_key (cert); + if (!k_pkey) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL); + err = gcry_sexp_new (&s_pkey, k_pkey, n, 0); + if (err) + goto leave; + + l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + { + gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = l_tmp; + } + algoname = gcry_sexp_nth_string (l1, 0); + if (!algoname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + algo = gcry_pk_map_name (algoname); + switch (algo) + { + case GCRY_PK_RSA: + algo = PIV_ALGORITHM_RSA; + break; + + case GCRY_PK_ECC: + case GCRY_PK_ECDSA: + case GCRY_PK_ECDH: + curve_name = gcry_pk_get_curve (s_pkey, 0, NULL); + if (curve_name && !strcmp (curve_name, "NIST P-256")) + algo = PIV_ALGORITHM_ECC_P256; + else if (curve_name && !strcmp (curve_name, "NIST P-384")) + algo = PIV_ALGORITHM_ECC_P384; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + log_error ("piv: certificate %s, curve '%s': %s\n", + dobj->keyref, curve_name, gpg_strerror (err)); + goto leave; + } + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_error ("piv: certificate %s, pubkey algo '%s': %s\n", + dobj->keyref, algoname, gpg_strerror (err)); + goto leave; + } + *r_mechanism = algo; + + leave: + gcry_free (algoname); + gcry_sexp_release (l1); + gcry_sexp_release (s_pkey); + ksba_free (k_pkey); + xfree (certbuf); + return err; +} + + +/* Return an allocated string to be used as prompt. Returns NULL on + * malloc error. */ +static char * +make_prompt (app_t app, int remaining, const char *firstline) +{ + char *serial, *tmpbuf, *result; + + serial = get_dispserialno (app, 0); + if (!serial) + return NULL; + + /* TRANSLATORS: Put a \x1f right before a colon. This can be + * used by pinentry to nicely align the names and values. Keep + * the %s at the start and end of the string. */ + result = xtryasprintf (_("%s" + "Number\x1f: %s%%0A" + "Holder\x1f: %s" + "%s"), + "\x1e", + serial, + "Unknown", /* Fixme */ + ""); + xfree (serial); + + /* Append a "remaining attempts" info if needed. */ + if (remaining != -1 && remaining < 3) + { + char *rembuf; + + /* TRANSLATORS: This is the number of remaining attempts to + * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ + rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); + if (rembuf) + { + tmpbuf = strconcat (firstline, "%0A%0A", result, + "%0A%0A", rembuf, NULL); + xfree (rembuf); + } + else + tmpbuf = NULL; + xfree (result); + result = tmpbuf; + } + else + { + tmpbuf = strconcat (firstline, "%0A%0A", result, NULL); + xfree (result); + result = tmpbuf; + } + + return result; +} + + +/* Helper for verify_chv to ask for the PIN and to prepare/pad it. On + * success the result is stored at (R_PIN,R_PINLEN). */ +static gpg_error_t +ask_and_prepare_chv (app_t app, int keyref, int ask_new, int remaining, + gpg_error_t (*pincb)(void*,const char *,char **), + void *pincb_arg, char **r_pin, unsigned int *r_pinlen) +{ + gpg_error_t err; + const char *label; + char *prompt; + char *pinvalue = NULL; + unsigned int pinlen; + char *pinbuffer = NULL; + int minlen, maxlen, padding, onlydigits; + + *r_pin = NULL; + *r_pinlen = 0; + + if (ask_new) + remaining = -1; + + if (remaining != -1) + log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining); + + switch (keyref) + { + case 0x00: + minlen = 6; + maxlen = 8; + padding = 1; + onlydigits = 1; + label = (ask_new? _("|N|Please enter the new Global-PIN") + /**/ : _("||Please enter the Global-PIN of your PIV card")); + break; + case 0x80: + minlen = 6; + maxlen = 8; + padding = 1; + onlydigits = 1; + label = (ask_new? _("|N|Please enter the new PIN") + /**/ : _("||Please enter the PIN of your PIV card")); + break; + case 0x81: + minlen = 8; + maxlen = 8; + padding = 0; + onlydigits = 0; + label = (ask_new? _("|N|Please enter the new Unblocking Key") + /**/ :_("||Please enter the Unblocking Key of your PIV card")); + break; + + case 0x96: + case 0x97: + case 0x98: + case 0x9B: + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + default: + return gpg_error (GPG_ERR_INV_ID); + } + + /* Ask for the PIN. */ + prompt = make_prompt (app, remaining, label); + err = pincb (pincb_arg, prompt, &pinvalue); + xfree (prompt); + prompt = NULL; + if (err) + { + log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err)); + return err; + } + + pinlen = pinvalue? strlen (pinvalue) : 0; + if (pinlen < minlen) + { + log_error (_("PIN for is too short; minimum length is %d\n"), minlen); + if (pinvalue) + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (pinlen > maxlen) + { + log_error (_("PIN for is too long; maximum length is %d\n"), maxlen); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (onlydigits && strspn (pinvalue, "0123456789") != pinlen) + { + log_error (_("PIN has invalid characters; only digits are allowed\n")); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + pinbuffer = xtrymalloc_secure (maxlen); + if (!pinbuffer) + { + err = gpg_error_from_syserror (); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return err; + } + + memcpy (pinbuffer, pinvalue, pinlen); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + if (padding) + { + memset (pinbuffer + pinlen, 0xff, maxlen - pinlen); + pinlen = maxlen; + } + + *r_pin = pinbuffer; + *r_pinlen = pinlen; + + return 0; +} + + +/* Verify the card holder verification identified by KEYREF. This is + * either the Appication PIN or the Global PIN. If FORCE is true a + * verification is always done. */ +static gpg_error_t +verify_chv (app_t app, int keyref, int force, + gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) +{ + gpg_error_t err; + unsigned char apdu[4]; + unsigned int sw; + int remaining; + char *pin = NULL; + unsigned int pinlen; + + /* First check whether a verify is at all needed. This is done with + * P1 being 0 and no Lc and command data send. */ + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + { + if (!force) /* No need to verification. */ + return 0; /* All fine. */ + remaining = -1; + } + else if ((sw & 0xfff0) == 0x63C0) + remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ + else + remaining = -1; + + err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, + &pin, &pinlen); + if (err) + return err; + + err = iso7816_verify (app->slot, keyref, pin, pinlen); + wipememory (pin, pinlen); + xfree (pin); + if (err) + log_error ("CHV %02X verification failed: %s\n", + keyref, gpg_strerror (err)); + + return err; +} + + +/* Handle the PASSWD command. Valid values for PWIDSTR are + * key references related to PINs; in particular: + * PIV.00 - The Global PIN + * PIV.80 - The Application PIN + * PIV.81 - The PIN Unblocking key + * The supported flags are: + * APP_CHANGE_FLAG_CLEAR Clear the PIN verification state. + * APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only + * allowed with PIV.80. + */ +static gpg_error_t +do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + int keyref, targetkeyref; + unsigned char apdu[4]; + unsigned int sw; + int remaining; + char *oldpin = NULL; + unsigned int oldpinlen; + char *newpin = NULL; + unsigned int newpinlen; + + (void)ctrl; + + /* Check for unknown flags. */ + if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET))) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + goto leave; + } + + /* Parse the keyref. */ + targetkeyref = keyref = parse_chv_keyref (pwidstr); + if (keyref == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + /* First see whether the special --clear mode has been requested. */ + if ((flags & APP_CHANGE_FLAG_CLEAR)) + { + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0xff; + apdu[3] = keyref; + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + goto leave; + } + + /* Prepare reset mode. */ + if ((flags & APP_CHANGE_FLAG_RESET)) + { + if (keyref == 0x81) + { + err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */ + goto leave; + } + /* Set the keyref to the PUK and keep the TARGETKEYREF. */ + keyref = 0x81; + } + + /* Get the remaining tries count. This is done by using the check + * for verified state feature. */ + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + remaining = -1; /* Already verified, thus full number of tries. */ + else if ((sw & 0xfff0) == 0x63C0) + remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ + else + remaining = -1; + + /* Ask for the old pin or puk. */ + err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, + &oldpin, &oldpinlen); + if (err) + return err; + + /* Verify the old pin so that we don't prompt for the new pin if the + * old is wrong. This is not possible for the PUK, though. */ + if (keyref != 0x81) + { + err = iso7816_verify (app->slot, keyref, oldpin, oldpinlen); + if (err) + { + log_error ("CHV %02X verification failed: %s\n", + keyref, gpg_strerror (err)); + goto leave; + } + } + + /* Ask for the new pin. */ + err = ask_and_prepare_chv (app, targetkeyref, 1, -1, pincb, pincb_arg, + &newpin, &newpinlen); + if (err) + return err; + + if ((flags & APP_CHANGE_FLAG_RESET)) + { + char *buf = xtrymalloc_secure (oldpinlen + newpinlen); + if (!buf) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (buf, oldpin, oldpinlen); + memcpy (buf+oldpinlen, newpin, newpinlen); + err = iso7816_reset_retry_counter_with_rc (app->slot, targetkeyref, + buf, oldpinlen+newpinlen); + xfree (buf); + if (err) + log_error ("resetting CHV %02X using CHV %02X failed: %s\n", + targetkeyref, keyref, gpg_strerror (err)); + } + else + { + err = iso7816_change_reference_data (app->slot, keyref, + oldpin, oldpinlen, + newpin, newpinlen); + if (err) + log_error ("CHV %02X changing PIN failed: %s\n", + keyref, gpg_strerror (err)); + } + + leave: + xfree (oldpin); + xfree (newpin); + return err; +} + + +/* Perform a simple verify operation for the PIN specified by PWIDSTR. + * For valid values see do_change_chv. */ +static gpg_error_t +do_check_chv (app_t app, const char *pwidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int keyref; + + keyref = parse_chv_keyref (pwidstr); + if (keyref == -1) + return gpg_error (GPG_ERR_INV_ID); + + return verify_chv (app, keyref, 0, pincb, pincb_arg); +} + + +/* Compute a digital signature using the GENERAL AUTHENTICATE command + * on INDATA which is expected to be the raw message digest. The + * KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The + * result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) is + * stored there and an error code returned. For ECDSA the result is + * the simple concatenation of R and S without any DER encoding. R + * and S are left extended with zeroes to make sure they have an equal + * length. If HASHALGO is not zero, the function prepends the hash's + * OID to the indata or checks that it is consistent. + */ +static gpg_error_t +do_sign (app_t app, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata_arg, size_t indatalen, + unsigned char **r_outdata, size_t *r_outdatalen) +{ + const unsigned char *indata = indata_arg; + gpg_error_t err; + data_object_t dobj; + unsigned char oidbuf[64]; + size_t oidbuflen; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + size_t n; + int keyref, mechanism; + unsigned char *indata_buffer = NULL; /* Malloced helper. */ + unsigned char *apdudata = NULL; + size_t apdudatalen; + int force_verify; + + if (!keyidstr || !*keyidstr) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + dobj = find_dobj_by_keyref (app, keyidstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + /* According to table 4b of SP800-73-4 the signing key always + * requires a verify. */ + switch (keyref) + { + case 0x9c: force_verify = 1; break; + default: force_verify = 0; break; + } + + + err = get_key_algorithm_by_dobj (app, dobj, &mechanism); + if (err) + goto leave; + + /* For ECC we need to remove the ASN.1 prefix from INDATA. For RSA + * we need to add the padding and possible also the ASN.1 prefix. */ + if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + { + int need_algo, need_digestlen; + + if (mechanism == PIV_ALGORITHM_ECC_P256) + { + need_algo = GCRY_MD_SHA256; + need_digestlen = 32; + } + else + { + need_algo = GCRY_MD_SHA384; + need_digestlen = 48; + } + + if (hashalgo && hashalgo != need_algo) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + log_error ("piv: hash algo %d does not match mechanism %d\n", + need_algo, mechanism); + goto leave; + } + + if (indatalen > need_digestlen) + { + oidbuflen = sizeof oidbuf; + err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen); + if (err) + { + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: no OID for hash algo %d\n", need_algo); + goto leave; + } + if (indatalen != oidbuflen + need_digestlen + || memcmp (indata, oidbuf, oidbuflen)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: bad input for signing with mechanism %d\n", + mechanism); + goto leave; + } + indata += oidbuflen; + indatalen -= oidbuflen; + } + } + else if (mechanism == PIV_ALGORITHM_RSA) + { + /* PIV requires 2048 bit RSA. */ + unsigned int framelen = 2048 / 8; + unsigned char *frame; + int i; + + oidbuflen = sizeof oidbuf; + if (!hashalgo) + { + /* We assume that indata already has the required + * digestinfo; thus merely prepend the padding below. */ + } + else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen))) + { + log_debug ("piv: no OID for hash algo %d\n", hashalgo); + goto leave; + } + else + { + unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo); + + if (indatalen == digestlen) + { + /* Plain hash in INDATA; prepend the digestinfo. */ + indata_buffer = xtrymalloc (oidbuflen + indatalen); + if (!indata_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (indata_buffer, oidbuf, oidbuflen); + memcpy (indata_buffer+oidbuflen, indata, indatalen); + indata = indata_buffer; + indatalen = oidbuflen + indatalen; + } + else if (indatalen == oidbuflen + digestlen + && !memcmp (indata, oidbuf, oidbuflen)) + ; /* Correct prefix. */ + else + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: bad input for signing with RSA and hash %d\n", + hashalgo); + goto leave; + } + } + /* Now prepend the pkcs#v1.5 padding. We require at least 8 + * byte of padding and 3 extra bytes for the prefix and the + * delimiting nul. */ + if (!indatalen || indatalen + 8 + 4 > framelen) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: input does not fit into a %u bit PKCS#v1.5 frame\n", + 8*framelen); + goto leave; + } + frame = xtrymalloc (framelen); + if (!frame) + { + err = gpg_error_from_syserror (); + goto leave; + } + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* Block type. */ + i = framelen - indatalen - 3 ; + memset (frame+n, 0xff, i); + n += i; + frame[n++] = 0; /* Delimiter. */ + memcpy (frame+n, indata, indatalen); + n += indatalen; + log_assert (n == framelen); + /* And now put it into the indata_buffer. */ + xfree (indata_buffer); + indata_buffer = frame; + indata = indata_buffer; + indatalen = framelen; + } + else + { + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism); + goto leave; + } + + /* Now verify the Application PIN. */ + err = verify_chv (app, 0x80, force_verify, pincb, pincb_arg); + if (err) + return err; + + /* Build the Dynamic Authentication Template. */ + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x7c, (size_t)0, NULL, /* Constructed. */ + (int)0x82, (size_t)0, "", + (int)0x81, (size_t)indatalen, indata, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + /* Note: the -1 requests command chaining. */ + err = iso7816_general_authenticate (app->slot, -1, + mechanism, keyref, + apdudata, (int)apdudatalen, 0, + &outdata, &outdatalen); + if (err) + goto leave; + + /* Parse the response. */ + if (outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n))) + { + if (mechanism == PIV_ALGORITHM_RSA) + { + memmove (outdata, outdata + (s - outdata), n); + outdatalen = n; + } + else /* ECC */ + { + const unsigned char *rval, *sval; + size_t rlen, rlenx, slen, slenx, resultlen; + char *result; + /* The result of an ECDSA signature is + * SEQUENCE { r INTEGER, s INTEGER } + * We re-pack that by concatenating R and S and making sure + * that both have the same length. We simplify parsing by + * using find_tlv and not a proper DER parser. */ + s = find_tlv (s, n, 0x30, &n); + if (!s) + goto bad_der; + rval = find_tlv (s, n, 0x02, &rlen); + if (!rval) + goto bad_der; + log_assert (n >= (rval-s)+rlen); + sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen); + if (!rval) + goto bad_der; + rlenx = slenx = 0; + if (rlen > slen) + slenx = rlen - slen; + else if (slen > rlen) + rlenx = slen - rlen; + + resultlen = rlen + rlenx + slen + slenx; + result = xtrycalloc (1, resultlen); + if (!result) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (result + rlenx, rval, rlen); + memcpy (result + rlenx + rlen + slenx, sval, slen); + xfree (outdata); + outdata = result; + outdatalen = resultlen; + } + } + else + { + bad_der: + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: response does not contain a proper result\n"); + goto leave; + } + + leave: + if (err) + { + xfree (outdata); + *r_outdata = NULL; + *r_outdatalen = 0; + } + else + { + *r_outdata = outdata; + *r_outdatalen = outdatalen; + } + xfree (apdudata); + xfree (indata_buffer); + return err; +} + + +/* AUTH for PIV cards is actually the same as SIGN. The difference + * between AUTH and SIGN is that AUTH expects that pkcs#1.5 padding + * for RSA has already been done (digestInfo part w/o the padding) + * whereas SIGN may accept a plain digest and does the padding if + * needed. This is also the reason why SIGN takes a hashalgo. */ +static gpg_error_t +do_auth (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **r_outdata, size_t *r_outdatalen) +{ + return do_sign (app, keyidstr, 0, pincb, pincb_arg, indata, indatalen, + r_outdata, r_outdatalen); +} + + +/* Decrypt the data in (INDATA,INDATALEN) and on success store the + * mallocated result at (R_OUTDATA,R_OUTDATALEN). */ +static gpg_error_t +do_decipher (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata_arg, size_t indatalen, + unsigned char **r_outdata, size_t *r_outdatalen, + unsigned int *r_info) +{ + const unsigned char *indata = indata_arg; + gpg_error_t err; + data_object_t dobj; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + size_t n; + int keyref, mechanism; + unsigned int framelen; + unsigned char *indata_buffer = NULL; /* Malloced helper. */ + unsigned char *apdudata = NULL; + size_t apdudatalen; + + if (!keyidstr || !*keyidstr) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + dobj = find_dobj_by_keyref (app, keyidstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + if (keyref == 0x9A || keyref == 0x9C || keyref == 0x9E) + { + /* Signing only reference. We only allow '9D' and the retired + * cert key management DOs. */ + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + err = get_key_algorithm_by_dobj (app, dobj, &mechanism); + if (err) + goto leave; + + switch (mechanism) + { + case PIV_ALGORITHM_ECC_P256: + framelen = 1+32+32; + break; + case PIV_ALGORITHM_ECC_P384: + framelen = 1+48+48; + break; + case PIV_ALGORITHM_RSA: + framelen = 2048 / 8; + break; + default: + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: unknown PIV mechanism %d while decrypting\n", mechanism); + goto leave; + } + + /* Check that the ciphertext has the right length; due to internal + * convey mechanism using MPIs leading zero bytes might have been + * lost. Adjust for this. Note that for ECC this actually + * superfluous because the first octet is always '04' to indicate an + * uncompressed point. */ + if (indatalen > framelen) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: input of %zu octets too large for mechanism %d\n", + indatalen, mechanism); + goto leave; + } + if (indatalen < framelen) + { + indata_buffer = xtrycalloc (1, framelen); + if (!indata_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (indata_buffer+(framelen-indatalen), indata, indatalen); + indata = indata_buffer; + indatalen = framelen; + } + + /* Now verify the Application PIN. */ + err = verify_chv (app, 0x80, 0, pincb, pincb_arg); + if (err) + return err; + + /* Build the Dynamic Authentication Template. */ + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x7c, (size_t)0, NULL, /* Constructed. */ + (int)0x82, (size_t)0, "", + mechanism == PIV_ALGORITHM_RSA? + (int)0x81 : (int)0x85, (size_t)indatalen, indata, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + /* Note: the -1 requests command chaining. */ + err = iso7816_general_authenticate (app->slot, -1, + mechanism, keyref, + apdudata, (int)apdudatalen, 0, + &outdata, &outdatalen); + if (err) + goto leave; + + /* Parse the response. */ + if (outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n))) + { + memmove (outdata, outdata + (s - outdata), n); + outdatalen = n; + } + else + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: response does not contain a proper result\n"); + goto leave; + } + + leave: + if (err) + { + xfree (outdata); + *r_outdata = NULL; + *r_outdatalen = 0; + } + else + { + *r_outdata = outdata; + *r_outdatalen = outdatalen; + } + *r_info = 0; + xfree (apdudata); + xfree (indata_buffer); + return err; +} + + +/* Check whether a key for DOBJ already exists. We detect this by + * reading the certificate described by DOBJ. If FORCE is TRUE a + * diagnositic will be printed but no error returned if the key + * already exists. The flag GENERATING is used to select a + * diagnositic. */ +static gpg_error_t +does_key_exist (app_t app, data_object_t dobj, int generating, int force) +{ + void *relptr; + unsigned char *buffer; + size_t buflen; + int found; + + relptr = get_one_do (app, dobj->tag, &buffer, &buflen, NULL); + found = (relptr && buflen); + xfree (relptr); + + if (found && !force) + { + log_error (_("key already exists\n")); + return gpg_error (GPG_ERR_EEXIST); + } + + if (found) + log_info (_("existing key will be replaced\n")); + else if (generating) + log_info (_("generating new key\n")); + else + log_info (_("writing new key\n")); + return 0; +} + + +/* Helper for do_writekey; here the RSA part. BUF, BUFLEN, and DEPTH + * are the current parser state of the S-expression with the key. */ +static gpg_error_t +writekey_rsa (app_t app, data_object_t dobj, int keyref, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + const unsigned char *rsa_n = NULL; + const unsigned char *rsa_e = NULL; + const unsigned char *rsa_p = NULL; + const unsigned char *rsa_q = NULL; + unsigned char *rsa_dpm1 = NULL; + unsigned char *rsa_dqm1 = NULL; + unsigned char *rsa_qinv = NULL; + size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; + size_t rsa_dpm1_len, rsa_dqm1_len, rsa_qinv_len; + unsigned char *apdudata = NULL; + size_t apdudatalen; + unsigned char tmpl[1]; + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break; + case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break; + case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break; + case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len; break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && mpi) + { + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + *mpi = tok; + *mpi_len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + /* Check that we have all parameters. */ + if (!rsa_n || !rsa_e || !rsa_p || !rsa_q) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + /* Fixme: Shall we check whether n == pq ? */ + + if (opt.verbose) + log_info ("RSA private key size is %u bytes\n", (unsigned int)rsa_n_len); + + /* Compute the dp, dq and u components. */ + { + gcry_mpi_t mpi_e, mpi_p, mpi_q; + gcry_mpi_t mpi_dpm1 = gcry_mpi_snew (0); + gcry_mpi_t mpi_dqm1 = gcry_mpi_snew (0); + gcry_mpi_t mpi_qinv = gcry_mpi_snew (0); + gcry_mpi_t mpi_tmp = gcry_mpi_snew (0); + + gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL); + gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL); + gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL); + + gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1); + gcry_mpi_invm (mpi_dpm1, mpi_e, mpi_tmp); + + gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1); + gcry_mpi_invm (mpi_dqm1, mpi_e, mpi_tmp); + + gcry_mpi_invm (mpi_qinv, mpi_q, mpi_p); + + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dpm1, &rsa_dpm1_len, mpi_dpm1); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dqm1, &rsa_dqm1_len, mpi_dqm1); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_qinv, &rsa_qinv_len, mpi_qinv); + + gcry_mpi_release (mpi_e); + gcry_mpi_release (mpi_p); + gcry_mpi_release (mpi_q); + gcry_mpi_release (mpi_dpm1); + gcry_mpi_release (mpi_dqm1); + gcry_mpi_release (mpi_qinv); + gcry_mpi_release (mpi_tmp); + } + + err = concat_tlv_list (1, &apdudata, &apdudatalen, + (int)0x01, (size_t)rsa_p_len, rsa_p, + (int)0x02, (size_t)rsa_q_len, rsa_q, + (int)0x03, (size_t)rsa_dpm1_len, rsa_dpm1, + (int)0x04, (size_t)rsa_dqm1_len, rsa_dqm1, + (int)0x05, (size_t)rsa_qinv_len, rsa_qinv, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + err = iso7816_send_apdu (app->slot, + -1, /* Use command chaining. */ + 0, /* Class */ + 0xfe, /* Ins: Yubikey Import Asym. Key. */ + PIV_ALGORITHM_RSA, /* P1 */ + keyref, /* P2 */ + apdudatalen,/* Lc */ + apdudata, /* data */ + NULL, NULL, NULL); + if (err) + goto leave; + + /* Write the public key to the cert object. */ + xfree (apdudata); + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x81, (size_t)rsa_n_len, rsa_n, + (int)0x82, (size_t)rsa_e_len, rsa_e, + (int)0, (size_t)0, NULL); + + if (err) + goto leave; + tmpl[0] = PIV_ALGORITHM_RSA; + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)apdudatalen, apdudata, + (int)0, (size_t)0, NULL); + + leave: + xfree (rsa_dpm1); + xfree (rsa_dqm1); + xfree (rsa_qinv); + xfree (apdudata); + return err; +} + + +/* Helper for do_writekey; here the ECC part. BUF, BUFLEN, and DEPTH + * are the current parser state of the S-expression with the key. */ +static gpg_error_t +writekey_ecc (app_t app, data_object_t dobj, int keyref, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + int mechanism = 0; + const unsigned char *ecc_q = NULL; + const unsigned char *ecc_d = NULL; + size_t ecc_q_len, ecc_d_len; + unsigned char *apdudata = NULL; + size_t apdudatalen; + unsigned char tmpl[1]; + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok && toklen == 5 && !memcmp (tok, "curve", 5)) + { + char *name; + const char *xname; + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + name = xtrymalloc (toklen+1); + if (!name) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (name, tok, toklen); + name[toklen] = 0; + /* Canonicalize the curve name. We use the openpgp + * functions here because Libgcrypt has no generic curve + * alias lookup feature and the PIV suppotred curves alre + * also supported by OpenPGP. */ + xname = openpgp_oid_to_curve (openpgp_curve_to_oid (name, NULL), 0); + xfree (name); + + if (xname && !strcmp (xname, "nistp256")) + mechanism = PIV_ALGORITHM_ECC_P256; + else if (xname && !strcmp (xname, "nistp384")) + mechanism = PIV_ALGORITHM_ECC_P384; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + goto leave; + } + } + else if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break; + case 'd': mpi = &ecc_d; mpi_len = &ecc_d_len; break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && mpi) + { + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + *mpi = tok; + *mpi_len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + /* Check that we have all parameters. */ + if (!mechanism || !ecc_q || !ecc_d) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + if (opt.verbose) + log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len); + + err = concat_tlv_list (1, &apdudata, &apdudatalen, + (int)0x06, (size_t)ecc_d_len, ecc_d, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + err = iso7816_send_apdu (app->slot, + -1, /* Use command chaining. */ + 0, /* Class */ + 0xfe, /* Ins: Yubikey Import Asym. Key. */ + mechanism, /* P1 */ + keyref, /* P2 */ + apdudatalen,/* Lc */ + apdudata, /* data */ + NULL, NULL, NULL); + if (err) + goto leave; + + /* Write the public key to the cert object. */ + xfree (apdudata); + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x86, (size_t)ecc_q_len, ecc_q, + (int)0, (size_t)0, NULL); + + if (err) + goto leave; + tmpl[0] = mechanism; + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)apdudatalen, apdudata, + (int)0, (size_t)0, NULL); + + + leave: + xfree (apdudata); + return err; +} + + +/* Write a key to a slot. This command requires proprietary + * extensions of the PIV specification and is thus only implemnted for + * supported card types. The input is a canonical encoded + * S-expression with the secret key in KEYDATA and its length (for + * assertion) in KEYDATALEN. KEYREFSTR needs to be the usual 2 + * hexdigit slot number prefixed with "PIV." PINCB and PINCB_ARG are + * not used for PIV cards. + * + * Supported FLAGS are: + * APP_WRITEKEY_FLAG_FORCE Overwrite existing key. + */ +static gpg_error_t +do_writekey (app_t app, ctrl_t ctrl, + const char *keyrefstr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + int force = !!(flags & APP_WRITEKEY_FLAG_FORCE); + data_object_t dobj; + int keyref; + const unsigned char *buf, *tok; + size_t buflen, toklen; + int depth; + + (void)ctrl; + (void)pincb; + (void)pincb_arg; + + if (!app->app_local->flags.yubikey) + { + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* Check keyref and test whether a key already exists. */ + dobj = find_dobj_by_keyref (app, keyrefstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + err = does_key_exist (app, dobj, 0, force); + if (err) + goto leave; + + /* Parse the S-expression with the key. */ + buf = keydata; + buflen = keydatalen; + depth = 0; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen)) + { + if (!tok) + ; + else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen)) + log_info ("protected-private-key passed to writekey\n"); + else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen)) + log_info ("shadowed-private-key passed to writekey\n"); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + /* First clear an existing key. We do this by writing an empty 7f49 + * tag. This will return GPG_ERR_NO_PUBKEY on a later read. */ + flush_cached_data (app, dobj->tag); + err = put_data (app->slot, dobj->tag, + (int)0x7f49, (size_t)0, "", + (int)0, (size_t)0, NULL); + if (err) + { + log_error ("piv: failed to clear the cert DO %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + + /* Divert to the algo specific implementation. */ + if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0) + err = writekey_rsa (app, dobj, keyref, buf, buflen, depth); + else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0) + err = writekey_ecc (app, dobj, keyref, buf, buflen, depth); + else + err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + + if (err) + { + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); + } + + leave: + return err; +} + + +/* Parse an RSA response object, consisting of the content of tag + * 0x7f49, into a gcrypt s-expression object and store that R_SEXP. + * On error NULL is stored at R_SEXP. */ +static gpg_error_t +genkey_parse_rsa (const unsigned char *data, size_t datalen, + gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + const unsigned char *m, *e; + unsigned char *mbuf = NULL; + unsigned char *ebuf = NULL; + size_t mlen, elen; + + *r_sexp = NULL; + + m = find_tlv (data, datalen, 0x0081, &mlen); + if (!m) + { + log_error (_("response does not contain the RSA modulus\n")); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + e = find_tlv (data, datalen, 0x0082, &elen); + if (!e) + { + log_error (_("response does not contain the RSA public exponent\n")); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + for (; mlen && !*m; mlen--, m++) /* Strip leading zeroes */ + ; + for (; elen && !*e; elen--, e++) /* Strip leading zeroes */ + ; + + mbuf = xtrymalloc (mlen + 1); + if (!mbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Prepend numbers with a 0 if needed. */ + if (mlen && (*m & 0x80)) + { + *mbuf = 0; + memcpy (mbuf+1, m, mlen); + mlen++; + } + else + memcpy (mbuf, m, mlen); + + ebuf = xtrymalloc (elen + 1); + if (!ebuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Prepend numbers with a 0 if needed. */ + if (elen && (*e & 0x80)) + { + *ebuf = 0; + memcpy (ebuf+1, e, elen); + elen++; + } + else + memcpy (ebuf, e, elen); + + err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))", + (int)mlen, mbuf, (int)elen, ebuf); + + leave: + xfree (mbuf); + xfree (ebuf); + return err; +} + + +/* Parse an ECC response object, consisting of the content of tag + * 0x7f49, into a gcrypt s-expression object and store that R_SEXP. + * On error NULL is stored at R_SEXP. MECHANISM specifies the + * curve. */ +static gpg_error_t +genkey_parse_ecc (const unsigned char *data, size_t datalen, int mechanism, + gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + const unsigned char *ecc_q; + size_t ecc_qlen; + const char *curve; + + *r_sexp = NULL; + + ecc_q = find_tlv (data, datalen, 0x0086, &ecc_qlen); + if (!ecc_q) + { + log_error (_("response does not contain the EC public key\n")); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + if (mechanism == PIV_ALGORITHM_ECC_P256) + curve = "nistp256"; + else if (mechanism == PIV_ALGORITHM_ECC_P384) + curve = "nistp384"; + else + { + err = gpg_error (GPG_ERR_BUG); /* Call with wrong parameters. */ + goto leave; + } + + + err = gcry_sexp_build (r_sexp, NULL, "(public-key(ecc(curve%s)(q%b)))", + curve, (int)ecc_qlen, ecc_q); + + leave: + return err; +} + + +/* Create a new keypair for KEYREF. If KEYTYPE is NULL a default + * keytype is selected, else it may be one of the strings: + * "rsa2048", "nistp256, or "nistp384". + * + * Supported FLAGS are: + * APP_GENKEY_FLAG_FORCE Overwrite existing key. + * + * Note that CREATETIME is not used for PIV cards. + * + * Because there seems to be no way to read the public key we need to + * retrieve it from a certificate. The GnuPG system however requires + * the use of app_readkey to fetch the public key from the card to + * create the certificate; to support this we temporary store the + * generated public key in the local context for use by app_readkey. + */ +static gpg_error_t +do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, + unsigned int flags, time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + data_object_t dobj; + unsigned char *buffer = NULL; + size_t buflen; + int force = !!(flags & APP_GENKEY_FLAG_FORCE); + int mechanism; + time_t start_at; + int keyref; + unsigned char tmpl[5]; + size_t tmpllen; + const unsigned char *keydata; + size_t keydatalen; + + (void)ctrl; + (void)createtime; + (void)pincb; + (void)pincb_arg; + + if (!keytype) + keytype = "rsa2048"; + + if (!strcmp (keytype, "rsa2048")) + mechanism = PIV_ALGORITHM_RSA; + else if (!strcmp (keytype, "nistp256")) + mechanism = PIV_ALGORITHM_ECC_P256; + else if (!strcmp (keytype, "nistp384")) + mechanism = PIV_ALGORITHM_ECC_P384; + else + return gpg_error (GPG_ERR_UNKNOWN_CURVE); + + /* We flush the cache to increase the I/O traffic before a key + * generation. This _might_ help the card to gather more entropy + * and is anyway a prerequisite for does_key_exist. */ + flush_cached_data (app, 0); + + /* Check whether a key already exists. */ + dobj = find_dobj_by_keyref (app, keyrefstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + err = does_key_exist (app, dobj, 1, force); + if (err) + goto leave; + + + /* Create the key. */ + log_info (_("please wait while key is being generated ...\n")); + start_at = time (NULL); + tmpl[0] = 0xac; + tmpl[1] = 3; + tmpl[2] = 0x80; + tmpl[3] = 1; + tmpl[4] = mechanism; + tmpllen = 5; + err = iso7816_generate_keypair (app->slot, 0, 0, keyref, + tmpl, tmpllen, 0, &buffer, &buflen); + if (err) + { + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + log_error (_("generating key failed\n")); + return err; + } + + { + int nsecs = (int)(time (NULL) - start_at); + log_info (ngettext("key generation completed (%d second)\n", + "key generation completed (%d seconds)\n", + nsecs), nsecs); + } + + /* Parse the result and store it as an s-expression in a dedicated + * cache for later retrieval by app_readkey. */ + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); + if (!keydata || !keydatalen) + { + err = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the public key data\n")); + goto leave; + } + + tmpl[0] = mechanism; + flush_cached_data (app, dobj->tag); + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)keydatalen, keydata, + (int)0, (size_t)0, NULL); + if (err) + { + log_error ("piv: failed to write key to the cert DO %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + + leave: + xfree (buffer); + return err; +} + + +/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR. + * CERTREFSTR is either the OID of the certificate's container data + * object or of the form "PIV.<two_hexdigit_keyref>". */ +static gpg_error_t +do_writecert (app_t app, ctrl_t ctrl, + const char *certrefstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + data_object_t dobj; + unsigned char *pk = NULL; + unsigned char *orig_pk = NULL; + size_t pklen, orig_pklen; + + (void)ctrl; + (void)pincb; /* Not used; instead authentication is needed. */ + (void)pincb_arg; + + if (!certlen) + return gpg_error (GPG_ERR_INV_CERT_OBJ); + + dobj = find_dobj_by_keyref (app, certrefstr); + if (!dobj || !*dobj->keyref) + return gpg_error (GPG_ERR_INV_ID); + + flush_cached_data (app, dobj->tag); + + /* Check that the public key parameters from the certificate match + * an already stored key. Note that we do not allow writing a + * certificate if no key has yet been created (GPG_ERR_NOT_FOUND) or + * if there is a problem reading the public key from the certificate + * GPG_ERR_NO_PUBKEY). We enforce this because otherwise the only + * way to detect whether a key exists is by trying to use that + * key. */ + err = do_readkey (app, certrefstr, &orig_pk, &orig_pklen); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = gpg_error (GPG_ERR_NO_SECKEY); /* Use a better error code. */ + goto leave; + } + + /* Compare pubkeys. */ + err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen); + if (err) + goto leave; /* No public key in new certificate. */ + if (orig_pklen != pklen || memcmp (orig_pk, pk, pklen)) + { + err = gpg_error (GPG_ERR_CONFLICT); + goto leave; + } + + err = put_data (app->slot, dobj->tag, + (int)0x70, (size_t)certlen, cert,/* Certificate */ + (int)0x71, (size_t)1, "", /* No compress */ + (int)0xfe, (size_t)0, "", /* Empty LRC. */ + (int)0, (size_t)0, NULL); + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + if (err) + log_error ("piv: failed to write cert to %s: %s\n", + dobj->keyref, gpg_strerror (err)); + + leave: + xfree (pk); + xfree (orig_pk); + return err; +} + + +/* Select the PIV application on the card in SLOT. This function must + * be used before any other PIV application functions. */ +gpg_error_t +app_select_piv (app_t app) +{ + static char const aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */ + 0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ }; + int slot = app->slot; + gpg_error_t err; + unsigned char *apt = NULL; + size_t aptlen; + const unsigned char *s; + size_t n; + + /* Note that we select using the AID without the 2 octet version + * number. This allows for better reporting of future specs. We + * need to use the use-zero-for-P2-flag. */ + err = iso7816_select_application_ext (slot, aid, sizeof aid, 0x0001, + &apt, &aptlen); + if (err) + goto leave; + + app->apptype = "PIV"; + app->did_chv1 = 0; + app->did_chv2 = 0; + app->did_chv3 = 0; + app->app_local = NULL; + + /* Check the Application Property Template. */ + if (opt.verbose) + { + /* We use a separate log_info to avoid the "DBG:" prefix. */ + log_info ("piv: APT="); + log_printhex (apt, aptlen, ""); + } + + s = find_tlv (apt, aptlen, 0x4F, &n); + if (!s || n != 6 || memcmp (s, aid+5, 4)) + { + /* The PIX does not match. */ + log_error ("piv: missing or invalid DO 0x4F in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + if (s[4] != 1 || s[5] != 0) + { + log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + app->appversion = ((s[4] << 8) | s[5]); + + s = find_tlv (apt, aptlen, 0x79, &n); + if (!s || n < 7) + { + log_error ("piv: missing or invalid DO 0x79 in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + s = find_tlv (s, n, 0x4F, &n); + if (!s || n != 5 || memcmp (s, aid, 5)) + { + /* The RID does not match. */ + log_error ("piv: missing or invalid DO 0x79.4F in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (app->cardtype && !strcmp (app->cardtype, "yubikey")) + app->app_local->flags.yubikey = 1; + + + /* FIXME: Parse the optional and conditional DOs in the APT. */ + + if (opt.verbose) + dump_all_do (slot); + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.readkey = do_readkey; + app->fnc.getattr = do_getattr; + app->fnc.setattr = do_setattr; + app->fnc.writecert = do_writecert; + app->fnc.writekey = do_writekey; + app->fnc.genkey = do_genkey; + app->fnc.sign = do_sign; + app->fnc.auth = do_auth; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = do_change_chv; + app->fnc.check_pin = do_check_chv; + + +leave: + xfree (apt); + if (err) + do_deinit (app); + return err; +} @@ -67,6 +67,7 @@ lock_app (app_t app, ctrl_t ctrl) } apdu_set_progress_cb (app->slot, print_progress_line, ctrl); + apdu_set_prompt_cb (app->slot, popup_prompt, ctrl); return 0; } @@ -76,6 +77,7 @@ static void unlock_app (app_t app) { apdu_set_progress_cb (app->slot, NULL, NULL); + apdu_set_prompt_cb (app->slot, NULL, NULL); if (npth_mutex_unlock (&app->lock)) { @@ -119,6 +121,9 @@ check_conflict (app_t app, const char *name) if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name))) return 0; + if (app->apptype && !strcmp (app->apptype, "UNDEFINED")) + return 0; + log_info ("application '%s' in use - can't switch\n", app->apptype? app->apptype : "<null>"); @@ -209,6 +214,64 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, if (!want_undefined) { err = iso7816_select_file (slot, 0x3F00, 1); + if (gpg_err_code (err) == GPG_ERR_CARD) + { + /* Might be SW==0x7D00. Let's test whether it is a Yubikey + * by selecting its manager application and then reading the + * config. */ + static char const yk_aid[] = + { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/ + unsigned char *buf; + size_t buflen; + const unsigned char *s0, *s1; + size_t n; + + if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid, + 0x0001) + && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, + NULL, &buf, &buflen)) + { + app->cardtype = "yubikey"; + if (opt.verbose) + { + log_info ("Yubico: config="); + log_printhex (buf, buflen, ""); + } + + /* We skip the first byte which seems to be the total + * length of the config data. */ + if (buflen > 1) + { + s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */ + if (s0 && n == 1) + { + s1 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ + if (s1 && n >= 4) + { + app->serialno = xtrymalloc (3 + 1 + n); + if (app->serialno) + { + app->serialnolen = 3 + 1 + n; + app->serialno[0] = 0xff; + app->serialno[1] = 0x02; + app->serialno[2] = 0x0; + app->serialno[3] = *s0; + memcpy (app->serialno + 4, s1, n); + /* Note that we do not clear the error + * so that no further serial number + * testing is done. After all we just + * set the serial number. */ + } + } + s1 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ + if (s1 && n == 3) + app->cardversion = ((s1[0]<<16)|(s1[1]<<8)|s1[2]); + } + } + xfree (buf); + } + } + if (!err) err = iso7816_select_file (slot, 0x2F02, 0); if (!err) @@ -249,25 +312,33 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, } } - /* For certain error codes, there is no need to try more. */ - if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT - || gpg_err_code (err) == GPG_ERR_ENODEV) - goto leave; - /* Figure out the application to use. */ if (want_undefined) { /* We switch to the "undefined" application only if explicitly requested. */ app->apptype = "UNDEFINED"; + /* Clear the error so that we don't run through the application + * selection chain. */ err = 0; } else - err = gpg_error (GPG_ERR_NOT_FOUND); + { + /* For certain error codes, there is no need to try more. */ + if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT + || gpg_err_code (err) == GPG_ERR_ENODEV) + goto leave; + + /* Set a default error so that we run through the application + * selecion chain. */ + err = gpg_error (GPG_ERR_NOT_FOUND); + } if (err && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) err = app_select_openpgp (app); + if (err && is_app_allowed ("piv") && (!name || !strcmp (name, "piv"))) + err = app_select_piv (app); if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) err = app_select_nks (app); if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) @@ -297,11 +368,8 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, } app->periodical_check_needed = periodical_check_needed; - - npth_mutex_lock (&app_list_lock); app->next = app_top; app_top = app; - npth_mutex_unlock (&app_list_lock); unlock_app (app); return 0; } @@ -320,6 +388,8 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, *r_app = NULL; + npth_mutex_lock (&app_list_lock); + if (scan || !app_top) { struct dev_list *l; @@ -328,7 +398,10 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, /* Scan the devices to find new device(s). */ err = apdu_dev_list_start (opt.reader_port, &l); if (err) - return err; + { + npth_mutex_unlock (&app_list_lock); + return err; + } while (1) { @@ -363,7 +436,6 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, scd_kick_the_loop (); } - npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) { lock_app (a, ctrl); @@ -406,6 +478,7 @@ get_supported_applications (void) { const char *list[] = { "openpgp", + "piv", "nks", "p15", "geldkarte", @@ -506,6 +579,7 @@ release_application (app_t app, int locked_already) FF 00 00 = For serial numbers starting with an FF FF 01 00 = Some german p15 cards return an empty serial number so the serial number from the EF(TokenInfo) is used instead. + FF 02 00 = Serial number from Yubikey config FF 7F 00 = No serialno. All other serial number not starting with FF are used as they are. @@ -561,7 +635,7 @@ app_get_serialno (app_t app) } -/* Write out the application specifig status lines for the LEARN +/* Write out the application specific status lines for the LEARN command. */ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) @@ -573,9 +647,19 @@ app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) if (!app->fnc.learn_status) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - /* We do not send APPTYPE if only keypairinfo is requested. */ - if (app->apptype && !(flags & 1)) - send_status_direct (ctrl, "APPTYPE", app->apptype); + /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ + if (!(flags &1)) + { + if (app->cardtype) + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + if (app->cardversion) + send_status_printf (ctrl, "CARDVERSION", "%X", app->cardversion); + if (app->apptype) + send_status_direct (ctrl, "APPTYPE", app->apptype); + if (app->appversion) + send_status_printf (ctrl, "APPVERSION", "%X", app->appversion); + } + err = lock_app (app, ctrl); if (err) return err; @@ -618,7 +702,7 @@ app_readcert (app_t app, ctrl_t ctrl, const char *certid, This function might not be supported by all applications. */ gpg_error_t -app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, +app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; @@ -637,7 +721,7 @@ app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, err = lock_app (app, ctrl); if (err) return err; - err= app->fnc.readkey (app, advanced, keyid, pk, pklen); + err= app->fnc.readkey (app, keyid, pk, pklen); unlock_app (app); return err; } @@ -654,6 +738,11 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (app->cardtype && name && !strcmp (name, "CARDTYPE")) + { + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + return 0; + } if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", app->apptype); @@ -677,7 +766,7 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.getattr (app, ctrl, name); + err = app->fnc.getattr (app, ctrl, name); unlock_app (app); return err; } @@ -863,8 +952,8 @@ app_writekey (app_t app, ctrl_t ctrl, /* Perform a SETATTR operation. */ gpg_error_t -app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - time_t createtime, +app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, + const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -879,7 +968,7 @@ app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.genkey (app, ctrl, keynostr, flags, + err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags, createtime, pincb, pincb_arg); unlock_app (app); if (opt.verbose) @@ -912,7 +1001,8 @@ app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ gpg_error_t -app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, +app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -927,8 +1017,7 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, - pincb, pincb_arg); + err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); @@ -936,7 +1025,7 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, } -/* Perform a VERIFY operation without doing anything lese. This may +/* Perform a VERIFY operation without doing anything else. This may be used to initialize a the PIN cache for long lasting other operations. Its use is highly application dependent. */ gpg_error_t diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index ae40f0118..69df17355 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -255,6 +255,9 @@ struct ccid_driver_s void (*progress_cb)(void *, const char *, int, int, int); void *progress_cb_arg; + void (*prompt_cb)(void *, int); + void *prompt_cb_arg; + unsigned char intr_buf[64]; struct libusb_transfer *transfer; }; @@ -1467,8 +1470,7 @@ intr_cb (struct libusb_transfer *transfer) DEBUGOUT_1 ("CCID: interrupt callback %d\n", transfer->status); - if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT - || transfer->status == LIBUSB_TRANSFER_NO_DEVICE) + if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { int err; @@ -1651,15 +1653,13 @@ ccid_open_usb_reader (const char *spec_reader_name, goto leave; } - if (set_no != 0) + /* Submit SET_INTERFACE control transfer which can reset the device. */ + rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no); + if (rc) { - rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no); - if (rc) - { - DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc); - rc = CCID_DRIVER_ERR_CARD_IO_ERROR; - goto leave; - } + DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc); + rc = CCID_DRIVER_ERR_CARD_IO_ERROR; + goto leave; } rc = ccid_vendor_specific_init (*handle); @@ -1802,6 +1802,19 @@ ccid_set_progress_cb (ccid_driver_t handle, } +int +ccid_set_prompt_cb (ccid_driver_t handle, + void (*cb)(void *, int), void *cb_arg) +{ + if (!handle) + return CCID_DRIVER_ERR_INV_VALUE; + + handle->prompt_cb = cb; + handle->prompt_cb_arg = cb_arg; + return 0; +} + + /* Close the reader HANDLE. */ int ccid_close_reader (ccid_driver_t handle) @@ -1921,7 +1934,7 @@ bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen, is the sequence number used to send the request and EXPECTED_TYPE the type of message we expect. Does checks on the ccid header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to - avoid debug messages in case of no error; this can be overriden + avoid debug messages in case of no error; this can be overridden with a glibal debug level of at least 3. Returns 0 on success. */ static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, @@ -1930,6 +1943,7 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, { int rc; int msglen; + int notified = 0; /* Fixme: The next line for the current Valgrind without support for USB IOCTLs. */ @@ -1982,14 +1996,25 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, we got the expected message type. This is in particular required for the Cherry keyboard which sends a time extension request for each key hit. */ - if ( !(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) + if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) { /* Card present and active, time extension requested. */ DEBUGOUT_2 ("time extension requested (%02X,%02X)\n", buffer[7], buffer[8]); + + /* Gnuk enhancement to prompt user input by ack button */ + if (buffer[8] == 0xff && !notified) + { + notified = 1; + handle->prompt_cb (handle->prompt_cb_arg, 1); + } + goto retry; } + if (notified) + handle->prompt_cb (handle->prompt_cb_arg, 0); + if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus) { DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]); @@ -2928,7 +2953,7 @@ ccid_transceive_apdu_level (ccid_driver_t handle, bit 3 unused bit 2..0 Source Node Address (SAD) - If node adresses are not used, SAD and DAD should be set to 0 on + If node addresses are not used, SAD and DAD should be set to 0 on the first block sent to the card. If they are used they should have different values (0 for one is okay); that first block sets up the addresses of the nodes. @@ -3270,7 +3295,7 @@ ccid_transceive (ccid_driver_t handle, /* Wait time extension request. */ unsigned char bwi = tpdu[3]; - /* Check if it's unsual value which can't be expressed in ATR. */ + /* Check if it's unusual value which can't be expressed in ATR. */ if (bwi > 15) wait_more = 1; diff --git a/scd/ccid-driver.h b/scd/ccid-driver.h index c31c25fa7..1550b3eba 100644 --- a/scd/ccid-driver.h +++ b/scd/ccid-driver.h @@ -126,6 +126,8 @@ int ccid_open_reader (const char *spec_reader_name, int ccid_set_progress_cb (ccid_driver_t handle, void (*cb)(void *, const char *, int, int, int), void *cb_arg); +int ccid_set_prompt_cb (ccid_driver_t handle, void (*cb)(void *, int), + void *cb_arg); int ccid_shutdown_reader (ccid_driver_t handle); int ccid_close_reader (ccid_driver_t handle); int ccid_get_atr (ccid_driver_t handle, diff --git a/scd/command.c b/scd/command.c index 66d9fb971..0d1a5cd3f 100644 --- a/scd/command.c +++ b/scd/command.c @@ -55,6 +55,9 @@ /* Maximum allowed size of certificate data as used in inquiries. */ #define MAXLEN_CERTDATA 16384 +/* Maximum allowed size for "SETATTR --inquire". */ +#define MAXLEN_SETATTRDATA 16384 + #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) @@ -333,7 +336,7 @@ static const char hlp_learn[] = "or a \"CANCEL\" to force the function to terminate with a Cancel\n" "error message.\n" "\n" - "With the option --keypairinfo only KEYPARIINFO lstatus lines are\n" + "With the option --keypairinfo only KEYPARIINFO status lines are\n" "returned.\n" "\n" "The response of this command is a list of status lines formatted as\n" @@ -346,11 +349,12 @@ static const char hlp_learn[] = " P15 = PKCS-15 structure used\n" " DINSIG = DIN SIG\n" " OPENPGP = OpenPGP card\n" + " PIV = PIV card\n" " NKS = NetKey card\n" "\n" "are implemented. These strings are aliases for the AID\n" "\n" - " S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id>\n" + " S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id> [<usage>]\n" "\n" "If there is no certificate yet stored on the card a single 'X' is\n" "returned as the keygrip. In addition to the keypair info, information\n" @@ -465,7 +469,7 @@ cmd_learn (assuan_context_t ctx, char *line) static const char hlp_readcert[] = - "READCERT <hexified_certid>|<keyid>\n" + "READCERT <hexified_certid>|<keyid>|<oid>\n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t @@ -498,7 +502,7 @@ cmd_readcert (assuan_context_t ctx, char *line) static const char hlp_readkey[] = - "READKEY [--advanced] <keyid>\n" + "READKEY [--advanced] <keyid>|<oid>\n" "\n" "Return the public key for the given cert or key ID as a standard\n" "S-expression.\n" @@ -512,11 +516,8 @@ cmd_readkey (assuan_context_t ctx, char *line) int rc; int advanced = 0; unsigned char *cert = NULL; - size_t ncert, n; - ksba_cert_t kc = NULL; - ksba_sexp_t p; - unsigned char *pk; - size_t pklen; + unsigned char *pk = NULL; + size_t ncert, pklen; if ((rc = open_card (ctrl))) return rc; @@ -525,59 +526,68 @@ cmd_readkey (assuan_context_t ctx, char *line) advanced = 1; line = skip_options (line); - line = xstrdup (line); /* Need a copy of the line. */ + /* If the application supports the READKEY function we use that. Otherwise we use the old way by extracting it from the certificate. */ - rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen); + rc = app_readkey (ctrl->app_ctx, ctrl, line, &pk, &pklen); if (!rc) - { /* Yeah, got that key - send it back. */ - rc = assuan_send_data (ctx, pk, pklen); - xfree (pk); - xfree (line); - line = NULL; - goto leave; - } - - if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION) - log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); - else + ; /* Okay, got that key. */ + else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION + || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { + /* Fall back to certificate reading. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) - log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + { + log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = app_help_pubkey_from_cert (cert, ncert, &pk, &pklen); + if (rc) + { + log_error ("failed to parse the certificate: %s\n", + gpg_strerror (rc)); + goto leave; + } } - xfree (line); - line = NULL; - if (rc) - goto leave; - - rc = ksba_cert_new (&kc); - if (rc) - goto leave; - - rc = ksba_cert_init_from_mem (kc, cert, ncert); - if (rc) + else { - log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); + log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); goto leave; } - p = ksba_cert_get_public_key (kc); - if (!p) + if (advanced) { - rc = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } + gcry_sexp_t s_key; + unsigned char *pkadv; + size_t pkadvlen; - n = gcry_sexp_canon_len (p, 0, NULL, NULL); - rc = assuan_send_data (ctx, p, n); - xfree (p); + rc = gcry_sexp_new (&s_key, pk, pklen, 0); + if (rc) + goto leave; + pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); + pkadv = xtrymalloc (pkadvlen); + if (!pkadv) + { + rc = gpg_error_from_syserror (); + goto leave; + } + log_assert (pkadvlen); + + gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen); + gcry_sexp_release (s_key); + /* (One less to adjust for the trailing '\0') */ + rc = assuan_send_data (ctx, pkadv, pkadvlen-1); + xfree (pkadv); + } + else + rc = assuan_send_data (ctx, pk, pklen); leave: - ksba_cert_release (kc); + xfree (pk); xfree (cert); return rc; } @@ -902,7 +912,7 @@ cmd_getattr (assuan_context_t ctx, char *line) static const char hlp_setattr[] = - "SETATTR <name> <value> \n" + "SETATTR [--inquire] <name> <value> \n" "\n" "This command is used to store data on a smartcard. The allowed\n" "names and values are depend on the currently selected smartcard\n" @@ -911,6 +921,10 @@ static const char hlp_setattr[] = "However, the current implementation assumes that NAME is not\n" "escaped; this works as long as no one uses arbitrary escaping.\n" "\n" + "If the option --inquire is used, VALUE shall not be given; instead\n" + "an inquiry using the keyword \"VALUE\" is used to retrieve it. The\n" + "value is in this case considered to be confidential and not logged.\n" + "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "setattr function of the actually used application (app-*.c) for\n" "details."; @@ -918,14 +932,18 @@ static gpg_error_t cmd_setattr (assuan_context_t ctx, char *orig_line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int rc; + gpg_error_t err; char *keyword; int keywordlen; size_t nbytes; char *line, *linebuf; + int opt_inquire; - if ((rc = open_card (ctrl))) - return rc; + opt_inquire = has_option (orig_line, "--inquire"); + orig_line = skip_options (orig_line); + + if ((err = open_card (ctrl))) + return err; /* We need to use a copy of LINE, because PIN_CB uses the same context and thus reuses the Assuan provided LINE. */ @@ -940,20 +958,38 @@ cmd_setattr (assuan_context_t ctx, char *orig_line) *line++ = 0; while (spacep (line)) line++; - nbytes = percent_plus_unescape_inplace (line, 0); + if (opt_inquire) + { + unsigned char *value; - rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, - (const unsigned char*)line, nbytes); - xfree (linebuf); + assuan_begin_confidential (ctx); + err = assuan_inquire (ctx, "VALUE", &value, &nbytes, MAXLEN_SETATTRDATA); + assuan_end_confidential (ctx); + if (!err) + { + err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + value, nbytes); + wipememory (value, nbytes); + xfree (value); + } - return rc; + } + else + { + nbytes = percent_plus_unescape_inplace (line, 0); + err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + (const unsigned char*)line, nbytes); + } + + xfree (linebuf); + return err; } static const char hlp_writecert[] = "WRITECERT <hexified_certid>\n" "\n" - "This command is used to store a certifciate on a smartcard. The\n" + "This command is used to store a certificate on a smartcard. The\n" "allowed certids depend on the currently selected smartcard\n" "application. The actual certifciate is requested using the inquiry\n" "\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n" @@ -1072,10 +1108,10 @@ cmd_writekey (assuan_context_t ctx, char *line) static const char hlp_genkey[] = - "GENKEY [--force] [--timestamp=<isodate>] <no>\n" + "GENKEY [--force] [--timestamp=<isodate>] <keyref>\n" "\n" - "Generate a key on-card identified by NO, which is application\n" - "specific. Return values are application specific. For OpenPGP\n" + "Generate a key on-card identified by <keyref>, which is application\n" + "specific. Return values are also application specific. For OpenPGP\n" "cards 3 status lines are returned:\n" "\n" " S KEY-FPR <hexstring>\n" @@ -1084,7 +1120,7 @@ static const char hlp_genkey[] = "\n" " 'p' and 'n' are the names of the RSA parameters; '-' is used to\n" " indicate that HEXDATA is the first chunk of a parameter given\n" - " by the next KEY-DATA.\n" + " by the next KEY-DATA. Only used by GnuPG version < 2.1.\n" "\n" "--force is required to overwrite an already existing key. The\n" "KEY-CREATED-AT is required for further processing because it is\n" @@ -1100,10 +1136,12 @@ static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int rc; - char *keyno; + gpg_error_t err; + char *keyref_buffer = NULL; + char *keyref; int force; const char *s; + char *opt_algo = NULL; time_t timestamp; force = has_option (line, "--force"); @@ -1119,29 +1157,38 @@ cmd_genkey (assuan_context_t ctx, char *line) else timestamp = 0; + err = get_option_value (line, "--algo", &opt_algo); + if (err) + goto leave; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no key number given"); - keyno = line; + keyref = line; while (*line && !spacep (line)) line++; *line = 0; - if ((rc = open_card (ctrl))) - return rc; + if ((err = open_card (ctrl))) + goto leave; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - keyno = xtrystrdup (keyno); - if (!keyno) - return out_of_core (); - rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, - timestamp, pin_cb, ctx); - xfree (keyno); + keyref = keyref_buffer = xtrystrdup (keyref); + if (!keyref) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = app_genkey (ctrl->app_ctx, ctrl, keyref, opt_algo, + force? APP_GENKEY_FLAG_FORCE : 0, + timestamp, pin_cb, ctx); - return rc; + leave: + xfree (keyref_buffer); + xfree (opt_algo); + return err; } @@ -1191,12 +1238,13 @@ cmd_random (assuan_context_t ctx, char *line) static const char hlp_passwd[] = - "PASSWD [--reset] [--nullpin] <chvno>\n" + "PASSWD [--reset] [--nullpin] [--clear] <chvno>\n" "\n" "Change the PIN or, if --reset is given, reset the retry counter of\n" "the card holder verification vector CHVNO. The option --nullpin is\n" - "used for TCOS cards to set the initial PIN. The format of CHVNO\n" - "depends on the card application."; + "used for TCOS cards to set the initial PIN. The option --clear clears\n" + "the security status associated with the PIN so that the PIN needs to\n" + "be presented again. The format of CHVNO depends on the card application."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { @@ -1209,6 +1257,8 @@ cmd_passwd (assuan_context_t ctx, char *line) flags |= APP_CHANGE_FLAG_RESET; if (has_option (line, "--nullpin")) flags |= APP_CHANGE_FLAG_NULLPIN; + if (has_option (line, "--clear")) + flags |= APP_CHANGE_FLAG_CLEAR; line = skip_options (line); @@ -1219,6 +1269,11 @@ cmd_passwd (assuan_context_t ctx, char *line) line++; *line = 0; + /* Do not allow other flags aside of --clear. */ + if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR)) + return set_error (GPG_ERR_UNSUPPORTED_OPERATION, + "--clear used with other options"); + if ((rc = open_card (ctrl))) return rc; @@ -1640,7 +1695,7 @@ cmd_apdu (assuan_context_t ctx, char *line) rc = apdu_send_direct (app->slot, exlen, apdu, apdulen, handle_more, - &result, &resultlen); + NULL, &result, &resultlen); if (rc) log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); else @@ -1898,6 +1953,54 @@ send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) } +/* This status functions expects a printf style format string. No + * filtering of the data is done instead the orintf formatted data is + * send using assuan_send_status. */ +gpg_error_t +send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) +{ + gpg_error_t err; + va_list arg_ptr; + assuan_context_t ctx; + + if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) + return 0; + + va_start (arg_ptr, format); + err = vprint_assuan_status (ctx, keyword, format, arg_ptr); + va_end (arg_ptr); + return err; +} + + +void +popup_prompt (void *opaque, int on) +{ + ctrl_t ctrl = opaque; + + if (ctrl) + { + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + if (ctx) + { + const char *cmd; + gpg_error_t err; + unsigned char *value; + size_t valuelen; + + if (on) + cmd = "POPUPPINPADPROMPT --ack"; + else + cmd = "DISMISSPINPADPROMPT"; + err = assuan_inquire (ctx, cmd, &value, &valuelen, 100); + if (!err) + xfree (value); + } + } +} + + /* Helper to send the clients a status change notification. */ void send_client_notifications (app_t app, int removal) diff --git a/scd/iso7816.c b/scd/iso7816.c index 29208c254..d9f3336c7 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -50,6 +50,7 @@ #define CMD_PUT_DATA 0xDA #define CMD_MSE 0x22 #define CMD_PSO 0x2A +#define CMD_GENERAL_AUTHENTICATE 0x87 #define CMD_INTERNAL_AUTHENTICATE 0x88 #define CMD_GENERATE_KEYPAIR 0x47 #define CMD_GET_CHALLENGE 0x84 @@ -66,6 +67,7 @@ map_sw (int sw) case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; case SW_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break; case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_ACK_TIMEOUT: ec = GPG_ERR_TIMEOUT; break; case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; @@ -138,6 +140,21 @@ iso7816_select_application (int slot, const char *aid, size_t aidlen, } +/* This is the same as iso7816_select_application but may return data + * at RESULT,RESULTLEN). */ +gpg_error_t +iso7816_select_application_ext (int slot, const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, size_t *resultlen) +{ + int sw; + sw = apdu_send (slot, 0, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0:0x0c, aidlen, aid, + result, resultlen); + return map_sw (sw); +} + + gpg_error_t iso7816_select_file (int slot, int tag, int is_dir) { @@ -205,29 +222,66 @@ iso7816_list_directory (int slot, int list_dirs, } +/* Wrapper around apdu_send. RESULT can be NULL if no result is + * expected. In addition to an gpg-error return code the actual + * status word is stored at R_SW unless that is NULL. */ +gpg_error_t +iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (result) + { + *result = NULL; + *resultlen = 0; + } + + sw = apdu_send (slot, extended_mode, class, ins, p0, p1, lc, data, + result, resultlen); + if (sw != SW_SUCCESS && result) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + if (r_sw) + *r_sw = sw; + return map_sw (sw); +} + + /* This function sends an already formatted APDU to the card. With HANDLE_MORE set to true a MORE DATA status will be handled internally. The return value is a gpg error code (i.e. a mapped status word). This is basically the same as apdu_send_direct but it maps the status word and does not return it in the result - buffer. */ + buffer. However, it R_SW is not NULL the status word is stored + R_SW for closer inspection. */ gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **result, size_t *resultlen) { - int sw; + int sw, sw2; - if (!result || !resultlen) - return gpg_error (GPG_ERR_INV_VALUE); - *result = NULL; - *resultlen = 0; + if (result) + { + *result = NULL; + *resultlen = 0; + } sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more, - result, resultlen); + &sw2, result, resultlen); if (!sw) { - if (*resultlen < 2) + if (!result) + sw = sw2; + else if (*resultlen < 2) sw = SW_HOST_GENERAL_ERROR; else { @@ -236,13 +290,15 @@ iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, (*resultlen)--; } } - if (sw != SW_SUCCESS) + if (sw != SW_SUCCESS && result) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; } + if (r_sw) + *r_sw = sw; return map_sw (sw); } @@ -324,6 +380,7 @@ iso7816_change_reference_data (int slot, int chvno, sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA, oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + wipememory (buf, oldchvlen+newchvlen); xfree (buf); return map_sw (sw); @@ -396,6 +453,70 @@ iso7816_get_data (int slot, int extended_mode, int tag, } +/* Perform a GET DATA command requesting TAG and storing the result in + * a newly allocated buffer at the address passed by RESULT. Return + * the length of this data at the address of RESULTLEN. This variant + * is needed for long (3 octet) tags. */ +gpg_error_t +iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag, + unsigned char **result, size_t *resultlen) +{ + int sw; + int le; + int datalen; + unsigned char data[5]; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (extended_mode > 0 && extended_mode < 256) + le = 65534; /* Not 65535 in case it is used as some special flag. */ + else if (extended_mode > 0) + le = extended_mode; + else + le = 256; + + data[0] = 0x5c; + if (tag <= 0xff) + { + data[1] = 1; + data[2] = tag; + datalen = 3; + } + else if (tag <= 0xffff) + { + data[1] = 2; + data[2] = (tag >> 8); + data[3] = tag; + datalen = 4; + } + else + { + data[1] = 3; + data[2] = (tag >> 16); + data[3] = (tag >> 8); + data[4] = tag; + datalen = 5; + } + + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA + 1, + 0x3f, 0xff, datalen, data, le, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + /* Perform a PUT DATA command on card in SLOT. Write DATA of length DATALEN to TAG. EXTENDED_MODE controls whether extended length headers or command chaining is used instead of single length @@ -427,7 +548,7 @@ iso7816_put_data_odd (int slot, int extended_mode, int tag, /* Manage Security Environment. This is a weird operation and there is no easy abstraction for it. Furthermore, some card seem to have - a different interpreation of 7816-8 and thus we resort to let the + a different interpretation of 7816-8 and thus we resort to let the caller decide what to do. */ gpg_error_t iso7816_manage_security_env (int slot, int p1, int p2, @@ -445,7 +566,7 @@ iso7816_manage_security_env (int slot, int p1, int p2, /* Perform the security operation COMPUTE DIGITAL SIGANTURE. On - success 0 is returned and the data is availavle in a newly + success 0 is returned and the data is available in a newly allocated buffer stored at RESULT with its length stored at RESULTLEN. For LE see do_generate_keypair. */ gpg_error_t @@ -542,7 +663,7 @@ iso7816_decipher (int slot, int extended_mode, } -/* For LE see do_generate_keypair. */ +/* For LE see do_generate_keypair. */ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, @@ -579,12 +700,50 @@ iso7816_internal_authenticate (int slot, int extended_mode, } +/* For LE see do_generate_keypair. */ +gpg_error_t +iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_GENERAL_AUTHENTICATE, algoref, keyref, + datalen, (const char*)data, + le, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + /* LE is the expected return length. This is usually 0 except if extended length mode is used and more than 256 byte will be returned. In that case a value of -1 uses a large default (e.g. 4096 bytes), a value larger 256 used that value. */ static gpg_error_t -do_generate_keypair (int slot, int extended_mode, int read_only, +do_generate_keypair (int slot, int extended_mode, int p1, int p2, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { @@ -596,7 +755,7 @@ do_generate_keypair (int slot, int extended_mode, int read_only, *resultlen = 0; sw = apdu_send_le (slot, extended_mode, - 0x00, CMD_GENERATE_KEYPAIR, read_only? 0x81:0x80, 0, + 0x00, CMD_GENERATE_KEYPAIR, p1, p2, datalen, data, le >= 0 && le < 256? 256:le, result, resultlen); @@ -614,12 +773,12 @@ do_generate_keypair (int slot, int extended_mode, int read_only, gpg_error_t -iso7816_generate_keypair (int slot, int extended_mode, +iso7816_generate_keypair (int slot, int extended_mode, int p1, int p2, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, extended_mode, 0, + return do_generate_keypair (slot, extended_mode, p1, p2, data, datalen, le, result, resultlen); } @@ -630,7 +789,7 @@ iso7816_read_public_key (int slot, int extended_mode, int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, extended_mode, 1, + return do_generate_keypair (slot, extended_mode, 0x81, 0, data, datalen, le, result, resultlen); } diff --git a/scd/iso7816.h b/scd/iso7816.h index 4c71bbd50..c1940ad8d 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -51,14 +51,24 @@ gpg_error_t iso7816_map_sw (int sw); gpg_error_t iso7816_select_application (int slot, const char *aid, size_t aidlen, unsigned int flags); +gpg_error_t iso7816_select_application_ext (int slot, + const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_select_file (int slot, int tag, int is_dir); gpg_error_t iso7816_select_path (int slot, const unsigned short *path, size_t pathlen); gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_check_pinpad (int slot, int command, pininfo_t *pininfo); @@ -78,6 +88,8 @@ gpg_error_t iso7816_reset_retry_counter_with_rc (int slot, int chvno, size_t datalen); gpg_error_t iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_put_data (int slot, int extended_mode, int tag, const void *data, size_t datalen); gpg_error_t iso7816_put_data_odd (int slot, int extended_mode, int tag, @@ -97,10 +109,19 @@ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, + size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_generate_keypair (int slot, int extended_mode, - const char *data, size_t datalen, - int le, - unsigned char **result, size_t *resultlen); + int p1, int p2, + const char *data, size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_read_public_key (int slot, int extended_mode, const char *data, size_t datalen, int le, diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 8f8a02619..507108db0 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -1069,7 +1069,7 @@ handle_signal (int signo) /* Create a name for the socket. We check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. - Retunrs: Pointer to an allcoated string with the absolute name of + Returns: Pointer to an allcoated string with the absolute name of the socket used. */ static char * create_socket_name (char *standard_name) diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 4797f3df0..73589ade8 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -123,6 +123,10 @@ int scd_command_handler (ctrl_t, int); void send_status_info (ctrl_t ctrl, const char *keyword, ...) GPGRT_ATTR_SENTINEL(1); void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args); +gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...) GPGRT_ATTR_PRINTF(3,4); + +void popup_prompt (void *opaque, int on); void send_client_notifications (app_t app, int removal); void scd_kick_the_loop (void); int get_active_connection_count (void); |