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 <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-01-31 14:26:17 +01:00
parent 0107984f9f
commit 1d57450f3e
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
2 changed files with 287 additions and 17 deletions

View File

@ -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; */

View File

@ -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 <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"
@ -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 <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"