aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--agent/call-scd.c3
-rw-r--r--agent/command.c14
-rw-r--r--scd/app-common.h3
-rw-r--r--scd/app-piv.c442
-rw-r--r--scd/iso7816.c33
-rw-r--r--scd/iso7816.h5
-rw-r--r--tools/card-call-scd.c45
-rw-r--r--tools/gpg-card.c67
-rw-r--r--tools/gpg-card.h3
9 files changed, 561 insertions, 54 deletions
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] <hexgrip> <serialno> <keyref> [<timestamp>]\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);
@@ -1719,6 +1719,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)
{
gpg_error_t err;
@@ -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);