scd:piv: Implement import of private keys for Yubikeys.
authorWerner Koch <wk@gnupg.org>
Tue, 5 Mar 2019 14:49:20 +0000 (15:49 +0100)
committerWerner Koch <wk@gnupg.org>
Tue, 5 Mar 2019 14:49:20 +0000 (15:49 +0100)
* 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 <wk@gnupg.org>
agent/call-scd.c
agent/command.c
scd/app-common.h
scd/app-piv.c
scd/iso7816.c
scd/iso7816.h
tools/card-call-scd.c
tools/gpg-card.c
tools/gpg-card.h

index 1189bd4..4c0186d 100644 (file)
@@ -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)
index 62b7014..5e2b6df 100644 (file)
@@ -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))
index 97274a7..2404086 100644 (file)
@@ -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.  */
 
index d55d71f..6d66115 100644 (file)
@@ -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;
index a9cd730..d9f3336 100644 (file)
@@ -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
index df5d25f..c1940ad 100644 (file)
@@ -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,
index 55ecf12..f7dbfd6 100644 (file)
@@ -1155,49 +1155,30 @@ scd_writecert (const char *certidstr,
 
 
 \f
-/* 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);
 }
index 3f972fe..bd450c0 100644 (file)
@@ -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;
index 03bad75..3a86a67 100644 (file)
@@ -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);