diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c
index 74541a03f..d54aff3a9 100644
--- a/common/openpgp-oid.c
+++ b/common/openpgp-oid.c
@@ -1,778 +1,778 @@
/* openpgp-oids.c - OID helper for OpenPGP
* Copyright (C) 2011 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file 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 .
*/
#include
#include
#include
#include
#include
#include "util.h"
#include "openpgpdefs.h"
/* A table with all our supported OpenPGP curves. */
static struct {
const char *name; /* Standard name. */
const char *oidstr; /* IETF formatted OID. */
unsigned int nbits; /* Nominal bit length of the curve. */
const char *alias; /* NULL or alternative name of the curve. */
const char *abbr; /* NULL or abbreviated name of the curve. */
int pubkey_algo; /* Required OpenPGP algo or 0 for ECDSA/ECDH. */
enum gcry_kem_algos kem_algo; /* 0 or the KEM algorithm for PQC. */
} oidtable[] = {
{ "Curve25519", "1.3.6.1.4.1.3029.1.5.1", 255, "cv25519", NULL,
PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X25519 /* only during development */},
{ "Ed25519", "1.3.6.1.4.1.11591.15.1", 255, "ed25519", NULL,
PUBKEY_ALGO_EDDSA },
{ "Curve25519", "1.3.101.110", 255, "cv25519", NULL,
PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X25519 },
{ "Ed25519", "1.3.101.112", 255, "ed25519", NULL,
PUBKEY_ALGO_EDDSA },
{ "X448", "1.3.101.111", 448, "cv448", NULL,
- PUBKEY_ALGO_ECDH },
+ PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X448 },
{ "Ed448", "1.3.101.113", 456, "ed448", NULL,
PUBKEY_ALGO_EDDSA },
{ "NIST P-256", "1.2.840.10045.3.1.7", 256, "nistp256" },
{ "NIST P-384", "1.3.132.0.34", 384, "nistp384" },
{ "NIST P-521", "1.3.132.0.35", 521, "nistp521" },
{ "brainpoolP256r1", "1.3.36.3.3.2.8.1.1.7", 256, NULL, "bp256" },
{ "brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 384, NULL, "bp384" },
{ "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 512, NULL, "bp512" },
{ "secp256k1", "1.3.132.0.10", 256 },
{ NULL, NULL, 0}
};
/* The OID for Curve Ed25519 in OpenPGP format. The shorter v5
* variant may only be used with v5 keys. */
static const char oid_ed25519[] =
{ 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01 };
static const char oid_ed25519_v5[] = { 0x03, 0x2b, 0x65, 0x70 };
/* The OID for Curve25519 in OpenPGP format. The shorter v5
* variant may only be used with v5 keys. */
static const char oid_cv25519[] =
{ 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01 };
static const char oid_cv25519_v5[] = { 0x03, 0x2b, 0x65, 0x6e };
/* The OID for X448 in OpenPGP format. */
/*
* Here, we have a little semantic discrepancy. X448 is the name of
* the ECDH computation and the OID is assigned to the algorithm in
* RFC 8410. Note that this OID is not the one which is assigned to
* the curve itself (originally in 8410). Nevertheless, we use "X448"
* for the curve in libgcrypt.
*/
static const char oid_cv448[] = { 0x03, 0x2b, 0x65, 0x6f };
/* The OID for Ed448 in OpenPGP format. */
static const char oid_ed448[] = { 0x03, 0x2b, 0x65, 0x71 };
/* A table to store keyalgo strings like "rsa2048 or "ed25519" so that
* we do not need to allocate them. This is currently a simple array
* but may eventually be changed to a fast data structure. Noet that
* unknown algorithms are stored with (NBITS,CURVE) set to (0,NULL). */
struct keyalgo_string_s
{
enum gcry_pk_algos algo; /* Mandatory. */
unsigned int nbits; /* Size for classical algos. */
char *curve; /* Curvename (OID) or NULL. */
char *name; /* Allocated name. */
};
static struct keyalgo_string_s *keyalgo_strings; /* The table. */
static size_t keyalgo_strings_size; /* Allocated size. */
static size_t keyalgo_strings_used; /* Used size. */
/* Helper for openpgp_oid_from_str. */
static size_t
make_flagged_int (unsigned long value, char *buf, size_t buflen)
{
int more = 0;
int shift;
/* fixme: figure out the number of bits in an ulong and start with
that value as shift (after making it a multiple of 7) a more
straigtforward implementation is to do it in reverse order using
a temporary buffer - saves a lot of compares */
for (more=0, shift=28; shift > 0; shift -= 7)
{
if (more || value >= (1<> shift);
value -= (value >> shift) << shift;
more = 1;
}
}
buf[buflen++] = value;
return buflen;
}
/* Convert the OID given in dotted decimal form in STRING to an DER
* encoding and store it as an opaque value at R_MPI. The format of
* the DER encoded is not a regular ASN.1 object but the modified
* format as used by OpenPGP for the ECC curve description. On error
* the function returns and error code an NULL is stored at R_BUG.
* Note that scanning STRING stops at the first white space
* character. */
gpg_error_t
openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi)
{
unsigned char *buf;
size_t buflen;
unsigned long val1, val;
const char *endp;
int arcno;
*r_mpi = NULL;
if (!string || !*string)
return gpg_error (GPG_ERR_INV_VALUE);
/* We can safely assume that the encoded OID is shorter than the string. */
buf = xtrymalloc (1 + strlen (string) + 2);
if (!buf)
return gpg_error_from_syserror ();
/* Save the first byte for the length. */
buflen = 1;
val1 = 0; /* Avoid compiler warning. */
arcno = 0;
do {
arcno++;
val = strtoul (string, (char**)&endp, 10);
if (!digitp (string) || !(*endp == '.' || !*endp))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_OID_STRING);
}
if (*endp == '.')
string = endp+1;
if (arcno == 1)
{
if (val > 2)
break; /* Not allowed, error caught below. */
val1 = val;
}
else if (arcno == 2)
{ /* Need to combine the first two arcs in one octet. */
if (val1 < 2)
{
if (val > 39)
{
xfree (buf);
return gpg_error (GPG_ERR_INV_OID_STRING);
}
buf[buflen++] = val1*40 + val;
}
else
{
val += 80;
buflen = make_flagged_int (val, buf, buflen);
}
}
else
{
buflen = make_flagged_int (val, buf, buflen);
}
} while (*endp == '.');
if (arcno == 1 || buflen < 2 || buflen > 254 )
{ /* It is not possible to encode only the first arc. */
xfree (buf);
return gpg_error (GPG_ERR_INV_OID_STRING);
}
*buf = buflen - 1;
*r_mpi = gcry_mpi_set_opaque (NULL, buf, buflen * 8);
if (!*r_mpi)
{
xfree (buf);
return gpg_error_from_syserror ();
}
return 0;
}
/* Return a malloced string representation of the OID in the buffer
* (BUF,LEN). In case of an error NULL is returned and ERRNO is set.
* As per OpenPGP spec the first byte of the buffer is the length of
* the rest; the function performs a consistency check. */
char *
openpgp_oidbuf_to_str (const unsigned char *buf, size_t len)
{
char *string, *p;
int n = 0;
unsigned long val, valmask;
valmask = (unsigned long)0xfe << (8 * (sizeof (valmask) - 1));
/* The first bytes gives the length; check consistency. */
if (!len || buf[0] != len -1)
{
gpg_err_set_errno (EINVAL);
return NULL;
}
/* Skip length byte. */
len--;
buf++;
/* To calculate the length of the string we can safely assume an
upper limit of 3 decimal characters per byte. Two extra bytes
account for the special first octet */
string = p = xtrymalloc (len*(1+3)+2+1);
if (!string)
return NULL;
if (!len)
{
*p = 0;
return string;
}
if (buf[0] < 40)
p += sprintf (p, "0.%d", buf[n]);
else if (buf[0] < 80)
p += sprintf (p, "1.%d", buf[n]-40);
else {
val = buf[n] & 0x7f;
while ( (buf[n]&0x80) && ++n < len )
{
if ( (val & valmask) )
goto badoid; /* Overflow. */
val <<= 7;
val |= buf[n] & 0x7f;
}
if (val < 80)
goto badoid;
val -= 80;
sprintf (p, "2.%lu", val);
p += strlen (p);
}
for (n++; n < len; n++)
{
val = buf[n] & 0x7f;
while ( (buf[n]&0x80) && ++n < len )
{
if ( (val & valmask) )
goto badoid; /* Overflow. */
val <<= 7;
val |= buf[n] & 0x7f;
}
sprintf (p, ".%lu", val);
p += strlen (p);
}
*p = 0;
return string;
badoid:
/* Return a special OID (gnu.gnupg.badoid) to indicate the error
case. The OID is broken and thus we return one which can't do
any harm. Formally this does not need to be a bad OID but an OID
with an arc that can't be represented in a 32 bit word is more
than likely corrupt. */
xfree (string);
return xtrystrdup ("1.3.6.1.4.1.11591.2.12242973");
}
/* Return a malloced string representation of the OID in the opaque
* MPI A. In case of an error NULL is returned and ERRNO is set. */
char *
openpgp_oid_to_str (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int lengthi;
if (!a
|| !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)
|| !(buf = gcry_mpi_get_opaque (a, &lengthi)))
{
gpg_err_set_errno (EINVAL);
return NULL;
}
return openpgp_oidbuf_to_str (buf, (lengthi+7)/8);
}
/* Return true if (BUF,LEN) represents the OID for Ed25519. */
int
openpgp_oidbuf_is_ed25519 (const void *buf, size_t len)
{
if (!buf)
return 0;
return ((len == DIM (oid_ed25519)
&& !memcmp (buf, oid_ed25519, DIM (oid_ed25519)))
|| (len == DIM (oid_ed25519_v5)
&& !memcmp (buf, oid_ed25519_v5, DIM (oid_ed25519_v5))));
}
/* Return true if A represents the OID for Ed25519. */
int
openpgp_oid_is_ed25519 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_ed25519 (buf, (nbits+7)/8);
}
/* Return true if (BUF,LEN) represents the OID for Curve25519. */
int
openpgp_oidbuf_is_cv25519 (const void *buf, size_t len)
{
if (!buf)
return 0;
return ((len == DIM (oid_cv25519)
&& !memcmp (buf, oid_cv25519, DIM (oid_cv25519)))
|| (len == DIM (oid_cv25519_v5)
&& !memcmp (buf, oid_cv25519_v5, DIM (oid_cv25519_v5))));
}
/* Return true if (BUF,LEN) represents the OID for Ed448. */
static int
openpgp_oidbuf_is_ed448 (const void *buf, size_t len)
{
return (buf && len == DIM (oid_ed448)
&& !memcmp (buf, oid_ed448, DIM (oid_ed448)));
}
/* Return true if (BUF,LEN) represents the OID for X448. */
static int
openpgp_oidbuf_is_cv448 (const void *buf, size_t len)
{
return (buf && len == DIM (oid_cv448)
&& !memcmp (buf, oid_cv448, DIM (oid_cv448)));
}
/* Return true if the MPI A represents the OID for Curve25519. */
int
openpgp_oid_is_cv25519 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_cv25519 (buf, (nbits+7)/8);
}
/* Return true if the MPI A represents the OID for Ed448. */
int
openpgp_oid_is_ed448 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_ed448 (buf, (nbits+7)/8);
}
/* Return true if the MPI A represents the OID for X448. */
int
openpgp_oid_is_cv448 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_cv448 (buf, (nbits+7)/8);
}
/* Map the Libgcrypt ECC curve NAME to an OID. If R_NBITS is not NULL
store the bit size of the curve there. Returns NULL for unknown
curve names. If R_ALGO is not NULL and a specific ECC algorithm is
required for this curve its OpenPGP algorithm number is stored
there; otherwise 0 is stored which indicates that ECDSA or ECDH can
be used. */
const char *
openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo)
{
int i;
unsigned int nbits = 0;
const char *oidstr = NULL;
int algo = 0;
if (name)
{
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (oidtable[i].name, name)
|| (oidtable[i].alias
&& !ascii_strcasecmp (oidtable[i].alias, name)))
{
oidstr = oidtable[i].oidstr;
nbits = oidtable[i].nbits;
algo = oidtable[i].pubkey_algo;
break;
}
if (!oidtable[i].name)
{
/* If not found assume the input is already an OID and check
whether we support it. */
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (name, oidtable[i].oidstr))
{
oidstr = oidtable[i].oidstr;
nbits = oidtable[i].nbits;
algo = oidtable[i].pubkey_algo;
break;
}
}
}
if (r_nbits)
*r_nbits = nbits;
if (r_algo)
*r_algo = algo;
return oidstr;
}
/* Map an OpenPGP OID to the Libgcrypt curve name. Returns NULL for
* unknown curve names. MODE defines which version of the curve name
* is returned. For example:
*
* | OID | mode=0 | mode=1 | mode=2 |
* |----------------------+-----------------+-----------------+----------|
* | 1.2.840.10045.3.1.7 | nistp256 | NIST P-256 | nistp256 |
* | 1.3.36.3.3.2.8.1.1.7 | brainpoolP256r1 | brainpoolP256r1 | bp256 |
*
* Thus mode 0 returns the name as commonly used gpg, mode 1 returns
* the canonical name, and mode 2 prefers an abbreviated name over the
* commonly used name.
*/
const char *
openpgp_oid_to_curve (const char *oidstr, int mode)
{
int i;
if (!oidstr)
return NULL;
for (i=0; oidtable[i].name; i++)
if (!strcmp (oidtable[i].oidstr, oidstr))
{
if (mode == 2)
{
if (oidtable[i].abbr)
return oidtable[i].abbr;
mode = 0; /* No abbreviation - fallback to mode 0. */
}
return !mode && oidtable[i].alias? oidtable[i].alias : oidtable[i].name;
}
return NULL;
}
/* Map an OpenPGP OID, name or alias to the Libgcrypt curve name.
* Returns NULL for unknown curve names. Unless CANON is set we
* prefer an alias name here which is more suitable for printing. */
const char *
openpgp_oid_or_name_to_curve (const char *oidname, int canon)
{
int i;
if (!oidname)
return NULL;
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (oidtable[i].oidstr, oidname)
|| !ascii_strcasecmp (oidtable[i].name, oidname)
|| (oidtable[i].alias
&& !ascii_strcasecmp (oidtable[i].alias, oidname)))
return !canon && oidtable[i].alias? oidtable[i].alias : oidtable[i].name;
return NULL;
}
/* Return the KEM algorithm id for the curve with OIDNAME. */
enum gcry_kem_algos
openpgp_oid_to_kem_algo (const char *oidname)
{
int i;
if (!oidname)
return 0;
for (i=0; oidtable[i].name; i++)
if (!strcmp (oidtable[i].oidstr, oidname))
return oidtable[i].kem_algo;
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (oidtable[i].name, oidname)
|| (oidtable[i].alias
&& !ascii_strcasecmp (oidtable[i].alias, oidname)))
return oidtable[i].kem_algo;
return 0;
}
/* Return true if the curve with NAME is supported. */
static int
curve_supported_p (const char *name)
{
int result = 0;
gcry_sexp_t keyparms;
if (!gcry_sexp_build (&keyparms, NULL, "(public-key(ecc(curve %s)))", name))
{
result = !!gcry_pk_get_curve (keyparms, 0, NULL);
gcry_sexp_release (keyparms);
}
return result;
}
/* Enumerate available and supported OpenPGP curves. The caller needs
to set the integer variable at ITERP to zero and keep on calling
this function until NULL is returned. */
const char *
openpgp_enum_curves (int *iterp)
{
int idx = *iterp;
while (idx >= 0 && idx < DIM (oidtable) && oidtable[idx].name)
{
if (curve_supported_p (oidtable[idx].name))
{
*iterp = idx + 1;
return oidtable[idx].alias? oidtable[idx].alias : oidtable[idx].name;
}
idx++;
}
*iterp = idx;
return NULL;
}
/* Return the Libgcrypt name for the gpg curve NAME if supported. If
* R_ALGO is not NULL the required OpenPGP public key algo or 0 is
* stored at that address. If R_NBITS is not NULL the nominal bitsize
* of the curves is stored there. NULL is returned if the curve is
* not supported. */
const char *
openpgp_is_curve_supported (const char *name, int *r_algo,
unsigned int *r_nbits)
{
int idx;
if (r_algo)
*r_algo = 0;
if (r_nbits)
*r_nbits = 0;
for (idx = 0; idx < DIM (oidtable) && oidtable[idx].name; idx++)
{
if ((!ascii_strcasecmp (name, oidtable[idx].name)
|| (oidtable[idx].alias
&& !ascii_strcasecmp (name, (oidtable[idx].alias)))
|| (oidtable[idx].abbr
&& !ascii_strcasecmp (name, (oidtable[idx].abbr))))
&& curve_supported_p (oidtable[idx].name))
{
if (r_algo)
*r_algo = oidtable[idx].pubkey_algo;
if (r_nbits)
*r_nbits = oidtable[idx].nbits;
return oidtable[idx].name;
}
}
return NULL;
}
/* Map a Gcrypt public key algorithm number to the used by OpenPGP.
* Returns 0 for unknown gcry algorithm. */
pubkey_algo_t
map_gcry_pk_to_openpgp (enum gcry_pk_algos algo)
{
switch (algo)
{
case GCRY_PK_EDDSA: return PUBKEY_ALGO_EDDSA;
case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA;
case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH;
case GCRY_PK_KEM: return PUBKEY_ALGO_KYBER;
default: return algo < 110 ? (pubkey_algo_t)algo : 0;
}
}
/* Map an OpenPGP public key algorithm number to the one used by
* Libgcrypt. Returns 0 for unknown gcry algorithm. */
enum gcry_pk_algos
map_openpgp_pk_to_gcry (pubkey_algo_t algo)
{
switch (algo)
{
case PUBKEY_ALGO_EDDSA: return GCRY_PK_EDDSA;
case PUBKEY_ALGO_ECDSA: return GCRY_PK_ECDSA;
case PUBKEY_ALGO_ECDH: return GCRY_PK_ECDH;
default: return algo < 110 ? (enum gcry_pk_algos)algo : 0;
}
}
/* Return a string describing the public key algorithm and the
* keysize. For elliptic curves the function prints the name of the
* curve because the keysize is a property of the curve. ALGO is the
* Gcrypt algorithm number, CURVE is either NULL or gives the OID of
* the curve, NBITS is either 0 or the size for algorithms like RSA.
* The returned string is taken from permanent table. Examples
* for the output are:
*
* "rsa3072" - RSA with 3072 bit
* "elg1024" - Elgamal with 1024 bit
* "ed25519" - ECC using the curve Ed25519.
* "E_1.2.3.4" - ECC using the unsupported curve with OID "1.2.3.4".
* "E_1.3.6.1.4.1.11591.2.12242973" - ECC with a bogus OID.
* "unknown_N" - Unknown OpenPGP algorithm N.
* If N is > 110 this is a gcrypt algo.
*/
const char *
get_keyalgo_string (enum gcry_pk_algos algo,
unsigned int nbits, const char *curve)
{
const char *prefix;
int i;
char *name, *curvebuf;
switch (algo)
{
case GCRY_PK_RSA: prefix = "rsa"; break;
case GCRY_PK_ELG: prefix = "elg"; break;
case GCRY_PK_DSA: prefix = "dsa"; break;
case GCRY_PK_ECC:
case GCRY_PK_ECDH:
case GCRY_PK_ECDSA:
case GCRY_PK_EDDSA: prefix = ""; break;
default: prefix = NULL; break;
}
if (prefix && *prefix && nbits)
{
for (i=0; i < keyalgo_strings_used; i++)
{
if (keyalgo_strings[i].algo == algo
&& keyalgo_strings[i].nbits
&& keyalgo_strings[i].nbits == nbits)
return keyalgo_strings[i].name;
}
/* Not yet in the table - add it. */
name = xasprintf ("%s%u", prefix, nbits);
nbits = nbits? nbits : 1; /* No nbits - oops - use 1 instead. */
curvebuf = NULL;
}
else if (prefix && !*prefix)
{
const char *curvename;
for (i=0; i < keyalgo_strings_used; i++)
{
if (keyalgo_strings[i].algo == algo
&& keyalgo_strings[i].curve && curve
&& !ascii_strcasecmp (keyalgo_strings[i].curve, curve))
return keyalgo_strings[i].name;
}
/* Not yet in the table - add it. */
curvename = openpgp_oid_or_name_to_curve (curve, 0);
if (curvename)
name = xasprintf ("%s", curvename);
else if (curve)
name = xasprintf ("E_%s", curve);
else
name = xasprintf ("E_error");
nbits = 0;
curvebuf = curve? xstrdup (curve) : NULL;
}
else
{
for (i=0; i < keyalgo_strings_used; i++)
{
if (keyalgo_strings[i].algo == algo
&& !keyalgo_strings[i].nbits
&& !keyalgo_strings[i].curve)
return keyalgo_strings[i].name;
}
/* Not yet in the table - add it. */
name = xasprintf ("unknown_%u", (unsigned int)algo);
nbits = 0;
curvebuf = NULL;
}
/* Store a new entry. This is a loop because of a possible nPth
* thread switch during xrealloc. */
while (keyalgo_strings_used >= keyalgo_strings_size)
{
keyalgo_strings_size += 10;
if (keyalgo_strings_size > 1024*1024)
log_fatal ("%s: table getting too large - possible DoS\n", __func__);
keyalgo_strings = xrealloc (keyalgo_strings, (keyalgo_strings_size
* sizeof *keyalgo_strings));
}
keyalgo_strings[keyalgo_strings_used].algo = algo;
keyalgo_strings[keyalgo_strings_used].nbits = nbits;
keyalgo_strings[keyalgo_strings_used].curve = curvebuf;
keyalgo_strings[keyalgo_strings_used].name = name;
keyalgo_strings_used++;
return name; /* Note that this is in the table. */
}
diff --git a/g10/pkglue.c b/g10/pkglue.c
index 2de0e80ab..fb39d5ba8 100644
--- a/g10/pkglue.c
+++ b/g10/pkglue.c
@@ -1,919 +1,946 @@
/* pkglue.c - public key operations glue code
* Copyright (C) 2000, 2003, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2024 g10 Code GmbH.
*
* 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 .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include "gpg.h"
#include "../common/util.h"
#include "pkglue.h"
#include "main.h"
#include "options.h"
+
+/* Maximum buffer sizes required for ECC KEM. */
+#define ECC_POINT_LEN_MAX (1+2*64)
+#define ECC_HASH_LEN_MAX 64
+
+
/* FIXME: Better change the function name because mpi_ is used by
gcrypt macros. */
gcry_mpi_t
get_mpi_from_sexp (gcry_sexp_t sexp, const char *item, int mpifmt)
{
gcry_sexp_t list;
gcry_mpi_t data;
list = gcry_sexp_find_token (sexp, item, 0);
log_assert (list);
data = gcry_sexp_nth_mpi (list, 1, mpifmt);
log_assert (data);
gcry_sexp_release (list);
return data;
}
/*
* SOS (Simply, Octet String) is an attempt to handle opaque octet
* string in OpenPGP, where well-formed MPI cannot represent octet
* string with leading zero octets.
*
* To retain maximum compatibility to existing MPI handling, SOS
* has same structure, but allows leading zero octets. When there
* is no leading zero octets, SOS representation is as same as MPI one.
* With leading zero octets, NBITS is 8*(length of octets), regardless
* of leading zero bits.
*/
/* Extract SOS representation from SEXP for PARAM, return the result
* in R_SOS. It is represented by opaque MPI with GCRYMPI_FLAG_USER2
* flag. */
gpg_error_t
sexp_extract_param_sos (gcry_sexp_t sexp, const char *param, gcry_mpi_t *r_sos)
{
gpg_error_t err;
gcry_sexp_t l2 = gcry_sexp_find_token (sexp, param, 0);
*r_sos = NULL;
if (!l2)
err = gpg_error (GPG_ERR_NO_OBJ);
else
{
size_t buflen;
void *p0 = gcry_sexp_nth_buffer (l2, 1, &buflen);
if (!p0)
err = gpg_error_from_syserror ();
else
{
gcry_mpi_t sos;
unsigned int nbits = buflen*8;
unsigned char *p = p0;
if (*p && nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
sos = gcry_mpi_set_opaque (NULL, p0, nbits);
if (sos)
{
gcry_mpi_set_flag (sos, GCRYMPI_FLAG_USER2);
*r_sos = sos;
err = 0;
}
else
err = gpg_error_from_syserror ();
}
gcry_sexp_release (l2);
}
return err;
}
/* "No leading zero octets" (nlz) version of the function above.
*
* This routine is used for backward compatibility to existing
* implementation with the weird handling of little endian integer
* representation with leading zero octets. For the sake of
* "well-fomed" MPI, which is designed for big endian integer, leading
* zero octets are removed when output, and they are recovered at
* input.
*
* Extract SOS representation from SEXP for PARAM, removing leading
* zeros, return the result in R_SOS. */
gpg_error_t
sexp_extract_param_sos_nlz (gcry_sexp_t sexp, const char *param,
gcry_mpi_t *r_sos)
{
gpg_error_t err;
gcry_sexp_t l2 = gcry_sexp_find_token (sexp, param, 0);
*r_sos = NULL;
if (!l2)
err = gpg_error (GPG_ERR_NO_OBJ);
else
{
size_t buflen;
const void *p0 = gcry_sexp_nth_data (l2, 1, &buflen);
if (!p0)
err = gpg_error_from_syserror ();
else
{
gcry_mpi_t sos;
unsigned int nbits = buflen*8;
const unsigned char *p = p0;
/* Strip leading zero bits. */
for (; nbits >= 8 && !*p; p++, nbits -= 8)
;
if (nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
sos = gcry_mpi_set_opaque_copy (NULL, p, nbits);
if (sos)
{
gcry_mpi_set_flag (sos, GCRYMPI_FLAG_USER2);
*r_sos = sos;
err = 0;
}
else
err = gpg_error_from_syserror ();
}
gcry_sexp_release (l2);
}
return err;
}
static byte *
get_data_from_sexp (gcry_sexp_t sexp, const char *item, size_t *r_size)
{
gcry_sexp_t list;
size_t valuelen;
const char *value;
byte *v;
if (DBG_CRYPTO)
log_printsexp ("get_data_from_sexp:", sexp);
list = gcry_sexp_find_token (sexp, item, 0);
log_assert (list);
value = gcry_sexp_nth_data (list, 1, &valuelen);
log_assert (value);
v = xtrymalloc (valuelen);
memcpy (v, value, valuelen);
gcry_sexp_release (list);
*r_size = valuelen;
return v;
}
/****************
* Emulate our old PK interface here - sometime in the future we might
* change the internal design to directly fit to libgcrypt.
*/
int
pk_verify (pubkey_algo_t pkalgo, gcry_mpi_t hash,
gcry_mpi_t *data, gcry_mpi_t *pkey)
{
gcry_sexp_t s_sig, s_hash, s_pkey;
int rc;
/* Make a sexp from pkey. */
if (pkalgo == PUBKEY_ALGO_DSA)
{
rc = 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]);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL_E || pkalgo == PUBKEY_ALGO_ELGAMAL)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
}
else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA)
{
char *curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(ecdsa(curve %s)(q%m)))",
curve, pkey[1]);
xfree (curve);
}
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
char *curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
const char *fmt;
if (openpgp_oid_is_ed25519 (pkey[0]))
fmt = "(public-key(ecc(curve %s)(flags eddsa)(q%m)))";
else
fmt = "(public-key(ecc(curve %s)(q%m)))";
rc = gcry_sexp_build (&s_pkey, NULL, fmt, curve, pkey[1]);
xfree (curve);
}
}
else
return GPG_ERR_PUBKEY_ALGO;
if (rc)
BUG (); /* gcry_sexp_build should never fail. */
/* Put hash into a S-Exp s_hash. */
if (pkalgo == PUBKEY_ALGO_EDDSA)
{
const char *fmt;
if (openpgp_oid_is_ed25519 (pkey[0]))
fmt = "(data(flags eddsa)(hash-algo sha512)(value %m))";
else
fmt = "(data(value %m))";
if (gcry_sexp_build (&s_hash, NULL, fmt, hash))
BUG (); /* gcry_sexp_build should never fail. */
}
else
{
if (gcry_sexp_build (&s_hash, NULL, "%m", hash))
BUG (); /* gcry_sexp_build should never fail. */
}
/* Put data into a S-Exp s_sig. */
s_sig = NULL;
if (pkalgo == PUBKEY_ALGO_DSA)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(dsa(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(ecdsa(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
gcry_mpi_t r = data[0];
gcry_mpi_t s = data[1];
if (openpgp_oid_is_ed25519 (pkey[0]))
{
size_t rlen, slen, n; /* (bytes) */
char buf[64];
unsigned int nbits;
unsigned int neededfixedlen = 256 / 8;
log_assert (neededfixedlen <= sizeof buf);
if (!r || !s)
rc = gpg_error (GPG_ERR_BAD_MPI);
else if ((rlen = (gcry_mpi_get_nbits (r)+7)/8) > neededfixedlen || !rlen)
rc = gpg_error (GPG_ERR_BAD_MPI);
else if ((slen = (gcry_mpi_get_nbits (s)+7)/8) > neededfixedlen || !slen)
rc = gpg_error (GPG_ERR_BAD_MPI);
else
{
/* We need to fixup the length in case of leading zeroes.
* OpenPGP does not allow leading zeroes and the parser for
* the signature packet has no information on the use curve,
* thus we need to do it here. We won't do it for opaque
* MPIs under the assumption that they are known to be fine;
* we won't see them here anyway but the check is anyway
* required. Fixme: A nifty feature for gcry_sexp_build
* would be a format to left pad the value (e.g. "%*M"). */
rc = 0;
if (rlen < neededfixedlen
&& !gcry_mpi_get_flag (r, GCRYMPI_FLAG_OPAQUE)
&& !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, r)))
{
log_assert (n < neededfixedlen);
memmove (buf + (neededfixedlen - n), buf, n);
memset (buf, 0, neededfixedlen - n);
r = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8);
}
else if (rlen < neededfixedlen
&& gcry_mpi_get_flag (r, GCRYMPI_FLAG_OPAQUE))
{
const unsigned char *p;
p = gcry_mpi_get_opaque (r, &nbits);
n = (nbits+7)/8;
memcpy (buf + (neededfixedlen - n), p, n);
memset (buf, 0, neededfixedlen - n);
gcry_mpi_set_opaque_copy (r, buf, neededfixedlen * 8);
}
if (slen < neededfixedlen
&& !gcry_mpi_get_flag (s, GCRYMPI_FLAG_OPAQUE)
&& !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, s)))
{
log_assert (n < neededfixedlen);
memmove (buf + (neededfixedlen - n), buf, n);
memset (buf, 0, neededfixedlen - n);
s = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8);
}
else if (slen < neededfixedlen
&& gcry_mpi_get_flag (s, GCRYMPI_FLAG_OPAQUE))
{
const unsigned char *p;
p = gcry_mpi_get_opaque (s, &nbits);
n = (nbits+7)/8;
memcpy (buf + (neededfixedlen - n), p, n);
memset (buf, 0, neededfixedlen - n);
gcry_mpi_set_opaque_copy (s, buf, neededfixedlen * 8);
}
}
}
else
rc = 0;
if (!rc)
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(eddsa(r%M)(s%M)))", r, s);
if (r != data[0])
gcry_mpi_release (r);
if (s != data[1])
gcry_mpi_release (s);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(elg(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S)
{
if (!data[0])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", data[0]);
}
else
BUG ();
if (!rc)
rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
gcry_sexp_release (s_sig);
gcry_sexp_release (s_hash);
gcry_sexp_release (s_pkey);
return rc;
}
#if GCRY_KEM_MLKEM1024_ENCAPS_LEN < GCRY_KEM_MLKEM768_ENCAPS_LEN \
|| GCRY_KEM_MLKEM1024_SHARED_LEN < GCRY_KEM_MLKEM768_SHARED_LEN
# error Bad Kyber constants in Libgcrypt
#endif
/* Core of the encryption for KEM algorithms. See pk_decrypt for a
* description of the arguments. */
static gpg_error_t
do_encrypt_kem (PKT_public_key *pk, gcry_mpi_t data, int seskey_algo,
gcry_mpi_t *resarr)
{
gpg_error_t err;
int i;
unsigned int nbits, n;
gcry_sexp_t s_data = NULL;
gcry_cipher_hd_t hd = NULL;
char *ecc_oid = NULL;
enum gcry_kem_algos kyber_algo, ecc_algo;
const unsigned char *ecc_pubkey;
size_t ecc_pubkey_len;
const unsigned char *kyber_pubkey;
size_t kyber_pubkey_len;
const unsigned char *seskey;
size_t seskey_len;
unsigned char *enc_seskey = NULL;
size_t enc_seskey_len;
+ int ecc_hash_algo;
- unsigned char ecc_ct[GCRY_KEM_ECC_X25519_ENCAPS_LEN];
- size_t ecc_ct_len = GCRY_KEM_ECC_X25519_ENCAPS_LEN;
- unsigned char ecc_ecdh[GCRY_KEM_RAW_X25519_SHARED_LEN];
- size_t ecc_ecdh_len = GCRY_KEM_RAW_X25519_SHARED_LEN;
- unsigned char ecc_ss[GCRY_KEM_RAW_X25519_SHARED_LEN];
- size_t ecc_ss_len = GCRY_KEM_RAW_X25519_SHARED_LEN;
+ unsigned char ecc_ct[ECC_POINT_LEN_MAX];
+ unsigned char ecc_ecdh[ECC_POINT_LEN_MAX];
+ unsigned char ecc_ss[ECC_HASH_LEN_MAX];
+ size_t ecc_ct_len, ecc_ecdh_len, ecc_ss_len;
unsigned char kyber_ct[GCRY_KEM_MLKEM1024_ENCAPS_LEN];
unsigned char kyber_ss[GCRY_KEM_MLKEM1024_SHARED_LEN];
size_t kyber_ct_len, kyber_ss_len;
char fixedinfo[1+MAX_FINGERPRINT_LEN];
int fixedlen;
unsigned char kek[32]; /* AES-256 is mandatory. */
size_t kek_len = 32;
/* For later error checking we make sure the array is cleared. */
resarr[0] = resarr[1] = resarr[2] = NULL;
/* As of now we use KEM only for the combined Kyber and thus a
* second public key is expected. Right now we take the keys
* directly from the PK->data elements. */
ecc_oid = openpgp_oid_to_str (pk->pkey[0]);
if (!ecc_oid)
{
err = gpg_error_from_syserror ();
log_error ("%s: error getting OID for ECC key\n", __func__);
goto leave;
}
ecc_algo = openpgp_oid_to_kem_algo (ecc_oid);
if (ecc_algo == GCRY_KEM_RAW_X25519)
{
if (!strcmp (ecc_oid, "1.3.6.1.4.1.3029.1.5.1"))
log_info ("Warning: "
"legacy OID for cv25519 accepted during develpment\n");
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
- if (ecc_pubkey_len != 33)
+ if (ecc_pubkey_len == 33 && *ecc_pubkey == 0x40)
+ {
+ ecc_pubkey++; /* Remove the 0x40 prefix. */
+ ecc_pubkey_len--;
+ }
+ if (ecc_pubkey_len != 32)
+ {
+ if (opt.verbose)
+ log_info ("%s: ECC public key length invalid (%zu)\n",
+ __func__, ecc_pubkey_len);
+ err = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+ ecc_ct_len = ecc_ecdh_len = 32;
+ ecc_ss_len = 32;
+ ecc_hash_algo = GCRY_MD_SHA3_256;
+ }
+ else if (ecc_algo == GCRY_KEM_RAW_X448)
+ {
+ ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
+ ecc_pubkey_len = (nbits+7)/8;
+ if (ecc_pubkey_len != 56)
{
if (opt.verbose)
log_info ("%s: ECC public key length invalid (%zu)\n",
__func__, ecc_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
- ecc_pubkey++; /* Remove the 0x40 prefix. */
- ecc_pubkey_len--;
+ ecc_ct_len = ecc_ecdh_len = 56;
+ ecc_ss_len = 64;
+ ecc_hash_algo = GCRY_MD_SHA3_512;
}
else
{
if (opt.verbose)
log_info ("%s: ECC curve %s not supported\n", __func__, ecc_oid);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
{
log_debug ("ECC curve: %s\n", ecc_oid);
log_printhex (ecc_pubkey, ecc_pubkey_len, "ECC pubkey:");
}
err = gcry_kem_encap (ecc_algo,
ecc_pubkey, ecc_pubkey_len,
ecc_ct, ecc_ct_len,
ecc_ecdh, ecc_ecdh_len,
NULL, 0);
if (err)
{
if (opt.verbose)
log_info ("%s: gcry_kem_encap for ECC (%s) failed\n",
__func__, ecc_oid);
goto leave;
}
if (DBG_CRYPTO)
{
log_printhex (ecc_ct, ecc_ct_len, "ECC ephem:");
log_printhex (ecc_ecdh, ecc_ecdh_len, "ECC ecdh:");
}
err = gnupg_ecc_kem_kdf (ecc_ss, ecc_ss_len,
- GCRY_MD_SHA3_256,
+ ecc_hash_algo,
ecc_ecdh, ecc_ecdh_len,
ecc_ct, ecc_ct_len,
ecc_pubkey, ecc_pubkey_len);
if (err)
{
if (opt.verbose)
log_info ("%s: kdf for ECC failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (ecc_ss, ecc_ss_len, "ECC shared:");
kyber_pubkey = gcry_mpi_get_opaque (pk->pkey[2], &nbits);
kyber_pubkey_len = (nbits+7)/8;
if (kyber_pubkey_len == GCRY_KEM_MLKEM768_PUBKEY_LEN)
{
kyber_algo = GCRY_KEM_MLKEM768;
kyber_ct_len = GCRY_KEM_MLKEM768_ENCAPS_LEN;
kyber_ss_len = GCRY_KEM_MLKEM768_SHARED_LEN;
}
else if (kyber_pubkey_len == GCRY_KEM_MLKEM1024_PUBKEY_LEN)
{
kyber_algo = GCRY_KEM_MLKEM1024;
kyber_ct_len = GCRY_KEM_MLKEM1024_ENCAPS_LEN;
kyber_ss_len = GCRY_KEM_MLKEM1024_SHARED_LEN;
}
else
{
if (opt.verbose)
log_info ("%s: Kyber public key length invalid (%zu)\n",
__func__, kyber_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (kyber_pubkey, kyber_pubkey_len, "|!trunc|Kyber pubkey:");
err = gcry_kem_encap (kyber_algo,
kyber_pubkey, kyber_pubkey_len,
kyber_ct, kyber_ct_len,
kyber_ss, kyber_ss_len,
NULL, 0);
if (err)
{
if (opt.verbose)
log_info ("%s: gcry_kem_encap for ECC failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
{
log_printhex (kyber_ct, kyber_ct_len, "|!trunc|Kyber ephem:");
log_printhex (kyber_ss, kyber_ss_len, "Kyber shared:");
}
fixedinfo[0] = seskey_algo;
v5_fingerprint_from_pk (pk, fixedinfo+1, NULL);
fixedlen = 33;
err = gnupg_kem_combiner (kek, kek_len,
ecc_ss, ecc_ss_len, ecc_ct, ecc_ct_len,
kyber_ss, kyber_ss_len, kyber_ct, kyber_ct_len,
fixedinfo, fixedlen);
if (err)
{
if (opt.verbose)
log_info ("%s: KEM combiner failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (kek, kek_len, "KEK:");
err = gcry_cipher_open (&hd, GCRY_CIPHER_AES256,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (hd, kek, kek_len);
if (err)
{
if (opt.verbose)
log_error ("%s: failed to initialize AESWRAP: %s\n", __func__,
gpg_strerror (err));
goto leave;
}
err = gcry_sexp_build (&s_data, NULL, "%m", data);
if (err)
goto leave;
n = gcry_cipher_get_algo_keylen (seskey_algo);
seskey = gcry_mpi_get_opaque (data, &nbits);
seskey_len = (nbits+7)/8;
if (seskey_len != n)
{
if (opt.verbose)
log_info ("%s: session key length %zu"
" does not match the length for algo %d\n",
__func__, seskey_len, seskey_algo);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (seskey, seskey_len, "seskey:");
enc_seskey_len = 1 + seskey_len + 8;
enc_seskey = xtrymalloc (enc_seskey_len);
if (!enc_seskey || enc_seskey_len > 254)
{
err = gpg_error_from_syserror ();
goto leave;
}
enc_seskey[0] = enc_seskey_len - 1;
err = gcry_cipher_encrypt (hd, enc_seskey+1, enc_seskey_len-1,
seskey, seskey_len);
if (err)
{
log_error ("%s: wrapping session key failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (enc_seskey, enc_seskey_len, "enc_seskey:");
resarr[0] = gcry_mpi_set_opaque_copy (NULL, ecc_ct, 8 * ecc_ct_len);
if (resarr[0])
resarr[1] = gcry_mpi_set_opaque_copy (NULL, kyber_ct, 8 * kyber_ct_len);
if (resarr[1])
resarr[2] = gcry_mpi_set_opaque_copy (NULL, enc_seskey, 8 * enc_seskey_len);
if (!resarr[0] || !resarr[1] || !resarr[2])
{
err = gpg_error_from_syserror ();
for (i=0; i < 3; i++)
gcry_mpi_release (resarr[i]), resarr[i] = NULL;
}
leave:
- wipememory (ecc_ct, ecc_ct_len);
- wipememory (ecc_ecdh, ecc_ecdh_len);
- wipememory (ecc_ss, ecc_ss_len);
+ wipememory (ecc_ct, sizeof ecc_ct);
+ wipememory (ecc_ecdh, sizeof ecc_ecdh);
+ wipememory (ecc_ss, sizeof ecc_ss);
wipememory (kyber_ct, sizeof kyber_ct);
wipememory (kyber_ss, sizeof kyber_ss);
wipememory (kek, kek_len);
xfree (enc_seskey);
gcry_cipher_close (hd);
xfree (ecc_oid);
return err;
}
/* Core of the encryption for the ECDH algorithms. See pk_decrypt for
* a description of the arguments. */
static gpg_error_t
do_encrypt_ecdh (PKT_public_key *pk, gcry_mpi_t data, gcry_mpi_t *resarr)
{
gcry_mpi_t *pkey = pk->pkey;
gcry_sexp_t s_ciph = NULL;
gcry_sexp_t s_data = NULL;
gcry_sexp_t s_pkey = NULL;
gpg_error_t err;
gcry_mpi_t k = NULL;
char *curve = NULL;
int with_djb_tweak_flag;
gcry_mpi_t public = NULL;
gcry_mpi_t result = NULL;
byte fp[MAX_FINGERPRINT_LEN];
byte *shared = NULL;
byte *p;
size_t nshared;
unsigned int nbits;
err = pk_ecdh_generate_ephemeral_key (pkey, &k);
if (err)
goto leave;
curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
{
err = gpg_error_from_syserror ();
goto leave;
}
with_djb_tweak_flag = openpgp_oid_is_cv25519 (pkey[0]);
/* Now use the ephemeral secret to compute the shared point. */
err = gcry_sexp_build (&s_pkey, NULL,
with_djb_tweak_flag ?
"(public-key(ecdh(curve%s)(flags djb-tweak)(q%m)))"
: "(public-key(ecdh(curve%s)(q%m)))",
curve, pkey[1]);
if (err)
goto leave;
/* Put K into a simplified S-expression. */
err = gcry_sexp_build (&s_data, NULL, "%m", k);
if (err)
goto leave;
/* Run encryption. */
err = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
if (err)
goto leave;
gcry_sexp_release (s_data); s_data = NULL;
gcry_sexp_release (s_pkey); s_pkey = NULL;
/* Get the shared point and the ephemeral public key. */
shared = get_data_from_sexp (s_ciph, "s", &nshared);
if (!shared)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = sexp_extract_param_sos (s_ciph, "e", &public);
gcry_sexp_release (s_ciph); s_ciph = NULL;
if (DBG_CRYPTO)
{
log_debug ("ECDH ephemeral key:");
gcry_mpi_dump (public);
log_printf ("\n");
}
fingerprint_from_pk (pk, fp, NULL);
p = gcry_mpi_get_opaque (data, &nbits);
result = NULL;
err = pk_ecdh_encrypt_with_shared_point (shared, nshared, fp, p,
(nbits+7)/8, pkey, &result);
if (err)
goto leave;
resarr[0] = public; public = NULL;
resarr[1] = result; result = NULL;
leave:
gcry_mpi_release (public);
gcry_mpi_release (result);
xfree (shared);
gcry_sexp_release (s_ciph);
gcry_sexp_release (s_data);
gcry_sexp_release (s_pkey);
xfree (curve);
gcry_mpi_release (k);
return err;
}
/* Core of the encryption for RSA and Elgamal algorithms. See
* pk_decrypt for a description of the arguments. */
static gpg_error_t
do_encrypt_rsa_elg (PKT_public_key *pk, gcry_mpi_t data, gcry_mpi_t *resarr)
{
pubkey_algo_t algo = pk->pubkey_algo;
gcry_mpi_t *pkey = pk->pkey;
gcry_sexp_t s_ciph = NULL;
gcry_sexp_t s_data = NULL;
gcry_sexp_t s_pkey = NULL;
gpg_error_t err;
if (algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E)
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
else
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))",
pkey[0], pkey[1]);
if (err)
goto leave;
err = gcry_sexp_build (&s_data, NULL, "%m", data);
if (err)
goto leave;
err = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
if (err)
goto leave;
gcry_sexp_release (s_data); s_data = NULL;
gcry_sexp_release (s_pkey); s_pkey = NULL;
resarr[0] = get_mpi_from_sexp (s_ciph, "a", GCRYMPI_FMT_USG);
if (!is_RSA (algo))
resarr[1] = get_mpi_from_sexp (s_ciph, "b", GCRYMPI_FMT_USG);
leave:
gcry_sexp_release (s_data);
gcry_sexp_release (s_pkey);
gcry_sexp_release (s_ciph);
return err;
}
/*
* Emulate our old PK interface here - sometime in the future we might
* change the internal design to directly fit to libgcrypt. PK is is
* the OpenPGP public key packet, DATA is an MPI with the to be
* encrypted data, and RESARR receives the encrypted data. RESARRAY
* is expected to be an two item array which will be filled with newly
* allocated MPIs. SESKEY_ALGO is required for public key algorithms
* which do not encode it in DATA.
*/
gpg_error_t
pk_encrypt (PKT_public_key *pk, gcry_mpi_t data, int seskey_algo,
gcry_mpi_t *resarr)
{
pubkey_algo_t algo = pk->pubkey_algo;
if (algo == PUBKEY_ALGO_KYBER)
return do_encrypt_kem (pk, data, seskey_algo, resarr);
else if (algo == PUBKEY_ALGO_ECDH)
return do_encrypt_ecdh (pk, data, resarr);
else if (algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E)
return do_encrypt_rsa_elg (pk, data, resarr);
else if (algo == PUBKEY_ALGO_RSA || algo == PUBKEY_ALGO_RSA_E)
return do_encrypt_rsa_elg (pk, data, resarr);
else
return gpg_error (GPG_ERR_PUBKEY_ALGO);
}
/* Check whether SKEY is a suitable secret key. */
int
pk_check_secret_key (pubkey_algo_t pkalgo, gcry_mpi_t *skey)
{
gcry_sexp_t s_skey;
int rc;
if (pkalgo == PUBKEY_ALGO_DSA)
{
rc = 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]);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E)
{
rc = 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]);
}
else if (is_RSA (pkalgo))
{
rc = 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]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA || pkalgo == PUBKEY_ALGO_ECDH)
{
char *curve = openpgp_oid_to_str (skey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_skey, NULL,
"(private-key(ecc(curve%s)(q%m)(d%m)))",
curve, skey[1], skey[2]);
xfree (curve);
}
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
char *curve = openpgp_oid_to_str (skey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
const char *fmt;
if (openpgp_oid_is_ed25519 (skey[0]))
fmt = "(private-key(ecc(curve %s)(flags eddsa)(q%m)(d%m)))";
else
fmt = "(private-key(ecc(curve %s)(q%m)(d%m)))";
rc = gcry_sexp_build (&s_skey, NULL, fmt, curve, skey[1], skey[2]);
xfree (curve);
}
}
else
return GPG_ERR_PUBKEY_ALGO;
if (!rc)
{
rc = gcry_pk_testkey (s_skey);
gcry_sexp_release (s_skey);
}
return rc;
}