From 1d57450f3e71b198e66e155a8ebbfab452f58ffc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 31 Jan 2019 14:26:17 +0100 Subject: [PATCH] scd: Add DES authentication for PIV card. * scd/app-piv.c (flush_cached_data): New. (auth_adm_key): New. (set_adm_key): New. (do_setattr): New. * scd/command.c (MAXLEN_SETATTRDATA): New. (cmd_setattr): Add an inquire option. Signed-off-by: Werner Koch --- scd/app-piv.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++++-- scd/command.c | 49 ++++++++-- 2 files changed, 287 insertions(+), 17 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index d984e9c7a..d34ff7d10 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -112,7 +112,7 @@ static struct data_object_s data_objects[] = { { 0x5FC122, 0, 0,0, 1, 0,0, 0, "", "2.16.23", "SM Cert Signer" }, { 0x5FC123, 0, 3,3, 1, 0,0, 0, "", "2.16.24", "Pairing Code Ref Data" }, { 0 } - /* Other key reference values without a tag: + /* 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 @@ -142,7 +142,7 @@ struct app_local_s { /* Various flags. */ struct { - unsigned int dummy:1; + unsigned int yubikey:1; /* This is on a Yubikey. */ } flags; }; @@ -266,6 +266,30 @@ get_cached_data (app_t app, int tag, } +/* Remove data object described by TAG from the cache. */ +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) + { + 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 @@ -552,6 +576,218 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) } +/* 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 (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 (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_SIGNATURE); + 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); + } + 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 0: + err = iso7816_put_data (app->slot, 0, table[idx].tag, value, valuelen); + if (err) + log_error ("failed to set '%s': %s\n", + table[idx].name, gpg_strerror (err)); + break; + + 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 @@ -1086,13 +1322,15 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, char *newpin = NULL; char *oldpin = NULL; - size_t newpinlen; - size_t oldpinlen; - const char *newdesc; - int pwid; + /* size_t newpinlen; */ + /* size_t oldpinlen; */ + /* const char *newdesc; */ + /* int pwid; */ pininfo_t pininfo; (void)ctrl; + (void)pincb; + (void)pincb_arg; /* The minimum and maximum lengths are enforced by PIV. */ memset (&pininfo, 0, sizeof pininfo); @@ -1416,6 +1654,9 @@ app_select_piv (app_t app) 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. */ @@ -1427,7 +1668,7 @@ app_select_piv (app_t app) app->fnc.readcert = do_readcert; app->fnc.readkey = NULL; app->fnc.getattr = do_getattr; - /* app->fnc.setattr = do_setattr; */ + app->fnc.setattr = do_setattr; /* app->fnc.writecert = do_writecert; */ /* app->fnc.writekey = do_writekey; */ /* app->fnc.genkey = do_genkey; */ diff --git a/scd/command.c b/scd/command.c index 044831f01..fb0ba98fc 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)) @@ -926,7 +929,7 @@ cmd_getattr (assuan_context_t ctx, char *line) static const char hlp_setattr[] = - "SETATTR \n" + "SETATTR [--inquire] \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" @@ -935,6 +938,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."; @@ -942,14 +949,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. */ @@ -964,20 +975,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; + + 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); + } + + } + else + { + nbytes = percent_plus_unescape_inplace (line, 0); + err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + (const unsigned char*)line, nbytes); + } - rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, - (const unsigned char*)line, nbytes); xfree (linebuf); - - return rc; + return err; } static const char hlp_writecert[] = "WRITECERT \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"