diff options
Diffstat (limited to 'scd/app-piv.c')
-rw-r--r-- | scd/app-piv.c | 442 |
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; |