aboutsummaryrefslogtreecommitdiffstats
path: root/scd/app-piv.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-03-05 14:49:20 +0000
committerWerner Koch <[email protected]>2019-03-05 14:49:20 +0000
commite897e1e255ef9870dfd1639d6f4e97bdf4e83b34 (patch)
tree5571c7633161ca25697fa7222ea8ec64f50091ce /scd/app-piv.c
parentgpg: Make invalid primary key algos obvious in key listings. (diff)
downloadgnupg-e897e1e255ef9870dfd1639d6f4e97bdf4e83b34.tar.gz
gnupg-e897e1e255ef9870dfd1639d6f4e97bdf4e83b34.zip
scd:piv: Implement import of private keys for Yubikeys.
* scd/app-piv.c (concat_tlv_list): Add arg 'secure' and adjust callers. (writekey_rsa, writekey_ecc): New. (do_writekey): New. (do_writecert): Provide a better error message for an empty cert. (app_select_piv): Register do_writekey. * scd/iso7816.c (iso7816_send_apdu): New. * scd/app-common.h (APP_WRITEKEY_FLAG_FORCE): New. * agent/command.c (cmd_keytocard): Make the timestamp optional. * tools/card-call-scd.c (inq_writekey_parms): Remove. (scd_writekey): Rewrite. * tools/gpg-card.c (cmd_writekey): New. (enum cmdids): Add cmdWRITEKEY. (dispatch_command, interactive_loop): Call cmd_writekey. -- This has been tested with gpgsm and RSA keys. For ECC keys only partly tested using the sample OpenPGP nistp256 and nistp384 keys because gpgsm does not yet support ECC certificates and thus we can't write the certificates to the cert object after a writekey. Note that they nevertheless show up in "gpgcard list" because gpg-card searches for them in gpg and gpgsm. However, this does not work completely. Signed-off-by: Werner Koch <[email protected]>
Diffstat (limited to 'scd/app-piv.c')
-rw-r--r--scd/app-piv.c442
1 files changed, 429 insertions, 13 deletions
diff --git a/scd/app-piv.c b/scd/app-piv.c
index d55d71f25..6d6611572 100644
--- a/scd/app-piv.c
+++ b/scd/app-piv.c
@@ -510,9 +510,10 @@ add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
/* 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. */
+ * 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 (unsigned char **r_result, size_t *r_resultlen, ...)
+concat_tlv_list (int secure, unsigned char **r_result, size_t *r_resultlen, ...)
{
gpg_error_t err;
va_list arg_ptr;
@@ -573,7 +574,7 @@ concat_tlv_list (unsigned char **r_result, size_t *r_resultlen, ...)
datalen += argv[i].len;
}
}
- data = xtrymalloc (datalen);
+ data = secure? xtrymalloc_secure (datalen) : xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
@@ -2220,7 +2221,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
return err;
/* Build the Dynamic Authentication Template. */
- err = concat_tlv_list (&apdudata, &apdudatalen,
+ 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,
@@ -2423,7 +2424,7 @@ do_decipher (app_t app, const char *keyidstr,
return err;
/* Build the Dynamic Authentication Template. */
- err = concat_tlv_list (&apdudata, &apdudatalen,
+ err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x7c, (size_t)0, NULL, /* Constructed. */
(int)0x82, (size_t)0, "",
mechanism == PIV_ALGORITHM_RSA?
@@ -2506,6 +2507,424 @@ does_key_exist (app_t app, data_object_t dobj, int generating, int force)
}
+/* 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. */
@@ -2694,10 +3113,6 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
goto leave;
- /* FIXME: Check that the authentication has already been done. */
-
-
-
/* Create the key. */
log_info (_("please wait while key is being generated ...\n"));
start_at = time (NULL);
@@ -2774,12 +3189,13 @@ do_writecert (app_t app, ctrl_t 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);
- /* FIXME: Check that the authentication has already been done. */
-
flush_cached_data (app, dobj->tag);
/* Check that the public key parameters from the certificate match
@@ -2796,6 +3212,7 @@ do_writecert (app_t app, ctrl_t ctrl,
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)
@@ -2806,7 +3223,6 @@ do_writecert (app_t app, ctrl_t ctrl,
goto leave;
}
-
err = put_data (app->slot, dobj->tag,
(int)0x70, (size_t)certlen, cert,/* Certificate */
(int)0x71, (size_t)1, "", /* No compress */
@@ -2917,7 +3333,7 @@ app_select_piv (app_t app)
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
app->fnc.writecert = do_writecert;
- /* app->fnc.writekey = do_writekey; */
+ app->fnc.writekey = do_writekey;
app->fnc.genkey = do_genkey;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;