summaryrefslogtreecommitdiffstats
path: root/agent/cvt-openpgp.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
commiteee068778cb28ecf3c14e1bf843a95547d72c42d (patch)
tree0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /agent/cvt-openpgp.c
parentInitial commit. (diff)
downloadgnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz
gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip
Adding upstream version 2.2.40.upstream/2.2.40
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'agent/cvt-openpgp.c')
-rw-r--r--agent/cvt-openpgp.c1413
1 files changed, 1413 insertions, 0 deletions
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
new file mode 100644
index 0000000..78f2989
--- /dev/null
+++ b/agent/cvt-openpgp.c
@@ -0,0 +1,1413 @@
+/* cvt-openpgp.c - Convert an OpenPGP key to our internal format.
+ * Copyright (C) 1998-2002, 2006, 2009, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2013, 2014 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "agent.h"
+#include "../common/i18n.h"
+#include "cvt-openpgp.h"
+#include "../common/host2net.h"
+
+
+/* Helper to pass data via the callback to do_unprotect. */
+struct try_do_unprotect_arg_s
+{
+ int is_v4;
+ int is_protected;
+ int pubkey_algo;
+ const char *curve;
+ int protect_algo;
+ char *iv;
+ int ivlen;
+ int s2k_mode;
+ int s2k_algo;
+ byte *s2k_salt;
+ u32 s2k_count;
+ u16 desired_csum;
+ gcry_mpi_t *skey;
+ size_t skeysize;
+ int skeyidx;
+ gcry_sexp_t *r_key;
+};
+
+
+
+/* Compute the keygrip from the public key and store it at GRIP. */
+static gpg_error_t
+get_keygrip (int pubkey_algo, const char *curve, gcry_mpi_t *pkey,
+ unsigned char *grip)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_pkey = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
+ pkey[0], pkey[1], pkey[2], pkey[3]);
+ break;
+
+ case GCRY_PK_ELG:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(elg(p%m)(g%m)(y%m)))",
+ pkey[0], pkey[1], pkey[2]);
+ break;
+
+ case GCRY_PK_RSA:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
+ break;
+
+ case GCRY_PK_ECC:
+ if (!curve)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ {
+ const char *format;
+
+ if (!strcmp (curve, "Ed25519"))
+ format = "(public-key(ecc(curve %s)(flags eddsa)(q%m)))";
+ else if (!strcmp (curve, "Curve25519"))
+ format = "(public-key(ecc(curve %s)(flags djb-tweak)(q%m)))";
+ else
+ format = "(public-key(ecc(curve %s)(q%m)))";
+
+ err = gcry_sexp_build (&s_pkey, NULL, format, curve, pkey[0]);
+ }
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err && !gcry_pk_get_keygrip (s_pkey, grip))
+ err = gpg_error (GPG_ERR_INTERNAL);
+
+ gcry_sexp_release (s_pkey);
+ return err;
+}
+
+
+/* Convert a secret key given as algorithm id and an array of key
+ 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,
+ const char *curve)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey = NULL;
+
+ *r_key = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))",
+ skey[0], skey[1], skey[2], skey[3], skey[4]);
+ break;
+
+ case GCRY_PK_ELG:
+ case GCRY_PK_ELG_E:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(elg(p%m)(g%m)(y%m)(x%m)))",
+ skey[0], skey[1], skey[2], skey[3]);
+ break;
+
+
+ case GCRY_PK_RSA:
+ case GCRY_PK_RSA_E:
+ case GCRY_PK_RSA_S:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(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_ECC:
+ if (!curve)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ {
+ const char *format;
+
+ if (!strcmp (curve, "Ed25519"))
+ /* Do not store the OID as name but the real name and the
+ EdDSA flag. */
+ format = "(private-key(ecc(curve %s)(flags eddsa)(q%m)(d%M)))";
+ else if (!strcmp (curve, "Curve25519"))
+ format = "(private-key(ecc(curve %s)(flags djb-tweak)(q%m)(d%m)))";
+ else
+ format = "(private-key(ecc(curve %s)(q%m)(d%m)))";
+
+ err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], skey[1]);
+ }
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err)
+ *r_key = s_skey;
+ return err;
+}
+
+
+/* Convert a secret key given as algorithm id, an array of key
+ parameters, and an S-expression of the original OpenPGP transfer
+ key into our s-expression based format. This is a variant of
+ convert_secret_key which is used for the openpgp-native protection
+ mode. Note that PUBKEY_ALGO has an gcrypt algorithm number. */
+static gpg_error_t
+convert_transfer_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
+ const char *curve, gcry_sexp_t transfer_key)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey = NULL;
+
+ *r_key = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build
+ (&s_skey, NULL,
+ "(protected-private-key(dsa(p%m)(q%m)(g%m)(y%m)"
+ "(protected openpgp-native%S)))",
+ skey[0], skey[1], skey[2], skey[3], transfer_key);
+ break;
+
+ case GCRY_PK_ELG:
+ err = gcry_sexp_build
+ (&s_skey, NULL,
+ "(protected-private-key(elg(p%m)(g%m)(y%m)"
+ "(protected openpgp-native%S)))",
+ skey[0], skey[1], skey[2], transfer_key);
+ break;
+
+
+ case GCRY_PK_RSA:
+ err = gcry_sexp_build
+ (&s_skey, NULL,
+ "(protected-private-key(rsa(n%m)(e%m)"
+ "(protected openpgp-native%S)))",
+ skey[0], skey[1], transfer_key );
+ break;
+
+ case GCRY_PK_ECC:
+ if (!curve)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ {
+ const char *format;
+
+ if (!strcmp (curve, "Ed25519"))
+ /* Do not store the OID as name but the real name and the
+ EdDSA flag. */
+ format = "(protected-private-key(ecc(curve %s)(flags eddsa)(q%m)"
+ "(protected openpgp-native%S)))";
+ else if (!strcmp (curve, "Curve25519"))
+ format = "(protected-private-key(ecc(curve %s)(flags djb-tweak)(q%m)"
+ "(protected openpgp-native%S)))";
+ else
+ format = "(protected-private-key(ecc(curve %s)(q%m)"
+ "(protected openpgp-native%S)))";
+
+ err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], transfer_key);
+ }
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err)
+ *r_key = s_skey;
+ return err;
+}
+
+
+/* Hash the passphrase and set the key. */
+static gpg_error_t
+hash_passphrase_and_set_key (const char *passphrase,
+ gcry_cipher_hd_t hd, int protect_algo,
+ int s2k_mode, int s2k_algo,
+ byte *s2k_salt, u32 s2k_count)
+{
+ gpg_error_t err;
+ unsigned char *key;
+ size_t keylen;
+
+ 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 ();
+
+ err = s2k_hash_passphrase (passphrase,
+ s2k_algo, s2k_mode, s2k_salt, s2k_count,
+ key, keylen);
+ if (!err)
+ err = gcry_cipher_setkey (hd, key, keylen);
+
+ xfree (key);
+ return err;
+}
+
+
+static u16
+checksum (const unsigned char *p, unsigned int n)
+{
+ u16 a;
+
+ for (a=0; n; n-- )
+ a += *p++;
+ return a;
+}
+
+
+/* Return the number of expected key parameters. */
+static void
+get_npkey_nskey (int pubkey_algo, size_t *npkey, size_t *nskey)
+{
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_RSA: *npkey = 2; *nskey = 6; break;
+ case GCRY_PK_ELG: *npkey = 3; *nskey = 4; break;
+ case GCRY_PK_ELG_E: *npkey = 3; *nskey = 4; break;
+ case GCRY_PK_DSA: *npkey = 4; *nskey = 5; break;
+ case GCRY_PK_ECC: *npkey = 1; *nskey = 2; break;
+ default: *npkey = 0; *nskey = 0; break;
+ }
+}
+
+
+/* Helper for do_unprotect. PUBKEY_ALOGO is the gcrypt algo number.
+ On success R_NPKEY and R_NSKEY receive the number or parameters for
+ the algorithm PUBKEY_ALGO and R_SKEYLEN the used length of
+ SKEY. */
+static int
+prepare_unprotect (int pubkey_algo, gcry_mpi_t *skey, size_t skeysize,
+ int s2k_mode,
+ unsigned int *r_npkey, unsigned int *r_nskey,
+ unsigned int *r_skeylen)
+{
+ size_t npkey, nskey, skeylen;
+ int i;
+
+ /* 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++)
+ ;
+ for (i=skeylen; i < skeysize; i++)
+ skey[i] = NULL;
+
+ /* Check some args. */
+ if (s2k_mode == 1001)
+ {
+ /* Stub key. */
+ log_info (_("secret key parts are not available\n"));
+ return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ }
+
+ if (gcry_pk_test_algo (pubkey_algo))
+ {
+ 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);
+ }
+
+ /* Get properties of the public key algorithm and do some
+ consistency checks. Note that we need at least NPKEY+1 elements
+ in the SKEY array. */
+ get_npkey_nskey (pubkey_algo, &npkey, &nskey);
+ if (!npkey || !nskey || npkey >= nskey)
+ return gpg_error (GPG_ERR_INTERNAL);
+ if (skeylen <= npkey)
+ return gpg_error (GPG_ERR_MISSING_VALUE);
+ if (nskey+1 >= skeysize)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+
+ /* Check that the public key parameters are all available and not
+ encrypted. */
+ for (i=0; i < npkey; i++)
+ {
+ if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ if (r_npkey)
+ *r_npkey = npkey;
+ if (r_nskey)
+ *r_nskey = nskey;
+ if (r_skeylen)
+ *r_skeylen = skeylen;
+ return 0;
+}
+
+
+/* Note that this function modifies SKEY. SKEYSIZE is the allocated
+ size of the array including the NULL item; this is used for a
+ bounds check. On success a converted key is stored at R_KEY. */
+static int
+do_unprotect (const char *passphrase,
+ int pkt_version, int pubkey_algo, int is_protected,
+ const char *curve, gcry_mpi_t *skey, size_t skeysize,
+ int protect_algo, void *protect_iv, size_t protect_ivlen,
+ int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count,
+ u16 desired_csum, gcry_sexp_t *r_key)
+{
+ gpg_error_t err;
+ unsigned int npkey, nskey, skeylen;
+ gcry_cipher_hd_t cipher_hd = NULL;
+ u16 actual_csum;
+ size_t nbytes;
+ int i;
+ gcry_mpi_t tmpmpi;
+
+ *r_key = NULL;
+
+ err = prepare_unprotect (pubkey_algo, skey, skeysize, s2k_mode,
+ &npkey, &nskey, &skeylen);
+ if (err)
+ return err;
+
+ /* Check whether SKEY is at all protected. If it is not protected
+ merely verify the checksum. */
+ if (!is_protected)
+ {
+ actual_csum = 0;
+ for (i=npkey; i < nskey; i++)
+ {
+ if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+
+ if (gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
+ {
+ unsigned int nbits;
+ const unsigned char *buffer;
+ buffer = gcry_mpi_get_opaque (skey[i], &nbits);
+ nbytes = (nbits+7)/8;
+ actual_csum += checksum (buffer, nbytes);
+ }
+ else
+ {
+ unsigned char *buffer;
+
+ err = gcry_mpi_aprint (GCRYMPI_FMT_PGP, &buffer, &nbytes,
+ skey[i]);
+ if (!err)
+ actual_csum += checksum (buffer, nbytes);
+ xfree (buffer);
+ }
+ if (err)
+ return err;
+ }
+
+ if (actual_csum != desired_csum)
+ return gpg_error (GPG_ERR_CHECKSUM);
+
+ goto do_convert;
+ }
+
+
+ if (gcry_cipher_test_algo (protect_algo))
+ {
+ /* The algorithm numbers are Libgcrypt numbers but fortunately
+ the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
+ numbers. */
+ log_info (_("protection algorithm %d (%s) is not supported\n"),
+ protect_algo, gnupg_cipher_algo_name (protect_algo));
+ return gpg_error (GPG_ERR_CIPHER_ALGO);
+ }
+
+ if (gcry_md_test_algo (s2k_algo))
+ {
+ log_info (_("protection hash algorithm %d (%s) is not supported\n"),
+ 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
+ | (protect_algo >= 100 ?
+ 0 : GCRY_CIPHER_ENABLE_SYNC)));
+ if (err)
+ {
+ log_error ("failed to open cipher_algo %d: %s\n",
+ protect_algo, gpg_strerror (err));
+ return err;
+ }
+
+ err = hash_passphrase_and_set_key (passphrase, cipher_hd, protect_algo,
+ s2k_mode, s2k_algo, s2k_salt, s2k_count);
+ if (err)
+ {
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+
+ gcry_cipher_setiv (cipher_hd, protect_iv, protect_ivlen);
+
+ actual_csum = 0;
+ if (pkt_version >= 4)
+ {
+ int ndata;
+ unsigned int ndatabits;
+ const unsigned char *p;
+ unsigned char *data;
+ u16 csum_pgp7 = 0;
+
+ if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE ))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ p = gcry_mpi_get_opaque (skey[npkey], &ndatabits);
+ ndata = (ndatabits+7)/8;
+
+ if (ndata > 1)
+ csum_pgp7 = buf16_to_u16 (p+ndata-2);
+ data = xtrymalloc_secure (ndata);
+ if (!data)
+ {
+ err = gpg_error_from_syserror ();
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+ gcry_cipher_decrypt (cipher_hd, data, ndata, p, ndata);
+
+ p = data;
+ if (is_protected == 2)
+ {
+ /* This is the new SHA1 checksum method to detect tampering
+ with the key as used by the Klima/Rosa attack. */
+ desired_csum = 0;
+ actual_csum = 1; /* Default to bad checksum. */
+
+ if (ndata < 20)
+ log_error ("not enough bytes for SHA-1 checksum\n");
+ 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);
+ gcry_md_final (h);
+ if (!memcmp (gcry_md_read (h, GCRY_MD_SHA1), data+ndata-20, 20))
+ actual_csum = 0; /* Digest does match. */
+ gcry_md_close (h);
+ }
+ }
+ else
+ {
+ /* Old 16 bit checksum method. */
+ if (ndata < 2)
+ {
+ log_error ("not enough bytes for checksum\n");
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ }
+ else
+ {
+ desired_csum = buf16_to_u16 (data+ndata-2);
+ actual_csum = checksum (data, ndata-2);
+ if (desired_csum != actual_csum)
+ {
+ /* This is a PGP 7.0.0 workaround */
+ desired_csum = csum_pgp7; /* Take the encrypted one. */
+ }
+ }
+ }
+
+ /* Better check it here. Otherwise the gcry_mpi_scan would fail
+ because the length may have an arbitrary value. */
+ if (desired_csum == actual_csum)
+ {
+ for (i=npkey; i < nskey; i++ )
+ {
+ if (gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, p, ndata, &nbytes))
+ {
+ /* Checksum was okay, but not correctly decrypted. */
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ break;
+ }
+ gcry_mpi_release (skey[i]);
+ skey[i] = tmpmpi;
+ ndata -= nbytes;
+ p += nbytes;
+ }
+ skey[i] = NULL;
+ skeylen = i;
+ assert (skeylen <= skeysize);
+
+ /* Note: at this point NDATA should be 2 for a simple
+ checksum or 20 for the sha1 digest. */
+ }
+ xfree(data);
+ }
+ else /* Packet version <= 3. */
+ {
+ unsigned char *buffer;
+
+ for (i = npkey; i < nskey; i++)
+ {
+ const unsigned char *p;
+ size_t ndata;
+ unsigned int ndatabits;
+
+ if (!skey[i] || !gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ p = gcry_mpi_get_opaque (skey[i], &ndatabits);
+ ndata = (ndatabits+7)/8;
+
+ if (!(ndata >= 2) || !(ndata == (buf16_to_ushort (p) + 7)/8 + 2))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ buffer = xtrymalloc_secure (ndata);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+
+ gcry_cipher_sync (cipher_hd);
+ buffer[0] = p[0];
+ buffer[1] = p[1];
+ gcry_cipher_decrypt (cipher_hd, buffer+2, ndata-2, p+2, ndata-2);
+ actual_csum += checksum (buffer, ndata);
+ err = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, buffer, ndata, &ndata);
+ xfree (buffer);
+ if (err)
+ {
+ /* Checksum was okay, but not correctly decrypted. */
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ break;
+ }
+ gcry_mpi_release (skey[i]);
+ skey[i] = tmpmpi;
+ }
+ }
+ gcry_cipher_close (cipher_hd);
+
+ /* Now let's see whether we have used the correct passphrase. */
+ if (actual_csum != desired_csum)
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+
+ do_convert:
+ if (nskey != skeylen)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ err = convert_secret_key (r_key, pubkey_algo, skey, curve);
+ if (err)
+ return err;
+
+ /* The checksum may fail, thus we also check the key itself. */
+ err = gcry_pk_testkey (*r_key);
+ if (err)
+ {
+ gcry_sexp_release (*r_key);
+ *r_key = NULL;
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+
+ return 0;
+}
+
+
+/* Callback function to try the unprotection from the passphrase query
+ code. */
+static gpg_error_t
+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,
+ arg->pubkey_algo, arg->is_protected,
+ arg->curve,
+ arg->skey, arg->skeysize,
+ arg->protect_algo, arg->iv, arg->ivlen,
+ arg->s2k_mode, arg->s2k_algo,
+ arg->s2k_salt, arg->s2k_count,
+ arg->desired_csum, arg->r_key);
+ /* 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;
+}
+
+
+/* See convert_from_openpgp for the core of the description. This
+ function adds an optional PASSPHRASE argument and uses this to
+ silently decrypt the key; CACHE_NONCE and R_PASSPHRASE must both be
+ NULL in this mode. */
+static gpg_error_t
+convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
+ unsigned char *grip, const char *prompt,
+ const char *cache_nonce, const char *passphrase,
+ unsigned char **r_key, char **r_passphrase)
+{
+ gpg_error_t err;
+ int unattended;
+ int from_native;
+ gcry_sexp_t top_list;
+ gcry_sexp_t list = NULL;
+ const char *value;
+ size_t valuelen;
+ char *string;
+ int idx;
+ int is_v4, is_protected;
+ int pubkey_algo;
+ int protect_algo = 0;
+ char iv[16];
+ int ivlen = 0;
+ int s2k_mode = 0;
+ int s2k_algo = 0;
+ byte s2k_salt[8];
+ u32 s2k_count = 0;
+ size_t npkey, nskey;
+ gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
+ char *curve = NULL;
+ u16 desired_csum;
+ int skeyidx = 0;
+ gcry_sexp_t s_skey = NULL;
+
+ *r_key = NULL;
+ if (r_passphrase)
+ *r_passphrase = NULL;
+ unattended = !r_passphrase;
+ from_native = (!cache_nonce && passphrase && !r_passphrase);
+
+ top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
+ if (!top_list)
+ goto bad_seckey;
+
+ list = gcry_sexp_find_token (top_list, "version", 0);
+ if (!list)
+ goto bad_seckey;
+ value = gcry_sexp_nth_data (list, 1, &valuelen);
+ if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4'))
+ goto bad_seckey;
+ is_v4 = (value[0] == '4');
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "protection", 0);
+ if (!list)
+ goto bad_seckey;
+ value = gcry_sexp_nth_data (list, 1, &valuelen);
+ if (!value)
+ goto bad_seckey;
+ if (valuelen == 4 && !memcmp (value, "sha1", 4))
+ is_protected = 2;
+ else if (valuelen == 3 && !memcmp (value, "sum", 3))
+ is_protected = 1;
+ else if (valuelen == 4 && !memcmp (value, "none", 4))
+ is_protected = 0;
+ else
+ goto bad_seckey;
+
+ if (is_protected)
+ {
+ string = gcry_sexp_nth_string (list, 2);
+ if (!string)
+ goto bad_seckey;
+ protect_algo = gcry_cipher_map_name (string);
+ xfree (string);
+
+ value = gcry_sexp_nth_data (list, 3, &valuelen);
+ if (!value || !valuelen || valuelen > sizeof iv)
+ goto bad_seckey;
+ memcpy (iv, value, valuelen);
+ ivlen = valuelen;
+
+ string = gcry_sexp_nth_string (list, 4);
+ if (!string)
+ goto bad_seckey;
+ s2k_mode = strtol (string, NULL, 10);
+ xfree (string);
+
+ string = gcry_sexp_nth_string (list, 5);
+ if (!string)
+ goto bad_seckey;
+ s2k_algo = gcry_md_map_name (string);
+ xfree (string);
+
+ value = gcry_sexp_nth_data (list, 6, &valuelen);
+ if (!value || !valuelen || valuelen > sizeof s2k_salt)
+ goto bad_seckey;
+ memcpy (s2k_salt, value, valuelen);
+
+ string = gcry_sexp_nth_string (list, 7);
+ if (!string)
+ goto bad_seckey;
+ s2k_count = strtoul (string, NULL, 10);
+ xfree (string);
+ }
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "algo", 0);
+ if (!list)
+ goto bad_seckey;
+ string = gcry_sexp_nth_string (list, 1);
+ if (!string)
+ goto bad_seckey;
+ pubkey_algo = gcry_pk_map_name (string);
+ xfree (string);
+
+ get_npkey_nskey (pubkey_algo, &npkey, &nskey);
+ if (!npkey || !nskey || npkey >= nskey)
+ goto bad_seckey;
+
+ if (npkey == 1) /* This is ECC */
+ {
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "curve", 0);
+ if (!list)
+ goto bad_seckey;
+ curve = gcry_sexp_nth_string (list, 1);
+ if (!curve)
+ goto bad_seckey;
+ }
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "skey", 0);
+ if (!list)
+ goto bad_seckey;
+ for (idx=0;;)
+ {
+ int is_enc;
+
+ value = gcry_sexp_nth_data (list, ++idx, &valuelen);
+ if (!value && skeyidx >= npkey)
+ break; /* Ready. */
+
+ /* Check for too many parameters. Note that depending on the
+ protection mode and version number we may see less than NSKEY
+ (but at least NPKEY+1) parameters. */
+ if (idx >= 2*nskey)
+ goto bad_seckey;
+ if (skeyidx >= DIM (skey)-1)
+ goto bad_seckey;
+
+ if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
+ goto bad_seckey;
+ is_enc = (value[0] == 'e');
+ value = gcry_sexp_nth_data (list, ++idx, &valuelen);
+ if (!value || !valuelen)
+ goto bad_seckey;
+ if (is_enc)
+ {
+ /* Encrypted parameters need to be stored as opaque. */
+ skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, valuelen*8);
+ if (!skey[skeyidx])
+ goto outofmem;
+ gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER1);
+ }
+ else
+ {
+ if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
+ value, valuelen, NULL))
+ goto bad_seckey;
+ }
+ skeyidx++;
+ }
+ skey[skeyidx++] = NULL;
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "csum", 0);
+ if (list)
+ {
+ string = gcry_sexp_nth_string (list, 1);
+ if (!string)
+ goto bad_seckey;
+ desired_csum = strtoul (string, NULL, 10);
+ xfree (string);
+ }
+ else
+ desired_csum = 0;
+
+
+ gcry_sexp_release (list); list = NULL;
+ gcry_sexp_release (top_list); top_list = NULL;
+
+#if 0
+ log_debug ("XXX is_v4=%d\n", is_v4);
+ log_debug ("XXX pubkey_algo=%d\n", pubkey_algo);
+ log_debug ("XXX is_protected=%d\n", is_protected);
+ log_debug ("XXX protect_algo=%d\n", protect_algo);
+ log_printhex (iv, ivlen, "XXX iv");
+ log_debug ("XXX ivlen=%d\n", ivlen);
+ log_debug ("XXX s2k_mode=%d\n", s2k_mode);
+ log_debug ("XXX s2k_algo=%d\n", s2k_algo);
+ log_printhex (s2k_salt, sizeof s2k_salt, "XXX s2k_salt");
+ log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count);
+ log_debug ("XXX curve='%s'\n", curve);
+ for (idx=0; skey[idx]; idx++)
+ gcry_log_debugmpi (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_USER1)
+ ? "skey(e)" : "skey(_)", skey[idx]);
+#endif /*0*/
+
+ err = get_keygrip (pubkey_algo, curve, skey, grip);
+ if (err)
+ goto leave;
+
+ if (!dontcare_exist && !from_native && !agent_key_available (grip))
+ {
+ err = gpg_error (GPG_ERR_EEXIST);
+ goto leave;
+ }
+
+ if (unattended && !from_native)
+ {
+ err = prepare_unprotect (pubkey_algo, skey, DIM(skey), s2k_mode,
+ NULL, NULL, NULL);
+ if (err)
+ goto leave;
+
+ err = convert_transfer_key (&s_skey, pubkey_algo, skey, curve, s_pgp);
+ if (err)
+ goto leave;
+ }
+ else
+ {
+ struct pin_entry_info_s *pi;
+ struct try_do_unprotect_arg_s pi_arg;
+
+ pi = xtrycalloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi)
+ return gpg_error_from_syserror ();
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi->min_digits = 0; /* We want a real passphrase. */
+ pi->max_digits = 16;
+ pi->max_tries = 3;
+ pi->check_cb = try_do_unprotect_cb;
+ pi->check_cb_arg = &pi_arg;
+ pi_arg.is_v4 = is_v4;
+ pi_arg.is_protected = is_protected;
+ pi_arg.pubkey_algo = pubkey_algo;
+ pi_arg.curve = curve;
+ pi_arg.protect_algo = protect_algo;
+ pi_arg.iv = iv;
+ pi_arg.ivlen = ivlen;
+ pi_arg.s2k_mode = s2k_mode;
+ pi_arg.s2k_algo = s2k_algo;
+ pi_arg.s2k_salt = s2k_salt;
+ pi_arg.s2k_count = s2k_count;
+ pi_arg.desired_csum = desired_csum;
+ pi_arg.skey = skey;
+ pi_arg.skeysize = DIM (skey);
+ pi_arg.skeyidx = skeyidx;
+ pi_arg.r_key = &s_skey;
+
+ err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ if (!is_protected)
+ {
+ err = try_do_unprotect_cb (pi);
+ if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ else if (cache_nonce)
+ {
+ char *cache_value;
+
+ cache_value = agent_get_cache (ctrl, 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);
+ }
+ else if (from_native)
+ {
+ if (strlen (passphrase) < pi->max_length)
+ strcpy (pi->pin, passphrase);
+ err = try_do_unprotect_cb (pi);
+ }
+ if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE && !from_native)
+ err = agent_askpin (ctrl, prompt, NULL, NULL, pi, NULL, 0);
+ skeyidx = pi_arg.skeyidx;
+ if (!err && r_passphrase && is_protected)
+ {
+ *r_passphrase = xtrystrdup (pi->pin);
+ if (!*r_passphrase)
+ err = gpg_error_from_syserror ();
+ }
+ xfree (pi);
+ if (err)
+ goto leave;
+ }
+
+ /* Save some memory and get rid of the SKEY array now. */
+ for (idx=0; idx < skeyidx; idx++)
+ gcry_mpi_release (skey[idx]);
+ skeyidx = 0;
+
+ /* Note that the padding is not required - we use it only because
+ that function allows us to create the result in secure memory. */
+ err = make_canon_sexp_pad (s_skey, 1, r_key, NULL);
+
+ leave:
+ xfree (curve);
+ gcry_sexp_release (s_skey);
+ gcry_sexp_release (list);
+ gcry_sexp_release (top_list);
+ for (idx=0; idx < skeyidx; idx++)
+ gcry_mpi_release (skey[idx]);
+ if (err && r_passphrase)
+ {
+ xfree (*r_passphrase);
+ *r_passphrase = NULL;
+ }
+ return err;
+
+ bad_seckey:
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ goto leave;
+
+ outofmem:
+ err = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+
+}
+
+
+/* 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. 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
+ passphrase. If R_PASSPHRASE is NULL the unattended conversion mode
+ will be used which uses the openpgp-native protection format for
+ the key. The keygrip will be stored at the 20 byte buffer pointed
+ to by GRIP. On error NULL is stored at all return arguments. */
+gpg_error_t
+convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
+ unsigned char *grip, const char *prompt,
+ const char *cache_nonce,
+ unsigned char **r_key, char **r_passphrase)
+{
+ return convert_from_openpgp_main (ctrl, s_pgp, dontcare_exist, grip, prompt,
+ cache_nonce, NULL,
+ r_key, r_passphrase);
+}
+
+/* This function is called by agent_unprotect to re-protect an
+ openpgp-native protected private-key into the standard private-key
+ protection format. */
+gpg_error_t
+convert_from_openpgp_native (ctrl_t ctrl,
+ gcry_sexp_t s_pgp, const char *passphrase,
+ unsigned char **r_key)
+{
+ gpg_error_t err;
+ unsigned char grip[20];
+
+ if (!passphrase)
+ return gpg_error (GPG_ERR_INTERNAL);
+
+ err = convert_from_openpgp_main (ctrl, s_pgp, 0, grip, NULL,
+ NULL, passphrase,
+ r_key, NULL);
+
+ /* On success try to re-write the key. */
+ if (!err)
+ {
+ if (*passphrase)
+ {
+ unsigned char *protectedkey = NULL;
+ size_t protectedkeylen;
+
+ if (!agent_protect (*r_key, passphrase,
+ &protectedkey, &protectedkeylen,
+ ctrl->s2k_count, -1))
+ agent_write_private_key (grip, protectedkey, protectedkeylen,
+ 1, 0, NULL, NULL, NULL);
+ xfree (protectedkey);
+ }
+ else
+ {
+ /* Empty passphrase: write key without protection. */
+ agent_write_private_key (grip,
+ *r_key,
+ gcry_sexp_canon_len (*r_key, 0, NULL,NULL),
+ 1, 0, NULL, NULL, 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)
+ {
+ 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;
+}
+
+
+/*
+ * Examining S_KEY in S-Expression and extract data.
+ * When REQ_PRIVATE_KEY_DATA == 1, S_KEY's CAR should be 'private-key',
+ * but it also allows shadowed or protected versions.
+ * On success, it returns 0, otherwise error number.
+ * R_ALGONAME is static string which is no need to free by caller.
+ * R_NPKEY is pointer to number of public key data.
+ * R_NSKEY is pointer to number of private key data.
+ * R_ELEMS is static string which is no need to free by caller.
+ * ARRAY contains public and private key data.
+ * ARRAYSIZE is the allocated size of the array for cross-checking.
+ * R_CURVE is pointer to S-Expression of the curve (can be NULL).
+ * R_FLAGS is pointer to S-Expression of the flags (can be NULL).
+ */
+gpg_error_t
+extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
+ const char **r_algoname, int *r_npkey, int *r_nskey,
+ const char **r_elems,
+ gcry_mpi_t *array, int arraysize,
+ gcry_sexp_t *r_curve, gcry_sexp_t *r_flags)
+{
+ gpg_error_t err;
+ gcry_sexp_t list, l2;
+ char *name;
+ const char *algoname, *format;
+ int npkey, nskey;
+ gcry_sexp_t curve = NULL;
+ gcry_sexp_t flags = NULL;
+
+ *r_curve = NULL;
+ *r_flags = NULL;
+
+ if (!req_private_key_data)
+ {
+ list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_key, "private-key", 0 );
+ }
+ else
+ list = gcry_sexp_find_token (s_key, "private-key", 0);
+
+ if (!list)
+ {
+ log_error ("invalid private key format\n");
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ 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. */
+ }
+
+ if (arraysize < 7)
+ BUG ();
+
+ /* Map NAME to a name as used by Libgcrypt. We do not use the
+ Libgcrypt function here because we need a lowercase name and
+ require special treatment for some algorithms. */
+ strlwr (name);
+ if (!strcmp (name, "rsa"))
+ {
+ algoname = "rsa";
+ format = "ned?p?q?u?";
+ npkey = 2;
+ nskey = 6;
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, array+2, array+3,
+ array+4, array+5, NULL);
+ }
+ else if (!strcmp (name, "elg"))
+ {
+ algoname = "elg";
+ format = "pgyx?";
+ npkey = 3;
+ nskey = 4;
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, array+2, array+3,
+ NULL);
+ }
+ else if (!strcmp (name, "dsa"))
+ {
+ algoname = "dsa";
+ format = "pqgyx?";
+ npkey = 4;
+ nskey = 5;
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, array+2, array+3,
+ array+4, NULL);
+ }
+ else if (!strcmp (name, "ecc") || !strcmp (name, "ecdsa"))
+ {
+ algoname = "ecc";
+ format = "qd?";
+ npkey = 1;
+ nskey = 2;
+ curve = gcry_sexp_find_token (list, "curve", 0);
+ flags = gcry_sexp_find_token (list, "flags", 0);
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, NULL);
+ }
+ else
+ {
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ }
+ xfree (name);
+ gcry_sexp_release (list);
+ if (err)
+ {
+ gcry_sexp_release (curve);
+ gcry_sexp_release (flags);
+ return err;
+ }
+ else
+ {
+ *r_algoname = algoname;
+ if (r_elems)
+ *r_elems = format;
+ *r_npkey = npkey;
+ if (r_nskey)
+ *r_nskey = nskey;
+ *r_curve = curve;
+ *r_flags = flags;
+
+ 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;
+ const char *algoname;
+ int npkey, nskey;
+ gcry_mpi_t array[10];
+ gcry_sexp_t curve = NULL;
+ gcry_sexp_t flags = NULL;
+ 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;
+
+ err = extract_private_key (s_key, 1, &algoname, &npkey, &nskey, NULL,
+ array, DIM (array), &curve, &flags);
+ 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[10+2];
+ gcry_sexp_t tmpkey;
+ gcry_sexp_t 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 %m");
+ format_args[j++] = array + npkey;
+ 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%S\n"
+ " (protection sha1 aes %b 1:3 sha1 %b %s))\n",
+ algoname,
+ curve,
+ 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]);
+ gcry_sexp_release (curve);
+ gcry_sexp_release (flags);
+
+ return err;
+}