diff --git a/agent/call-scd.c b/agent/call-scd.c index 1189bd477..4c0186d74 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1088,7 +1088,8 @@ agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, char line[ASSUAN_LINELENGTH]; struct inq_needpin_parm_s parms; - (void)serialno; + (void)serialno; /* NULL or a number to check for the correct card. + * But is is not implemented. */ err = start_scd (ctrl); if (err) diff --git a/agent/command.c b/agent/command.c index 62b701467..5e2b6df2b 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2486,8 +2486,8 @@ cmd_delete_key (assuan_context_t ctx, char *line) static const char hlp_keytocard[] = "KEYTOCARD [--force] []\n" "\n" - "TIMESTAMP is required for OpenPGP and defaults to the Epoch." - ; + "TIMESTAMP is required for OpenPGP and defaults to the Epoch. The\n" + "SERIALNO is used for checking; use \"-\" to disable the check."; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { @@ -2527,8 +2527,18 @@ cmd_keytocard (assuan_context_t ctx, char *line) goto leave; } + /* Note that checking of the s/n is currently not implemented but we + * want to provide a clean interface if we ever implement it. */ serialno = argv[1]; + if (!strcmp (serialno, "-")) + serialno = NULL; + keyref = argv[2]; + + /* FIXME: Default to the creation time as stored in the private + * key. The parameter is here so that gpg can make sure that the + * timestamp as used for key creation (and thus the openPGP + * fingerprint) is used. */ timestamp_str = argc > 3? argv[3] : "19700101T000000"; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) diff --git a/scd/app-common.h b/scd/app-common.h index 97274a7cb..2404086e9 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -33,6 +33,9 @@ /* Flags used with app_genkey. */ #define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ +/* Flags used with app_writekey. */ +#define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ + /* Bit flags set by the decipher function into R_INFO. */ #define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ 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; diff --git a/scd/iso7816.c b/scd/iso7816.c index a9cd73014..d9f3336c7 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -222,6 +222,39 @@ iso7816_list_directory (int slot, int list_dirs, } +/* Wrapper around apdu_send. RESULT can be NULL if no result is + * expected. In addition to an gpg-error return code the actual + * status word is stored at R_SW unless that is NULL. */ +gpg_error_t +iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (result) + { + *result = NULL; + *resultlen = 0; + } + + sw = apdu_send (slot, extended_mode, class, ins, p0, p1, lc, data, + result, resultlen); + if (sw != SW_SUCCESS && result) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + if (r_sw) + *r_sw = sw; + return map_sw (sw); +} + + /* This function sends an already formatted APDU to the card. With HANDLE_MORE set to true a MORE DATA status will be handled internally. The return value is a gpg error code (i.e. a mapped diff --git a/scd/iso7816.h b/scd/iso7816.h index df5d25fe8..c1940ad8d 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -61,6 +61,11 @@ gpg_error_t iso7816_select_path (int slot, const unsigned short *path, size_t pathlen); gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, int handle_more, unsigned int *r_sw, diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 55ecf126e..f7dbfd6ec 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -1155,49 +1155,30 @@ scd_writecert (const char *certidstr, -/* Handle a KEYDATA inquiry. Note, we only send the data, - assuan_transact takes care of flushing and writing the end */ -static gpg_error_t -inq_writekey_parms (void *opaque, const char *line) -{ - gpg_error_t err; - struct writekey_parm_s *parm = opaque; - - if (has_leading_keyword (line, "KEYDATA")) - { - err = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen); - } - else - err = default_inq_cb (parm->dflt, line); - - return err; -} - - -/* Send a WRITEKEY command to the SCdaemon. */ +/* Send a WRITEKEY command to the agent (so that the agent can fetch + * the key to write). KEYGRIP is the hexified keygrip of the source + * key which will be written to tye slot KEYREF. FORCE must be true + * to overwrite an existing key. */ gpg_error_t -scd_writekey (int keyno, const unsigned char *keydata, size_t keydatalen) +scd_writekey (const char *keyref, int force, const char *keygrip) { gpg_error_t err; + struct default_inq_parm_s parm; char line[ASSUAN_LINELENGTH]; - struct writekey_parm_s parms; - struct default_inq_parm_s dfltparm; - memset (&parms, 0, sizeof parms); - memset (&dfltparm, 0, sizeof dfltparm); + memset (&parm, 0, sizeof parm); err = start_agent (0); if (err) return err; - snprintf (line, sizeof line, "SCD WRITEKEY --force OPENPGP.%d", keyno); - dfltparm.ctx = agent_ctx; - parms.dflt = &dfltparm; - parms.keydata = keydata; - parms.keydatalen = keydatalen; - + /* Note: We don't send the s/n but "-" because gpg-agent has + * currently no use for it. */ + /* FIXME: For OpenPGP we should provide the creation time. */ + snprintf (line, sizeof line, "KEYTOCARD%s %s - %s", + force? " --force":"", keygrip, keyref); err = assuan_transact (agent_ctx, line, NULL, NULL, - inq_writekey_parms, &parms, NULL, NULL); + default_inq_cb, &parm, NULL, NULL); return status_sc_op_failure (err); } diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 3f972fee4..bd450c0bb 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1667,7 +1667,7 @@ cmd_readcert (card_info_t info, char *argstr) if (!info) return print_help ("READCERT CERTREF > FILE\n\n" - "Read the certificate for key 3 and store it in FILE.", + "Read the certificate for key CERTREF and store it in FILE.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); argstr = skip_options (argstr); @@ -1718,6 +1718,62 @@ cmd_readcert (card_info_t info, char *argstr) } +static gpg_error_t +cmd_writekey (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_force; + char *argv[2]; + int argc; + char *keyref_buffer = NULL; + char *keyref; + char *keygrip; + + if (!info) + return print_help + ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" + "Write a private key object identified by KEYGRIP to slot KEYREF.\n" + "Use --force to overwrite an existing key.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + opt_force = has_leading_option (argstr, "--force"); + argstr = skip_options (argstr); + + argc = split_fields (argstr, argv, DIM (argv)); + if (argc < 2) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + /* Upcase the keyref; prepend cardtype if needed. */ + keyref = argv[0]; + if (!strchr (keyref, '.')) + keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", + keyref, NULL); + else + keyref_buffer = xstrdup (keyref); + ascii_strupr (keyref_buffer); + keyref = keyref_buffer; + + /* Get the keygrip. */ + keygrip = argv[1]; + if (strlen (keygrip) != 40 + && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) + { + log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_writekey (keyref, opt_force, keygrip); + + leave: + xfree (keyref_buffer); + return err; +} + + static gpg_error_t cmd_forcesig (card_info_t info) { @@ -2683,7 +2739,7 @@ ask_card_keyattr (int keyno, const struct key_attr *current, (void)algo; err = GPG_ERR_NOT_IMPLEMENTED; goto leave; - /* FIXME: We need to mve the ask_cure code out to common or + /* FIXME: We need to move the ask_cure code out to common or * provide another sultion. */ /* curve = ask_curve (&algo, NULL, curve); */ /* if (curve) */ @@ -2747,7 +2803,7 @@ do_change_keyattr (int keyno, const struct key_attr *key_attr) keyno+1, key_attr->algo, key_attr->curve); else { - /* FIXME: Above we use opnepgp algo names but in the error + /* FIXME: Above we use openpgp algo names but in the error * message we use the gcrypt names. We should settle for a * consistent solution. */ log_error (_("public key algorithm %d (%s) is not supported\n"), @@ -2918,7 +2974,7 @@ enum cmdids cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, - cmdREADCERT, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, + cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdKEYATTR, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdINVCMD }; @@ -2958,6 +3014,7 @@ static struct { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, + { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { NULL, cmdINVCMD, NULL } }; @@ -3084,6 +3141,7 @@ dispatch_command (card_info_t info, const char *orig_command) case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; @@ -3314,6 +3372,7 @@ interactive_loop (void) case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; diff --git a/tools/gpg-card.h b/tools/gpg-card.h index 03bad7530..3a86a67ec 100644 --- a/tools/gpg-card.h +++ b/tools/gpg-card.h @@ -208,8 +208,7 @@ gpg_error_t scd_setattr (const char *name, const unsigned char *value, size_t valuelen); gpg_error_t scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen); -gpg_error_t scd_writekey (int keyno, - const unsigned char *keydata, size_t keydatalen); +gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip); gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime); gpg_error_t scd_serialno (char **r_serialno, const char *demand);