mirror of
git://git.gnupg.org/gnupg.git
synced 2025-01-03 12:11:33 +01:00
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:
parent
0107984f9f
commit
1d57450f3e
255
scd/app-piv.c
255
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; */
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user