Exporting secret keys via gpg-agent is now basically supported.
[gnupg.git] / g10 / call-agent.c
index 9379fcb..bdf4c95 100644 (file)
@@ -50,8 +50,9 @@ static int did_early_card_test;
 
 struct cipher_parm_s 
 {
+  ctrl_t ctrl;
   assuan_context_t ctx;
-  const char *ciphertext;
+  unsigned char *ciphertext;
   size_t ciphertextlen;
 };
 
@@ -76,6 +77,13 @@ struct genkey_parm_s
   const char *keyparms;
 };
 
+struct import_key_parm_s
+{
+  ctrl_t ctrl;
+  assuan_context_t ctx;
+  const void *key;
+  size_t keylen;
+};
 
 
 static gpg_error_t learn_status_cb (void *opaque, const char *line);
@@ -104,7 +112,6 @@ status_sc_op_failure (int rc)
 
 
 
-
 /* Try to connect to the agent via socket or fork it off and work by
    pipes.  Handle the server's initial greeting */
 static int
@@ -894,7 +901,27 @@ membuf_data_cb (void *opaque, const void *buffer, size_t length)
     put_membuf (data, buffer, length);
   return 0;
 }
-  
+
+/* Helper returning a command option to describe the used hash
+   algorithm.  See scd/command.c:cmd_pksign.  */
+static const char *
+hash_algo_option (int algo)
+{
+  switch (algo)
+    {
+    case GCRY_MD_RMD160: return "--hash=rmd160";
+    case GCRY_MD_SHA1  : return "--hash=sha1";
+    case GCRY_MD_SHA224: return "--hash=sha224";
+    case GCRY_MD_SHA256: return "--hash=sha256";
+    case GCRY_MD_SHA384: return "--hash=sha384";
+    case GCRY_MD_SHA512: return "--hash=sha512";
+    case GCRY_MD_MD5   : return "--hash=md5";
+    default:             return "";
+    }
+}
+
+
 /* Send a sign command to the scdaemon via gpg-agent's pass thru
    mechanism. */
 int
@@ -935,14 +962,11 @@ agent_scd_pksign (const char *serialno, int hashalgo,
     return rc;
 
   init_membuf (&data, 1024);
-#if 0
-  if (!hashalgo) /* Temporary test hack. */
-    snprintf (line, DIM(line)-1, "SCD PKAUTH %s", serialno);
-  else
-#endif
-    snprintf (line, DIM(line)-1, "SCD PKSIGN %s%s",
-              hashalgo == GCRY_MD_RMD160? "--hash=rmd160 " : "",
-              serialno);
+  /* if (!hashalgo) /\* Temporary test hack. *\/ */
+  /*   snprintf (line, DIM(line)-1, "SCD PKAUTH %s", serialno); */
+  /* else */
+  snprintf (line, DIM(line)-1, "SCD PKSIGN %s %s",
+            hash_algo_option (hashalgo), serialno);
   line[DIM(line)-1] = 0;
   rc = assuan_transact (agent_ctx, line, membuf_data_cb, &data,
                         default_inq_cb, NULL, NULL, NULL);
@@ -1294,28 +1318,81 @@ agent_get_s2k_count (unsigned long *r_count)
 
 
 \f
-/* Ask the agent whether a secret key with the given keygrip is
-   known.  */
+/* Ask the agent whether a secret key for the given public key is
+   available.  Returns 0 if available.  */
 gpg_error_t
-agent_havekey (ctrl_t ctrl, const char *hexkeygrip)
+agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
 {
   gpg_error_t err;
   char line[ASSUAN_LINELENGTH];
+  char *hexgrip;
 
   err = start_agent (ctrl, 0);
   if (err)
     return err;
 
-  if (!hexkeygrip || strlen (hexkeygrip) != 40)
-    return gpg_error (GPG_ERR_INV_VALUE);
+  err = hexkeygrip_from_pk (pk, &hexgrip);
+  if (err)
+    return err;
 
-  snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip);
-  line[DIM(line)-1] = 0;
+  snprintf (line, sizeof line, "HAVEKEY %s", hexgrip);
+  xfree (hexgrip);
 
   err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   return err;
 }
 
+/* Ask the agent whether a secret key is availabale for any of the
+   keys (primary or sub) in KEYBLOCK.  Returns 0 if available.  */
+gpg_error_t
+agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  char *p;
+  kbnode_t kbctx, node;
+  int nkeys;
+  unsigned char grip[20];
+
+  err = start_agent (ctrl, 0);
+  if (err)
+    return err;
+
+  err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was
+                                          found in KEYBLOCK.  */
+  p = stpcpy (line, "HAVEKEY");
+  for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); )
+    if (node->pkt->pkttype == PKT_PUBLIC_KEY
+        || node->pkt->pkttype == PKT_PUBLIC_SUBKEY
+        || node->pkt->pkttype == PKT_SECRET_KEY
+        || node->pkt->pkttype == PKT_SECRET_SUBKEY)
+      {
+        if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2))
+          {
+            err = assuan_transact (agent_ctx, line,
+                                   NULL, NULL, NULL, NULL, NULL, NULL);
+            if (err != gpg_err_code (GPG_ERR_NO_SECKEY))
+              break; /* Seckey available or unexpected error - ready.  */
+            p = stpcpy (line, "HAVEKEY");
+            nkeys = 0;
+          }
+
+        err = keygrip_from_pk (node->pkt->pkt.public_key, grip);
+        if (err)
+          return err;
+        *p++ = ' ';
+        bin2hex (grip, 20, p);
+        p += 40;
+        nkeys++;
+      }
+
+  if (!err && nkeys)
+    err = assuan_transact (agent_ctx, line,
+                           NULL, NULL, NULL, NULL, NULL, NULL);
+
+  return err;
+}
+
 
 \f
 static gpg_error_t
@@ -1383,6 +1460,33 @@ agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno)
   return err;
 }
 
+\f
+/* Status callback for agent_import_key, agent_export_key and
+   agent_genkey.  */
+static gpg_error_t
+cache_nonce_status_cb (void *opaque, const char *line)
+{
+  char **cache_nonce = opaque;
+  const char *keyword = line;
+  int keywordlen;
+
+  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+    ;
+  while (spacep (line))
+    line++;
+
+  if (keywordlen == 11 && !memcmp (keyword, "CACHE_NONCE", keywordlen))
+    {
+      if (cache_nonce)
+        {
+          xfree (*cache_nonce);
+          *cache_nonce = xtrystrdup (line);
+        }
+    }
+
+  return 0;
+}
+
 
 \f
 /* Handle a KEYPARMS inquiry.  Note, we only send the data,
@@ -1409,13 +1513,15 @@ inq_genkey_parms (void *opaque, const char *line)
    S-expression giving the parameters of the key.  gpg-agent passes it
    gcry_pk_genkey.  */
 gpg_error_t
-agent_genkey (ctrl_t ctrl, const char *keyparms, gcry_sexp_t *r_pubkey)
+agent_genkey (ctrl_t ctrl, char **cache_nonce_addr,
+              const char *keyparms, gcry_sexp_t *r_pubkey)
 {
   gpg_error_t err;
   struct genkey_parm_s gk_parm;
   membuf_t data;
   size_t len;
   unsigned char *buf;
+  char line[ASSUAN_LINELENGTH];
 
   *r_pubkey = NULL;
   err = start_agent (ctrl, 0);
@@ -1431,9 +1537,13 @@ agent_genkey (ctrl_t ctrl, const char *keyparms, gcry_sexp_t *r_pubkey)
   gk_parm.ctrl     = ctrl;
   gk_parm.ctx      = agent_ctx;
   gk_parm.keyparms = keyparms;
-  err = assuan_transact (agent_ctx, "GENKEY",
+  snprintf (line, sizeof line, "GENKEY%s%s",
+            cache_nonce_addr && *cache_nonce_addr? " ":"",
+            cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
+  err = assuan_transact (agent_ctx, line,
                          membuf_data_cb, &data, 
-                         inq_genkey_parms, &gk_parm, NULL, NULL);
+                         inq_genkey_parms, &gk_parm, 
+                         cache_nonce_status_cb, cache_nonce_addr);
   if (err)
     {
       xfree (get_membuf (&data, &len));
@@ -1508,9 +1618,11 @@ agent_genkey (ctrl_t ctrl, const char *keyparms, gcry_sexp_t *r_pubkey)
    the hex string KEYGRIP.  DESC is a description of the key to be
    displayed if the agent needs to ask for the PIN.  DIGEST and
    DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id
-   used to compute the digest.  */
+   used to compute the digest.  If CACHE_NONCE is used the agent is
+   advised to firts try a passphrase associated with that nonce. */
 gpg_error_t
-agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
+agent_pksign (ctrl_t ctrl, const char *cache_nonce,
+              const char *keygrip, const char *desc,
               unsigned char *digest, size_t digestlen, int digestalgo,
               gcry_sexp_t *r_sigval)
 {
@@ -1557,7 +1669,11 @@ agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
     return err;
 
   init_membuf (&data, 1024);
-  err = assuan_transact (agent_ctx, "PKSIGN",
+
+  snprintf (line, sizeof line, "PKSIGN%s%s",
+            cache_nonce? " -- ":"",
+            cache_nonce? cache_nonce:"");
+  err = assuan_transact (agent_ctx, line,
                         membuf_data_cb, &data, default_inq_cb, ctrl,
                         NULL, NULL);
   if (err)
@@ -1580,3 +1696,278 @@ agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
 }
 
 
+\f
+/* Handle a CIPHERTEXT inquiry.  Note, we only send the data,
+   assuan_transact takes care of flushing and writing the END. */
+static gpg_error_t
+inq_ciphertext_cb (void *opaque, const char *line)
+{
+  struct cipher_parm_s *parm = opaque; 
+  int rc;
+
+  if (!strncmp (line, "CIPHERTEXT", 10) && (line[10]==' '||!line[10]))
+    {
+      assuan_begin_confidential (parm->ctx);
+      rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen);
+      assuan_end_confidential (parm->ctx);
+    }
+  else
+    rc = default_inq_cb (parm->ctrl, line);
+
+  return rc; 
+}
+
+
+/* Call the agent to do a decrypt operation using the key identified
+   by the hex string KEYGRIP and the input data S_CIPHERTEXT.  On the
+   success the decoded value is stored verbatim at R_BUF and its
+   length at R_BUF; the callers needs to release it.  */
+gpg_error_t
+agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
+                 gcry_sexp_t s_ciphertext,
+                 unsigned char **r_buf, size_t *r_buflen)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  membuf_t data;
+  size_t n, len;
+  char *p, *buf, *endp;
+  
+  if (!keygrip || strlen(keygrip) != 40 || !s_ciphertext || !r_buf || !r_buflen)
+    return gpg_error (GPG_ERR_INV_VALUE);
+  *r_buf = NULL;
+
+  err = start_agent (ctrl, 0);
+  if (err)
+    return err;
+
+  err = assuan_transact (agent_ctx, "RESET",
+                         NULL, NULL, NULL, NULL, NULL, NULL);
+  if (err)
+    return err;
+
+  snprintf (line, sizeof line, "SETKEY %s", keygrip);
+  err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+  if (err)
+    return err;
+
+  if (desc)
+    {
+      snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc);
+      line[DIM(line)-1] = 0;
+      err = assuan_transact (agent_ctx, line,
+                            NULL, NULL, NULL, NULL, NULL, NULL);
+      if (err)
+        return err;
+    }
+
+  init_membuf_secure (&data, 1024);
+  {
+    struct cipher_parm_s parm;
+    
+    parm.ctrl = ctrl;
+    parm.ctx = agent_ctx;
+    err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen);
+    if (err)
+      return err;
+    err = assuan_transact (agent_ctx, "PKDECRYPT",
+                           membuf_data_cb, &data,
+                           inq_ciphertext_cb, &parm, NULL, NULL);
+    xfree (parm.ciphertext);
+  }
+  if (err)
+    {
+      xfree (get_membuf (&data, &len));
+      return err;
+    }
+
+  put_membuf (&data, "", 1); /* Make sure it is 0 terminated.  */
+  buf = get_membuf (&data, &len);
+  if (!buf)
+    return gpg_error_from_syserror ();
+  assert (len); /* (we forced Nul termination.)  */
+
+  if (*buf != '(')
+    {
+      xfree (buf);
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+
+  if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */
+    {
+      xfree (buf);
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+  len -= 11;   /* Count only the data of the second part. */
+  p = buf + 8; /* Skip leading parenthesis and the value tag. */
+
+  n = strtoul (p, &endp, 10);
+  if (!n || *endp != ':')
+    {
+      xfree (buf);
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+  endp++;
+  if (endp-p+n > len)
+    {
+      xfree (buf);
+      return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
+    }
+  
+  memmove (buf, endp, n);
+
+  *r_buflen = n;
+  *r_buf = buf;
+  return 0;
+}
+
+
+\f
+/* Retrieve a key encryption key from the agent.  With FOREXPORT true
+   the key shall be used for export, with false for import.  On success
+   the new key is stored at R_KEY and its length at R_KEKLEN.  */
+gpg_error_t
+agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen)
+{
+  gpg_error_t err;
+  membuf_t data;
+  size_t len;
+  unsigned char *buf;
+  char line[ASSUAN_LINELENGTH];
+
+  *r_kek = NULL;
+  err = start_agent (ctrl, 0);
+  if (err)
+    return err;
+
+  snprintf (line, DIM(line)-1, "KEYWRAP_KEY %s",
+            forexport? "--export":"--import");
+
+  init_membuf_secure (&data, 64);
+  err = assuan_transact (agent_ctx, line,
+                         membuf_data_cb, &data, 
+                         default_inq_cb, ctrl, NULL, NULL);
+  if (err)
+    {
+      xfree (get_membuf (&data, &len));
+      return err;
+    }
+  buf = get_membuf (&data, &len);
+  if (!buf)
+    return gpg_error_from_syserror ();
+  *r_kek = buf;
+  *r_keklen = len;
+  return 0;
+}
+
+
+\f
+/* Handle the inquiry for an IMPORT_KEY command.  */
+static gpg_error_t
+inq_import_key_parms (void *opaque, const char *line)
+{
+  struct import_key_parm_s *parm = opaque; 
+  gpg_error_t err;
+
+  if (!strncmp (line, "KEYDATA", 7) && (line[7]==' '||!line[7]))
+    {
+      err = assuan_send_data (parm->ctx, parm->key, parm->keylen);
+    }
+  else
+    err = default_inq_cb (parm->ctrl, line);
+
+  return err; 
+}
+
+
+/* Call the agent to import a key into the agent.  */
+gpg_error_t
+agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
+                  const void *key, size_t keylen)
+{
+  gpg_error_t err;
+  struct import_key_parm_s parm;
+  char line[ASSUAN_LINELENGTH];
+
+  err = start_agent (ctrl, 0);
+  if (err)
+    return err;
+
+  if (desc)
+    {
+      snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc);
+      line[DIM(line)-1] = 0;
+      err = assuan_transact (agent_ctx, line,
+                            NULL, NULL, NULL, NULL, NULL, NULL);
+      if (err)
+        return err;
+    }
+
+  parm.ctrl   = ctrl;
+  parm.ctx    = agent_ctx;
+  parm.key    = key;
+  parm.keylen = keylen;
+
+  snprintf (line, sizeof line, "IMPORT_KEY%s%s",
+            cache_nonce_addr && *cache_nonce_addr? " ":"",
+            cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
+  err = assuan_transact (agent_ctx, line,
+                         NULL, NULL, inq_import_key_parms, &parm,
+                         cache_nonce_status_cb, cache_nonce_addr);
+  return err;
+}
+
+
+\f
+/* Receive a secret key from the agent.  HEXKEYGRIP is the hexified
+   keygrip, DESC a prompt to be displayed with the agent's passphrase
+   question (needs to be plus+percent escaped).  On success the key is
+   stored as a canonical S-expression at R_RESULT and R_RESULTLEN.  */
+gpg_error_t
+agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
+                  char **cache_nonce_addr,
+                  unsigned char **r_result, size_t *r_resultlen)
+{
+  gpg_error_t err;
+  membuf_t data;
+  size_t len;
+  unsigned char *buf;
+  char line[ASSUAN_LINELENGTH];
+
+  *r_result = NULL;
+
+  err = start_agent (ctrl, 0);
+  if (err)
+    return err;
+
+  if (desc)
+    {
+      snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc);
+      err = assuan_transact (agent_ctx, line,
+                             NULL, NULL, NULL, NULL, NULL, NULL);
+      if (err)
+        return err;
+    }
+
+  snprintf (line, DIM(line)-1, "EXPORT_KEY --openpgp %s%s %s", 
+            cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
+            cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
+            hexkeygrip);
+
+  init_membuf_secure (&data, 1024);
+  err = assuan_transact (agent_ctx, line,
+                         membuf_data_cb, &data, 
+                         default_inq_cb, ctrl,
+                         cache_nonce_status_cb, cache_nonce_addr);
+  if (err)
+    {
+      xfree (get_membuf (&data, &len));
+      return err;
+    }
+  buf = get_membuf (&data, &len);
+  if (!buf)
+    return gpg_error_from_syserror ();
+  *r_result = buf;
+  *r_resultlen = len;
+  return 0;
+}