mirror of
git://git.gnupg.org/gnupg.git
synced 2024-09-07 12:56:21 +02:00
1543 lines
46 KiB
C
1543 lines
46 KiB
C
/* 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 "agent.h"
|
|
#include "../common/i18n.h"
|
|
#include "cvt-openpgp.h"
|
|
#include "../common/host2net.h"
|
|
#include "../common/openpgpdefs.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;
|
|
gcry_mpi_t pubkey = NULL;
|
|
pubkey_algo_t pkalgo = 0; /* Specify NONE */
|
|
|
|
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
|
|
{
|
|
if (!strcmp (curve, "Ed448"))
|
|
pkalgo = PUBKEY_ALGO_EDDSA;
|
|
else if (!strcmp (curve, "X448"))
|
|
pkalgo = PUBKEY_ALGO_ECDH;
|
|
format = "(public-key(ecc(curve %s)(q%m)))";
|
|
}
|
|
|
|
if (pkalgo)
|
|
{
|
|
pubkey = openpgp_ecc_parse_key (pkalgo, curve, pkey[0]);
|
|
err = gcry_sexp_build (&s_pkey, NULL, format, curve, pubkey);
|
|
gcry_mpi_release (pubkey);
|
|
}
|
|
else
|
|
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;
|
|
gcry_mpi_t pubkey = NULL;
|
|
gcry_mpi_t seckey = NULL;
|
|
pubkey_algo_t pkalgo = 0; /* Specify NONE */
|
|
|
|
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
|
|
{
|
|
if (!strcmp (curve, "Ed448"))
|
|
pkalgo = PUBKEY_ALGO_EDDSA;
|
|
else if (!strcmp (curve, "X448"))
|
|
pkalgo = PUBKEY_ALGO_ECDH;
|
|
format = "(private-key(ecc(curve %s)(q%m)(d%m)))";
|
|
}
|
|
|
|
if (pkalgo)
|
|
{
|
|
pubkey = openpgp_ecc_parse_key (pkalgo, curve, skey[0]);
|
|
seckey = openpgp_ecc_parse_key (pkalgo, curve, skey[1]);
|
|
err = gcry_sexp_build (&s_skey, NULL, format, curve, pubkey, seckey);
|
|
gcry_mpi_release (pubkey);
|
|
gcry_mpi_release (seckey);
|
|
}
|
|
else
|
|
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;
|
|
}
|
|
|
|
|
|
/* Scan octet string in the PGP format (length-in-two-octet octets) */
|
|
static int
|
|
scan_pgp_format (gcry_mpi_t *r_mpi, int pubkey_algo,
|
|
const unsigned char *buffer,
|
|
size_t buflen, size_t *r_nbytes)
|
|
{
|
|
/* Using gcry_mpi_scan with GCRYMPI_FLAG_PGP can be used if it is
|
|
MPI, but it will be "normalized" removing leading zeros. */
|
|
unsigned int nbits, nbytes;
|
|
|
|
if (pubkey_algo != GCRY_PK_ECC)
|
|
return gcry_mpi_scan (r_mpi, GCRYMPI_FMT_PGP, buffer, buflen, r_nbytes);
|
|
|
|
/* It's ECC, where we use SOS. */
|
|
|
|
if (buflen < 2)
|
|
return GPG_ERR_INV_OBJ;
|
|
|
|
nbits = (buffer[0] << 8) | buffer[1];
|
|
if (nbits >= 16384)
|
|
return GPG_ERR_INV_OBJ;
|
|
|
|
nbytes = (nbits + 7) / 8;
|
|
if (buflen < nbytes + 2)
|
|
return GPG_ERR_INV_OBJ;
|
|
|
|
*r_nbytes = nbytes + 2;
|
|
*r_mpi = gcry_mpi_set_opaque_copy (NULL, buffer+2, nbits);
|
|
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++)
|
|
{
|
|
unsigned char *buffer;
|
|
|
|
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;
|
|
buffer = gcry_mpi_get_opaque (skey[i], &nbits);
|
|
nbytes = (nbits+7)/8;
|
|
|
|
nbits = nbytes * 8;
|
|
if (*buffer)
|
|
if (nbits >= 8 && !(*buffer & 0x80))
|
|
if (--nbits >= 7 && !(*buffer & 0x40))
|
|
if (--nbits >= 6 && !(*buffer & 0x20))
|
|
if (--nbits >= 5 && !(*buffer & 0x10))
|
|
if (--nbits >= 4 && !(*buffer & 0x08))
|
|
if (--nbits >= 3 && !(*buffer & 0x04))
|
|
if (--nbits >= 2 && !(*buffer & 0x02))
|
|
if (--nbits >= 1 && !(*buffer & 0x01))
|
|
--nbits;
|
|
|
|
actual_csum += (nbits >> 8);
|
|
actual_csum += (nbits & 0xff);
|
|
actual_csum += checksum (buffer, nbytes);
|
|
}
|
|
else
|
|
{
|
|
err = gcry_mpi_aprint (GCRYMPI_FMT_PGP, &buffer, &nbytes,
|
|
skey[i]);
|
|
if (err)
|
|
return err;
|
|
|
|
actual_csum += checksum (buffer, nbytes);
|
|
xfree (buffer);
|
|
}
|
|
}
|
|
|
|
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;
|
|
gcry_mpi_t skey_encrypted = skey[npkey];
|
|
|
|
if (!gcry_mpi_get_flag (skey_encrypted, GCRYMPI_FLAG_USER1))
|
|
{
|
|
gcry_cipher_close (cipher_hd);
|
|
return gpg_error (GPG_ERR_BAD_SECKEY);
|
|
}
|
|
p = gcry_mpi_get_opaque (skey_encrypted, &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 (scan_pgp_format (&tmpmpi, pubkey_algo, p, ndata, &nbytes))
|
|
break;
|
|
skey[i] = tmpmpi;
|
|
ndata -= nbytes;
|
|
p += nbytes;
|
|
}
|
|
|
|
if (i == nskey)
|
|
{
|
|
skey[nskey] = NULL;
|
|
skeylen = nskey;
|
|
gcry_mpi_release (skey_encrypted);
|
|
|
|
log_assert (skeylen <= skeysize);
|
|
/* Note: at this point NDATA should be 2 for a simple
|
|
checksum or 20 for the sha1 digest. */
|
|
}
|
|
else
|
|
{
|
|
/* Checksum was okay, but not correctly decrypted. */
|
|
desired_csum = 0;
|
|
actual_csum = 1; /* Mark checksum bad. */
|
|
|
|
/* Recover encrypted SKEY. */
|
|
for (--i; i >= npkey; i--)
|
|
{
|
|
gcry_mpi_release (skey[i]);
|
|
skey[i] = NULL;
|
|
}
|
|
skey[npkey] = skey_encrypted;
|
|
}
|
|
}
|
|
xfree(data);
|
|
}
|
|
else /* Packet version <= 3. */
|
|
{
|
|
unsigned char *buffer;
|
|
gcry_mpi_t skey_tmpmpi[10];
|
|
|
|
log_assert (nskey - npkey <= 10);
|
|
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_USER1))
|
|
{
|
|
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 = scan_pgp_format (&tmpmpi, pubkey_algo, buffer, ndata, &nbytes);
|
|
xfree (buffer);
|
|
if (err)
|
|
break;
|
|
skey_tmpmpi[i - npkey] = tmpmpi;
|
|
}
|
|
|
|
if (i == nskey)
|
|
{
|
|
for (i = npkey; i < nskey; i++)
|
|
{
|
|
gcry_mpi_release (skey[i]);
|
|
skey[i] = skey_tmpmpi[i - npkey];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Checksum was okay, but not correctly decrypted. */
|
|
desired_csum = 0;
|
|
actual_csum = 1; /* Mark checksum bad. */
|
|
|
|
for (--i; i >= npkey; i--)
|
|
gcry_mpi_release (skey_tmpmpi[i - npkey]);
|
|
}
|
|
}
|
|
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 || npkey == 1 /* This is ECC */)
|
|
{
|
|
skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, valuelen*8);
|
|
if (!skey[skeyidx])
|
|
goto outofmem;
|
|
if (is_enc)
|
|
/* Encrypted parameters need to have a USER1 flag. */
|
|
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)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
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,
|
|
NULL, NULL, 0);
|
|
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, NULL, NULL, 0);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
log_assert (npkey < nskey);
|
|
log_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++ )
|
|
{
|
|
if (gcry_mpi_get_flag (array[i], GCRYMPI_FLAG_OPAQUE))
|
|
{
|
|
p = gcry_mpi_get_opaque (array[i], &nbits[j]);
|
|
narr[j] = (nbits[j] + 7)/8;
|
|
data = xtrymalloc_secure (narr[j]);
|
|
if (!data)
|
|
err = gpg_error_from_syserror ();
|
|
else
|
|
{
|
|
memcpy (data, p, narr[j]);
|
|
bufarr[j] = data;
|
|
err = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]);
|
|
nbits[j] = gcry_mpi_get_nbits (array[i]);
|
|
}
|
|
if (err)
|
|
{
|
|
for (i = 0; i < j; i++)
|
|
xfree (bufarr[i]);
|
|
return err;
|
|
}
|
|
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;
|
|
}
|
|
log_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);
|
|
gcry_mpi_set_flag (array[npkey], GCRYMPI_FLAG_USER1);
|
|
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, *elems;
|
|
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 = elems = "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 = elems = "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 = elems = "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?";
|
|
elems = "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 = elems;
|
|
*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;
|
|
}
|