aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-01-31 13:26:17 +0000
committerWerner Koch <[email protected]>2019-01-31 13:26:17 +0000
commit1d57450f3e71b198e66e155a8ebbfab452f58ffc (patch)
tree9087cbce3d5d26e722cde83e8b86783e9dca2058
parentcard: Cache the results from gpg and gpgsm. (diff)
downloadgnupg-1d57450f3e71b198e66e155a8ebbfab452f58ffc.tar.gz
gnupg-1d57450f3e71b198e66e155a8ebbfab452f58ffc.zip
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 <[email protected]>
-rw-r--r--scd/app-piv.c255
-rw-r--r--scd/command.c49
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 <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;
- 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"