Fix strerror vs. gpg_strerror usage.
[gnupg.git] / agent / cvt-openpgp.c
index a392518..ec0fd0a 100644 (file)
@@ -30,7 +30,7 @@
 
 
 /* Helper to pass data via the callback to do_unprotect. */
-struct try_do_unprotect_arg_s 
+struct try_do_unprotect_arg_s
 {
   int  is_v4;
   int  is_protected;
@@ -80,6 +80,14 @@ get_keygrip (int pubkey_algo, gcry_mpi_t *pkey, unsigned char *grip)
                              "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
       break;
 
+    case GCRY_PK_ECDSA:
+    case GCRY_PK_ECDH:
+      err = gcry_sexp_build (&s_pkey, NULL,
+                             "(public-key(ecc(p%m)(a%m)(b%m)(g%m)(n%m)(q%m)))",
+                             pkey[0], pkey[1], pkey[2], pkey[3], pkey[4],
+                             pkey[5]);
+      break;
+
     default:
       err = gpg_error (GPG_ERR_PUBKEY_ALGO);
       break;
@@ -94,7 +102,8 @@ get_keygrip (int pubkey_algo, gcry_mpi_t *pkey, unsigned char *grip)
 
 
 /* Convert a secret key given as algorithm id and an array of key
-   parameters into our s-expression based format.  */
+   parameters into our s-expression based format.  Note that
+   PUBKEY_ALGO has an gcrypt algorithm number. */
 static gpg_error_t
 convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey)
 {
@@ -126,6 +135,19 @@ convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey)
                              "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
                              skey[0], skey[1], skey[2], skey[3], skey[4],
                              skey[5]);
+      break;
+
+    case GCRY_PK_ECDSA:
+    case GCRY_PK_ECDH:
+      /* Although our code would work with "ecc" we explicitly use
+         "ecdh" or "ecdsa" to implicitly set the key capabilities.  */
+      err = gcry_sexp_build (&s_skey, NULL,
+                             "(private-key(%s(p%m)(a%m)(b%m)(g%m)(n%m)(q%m)"
+                             "(d%m)))",
+                             pubkey_algo == GCRY_PK_ECDSA?"ecdsa":"ecdh",
+                             skey[0], skey[1], skey[2], skey[3], skey[4],
+                             skey[5], skey[6]);
+      break;
 
     default:
       err = gpg_error (GPG_ERR_PUBKEY_ALGO);
@@ -153,7 +175,7 @@ hash_passphrase_and_set_key (const char *passphrase,
   keylen = gcry_cipher_get_algo_keylen (protect_algo);
   if (!keylen)
     return gpg_error (GPG_ERR_INTERNAL);
-  
+
   key = xtrymalloc_secure (keylen);
   if (!key)
     return gpg_error_from_syserror ();
@@ -173,7 +195,7 @@ static u16
 checksum (const unsigned char *p, unsigned int n)
 {
   u16 a;
-  
+
   for (a=0; n; n-- )
     a += *p++;
   return a;
@@ -201,6 +223,10 @@ do_unprotect (const char *passphrase,
 
   *r_key = NULL;
 
+ /* Unfortunately, the OpenPGP PK algorithm numbers need to be
+    re-mapped for Libgcrypt.    */
+  pubkey_algo = map_pk_openpgp_to_gcry (pubkey_algo);
+
   /* Count the actual number of MPIs is in the array and set the
      remainder to NULL for easier processing later on.  */
   for (skeylen = 0; skey[skeylen]; skeylen++)
@@ -218,9 +244,6 @@ do_unprotect (const char *passphrase,
 
   if (gcry_pk_test_algo (pubkey_algo))
     {
-      /* The algorithm numbers are Libgcrypt numbers but fortunately
-         the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
-         numbers.  */
       log_info (_("public key algorithm %d (%s) is not supported\n"),
                 pubkey_algo, gcry_pk_algo_name (pubkey_algo));
       return gpg_error (GPG_ERR_PUBKEY_ALGO);
@@ -240,7 +263,7 @@ do_unprotect (const char *passphrase,
     return gpg_error (GPG_ERR_MISSING_VALUE);
   if (nskey+1 >= skeysize)
     return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
-  
+
   /* Check whether SKEY is at all protected.  If it is not protected
      merely verify the checksum.  */
   if (!is_protected)
@@ -252,7 +275,7 @@ do_unprotect (const char *passphrase,
         {
           if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
             return gpg_error (GPG_ERR_BAD_SECKEY);
-          
+
           err = gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, &nbytes, skey[i]);
           if (!err)
             {
@@ -269,7 +292,7 @@ do_unprotect (const char *passphrase,
           if (err)
             return err;
         }
-      
+
       if (actual_csum != desired_csum)
         return gpg_error (GPG_ERR_CHECKSUM);
       return 0;
@@ -282,7 +305,7 @@ do_unprotect (const char *passphrase,
          the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
          numbers.  */
       log_info (_("protection algorithm %d (%s) is not supported\n"),
-                protect_algo, gcry_cipher_algo_name (protect_algo));
+                protect_algo, gnupg_cipher_algo_name (protect_algo));
       return gpg_error (GPG_ERR_CIPHER_ALGO);
     }
 
@@ -292,7 +315,7 @@ do_unprotect (const char *passphrase,
                 s2k_algo, gcry_md_algo_name (s2k_algo));
       return gpg_error (GPG_ERR_DIGEST_ALGO);
     }
-  
+
   err = gcry_cipher_open (&cipher_hd, protect_algo,
                           GCRY_CIPHER_MODE_CFB,
                           (GCRY_CIPHER_SECURE
@@ -311,10 +334,10 @@ do_unprotect (const char *passphrase,
     {
       gcry_cipher_close (cipher_hd);
       return err;
-    }  
+    }
 
   gcry_cipher_setiv (cipher_hd, protect_iv, protect_ivlen);
-  
+
   actual_csum = 0;
   if (pkt_version >= 4)
     {
@@ -347,15 +370,15 @@ do_unprotect (const char *passphrase,
         {
           /* This is the new SHA1 checksum method to detect tampering
              with the key as used by the Klima/Rosa attack.  */
-          desired_csum = 0; 
+          desired_csum = 0;
           actual_csum = 1;  /* Default to bad checksum.  */
 
-          if (ndata < 20) 
+          if (ndata < 20)
             log_error ("not enough bytes for SHA-1 checksum\n");
-          else 
+          else
             {
               gcry_md_hd_t h;
-              
+
               if (gcry_md_open (&h, GCRY_MD_SHA1, 1))
                 BUG(); /* Algo not available. */
               gcry_md_write (h, data, ndata - 20);
@@ -365,13 +388,13 @@ do_unprotect (const char *passphrase,
               gcry_md_close (h);
             }
         }
-      else 
+      else
         {
           /* Old 16 bit checksum method.  */
           if (ndata < 2)
             {
               log_error ("not enough bytes for checksum\n");
-              desired_csum = 0; 
+              desired_csum = 0;
               actual_csum = 1;  /* Mark checksum bad.  */
             }
           else
@@ -385,7 +408,7 @@ do_unprotect (const char *passphrase,
                 }
             }
         }
-      
+
       /* Better check it here.  Otherwise the gcry_mpi_scan would fail
          because the length may have an arbitrary value.  */
       if (desired_csum == actual_csum)
@@ -436,7 +459,7 @@ do_unprotect (const char *passphrase,
               gcry_cipher_close (cipher_hd);
               return gpg_error (GPG_ERR_BAD_SECKEY);
             }
-          
+
           buffer = xtrymalloc_secure (ndata);
           if (!buffer)
             {
@@ -444,7 +467,7 @@ do_unprotect (const char *passphrase,
               gcry_cipher_close (cipher_hd);
               return err;
             }
-              
+
           gcry_cipher_sync (cipher_hd);
           buffer[0] = p[0];
           buffer[1] = p[1];
@@ -497,7 +520,8 @@ try_do_unprotect_cb (struct pin_entry_info_s *pi)
   gpg_error_t err;
   struct try_do_unprotect_arg_s *arg = pi->check_cb_arg;
 
-  err = do_unprotect (pi->pin, arg->is_v4? 4:3,
+  err = do_unprotect (pi->pin,
+                      arg->is_v4? 4:3,
                       arg->pubkey_algo, arg->is_protected,
                       arg->skey, arg->skeysize,
                       arg->protect_algo, arg->iv, arg->ivlen,
@@ -507,15 +531,16 @@ try_do_unprotect_cb (struct pin_entry_info_s *pi)
   /* SKEY may be modified now, thus we need to re-compute SKEYIDX.  */
   for (arg->skeyidx = 0; (arg->skeyidx < arg->skeysize
                           && arg->skey[arg->skeyidx]); arg->skeyidx++)
-         ;
+    ;
   return err;
 }
 
 
 /* Convert an OpenPGP transfer key into our internal format.  Before
    asking for a passphrase we check whether the key already exists in
-   our key storage.  S_PGP is the OpenPGP key in transfer format.  On
-   success R_KEY will receive a canonical encoded S-expression with
+   our key storage.  S_PGP is the OpenPGP key in transfer format.  If
+   CACHE_NONCE is given the passphrase will be looked up in the cache.
+   On success R_KEY will receive a canonical encoded S-expression with
    the unprotected key in our internal format; the caller needs to
    release that memory.  The passphrase used to decrypt the OpenPGP
    key will be returned at R_PASSPHRASE; the caller must release this
@@ -523,9 +548,10 @@ try_do_unprotect_cb (struct pin_entry_info_s *pi)
    pointed to by GRIP.  On error NULL is stored at all return
    arguments.  */
 gpg_error_t
-convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, 
-                 unsigned char *grip, const char *prompt,
-                 unsigned char **r_key, char **r_passphrase)
+convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
+                      unsigned char *grip, const char *prompt,
+                      const char *cache_nonce,
+                      unsigned char **r_key, char **r_passphrase)
 {
   gpg_error_t err;
   gcry_sexp_t top_list;
@@ -590,7 +616,7 @@ convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
       if (!protect_algo && !!strcmp (string, "IDEA"))
         protect_algo = GCRY_CIPHER_IDEA;
       xfree (string);
-      
+
       value = gcry_sexp_nth_data (list, 3, &valuelen);
       if (!value || !valuelen || valuelen > sizeof iv)
         goto bad_seckey;
@@ -759,7 +785,24 @@ convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
   pi_arg.skeysize = DIM (skey);
   pi_arg.skeyidx = skeyidx;
   pi_arg.r_key = &s_skey;
-  err = agent_askpin (ctrl, prompt, NULL, NULL, pi);
+
+  err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
+  if (cache_nonce)
+    {
+      char *cache_value;
+
+      cache_value = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
+      if (cache_value)
+        {
+          if (strlen (cache_value) < pi->max_length)
+            strcpy (pi->pin, cache_value);
+          xfree (cache_value);
+        }
+      if (*pi->pin)
+        err = try_do_unprotect_cb (pi);
+    }
+  if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
+    err = agent_askpin (ctrl, prompt, NULL, NULL, pi);
   skeyidx = pi_arg.skeyidx;
   if (!err)
     {
@@ -796,7 +839,7 @@ convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
  bad_seckey:
   err = gpg_error (GPG_ERR_BAD_SECKEY);
   goto leave;
-  
+
  outofmem:
   err = gpg_error (GPG_ERR_ENOMEM);
   goto leave;
@@ -804,4 +847,270 @@ convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
 }
 
 
+\f
+static gpg_error_t
+key_from_sexp (gcry_sexp_t sexp, const char *elems, gcry_mpi_t *array)
+{
+  gpg_error_t err = 0;
+  gcry_sexp_t l2;
+  int idx;
+
+  for (idx=0; *elems; elems++, idx++)
+    {
+      l2 = gcry_sexp_find_token (sexp, elems, 1);
+      if (!l2)
+        {
+          err = gpg_error (GPG_ERR_NO_OBJ); /* Required parameter not found.  */
+          goto leave;
+        }
+      array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+      gcry_sexp_release (l2);
+      if (!array[idx])
+        {
+          err = gpg_error (GPG_ERR_INV_OBJ); /* Required parameter invalid.  */
+          goto leave;
+        }
+    }
+
+ leave:
+  if (err)
+    {
+      int i;
+
+      for (i=0; i < idx; i++)
+        {
+          gcry_mpi_release (array[i]);
+          array[i] = NULL;
+        }
+    }
+  return err;
+}
+
+
+/* Given an ARRAY of mpis with the key parameters, protect the secret
+   parameters in that array and replace them by one opaque encoded
+   mpi.  NPKEY is the number of public key parameters and NSKEY is
+   the number of secret key parameters (including the public ones).
+   On success the array will have NPKEY+1 elements.  */
+static gpg_error_t
+apply_protection (gcry_mpi_t *array, int npkey, int nskey,
+                  const char *passphrase,
+                  int protect_algo, void *protect_iv, size_t protect_ivlen,
+                  int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count)
+{
+  gpg_error_t err;
+  int i, j;
+  gcry_cipher_hd_t cipherhd;
+  unsigned char *bufarr[10];
+  size_t narr[10];
+  unsigned int nbits[10];
+  int ndata;
+  unsigned char *p, *data;
+
+  assert (npkey < nskey);
+  assert (nskey < DIM (bufarr));
+
+  /* Collect only the secret key parameters into BUFARR et al and
+     compute the required size of the data buffer.  */
+  ndata = 20; /* Space for the SHA-1 checksum.  */
+  for (i = npkey, j = 0; i < nskey; i++, j++ )
+    {
+      err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]);
+      if (err)
+        {
+          err = gpg_error_from_syserror ();
+          for (i = 0; i < j; i++)
+            xfree (bufarr[i]);
+          return err;
+        }
+      nbits[j] = gcry_mpi_get_nbits (array[i]);
+      ndata += 2 + narr[j];
+    }
+
+  /* Allocate data buffer and stuff it with the secret key parameters.  */
+  data = xtrymalloc_secure (ndata);
+  if (!data)
+    {
+      err = gpg_error_from_syserror ();
+      for (i = 0; i < (nskey-npkey); i++ )
+        xfree (bufarr[i]);
+      return err;
+    }
+  p = data;
+  for (i = 0; i < (nskey-npkey); i++ )
+    {
+      *p++ = nbits[i] >> 8 ;
+      *p++ = nbits[i];
+      memcpy (p, bufarr[i], narr[i]);
+      p += narr[i];
+      xfree (bufarr[i]);
+      bufarr[i] = NULL;
+    }
+  assert (p == data + ndata - 20);
+
+  /* Append a hash of the secret key parameters.  */
+  gcry_md_hash_buffer (GCRY_MD_SHA1, p, data, ndata - 20);
+
+  /* Encrypt it.  */
+  err = gcry_cipher_open (&cipherhd, protect_algo,
+                          GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
+  if (!err)
+    err = hash_passphrase_and_set_key (passphrase, cipherhd, protect_algo,
+                                       s2k_mode, s2k_algo, s2k_salt, s2k_count);
+  if (!err)
+    err = gcry_cipher_setiv (cipherhd, protect_iv, protect_ivlen);
+  if (!err)
+    err = gcry_cipher_encrypt (cipherhd, data, ndata, NULL, 0);
+  gcry_cipher_close (cipherhd);
+  if (err)
+    {
+      xfree (data);
+      return err;
+    }
+
+  /* Replace the secret key parameters in the array by one opaque value.  */
+  for (i = npkey; i < nskey; i++ )
+    {
+      gcry_mpi_release (array[i]);
+      array[i] = NULL;
+    }
+  array[npkey] = gcry_mpi_set_opaque (NULL, data, ndata*8);
+  return 0;
+}
+
+
+/* Convert our key S_KEY into an OpenPGP key transfer format.  On
+   success a canonical encoded S-expression is stored at R_TRANSFERKEY
+   and its length at R_TRANSFERKEYLEN; this S-expression is also
+   padded to a multiple of 64 bits.  */
+gpg_error_t
+convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
+                    unsigned char **r_transferkey, size_t *r_transferkeylen)
+{
+  gpg_error_t err;
+  gcry_sexp_t list, l2;
+  char *name;
+  int algo;
+  const char *algoname;
+  const char *elems;
+  int npkey, nskey;
+  gcry_mpi_t array[10];
+  char protect_iv[16];
+  char salt[8];
+  unsigned long s2k_count;
+  int i, j;
+
+  (void)ctrl;
+
+  *r_transferkey = NULL;
+
+  for (i=0; i < DIM (array); i++)
+    array[i] = NULL;
+
+  list = gcry_sexp_find_token (s_key, "private-key", 0);
+  if (!list)
+    return gpg_error (GPG_ERR_NO_OBJ); /* Does not contain a key object.  */
+  l2 = gcry_sexp_cadr (list);
+  gcry_sexp_release (list);
+  list = l2;
+  name = gcry_sexp_nth_string (list, 0);
+  if (!name)
+    {
+      gcry_sexp_release (list);
+      return gpg_error (GPG_ERR_INV_OBJ); /* Invalid structure of object. */
+    }
+
+  algo = gcry_pk_map_name (name);
+  xfree (name);
+
+  switch (algo)
+    {
+    case GCRY_PK_RSA:   algoname = "rsa";   npkey = 2; elems = "nedpqu";  break;
+    case GCRY_PK_ELG:   algoname = "elg";   npkey = 3; elems = "pgyx";    break;
+    case GCRY_PK_ELG_E: algoname = "elg";   npkey = 3; elems = "pgyx";    break;
+    case GCRY_PK_DSA:   algoname = "dsa";   npkey = 4; elems = "pqgyx";   break;
+    case GCRY_PK_ECDSA: algoname = "ecdsa"; npkey = 6; elems = "pabgnqd"; break;
+    case GCRY_PK_ECDH:  algoname = "ecdh";  npkey = 6; elems = "pabgnqd"; break;
+    default:            algoname = "";      npkey = 0; elems = NULL;      break;
+    }
+  assert (!elems || strlen (elems) < DIM (array) );
+  nskey = elems? strlen (elems) : 0;
+
+  if (!elems)
+    err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+  else
+    err = key_from_sexp (list, elems, array);
+  gcry_sexp_release (list);
+  if (err)
+    return err;
+
+  gcry_create_nonce (protect_iv, sizeof protect_iv);
+  gcry_create_nonce (salt, sizeof salt);
+  /* We need to use the encoded S2k count.  It is not possible to
+     encode it after it has been used because the encoding procedure
+     may round the value up.  */
+  s2k_count = get_standard_s2k_count_rfc4880 ();
+  err = apply_protection (array, npkey, nskey, passphrase,
+                          GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
+                          3, GCRY_MD_SHA1, salt, s2k_count);
+  /* Turn it into the transfer key S-expression.  Note that we always
+     return a protected key.  */
+  if (!err)
+    {
+      char countbuf[35];
+      membuf_t mbuf;
+      void *format_args_buf_ptr[1];
+      int   format_args_buf_int[1];
+      void *format_args[10+2];
+      unsigned int n;
+      gcry_sexp_t tmpkey, tmpsexp = NULL;
+
+      snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
+
+      init_membuf (&mbuf, 50);
+      put_membuf_str (&mbuf, "(skey");
+      for (i=j=0; i < npkey; i++)
+        {
+          put_membuf_str (&mbuf, " _ %m");
+          format_args[j++] = array + i;
+        }
+      put_membuf_str (&mbuf, " e %b");
+      format_args_buf_ptr[0] = gcry_mpi_get_opaque (array[npkey], &n);
+      format_args_buf_int[0] = (n+7)/8;
+      format_args[j++] = format_args_buf_int;
+      format_args[j++] = format_args_buf_ptr;
+      put_membuf_str (&mbuf, ")\n");
+      put_membuf (&mbuf, "", 1);
+
+      tmpkey = NULL;
+      {
+        char *format = get_membuf (&mbuf, NULL);
+        if (!format)
+          err = gpg_error_from_syserror ();
+        else
+          err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args);
+        xfree (format);
+      }
+      if (!err)
+        err = gcry_sexp_build (&tmpsexp, NULL,
+                               "(openpgp-private-key\n"
+                               " (version 1:4)\n"
+                               " (algo %s)\n"
+                               " %S\n"
+                               " (protection sha1 aes %b 1:3 sha1 %b %s))\n",
+                               algoname,
+                               tmpkey,
+                               (int)sizeof protect_iv, protect_iv,
+                               (int)sizeof salt, salt,
+                               countbuf);
+      gcry_sexp_release (tmpkey);
+      if (!err)
+        err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen);
+      gcry_sexp_release (tmpsexp);
+    }
+
+  for (i=0; i < DIM (array); i++)
+    gcry_mpi_release (array[i]);
 
+  return err;
+}