diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c
index 10bd92152..5509b1169 100644
--- a/agent/pkdecrypt.c
+++ b/agent/pkdecrypt.c
@@ -1,652 +1,658 @@
/* pkdecrypt.c - public key decryption (well, actually using a secret key)
* Copyright (C) 2001, 2003 Free Software Foundation, Inc.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "agent.h"
#include "../common/openpgpdefs.h"
/* Table with parameters for KEM decryption. Use get_ecc_parms to
* find an entry. */
struct ecc_params
{
const char *curve; /* Canonical name of the curve. */
size_t pubkey_len; /* Pubkey in the SEXP representation. */
size_t scalar_len;
size_t point_len;
size_t shared_len;
int hash_algo;
int algo;
int scalar_reverse;
};
static const struct ecc_params ecc_table[] =
{
{
"Curve25519",
33, 32, 32, 32,
GCRY_MD_SHA3_256, GCRY_KEM_RAW_X25519,
1
},
{
"X448",
56, 56, 56, 64,
GCRY_MD_SHA3_512, GCRY_KEM_RAW_X448,
0
},
{
"brainpoolP256r1",
65, 32, 65, 32,
GCRY_MD_SHA3_256, GCRY_KEM_RAW_BP256,
0
},
{
"brainpoolP384r1",
97, 48, 97, 64,
GCRY_MD_SHA3_512, GCRY_KEM_RAW_BP384,
0
},
+ {
+ "brainpoolP512r1",
+ 129, 64, 129, 64,
+ GCRY_MD_SHA3_512, GCRY_KEM_RAW_BP512,
+ 0
+ },
{ NULL, 0, 0, 0, 0, 0, 0, 0 }
};
/* Maximum buffer sizes required for ECC KEM. Keep this aligned to
* the ecc_table above. */
#define ECC_SCALAR_LEN_MAX 64
#define ECC_POINT_LEN_MAX (1+2*64)
#define ECC_HASH_LEN_MAX 64
/* Return the ECC parameters for CURVE. CURVE is expected to be the
* canonical name. */
static const struct ecc_params *
get_ecc_params (const char *curve)
{
int i;
for (i = 0; ecc_table[i].curve; i++)
if (!strcmp (ecc_table[i].curve, curve))
return &ecc_table[i];
return NULL;
}
/* DECRYPT the stuff in ciphertext which is expected to be a S-Exp.
Try to get the key from CTRL and write the decoded stuff back to
OUTFP. The padding information is stored at R_PADDING with -1
for not known. */
gpg_error_t
agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *ciphertext, size_t ciphertextlen,
membuf_t *outbuf, int *r_padding)
{
gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL;
unsigned char *shadow_info = NULL;
gpg_error_t err = 0;
int no_shadow_info = 0;
char *buf = NULL;
size_t len;
*r_padding = -1;
if (!ctrl->have_keygrip)
{
log_error ("speculative decryption not yet supported\n");
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
err = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen);
if (err)
{
log_error ("failed to convert ciphertext: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
{
log_printhex (ctrl->keygrip, 20, "keygrip:");
log_printhex (ciphertext, ciphertextlen, "cipher: ");
}
err = agent_key_from_file (ctrl, NULL, desc_text,
NULL, &shadow_info,
CACHE_MODE_NORMAL, NULL, &s_skey, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_NO_SECKEY)
no_shadow_info = 1;
else if (err)
{
log_error ("failed to read the secret key\n");
goto leave;
}
if (shadow_info || no_shadow_info)
{ /* divert operation to the smartcard */
if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL))
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto leave;
}
if (s_skey && agent_is_tpm2_key (s_skey))
err = divert_tpm2_pkdecrypt (ctrl, ciphertext, shadow_info,
&buf, &len, r_padding);
else
err = divert_pkdecrypt (ctrl, ctrl->keygrip, ciphertext,
&buf, &len, r_padding);
if (err)
{
/* We restore the original error (ie. no seckey) is no card
* has been found and we have no shadow key. This avoids a
* surprising "card removed" error code. */
if ((gpg_err_code (err) == GPG_ERR_CARD_REMOVED
|| gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
&& no_shadow_info)
err = gpg_error (GPG_ERR_NO_SECKEY);
else
log_error ("smartcard decryption failed: %s\n", gpg_strerror (err));
goto leave;
}
put_membuf_printf (outbuf, "(5:value%u:", (unsigned int)len);
put_membuf (outbuf, buf, len);
put_membuf (outbuf, ")", 2);
}
else
{ /* No smartcard, but a private key */
/* if (DBG_CRYPTO ) */
/* { */
/* log_debug ("skey: "); */
/* gcry_sexp_dump (s_skey); */
/* } */
err = gcry_pk_decrypt (&s_plain, s_cipher, s_skey);
if (err)
{
log_error ("decryption failed: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
{
log_debug ("plain: ");
gcry_sexp_dump (s_plain);
}
len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (len);
buf = xmalloc (len);
len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len);
log_assert (len);
if (*buf == '(')
put_membuf (outbuf, buf, len);
else
{
/* Old style libgcrypt: This is only an S-expression
part. Turn it into a complete S-expression. */
put_membuf (outbuf, "(5:value", 8);
put_membuf (outbuf, buf, len);
put_membuf (outbuf, ")", 2);
}
}
leave:
gcry_sexp_release (s_skey);
gcry_sexp_release (s_plain);
gcry_sexp_release (s_cipher);
xfree (buf);
xfree (shadow_info);
return err;
}
/* Reverse BUFFER to change the endianness. */
static void
reverse_buffer (unsigned char *buffer, unsigned int length)
{
unsigned int tmp, i;
for (i=0; i < length/2; i++)
{
tmp = buffer[i];
buffer[i] = buffer[length-1-i];
buffer[length-1-i] = tmp;
}
}
/* For composite PGP KEM (ECC+ML-KEM), decrypt CIPHERTEXT using KEM API.
First keygrip is for ECC, second keygrip is for PQC. CIPHERTEXT
should follow the format of:
(enc-val(pqc(c%d)(e%m)(k%m)(s%m)(fixed-info&)))
c: cipher identifier (symmetric)
e: ECDH ciphertext
k: ML-KEM ciphertext
s: encrypted session key
fixed-info: A buffer with the fixed info.
FIXME: For now, possible keys on smartcard are not supported.
*/
static gpg_error_t
composite_pgp_kem_decrypt (ctrl_t ctrl, const char *desc_text,
gcry_sexp_t s_cipher, membuf_t *outbuf)
{
gcry_sexp_t s_skey0 = NULL;
gcry_sexp_t s_skey1 = NULL;
unsigned char *shadow_info = NULL;
gpg_error_t err = 0;
const struct ecc_params *ecc;
unsigned int nbits;
const unsigned char *p;
size_t len;
int algo;
gcry_mpi_t encrypted_sessionkey_mpi = NULL;
const unsigned char *encrypted_sessionkey;
size_t encrypted_sessionkey_len;
gcry_mpi_t ecc_sk_mpi = NULL;
unsigned char ecc_sk[ECC_SCALAR_LEN_MAX];
gcry_mpi_t ecc_pk_mpi = NULL;
unsigned char ecc_pk[ECC_POINT_LEN_MAX];
gcry_mpi_t ecc_ct_mpi = NULL;
const unsigned char *ecc_ct;
unsigned char ecc_ecdh[ECC_POINT_LEN_MAX];
unsigned char ecc_ss[ECC_HASH_LEN_MAX];
enum gcry_kem_algos mlkem_kem_algo;
gcry_mpi_t mlkem_sk_mpi = NULL;
gcry_mpi_t mlkem_ct_mpi = NULL;
const unsigned char *mlkem_sk;
size_t mlkem_sk_len;
const unsigned char *mlkem_ct;
size_t mlkem_ct_len;
unsigned char mlkem_ss[GCRY_KEM_MLKEM1024_SHARED_LEN];
size_t mlkem_ss_len;
unsigned char kek[32];
size_t kek_len = 32; /* AES-256 is mandatory */
gcry_cipher_hd_t hd;
unsigned char sessionkey[256];
size_t sessionkey_len;
gcry_buffer_t fixed_info = { 0, 0, 0, NULL };
gcry_sexp_t curve = NULL;
char *curve_name = NULL;
err = agent_key_from_file (ctrl, NULL, desc_text,
ctrl->keygrip, &shadow_info,
CACHE_MODE_NORMAL, NULL, &s_skey0, NULL, NULL);
if (err)
{
log_error ("failed to read the secret key\n");
goto leave;
}
err = agent_key_from_file (ctrl, NULL, desc_text,
ctrl->keygrip1, &shadow_info,
CACHE_MODE_NORMAL, NULL, &s_skey1, NULL, NULL);
if (err)
{
log_error ("failed to read the another secret key\n");
goto leave;
}
/* Here assumes no smartcard, but private keys */
err = gcry_sexp_extract_param (s_cipher, NULL, "%dc/eks&'fixed-info'",
&algo, &ecc_ct_mpi, &mlkem_ct_mpi,
&encrypted_sessionkey_mpi, &fixed_info, NULL);
if (err)
{
if (opt.verbose)
log_info ("%s: extracting parameters failed\n", __func__);
goto leave;
}
len = gcry_cipher_get_algo_keylen (algo);
encrypted_sessionkey = gcry_mpi_get_opaque (encrypted_sessionkey_mpi, &nbits);
encrypted_sessionkey_len = (nbits+7)/8;
if (len == 0 || encrypted_sessionkey_len != len + 8)
{
if (opt.verbose)
log_info ("%s: encrypted session key length %zu"
" does not match the length for algo %d\n",
__func__, encrypted_sessionkey_len, algo);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Firstly, ECC part. */
curve = gcry_sexp_find_token (s_skey0, "curve", 0);
if (!curve)
{
if (opt.verbose)
log_info ("%s: no curve given\n", __func__);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
curve_name = gcry_sexp_nth_string (curve, 1);
ecc = get_ecc_params (curve_name);
if (!ecc)
{
if (opt.verbose)
log_info ("%s: curve '%s' not supported\n", __func__, curve_name);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
err = gcry_sexp_extract_param (s_skey0, NULL, "/qd",
&ecc_pk_mpi, &ecc_sk_mpi, NULL);
if (err)
{
if (opt.verbose)
log_info ("%s: extracting q and d from ECC key failed\n", __func__);
goto leave;
}
p = gcry_mpi_get_opaque (ecc_pk_mpi, &nbits);
len = (nbits+7)/8;
if (len != ecc->pubkey_len)
{
if (opt.verbose)
log_info ("%s: ECC public key length invalid (%zu)\n", __func__, len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
else if (len == ecc->point_len)
memcpy (ecc_pk, p, ecc->point_len);
else if (len == ecc->point_len + 1 && p[0] == 0x40)
/* Remove the 0x40 prefix (for Curve25519) */
memcpy (ecc_pk, p+1, ecc->point_len);
else
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
mpi_release (ecc_pk_mpi);
ecc_pk_mpi = NULL;
p = gcry_mpi_get_opaque (ecc_sk_mpi, &nbits);
len = (nbits+7)/8;
if (len > ecc->scalar_len)
{
if (opt.verbose)
log_info ("%s: ECC secret key too long (%zu)\n", __func__, len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
memset (ecc_sk, 0, ecc->scalar_len - len);
memcpy (ecc_sk + ecc->scalar_len - len, p, len);
if (ecc->scalar_reverse)
reverse_buffer (ecc_sk, ecc->scalar_len);
mpi_release (ecc_sk_mpi);
ecc_sk_mpi = NULL;
ecc_ct = gcry_mpi_get_opaque (ecc_ct_mpi, &nbits);
if (ecc->point_len != (nbits+7)/8)
{
if (opt.verbose)
log_info ("%s: ECC cipher text length invalid (%zu)\n",
__func__, ecc->point_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
{
log_debug ("ECC curve: %s\n", curve_name);
log_printhex (ecc_pk, ecc->pubkey_len, "ECC pubkey:");
log_printhex (ecc_sk, ecc->scalar_len, "ECC seckey:");
log_printhex (ecc_ct, ecc->point_len, "ECC ephem:");
}
err = gcry_kem_decap (ecc->algo, ecc_sk, ecc->scalar_len,
ecc_ct, ecc->point_len, ecc_ecdh, ecc->point_len, NULL, 0);
if (err)
{
if (opt.verbose)
log_info ("%s: gcry_kem_decap for ECC failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (ecc_ecdh, ecc->point_len, "ECC ecdh:");
err = gnupg_ecc_kem_kdf (ecc_ss, ecc->shared_len, ecc->hash_algo,
ecc_ecdh, ecc->point_len, ecc_ct, ecc->point_len,
ecc_pk, ecc->point_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->shared_len, "ECC shared:");
/* Secondly, PQC part. For now, we assume ML-KEM. */
err = gcry_sexp_extract_param (s_skey1, NULL, "/s", &mlkem_sk_mpi, NULL);
if (err)
{
if (opt.verbose)
log_info ("%s: extracting s from PQ key failed\n", __func__);
goto leave;
}
mlkem_sk = gcry_mpi_get_opaque (mlkem_sk_mpi, &nbits);
mlkem_sk_len = (nbits+7)/8;
if (mlkem_sk_len == GCRY_KEM_MLKEM512_SECKEY_LEN)
{
mlkem_kem_algo = GCRY_KEM_MLKEM512;
mlkem_ss_len = GCRY_KEM_MLKEM512_SHARED_LEN;
mlkem_ct_len = GCRY_KEM_MLKEM512_CIPHER_LEN;
}
else if (mlkem_sk_len == GCRY_KEM_MLKEM768_SECKEY_LEN)
{
mlkem_kem_algo = GCRY_KEM_MLKEM768;
mlkem_ss_len = GCRY_KEM_MLKEM768_SHARED_LEN;
mlkem_ct_len = GCRY_KEM_MLKEM768_CIPHER_LEN;
}
else if (mlkem_sk_len == GCRY_KEM_MLKEM1024_SECKEY_LEN)
{
mlkem_kem_algo = GCRY_KEM_MLKEM1024;
mlkem_ss_len = GCRY_KEM_MLKEM1024_SHARED_LEN;
mlkem_ct_len = GCRY_KEM_MLKEM1024_CIPHER_LEN;
}
else
{
if (opt.verbose)
log_info ("%s: PQ key length invalid (%zu)\n", __func__, mlkem_sk_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
mlkem_ct = gcry_mpi_get_opaque (mlkem_ct_mpi, &nbits);
len = (nbits+7)/8;
if (len != mlkem_ct_len)
{
if (opt.verbose)
log_info ("%s: PQ cipher text length invalid (%zu)\n",
__func__, mlkem_ct_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
err = gcry_kem_decap (mlkem_kem_algo, mlkem_sk, mlkem_sk_len,
mlkem_ct, mlkem_ct_len, mlkem_ss, mlkem_ss_len,
NULL, 0);
if (err)
{
if (opt.verbose)
log_info ("%s: gcry_kem_decap for PQ failed\n", __func__);
goto leave;
}
mpi_release (mlkem_sk_mpi);
mlkem_sk_mpi = NULL;
/* Then, combine two shared secrets and ciphertexts into one KEK */
err = gnupg_kem_combiner (kek, kek_len,
ecc_ss, ecc->shared_len, ecc_ct, ecc->point_len,
mlkem_ss, mlkem_ss_len, mlkem_ct, mlkem_ct_len,
fixed_info.data, fixed_info.size);
if (err)
{
if (opt.verbose)
log_info ("%s: KEM combiner failed\n", __func__);
goto leave;
}
mpi_release (ecc_ct_mpi);
ecc_ct_mpi = NULL;
mpi_release (mlkem_ct_mpi);
mlkem_ct_mpi = NULL;
if (DBG_CRYPTO)
{
log_printhex (kek, kek_len, "KEK key: ");
}
err = gcry_cipher_open (&hd, GCRY_CIPHER_AES256,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
{
if (opt.verbose)
log_error ("ecdh failed to initialize AESWRAP: %s\n",
gpg_strerror (err));
goto leave;
}
err = gcry_cipher_setkey (hd, kek, kek_len);
sessionkey_len = encrypted_sessionkey_len - 8;
err = gcry_cipher_decrypt (hd, sessionkey, sessionkey_len,
encrypted_sessionkey, encrypted_sessionkey_len);
gcry_cipher_close (hd);
mpi_release (encrypted_sessionkey_mpi);
encrypted_sessionkey_mpi = NULL;
if (err)
{
log_error ("KEM decrypt failed: %s\n", gpg_strerror (err));
goto leave;
}
put_membuf_printf (outbuf,
"(5:value%u:", (unsigned int)sessionkey_len);
put_membuf (outbuf, sessionkey, sessionkey_len);
put_membuf (outbuf, ")", 2);
leave:
wipememory (ecc_sk, sizeof ecc_sk);
wipememory (ecc_ecdh, sizeof ecc_ecdh);
wipememory (ecc_ss, sizeof ecc_ss);
wipememory (mlkem_ss, sizeof mlkem_ss);
wipememory (kek, sizeof kek);
wipememory (sessionkey, sizeof sessionkey);
mpi_release (mlkem_sk_mpi);
mpi_release (ecc_pk_mpi);
mpi_release (ecc_sk_mpi);
mpi_release (ecc_ct_mpi);
mpi_release (mlkem_ct_mpi);
mpi_release (encrypted_sessionkey_mpi);
gcry_free (fixed_info.data);
gcry_sexp_release (curve);
xfree (curve_name);
gcry_sexp_release (s_skey0);
gcry_sexp_release (s_skey1);
return err;
}
/* DECRYPT the encrypted stuff (like encrypted session key) in
CIPHERTEXT using KEM API, with KEMID. Keys (or a key) are
specified in CTRL. DESC_TEXT is used to retrieve private key.
OPTION can be specified for upper layer option for KEM. Decrypted
stuff (like session key) is written to OUTBUF.
*/
gpg_error_t
agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid,
const unsigned char *ciphertext, size_t ciphertextlen,
const unsigned char *option, size_t optionlen,
membuf_t *outbuf)
{
gcry_sexp_t s_cipher = NULL;
gpg_error_t err = 0;
/* For now, only PQC-PGP is supported. */
if (kemid != KEM_PQC_PGP)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
(void)optionlen;
if (kemid == KEM_PQC_PGP && option)
{
log_error ("PQC-PGP requires no option\n");
return gpg_error (GPG_ERR_INV_ARG);
}
if (!ctrl->have_keygrip)
{
log_error ("speculative decryption not yet supported\n");
return gpg_error (GPG_ERR_NO_SECKEY);
}
if (!ctrl->have_keygrip1)
{
log_error ("Composite KEM requires two KEYGRIPs\n");
return gpg_error (GPG_ERR_NO_SECKEY);
}
err = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen);
if (err)
{
log_error ("failed to convert ciphertext: %s\n", gpg_strerror (err));
return gpg_error (GPG_ERR_INV_DATA);
}
if (DBG_CRYPTO)
{
log_printhex (ctrl->keygrip, 20, "keygrip0:");
log_printhex (ctrl->keygrip1, 20, "keygrip1:");
gcry_log_debugsxp ("cipher", s_cipher);
}
err = composite_pgp_kem_decrypt (ctrl, desc_text, s_cipher, outbuf);
gcry_sexp_release (s_cipher);
return err;
}
diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c
index a374904cf..92f0dfbcd 100644
--- a/common/openpgp-oid.c
+++ b/common/openpgp-oid.c
@@ -1,780 +1,781 @@
/* 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, 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",
0, GCRY_KEM_RAW_BP256 },
{ "brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 384, NULL, "bp384",
0, GCRY_KEM_RAW_BP384 },
- { "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 512, NULL, "bp512" },
+ { "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 512, NULL, "bp512",
+ 0, GCRY_KEM_RAW_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 170a1c54b..f4efa8fc5 100644
--- a/g10/pkglue.c
+++ b/g10/pkglue.c
@@ -1,978 +1,994 @@
/* 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[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 && *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_ct_len = ecc_ecdh_len = 56;
ecc_ss_len = 64;
ecc_hash_algo = GCRY_MD_SHA3_512;
}
else if (ecc_algo == GCRY_KEM_RAW_BP256)
{
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
if (ecc_pubkey_len != 65)
{
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 = 65;
ecc_ss_len = 32;
ecc_hash_algo = GCRY_MD_SHA3_256;
}
else if (ecc_algo == GCRY_KEM_RAW_BP384)
{
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
if (ecc_pubkey_len != 97)
{
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 = 97;
ecc_ss_len = 64;
ecc_hash_algo = GCRY_MD_SHA3_512;
}
+ else if (ecc_algo == GCRY_KEM_RAW_BP512)
+ {
+ ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
+ ecc_pubkey_len = (nbits+7)/8;
+ if (ecc_pubkey_len != 129)
+ {
+ 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 = 129;
+ 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,
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, 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;
}
diff --git a/tests/openpgp/Makefile.am b/tests/openpgp/Makefile.am
index e32ff3d17..d1f04e99b 100644
--- a/tests/openpgp/Makefile.am
+++ b/tests/openpgp/Makefile.am
@@ -1,307 +1,322 @@
# Makefile.am - For tests/openpgp
# Copyright (C) 1998, 1999, 2000, 2001, 2003,
# 2010 Free Software Foundation, Inc.
#
# 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 .
# Process this file with automake to create Makefile.in
# Programs required before we can run these tests.
required_pgms = ../../g10/gpg$(EXEEXT) ../../agent/gpg-agent$(EXEEXT) \
../../tools/gpg-connect-agent$(EXEEXT) \
../gpgscm/gpgscm$(EXEEXT)
AM_CPPFLAGS = -I$(top_srcdir)/common
include $(top_srcdir)/am/cmacros.am
AM_CFLAGS =
noinst_PROGRAMS = fake-pinentry
fake_pinentry_SOURCES = fake-pinentry.c
TESTS_ENVIRONMENT = LC_ALL=C \
EXEEXT=$(EXEEXT) \
PATH="../gpgscm:$(PATH)" \
abs_top_srcdir="$(abs_top_srcdir)" \
objdir="$(abs_top_builddir)" \
GNUPG_BUILD_ROOT="$(abs_top_builddir)" \
GNUPG_IN_TEST_SUITE=fact \
GPGSCM_PATH="$(abs_top_srcdir)/tests/gpgscm"
XTESTS = \
version.scm \
enarmor.scm \
mds.scm \
decrypt.scm \
decrypt-sym.scm \
decrypt-multifile.scm \
decrypt-dsa.scm \
decrypt-session-key.scm \
decrypt-unwrap-verify.scm \
sigs.scm \
sigs-dsa.scm \
encrypt.scm \
encrypt-multifile.scm \
encrypt-dsa.scm \
compression.scm \
seat.scm \
clearsig.scm \
encryptp.scm \
detach.scm \
detachm.scm \
armsigs.scm \
armencrypt.scm \
armencryptp.scm \
signencrypt.scm \
signencrypt-dsa.scm \
armsignencrypt.scm \
armdetach.scm \
armdetachm.scm \
genkey1024.scm \
conventional.scm \
conventional-mdc.scm \
multisig.scm \
verify.scm \
verify-multifile.scm \
gpgv.scm \
gpgv-forged-keyring.scm \
armor.scm \
import.scm \
import-revocation-certificate.scm \
ecc.scm \
4gb-packet.scm \
tofu.scm \
trust-pgp-1.scm \
trust-pgp-2.scm \
trust-pgp-3.scm \
trust-pgp-4.scm \
gpgtar.scm \
use-exact-key.scm \
default-key.scm \
export.scm \
ssh-import.scm \
ssh-export.scm \
quick-key-manipulation.scm \
key-selection.scm \
delete-keys.scm \
gpgconf.scm \
issue2015.scm \
issue2346.scm \
issue2417.scm \
issue2419.scm \
issue2929.scm \
issue2941.scm
# XXX: Currently, one cannot override automake's 'check' target. As a
# workaround, we avoid defining 'TESTS', thus automake will not emit
# the 'check' target. For extra robustness, we merely define a
# dependency on 'xcheck', so this hack should also work even if
# automake would emit the 'check' target, as adding dependencies to
# targets is okay.
check: xcheck
.PHONY: xcheck
xcheck:
$(TESTS_ENVIRONMENT) $(abs_top_builddir)/tests/gpgscm/gpgscm$(EXEEXT) \
$(abs_srcdir)/run-tests.scm $(TESTFLAGS) $(TESTS)
TEST_FILES = pubring.asc secring.asc plain-1o.asc plain-2o.asc plain-3o.asc \
plain-1.asc plain-2.asc plain-3.asc plain-1-pgp.asc \
plain-largeo.asc plain-large.asc \
pubring.pkr.asc secring.skr.asc secdemo.asc pubdemo.asc \
bug537-test.data.asc bug894-test.asc \
bug1223-good.asc bug1223-bogus.asc 4gb-packet.asc \
tofu/conflicting/1C005AF3.gpg \
tofu/conflicting/1C005AF3-secret.gpg \
tofu/conflicting/1C005AF3-1.txt \
tofu/conflicting/1C005AF3-2.txt \
tofu/conflicting/1C005AF3-3.txt \
tofu/conflicting/1C005AF3-4.txt \
tofu/conflicting/1C005AF3-5.txt \
tofu/conflicting/B662E42F.gpg \
tofu/conflicting/B662E42F-secret.gpg \
tofu/conflicting/B662E42F-1.txt \
tofu/conflicting/B662E42F-2.txt \
tofu/conflicting/B662E42F-3.txt \
tofu/conflicting/B662E42F-4.txt \
tofu/conflicting/B662E42F-5.txt \
tofu/conflicting/BE04EB2B.gpg \
tofu/conflicting/BE04EB2B-secret.gpg \
tofu/conflicting/BE04EB2B-1.txt \
tofu/conflicting/BE04EB2B-2.txt \
tofu/conflicting/BE04EB2B-3.txt \
tofu/conflicting/BE04EB2B-4.txt \
tofu/conflicting/BE04EB2B-5.txt \
tofu/cross-sigs/EC38277E-secret.gpg \
tofu/cross-sigs/EC38277E-1.gpg \
tofu/cross-sigs/EC38277E-1.txt \
tofu/cross-sigs/EC38277E-2.gpg \
tofu/cross-sigs/EC38277E-2.txt \
tofu/cross-sigs/EC38277E-3.txt \
tofu/cross-sigs/871C2247-secret.gpg \
tofu/cross-sigs/871C2247-1.gpg \
tofu/cross-sigs/871C2247-1.txt \
tofu/cross-sigs/871C2247-2.gpg \
tofu/cross-sigs/871C2247-2.txt \
tofu/cross-sigs/871C2247-3.gpg \
tofu/cross-sigs/871C2247-3.txt \
tofu/cross-sigs/871C2247-4.gpg \
tofu/cross-sigs/README \
key-selection/0.asc \
key-selection/1.asc \
key-selection/2.asc \
key-selection/3.asc \
key-selection/4.asc \
trust-pgp/scenario1.asc \
trust-pgp/scenario2.asc \
trust-pgp/scenario3.asc \
trust-pgp/scenario4.asc \
trust-pgp/alice.sec.asc \
trust-pgp/bobby.sec.asc \
trust-pgp/carol.sec.asc \
trust-pgp/david.sec.asc \
trust-pgp/frank.sec.asc \
trust-pgp/grace.sec.asc \
trust-pgp/heidi.sec.asc
data_files = data-500 data-9000 data-32000 data-80000 plain-large
priv_keys = privkeys/50B2D4FA4122C212611048BC5FC31BD44393626E.asc \
privkeys/7E201E28B6FEB2927B321F443205F4724EBE637E.asc \
privkeys/13FDB8809B17C5547779F9D205C45F47CE0217CE.asc \
privkeys/343D8AF79796EE107D645A2787A9D9252F924E6F.asc \
privkeys/8B5ABF3EF9EB8D96B91A0B8C2C4401C91C834C34.asc \
privkeys/0D6F6AD4C4C803B25470F9104E9F4E6A4CA64255.asc \
privkeys/FD692BD59D6640A84C8422573D469F84F3B98E53.asc \
privkeys/76F7E2B35832976B50A27A282D9B87E44577EB66.asc \
privkeys/A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD.asc \
privkeys/0DD40284FF992CD24DC4AAC367037E066FCEE26A.asc \
privkeys/2BC997C0B8691D41D29A4EC81CCBCF08454E4961.asc \
privkeys/3C9D5ECA70130C2DBB1FC6AC0076BEEEC197716F.asc \
privkeys/449E644892C951A37525654730DD32C202079926.asc \
privkeys/58FFE844087634E62440224908BDE44BEA7EB730.asc \
privkeys/4DF9172D6FF428C97A0E9AA96F03E8BCE3B2F188.asc \
privkeys/9D7CD8F53F2F14C3E2177D1E9D1D11F39513A4A4.asc \
privkeys/6E6B7ED0BD4425018FFC54F3921D5467A3AE00EB.asc \
privkeys/C905D0AB6AE9655C5A35975939997BBF3325D6DD.asc \
privkeys/B2BAA7144303DF19BB6FDE23781DD3FDD97918D4.asc \
privkeys/CF60965BF51F67CF80DECE853E0D2D343468571D.asc \
privkeys/DF00E361D34F80868D06879AC21D7A7D4E4FAD76.asc \
privkeys/00FE67F28A52A8AA08FFAED20AF832DA916D1985.asc \
privkeys/1DF48228FEFF3EC2481B106E0ACA8C465C662CC5.asc \
privkeys/A2832820DC9F40751BDCD375BB0945BA33EC6B4C.asc \
privkeys/ADE710D74409777B7729A7653373D820F67892E0.asc \
privkeys/CEFC51AF91F68A2904FBFF62C4F075A4785B803F.asc \
privkeys/1E28F20E41B54C2D1234D896096495FF57E08D18.asc \
privkeys/EB33B687EB8581AB64D04852A54453E85F3DF62D.asc \
privkeys/C6A6390E9388CDBAD71EAEA698233FE5E04F001E.asc \
privkeys/D69102E0F5AC6B6DB8E4D16DA8E18CF46D88CAE3.asc \
privkeys/891067FFFC6D67D37BD4BFC399191C5F3989D1B5.key \
privkeys/F27FC04CB01723A4CB6F5399F7B86CCD82C0169C.key \
privkeys/DC60E0AE48E0F14E8FD7C9C36E18C6651E99BA93.key \
privkeys/2F4CD0990D56D41A74456668469E3139A7960CD4.key \
privkeys/8B2E1355C97C34E0AC1CBC9DFDF2526BFE8990A7.key \
privkeys/F5DB116462B7BD2FA83A4453C4DFA2AE8604FB59.key \
privkeys/8F9ABF3E5BBFC50D168DD524EB8F7263E7B33859.key \
- privkeys/A1598F57316F7FEC3F946895E35A7D2EAE8D3A13.key
+ privkeys/A1598F57316F7FEC3F946895E35A7D2EAE8D3A13.key \
+ privkeys/19C87B74004E9839F3D56992B0A9943BF90B56F7.key \
+ privkeys/513906BEA5A40F25C9D6EBBCEF62D0784E7235A5.key \
+ privkeys/6EC551A7895031EE4543A1C789E16E6A6C229CFC.key \
+ privkeys/702F599E35E6E0BE68E6FDF25D887229D42780F7.key \
+ privkeys/7C31A4A632A49C4E8B1C8CBA53976ADFF714510F.key \
+ privkeys/A1ABFD89944870D04039D40C218EE127254AEEE9.key \
+ privkeys/A87B85D88DB8B2B5A62A9958C8F2878F49605D09.key \
+ privkeys/D54E9B75C3541D95C45E430DAC9645E9FB62C668.key \
+ privkeys/EAD718DCE3D2F33A20BFC8BA617844DEF3FFAF3A.key
sample_keys = samplekeys/README \
samplekeys/ecc-sample-1-pub.asc \
samplekeys/ecc-sample-2-pub.asc \
samplekeys/ecc-sample-3-pub.asc \
samplekeys/ecc-sample-1-sec.asc \
samplekeys/ecc-sample-2-sec.asc \
samplekeys/ecc-sample-3-sec.asc \
samplekeys/eddsa-sample-1-pub.asc \
samplekeys/eddsa-sample-1-sec.asc \
samplekeys/dda252ebb8ebe1af-1.asc \
samplekeys/dda252ebb8ebe1af-2.asc \
samplekeys/whats-new-in-2.1.asc \
samplekeys/e2e-p256-1-clr.asc \
samplekeys/e2e-p256-1-prt.asc \
samplekeys/E657FB607BB4F21C90BB6651BC067AF28BC90111.asc \
samplekeys/rsa-rsa-sample-1.asc \
samplekeys/ed25519-cv25519-sample-1.asc \
samplekeys/ed25519-cv25519-sample-2.asc \
samplekeys/silent-running.asc \
samplekeys/ssh-dsa.key \
samplekeys/ssh-ecdsa.key \
samplekeys/ssh-ed25519.key \
samplekeys/ssh-rsa.key \
samplekeys/issue2346.gpg \
samplekeys/authenticate-only.pub.asc \
samplekeys/authenticate-only.sec.asc \
samplekeys/pqc-sample-1.key.asc \
- samplekeys/pqc-sample-2.key.asc
+ samplekeys/pqc-sample-2.key.asc \
+ samplekeys/pqc-sample-3.key.asc \
+ samplekeys/pqc-sample-4.key.asc \
+ samplekeys/pqc-sample-5.key.asc
sample_msgs = samplemsgs/clearsig-1-key-1.asc \
samplemsgs/clearsig-2-keys-1.asc \
samplemsgs/clearsig-2-keys-2.asc \
samplemsgs/enc-sym-cfb-1.asc \
samplemsgs/enc-sym-cfb-2.asc \
samplemsgs/enc-sym-ocb-1.asc \
samplemsgs/enc-sym-ocb-2.asc \
samplemsgs/enc-1-key-1.asc \
samplemsgs/enc-1-key-2.asc \
samplemsgs/enc-2-keys-1.asc \
samplemsgs/enc-2-keys-2.asc \
samplemsgs/enc-2-keys-hh-1.asc \
samplemsgs/enc-2-keys-hr-1.asc \
samplemsgs/enc-2-keys-rh-1.asc \
samplemsgs/encsig-2-2-keys-3.asc \
samplemsgs/encsig-2-2-keys-4.asc \
samplemsgs/encsig-2-keys-1.asc \
samplemsgs/encsig-2-keys-2.asc \
samplemsgs/encsig-2-keys-3.asc \
samplemsgs/encsig-2-keys-4.asc \
samplemsgs/encz0-1-key-1.asc \
samplemsgs/encz0-1-key-2.asc \
samplemsgs/issue2419.asc \
samplemsgs/revoke-2D727CC768697734.asc \
samplemsgs/sig-1-key-1.asc \
samplemsgs/sig-1-key-2.asc \
samplemsgs/sig-2-keys-1.asc \
samplemsgs/sig-2-keys-2.asc \
samplemsgs/signed-1-key-1.asc \
samplemsgs/signed-1-key-2.asc \
samplemsgs/signed-2-keys-1.asc \
samplemsgs/signed-2-keys-2.asc \
samplemsgs/pqc-sample-1.enc.asc \
- samplemsgs/pqc-sample-2.enc.asc
+ samplemsgs/pqc-sample-2.enc.asc \
+ samplemsgs/pqc-sample-3.enc.asc \
+ samplemsgs/pqc-sample-4.enc.asc \
+ samplemsgs/pqc-sample-5.enc.asc
EXTRA_DIST = defs.scm trust-pgp/common.scm $(XTESTS) $(TEST_FILES) \
mkdemodirs signdemokey $(priv_keys) $(sample_keys) \
$(sample_msgs) ChangeLog-2011 run-tests.scm \
setup.scm shell.scm all-tests.scm signed-messages.scm
CLEANFILES = prepared.stamp x y yy z out err $(data_files) \
plain-1 plain-2 plain-3 trustdb.gpg *.lock .\#lk* \
*.log gpg_dearmor gpg.conf gpg-agent.conf S.gpg-agent \
pubring.gpg pubring.gpg~ pubring.kbx pubring.kbx~ \
common.conf secring.gpg pubring.pkr secring.skr \
gnupg-test.stop random_seed gpg-agent.log tofu.db \
passphrases sshcontrol S.gpg-agent.ssh report.xml
clean-local:
-rm -rf private-keys-v1.d openpgp-revocs.d tofu.d gpgtar.d
# We need to depend on a couple of programs so that the tests don't
# start before all programs are built.
all-local: $(required_pgms)
diff --git a/tests/openpgp/privkeys/19C87B74004E9839F3D56992B0A9943BF90B56F7.key b/tests/openpgp/privkeys/19C87B74004E9839F3D56992B0A9943BF90B56F7.key
new file mode 100644
index 000000000..d0c602d3f
--- /dev/null
+++ b/tests/openpgp/privkeys/19C87B74004E9839F3D56992B0A9943BF90B56F7.key
@@ -0,0 +1,7 @@
+Created: 20240423T121650
+Key: (private-key (ecc (curve brainpoolP384r1)(q
+ #0472FB0D5A0A01E55C29E9FB8C5C425BDF37150DAFA3C556C786E2FEF9E011919E68
+ 3DBC7731D1281FDB9780C4B7FD7785198516BE2033D06448BA7EA39C2BCB7128BC1E0B
+ 3F81F2E734434E6FE96B29E19C57B423C5009134010CD87FADCA63A1#)(d
+ #474FD16712E9A8EC87A6F94E553E369358985475B453E95CFFD2123E4E97679720AA
+ 269CD6002DC688C9F3B9B8C456F1#)))
diff --git a/tests/openpgp/privkeys/513906BEA5A40F25C9D6EBBCEF62D0784E7235A5.key b/tests/openpgp/privkeys/513906BEA5A40F25C9D6EBBCEF62D0784E7235A5.key
new file mode 100644
index 000000000..c902ecf25
--- /dev/null
+++ b/tests/openpgp/privkeys/513906BEA5A40F25C9D6EBBCEF62D0784E7235A5.key
@@ -0,0 +1,8 @@
+Created: 20240423T151658
+Key: (private-key (ecc (curve brainpoolP512r1)(q
+ #046E26896EAC8D3D7F31F0B57439FFDBC0078841CEF7A9A98AB15F489FEE34E9D15E
+ 2050EAFCA0CD4C7021E5E018F601EEC7EDDE1AACE959EA13F84143861489DD54ADAA4F
+ F86E5FF75E3CC2EA6453716075DD908B9647B45257A64AE88DD390D7325B9E30698027
+ 16B3743AFE7A7E44495AF625C3E009C581C63E341A16A23D07#)(d
+ #1A1364149C7AB54D76D9345424EF755139031E85B5B7DEB0D221855D0189A579614B
+ BCB6D01D9E02627F5C187338D7A176A830DF55422FE20D3BFF7812255C1A#)))
diff --git a/tests/openpgp/privkeys/6EC551A7895031EE4543A1C789E16E6A6C229CFC.key b/tests/openpgp/privkeys/6EC551A7895031EE4543A1C789E16E6A6C229CFC.key
new file mode 100644
index 000000000..e9df95c0a
--- /dev/null
+++ b/tests/openpgp/privkeys/6EC551A7895031EE4543A1C789E16E6A6C229CFC.key
@@ -0,0 +1,137 @@
+Created: 20240423T151658
+Key: (private-key (kyber1024 (p #BE7768B33800B583978414965EC439D29A4180
+ 655B0A58BA40CB9C29D10088A07D66B47AE6124B986156A9347878FBCFC0351049D2C1
+ 8835CEE72BADE28B7AC8BA7FF8345AD885B1E730409939C5B3A1A5867AB7912538F234
+ 59AA335204ECC5E94A9B651772A9AC5C60A5137AA74719996479D7A424839172398078
+ 57493825896C628DA1726418D843E4946F9838A2E2DA5B93C6805995CD7D4B26EB79BC
+ BD645E52B31703986686CB2AB26A22029879BD57212928096F0954A65654FAD6651FEB
+ 11039C6E0F03172025303480AE6F5A0ED786A17E3779ABD775B15407B6560C97C42CF8
+ 6A63D7AA1032767D20D97EAD7A3EFCD346D00B4DFD147FAE9C7BCC64224837B6C62770
+ 76E25BE756C201A58445779AE98679C753C2BBA69425D343A5156EAED17AACF0B939C4
+ 9B3FE83AA80C5E4399BE81C13F0A2CB4E6B08E588A13B8944AC9F74D79541FAB1691BE
+ 88156F920FA836C4D41AC4965009FAA174DA36B5521C79F4313B498525311369261B64
+ 48FC7AEE6B9D44455BF6347C35F2B99F95254C6027759B99CA16659006BAD1F7C0B20C
+ BDBFF66A8F454E24863B175A0B614830AC8ACB1AFB93F1D3C54F7216DAA05AE210B57B
+ 503E4657B6870A8CD448644B7A5B68F565E9F51C9F12890AC8315ED52015F6BBED89C6
+ 924BC756EB752FB3502BC50CAFA073E1B6AE3DD29E0C9269F8B505CA0654702114F03B
+ AE81C945B13631EA6C28B244BCDFB159B55B1EACAC30AF7872BD3C8F5BEA0EE9056DB2
+ E6C87C9B204AB21018696217D648EFCBBB4C078C16751B03702BAF447FFB741FDB2455
+ AF5A9BCA6A3340C5B8F6B82748723024B20E6F5A3D56278DBC975D4EA70D9399A6E355
+ 533920A3C5E858AD5180BD4434F3E42B971908AA591ABBF3C1EBF257627CB2BF740DC1
+ B88C7551A5B8462F6CE3B58C65A390C07C01A15C2BC624AB007924D9B76DD1A50603C1
+ 92278A24CC86CFAA952E66ACFE99A800D56C473A7877955FC7D83C1DD548A57527B259
+ 0B56B6CB3E8525F06986725379C4130F8FE15524D569346AAF451538DC663149F1946D
+ 5509BE3BB6026031F87AC4B21B80D66981F8527CDAF0AD4BFC2A3B3265BDAC4E65190B
+ 5D777275896EA238606CA7B2173C1C4DE01058965128BC866B8CC2991045AE47324243
+ CBB0808E2F851FC9563779AC269664B01AECBEF45465FEB9584CC47F3B031794594BF5
+ 7497446B0A856755429A7389A21AB3573C2D8A036238486451013D647D288979200A83
+ 72C6039FA23E35877008408C7C815E55035A5BA9165BBB3BA56A96253B526DDC37FCC2
+ 9F16F18E7D6676A393B867849C85927CAF996A9FE388FF880D5615AC64B7B4251803D7
+ 9599D2232D560C85F562643E9B9F33F8139D0B61ABE98F76043E5166145A045B83E574
+ AAB35D9D119A3EB3107FB3388EF483B056726B3036C29663018C929FD972D444C54373
+ 16D749AF0D12556F159FC02584C6B8456BF56DC2128DCA61B794E7AAFB61969354B5A6
+ DA82EB545CFA242CE997A5168AA43926131165B534FBBE1F15A4D4432FF5BB8446A575
+ E528480B7C3FA0328C8134C0BF778792C05902EB32476326AE979CCF0C80C39C4B7332
+ 9FC4EA365164A848D529BCB43985D35B3AE30B02AC057E1910BDB616F50A79F22441B3
+ 51C77C8A17AB4052F44930B991AF7C751209253C4D27C8C5166C3285A1B1D39981B85E
+ CE5CC7AF11B4B8B38170963766E6130A66ABD736478067C0A86470C542216AE1593591
+ 62F72C34241601E4F54087D64A8752C38FD85CC09C0C7F38CE3A0320E5E16B507302D7
+ 830EA18793774A3D7BAA807971AD17332D8862C0FA757D98782CE927577A32B45BF18E
+ EE77B393F410620C03093620470BBC93313489F11C96871C42DA296909ABF16A885950
+ A2CF896CD18A61D54B3C529C96355AC2D3C69324C3AE634C8718C3CBFA734C40E78F91
+ 53A1286B379E0B7636141DD20B43A26B6BB2E03A56F008F0868CE2B940CF108FCE7954
+ C0A1B933CC15FE156E2C4C4E4EDCBBA1E87108E3CFF24865548A838E7619315B25C265
+ 3465158CAE4683AA03696870C397359B118CB37AA853D46CC5B05ACFDBC010C8984284
+ 07009340A4793A05718B0E12E95FA8685A213B8559B44021B8C3A458AF81C2476775AB
+ F0015DBFF820427CA306265DE9AE0F4AE474DF627DCD167AEA9FB7A6EB3F021BA4A78A
+ E111275F13AC923E23#)(s #2EB39309148C404770B9EC5188D13FEDD24E3DAA78FCC3
+ 5AAA6840B1458E30511A973C389FF3B4F8073ACF9780AC09A648822E460C6399C125B5
+ 191DCF16C92C350FFCF25A94E22E4E80C9A25C18DFC884D9536D2E8469CAC88781726C
+ CE899E477BAFE3B25F3319A310E499AF030CC65867E207D0BCFA1C1EF711F2403B8293
+ 7F83A0859051CDC9AA9B43B53197CB47B1F47BC6BB5A073A3D5B2182A312C747A50C5B
+ E67CB43315019CB0B6D73216943144A5228307A4D7FA3C88B0B3AA1B088CCC73A81AA3
+ 0EF56D06D3A2139517215A843E093C1F61C1B1890FE5E9A0FC64986622BBFF7635F6F7
+ 70C1325727300665B437544456C1211FC7D1753B724DB691CAE729BC89CA97B5C60363
+ 710FF5A2502C54C429591824A62547D70FB17A8D177979473B254E12BA1A589B1039CC
+ 9AF061FF098AF5B09B89334DF3E54B6D524FB8B99F7C9A41F4731175D4904BA040A201
+ 76BF153241FC5F38D63665629C75C97935F99DF7B7B415816705C00A47020DB2D8A6C7
+ 945FD950206FB1CE970A973A285469FC6B2D495BCC50BACCCC9DD03A9D85C2B667BB64
+ 887A37EB645DC5F4281ABA6FA5D2B73C0B97D9AC9220DB5A0ADABA6B438367E60E0412
+ 453E6791C275A19F1B44D23A9C00E11610EABC332746F6FC09DDAAA402475E7528C57F
+ 8B79BE8B506F100E43AB3131C138225622DC597F0405B86A3CCABF2B45CBA028FE1221
+ A9AB4368EA6D9129A80A26B2F8CB76438A15D0FC8BD0B26BD6D5BDAB75BA25F636A889
+ 95DCE2656A21C66C3CCBE2CACF38C66F2D66BB55C81A66C1695BA5391BA327F44B6596
+ 4A035A37C6C3BA7198F27E758C822EDAC40E50CBC86274E6EC6710B74178321FEAD3B7
+ 20319A50D34EAC953424379CCCF9753E3C26D60B896A555C62D21F226899620995CCEA
+ 312F840D08294A6816789DC77B1D730151FA5084BB8CA3CBA402E834CA5C2069D6455B
+ 133864F64CCDAA7C98B7038AD6A1FCF24C81E6B20D2C7BA1B1B45B4A949D48AC51683F
+ A8C4938B4CBBA49C258318742BFAA172DC58C5E628BD9A10A9C54116705B6E9C80D936
+ 2C62E26DC5A132F02B2A4B1A2376A74668ECA44D2080C414941C6A3836CA90C590CD3D
+ 2B74F2C33C82288B15B708F4178D7B1A01990308DC76C0C83794063B0D2F434EE86AAE
+ 9240163AB0AB49DC36F5AC925F05C38CD6722E70B09F8871F04066DF90A4EF06AB3776
+ 161DD8817313653A0A58CCE69040EBB5D84B9B1DEB0892E8071B073965D68F4C191354
+ 184E2D0100A272636560C305394185D01D0FE527A66CC1F4ECB34C208222FB505FB39F
+ 5B47A6CE87BA58C848EAD5ADC78B17CE6584A4336C3AF8C23C9BBE805ACA16AC53247A
+ 621D074A1AC79339B83661D7018E55A6091C58441606A862C29A79BCB7600041AC7CFF
+ 5C76961B48D4C47437E3119D2697B28502748036784A99F4D07F21FC16C6A1C5F44070
+ C2F07F71FC74D91114C8E71053E18A59EB567C1445CBE3216CAB431FE59D42E627BB8C
+ 5C905C4FDA2B1C0A5B3B67567879660E044B5FA0666310939250020AAF31A6FA789B38
+ 904121713F08A5760EAAC87EA58444D25FC40787A3600B55059FE081AA535BBF6DF25C
+ 1C4A27DBA8A243AC88A4D879BBAB260BFA62DF0557BF7B7466654DE5C69DDBB18C3E17
+ B5EBD0AD91A80AEA71C2F5F076794C1B60323E7C5416F11179CAD8A6F48C943AAB6265
+ 6A9AFAFA2F5CE8918935C21E169806463CE140B1CF6930B9A3AD4AFCA1B2607867B441
+ 5EE696E3949D53AAA74A8692913AA900E5A32BD7C9482284F1133245A08EBA41C17C70
+ 987FC106C3961B3A13C2E9966DC6F9B42CFBA39EBA7620CA6E42DC7F58759315F04703
+ 262E334B45CDD90FDE270C679A67C91B8D80076E4F539678822AF993633EC54FE6486D
+ 53E51DE286C5887B66A6188BC7E88BFF4455C03160F3A2AF2BF9C7EED288D1F977EB69
+ 72BA48742543325FB667DA80B5B6CB3C70B76C15940A93CB2ABAC76315F18E7A578370
+ 6320299C33B8636E0354966E46585025462444300BFB94C36784904A1DF199C6B1AB62
+ 410723D6365F50111CD7854384296D47A1BA7B241FD179443C5721D3655435069C0F6A
+ 4DD6E21C40F3CAA5502431601486DB3ADC329888D7ACB364B3902B6781B52712C70054
+ 8C61F811184A00AEBE7768B33800B583978414965EC439D29A4180655B0A58BA40CB9C
+ 29D10088A07D66B47AE6124B986156A9347878FBCFC0351049D2C18835CEE72BADE28B
+ 7AC8BA7FF8345AD885B1E730409939C5B3A1A5867AB7912538F23459AA335204ECC5E9
+ 4A9B651772A9AC5C60A5137AA74719996479D7A42483917239807857493825896C628D
+ A1726418D843E4946F9838A2E2DA5B93C6805995CD7D4B26EB79BCBD645E52B3170398
+ 6686CB2AB26A22029879BD57212928096F0954A65654FAD6651FEB11039C6E0F031720
+ 25303480AE6F5A0ED786A17E3779ABD775B15407B6560C97C42CF86A63D7AA1032767D
+ 20D97EAD7A3EFCD346D00B4DFD147FAE9C7BCC64224837B6C6277076E25BE756C201A5
+ 8445779AE98679C753C2BBA69425D343A5156EAED17AACF0B939C49B3FE83AA80C5E43
+ 99BE81C13F0A2CB4E6B08E588A13B8944AC9F74D79541FAB1691BE88156F920FA836C4
+ D41AC4965009FAA174DA36B5521C79F4313B498525311369261B6448FC7AEE6B9D4445
+ 5BF6347C35F2B99F95254C6027759B99CA16659006BAD1F7C0B20CBDBFF66A8F454E24
+ 863B175A0B614830AC8ACB1AFB93F1D3C54F7216DAA05AE210B57B503E4657B6870A8C
+ D448644B7A5B68F565E9F51C9F12890AC8315ED52015F6BBED89C6924BC756EB752FB3
+ 502BC50CAFA073E1B6AE3DD29E0C9269F8B505CA0654702114F03BAE81C945B13631EA
+ 6C28B244BCDFB159B55B1EACAC30AF7872BD3C8F5BEA0EE9056DB2E6C87C9B204AB210
+ 18696217D648EFCBBB4C078C16751B03702BAF447FFB741FDB2455AF5A9BCA6A3340C5
+ B8F6B82748723024B20E6F5A3D56278DBC975D4EA70D9399A6E355533920A3C5E858AD
+ 5180BD4434F3E42B971908AA591ABBF3C1EBF257627CB2BF740DC1B88C7551A5B8462F
+ 6CE3B58C65A390C07C01A15C2BC624AB007924D9B76DD1A50603C192278A24CC86CFAA
+ 952E66ACFE99A800D56C473A7877955FC7D83C1DD548A57527B2590B56B6CB3E8525F0
+ 6986725379C4130F8FE15524D569346AAF451538DC663149F1946D5509BE3BB6026031
+ F87AC4B21B80D66981F8527CDAF0AD4BFC2A3B3265BDAC4E65190B5D777275896EA238
+ 606CA7B2173C1C4DE01058965128BC866B8CC2991045AE47324243CBB0808E2F851FC9
+ 563779AC269664B01AECBEF45465FEB9584CC47F3B031794594BF57497446B0A856755
+ 429A7389A21AB3573C2D8A036238486451013D647D288979200A8372C6039FA23E3587
+ 7008408C7C815E55035A5BA9165BBB3BA56A96253B526DDC37FCC29F16F18E7D6676A3
+ 93B867849C85927CAF996A9FE388FF880D5615AC64B7B4251803D79599D2232D560C85
+ F562643E9B9F33F8139D0B61ABE98F76043E5166145A045B83E574AAB35D9D119A3EB3
+ 107FB3388EF483B056726B3036C29663018C929FD972D444C5437316D749AF0D12556F
+ 159FC02584C6B8456BF56DC2128DCA61B794E7AAFB61969354B5A6DA82EB545CFA242C
+ E997A5168AA43926131165B534FBBE1F15A4D4432FF5BB8446A575E528480B7C3FA032
+ 8C8134C0BF778792C05902EB32476326AE979CCF0C80C39C4B73329FC4EA365164A848
+ D529BCB43985D35B3AE30B02AC057E1910BDB616F50A79F22441B351C77C8A17AB4052
+ F44930B991AF7C751209253C4D27C8C5166C3285A1B1D39981B85ECE5CC7AF11B4B8B3
+ 8170963766E6130A66ABD736478067C0A86470C542216AE159359162F72C34241601E4
+ F54087D64A8752C38FD85CC09C0C7F38CE3A0320E5E16B507302D7830EA18793774A3D
+ 7BAA807971AD17332D8862C0FA757D98782CE927577A32B45BF18EEE77B393F410620C
+ 03093620470BBC93313489F11C96871C42DA296909ABF16A885950A2CF896CD18A61D5
+ 4B3C529C96355AC2D3C69324C3AE634C8718C3CBFA734C40E78F9153A1286B379E0B76
+ 36141DD20B43A26B6BB2E03A56F008F0868CE2B940CF108FCE7954C0A1B933CC15FE15
+ 6E2C4C4E4EDCBBA1E87108E3CFF24865548A838E7619315B25C2653465158CAE4683AA
+ 03696870C397359B118CB37AA853D46CC5B05ACFDBC010C898428407009340A4793A05
+ 718B0E12E95FA8685A213B8559B44021B8C3A458AF81C2476775ABF0015DBFF820427C
+ A306265DE9AE0F4AE474DF627DCD167AEA9FB7A6EB3F021BA4A78AE111275F13AC923E
+ 23D1F7082DD2B7C9BA1CF7957573C9778E452967D9D35277277471D1E15EC88ED243E0
+ ABDAF1149BF31CEC85D81BB32BF3CFFB4E62301100C95B53023559948E18#)))
diff --git a/tests/openpgp/privkeys/702F599E35E6E0BE68E6FDF25D887229D42780F7.key b/tests/openpgp/privkeys/702F599E35E6E0BE68E6FDF25D887229D42780F7.key
new file mode 100644
index 000000000..cb3e6ea3e
--- /dev/null
+++ b/tests/openpgp/privkeys/702F599E35E6E0BE68E6FDF25D887229D42780F7.key
@@ -0,0 +1,7 @@
+Created: 20240423T121603
+Key: (private-key (ecc (curve brainpoolP384r1)(q
+ #044F9318AAA2E3A3D28DA76F4B3B6B7CCAFA9B77A571E9A5BFFDEAC24A0FD96C6BB3
+ 8F74FCF980696EDD5F4CBCA2B628AE24C9DBC1C60EF1D5809D4D544EBAA01F7744233E
+ 248106D98A67CE1ED52D14FA942F6090C9988AA5EEB2368E19F679E2#)(d
+ #0D146C6EDEC6AE142765DABBFFEF4EA6CA290EB7DDF99676F3F59AE6CA3942531B31
+ 7330A07F5C8AAAEDFF69E6855301#)))
diff --git a/tests/openpgp/privkeys/7C31A4A632A49C4E8B1C8CBA53976ADFF714510F.key b/tests/openpgp/privkeys/7C31A4A632A49C4E8B1C8CBA53976ADFF714510F.key
new file mode 100644
index 000000000..e46ebbc6e
--- /dev/null
+++ b/tests/openpgp/privkeys/7C31A4A632A49C4E8B1C8CBA53976ADFF714510F.key
@@ -0,0 +1,137 @@
+Created: 20240423T121650
+Key: (private-key (kyber1024 (p #9A9A0790782C7F4A7F51934777FBCF7E56C66C
+ 2A56158680E424196D2749B7D624FDC9AC2B890BF8594CAE427E79DB4B0551A1512736
+ ACA58CA8ACB41DDB854A08B546170190075051E91A85529795044500E239514A2ABAD1
+ BE0D4C9F6512A887C30903AABECFDB6A6708185641402473637C80806162BDF85C2133
+ B0BBA05450E638860784902616621FE7529A107A5A976FAC4985C3583BEFF33DC4A05E
+ 52767599D426C11BBDE0A0C0BC45B6AEC3747A316E3119ADA71A5B6589927797483E36
+ 13E9F836C87685A26BB35C93CCDCC8B2C3B8AF38C60B2B8BB54B21C9ADC91644D541E1
+ 70AE1A97ABCAC321BF41855A48B71CF9250A39948601100AD1B26DA8740E2AA117E60F
+ B813848A126FDB87C0F2856B0497104250590B9041919A0253ECA5F283688D25C7788B
+ 68A21450F388B1FFDBB6736A9E38CCBC2A2B0F6A64AD45F882EDAC680AF060C0FA2B73
+ 921E7F053EDA54549176BE0E65ABD94B490FE75681822B21B4784D849FB32C76DF08C8
+ 17846A2628B1118580818740B637CEF7B31C1EC2B10FA7ADB8B4AEC9B541C568244E74
+ A4D94817E7B294B78A637A8AAD9F762028398B757063445C1BEE807B316788A3FCB7F8
+ A288C5463975301E4EA27171738EA0983D5AE01CD38811E9699F5065101CCCA251C052
+ 4CB5B147B98FF65902243C98B1992475D297910383F6AAC31BE8A32D8A6341F436F5C4
+ 928826AD4628C51A443826C05314E11851BC41CA1571E43061BB730A5AA1CBC9963690
+ 3745458851E28864E54A885C0BC88C423670795777784A9607A3BD2004F1F51871000D
+ C6F2578AC434D486871E665D14C76B1A416CC72676A96C4E6F79B75BE23FA31C2527D7
+ C976133F4B211E07E0BF4ED46C2FB77D81272290DB760DE8B567527B36316276AA9A36
+ 89AF4B3790C08820A844258D139856944EB8C40C9EBB6D199A196D446FCF25B0E61688
+ 4591950021AC9D5B62AFF2A46A955C36D21EC41B294EE3AC6235323BA1B3A3000B384B
+ 87813550FCAA470C6C6CBCC16D8732C5A3387C16C87B34B60D56B71797B177584C6A20
+ 804C9C145698A2ADA0453ABB290B36124D2690124D1B4A0FB37BAA9A1BA0237690018B
+ 66E426B7F1400A8B3C0635A79689861F3B1A8041919F600B435179E375859591293E98
+ 209A2B07AFE028982B5532275157E0545F844BC07C0491E55A2757A6DBDBB0A0817D58
+ 0CA677E8C60823AEFC76B42845AB54C40BF0D5733198C326AAA99F11667FD583A97195
+ D4091BA790C419BB2312684479E15E09D94974B5BF747CCC99BB9F348C411178645555
+ 372EE08C514B6AF5BC4EAF357FE0132AF374475BFC7754B29945D525E5E45561967002
+ 452C9B6A99B7340577D9AC5184911D3BA1E833C3FCD93B3401B3E8913721179D75AAB9
+ 94A17FA6C9C62C2CCE4194C563B4A78BABAE8CC82E177835AB351884FA1638898D5491
+ 2A2E87A5BF14170FD689E0E494DED76C43EBC23CE564B0EB94A5927DEC720C98346562
+ D375E116AE6E00B5081B69CC45AB8263ADA4304C50615FCB3C78FA37961953560912B0
+ 1D2C255541B94C929E1F105B6F462A36436090461473ECAA05BCCCB26536DFC157EA60
+ B37B49B4A17154AE6003A1C053E52329DF375F86A48300B7855C6865A7FAAA3F04A10D
+ 7821B18A978B8AA8DD096C6C101015C4CF385422CD1105E765201A445D9D22B3C61525
+ C4A09C692CA838E01EEF6B45F82A25B9F15108441A5911C5529498BB629A22543239EA
+ 9765453633C51E75B8C62F2C2D5E8A524E69097DF8480E8347F0F94ECC193DD9E2C86B
+ 7134066189C7FA6BC12331D5622CCAC42D090553E1191186E6315165BAD5039AA3A75A
+ 777AB9057A59D463C0E93B5DC34A058519848C78A607ECC631CC050554CEA61B29658A
+ 75D4D33DBC95AF5E587C1EC3B2B8E717FE85B8D3D41AE5C4700DCC449EA65491B55685
+ 791686CB9B51E97B18671D92F1A356A3060001CAB220707206B485EA6E0BD85F53719D
+ FA3876C6534058C4C291330244C19AC87B944B6945B77B44577772DDC8727C60260C5B
+ 17B4819A2C801A9D168D0EEA03BCA50236BC20F69886CEFB0C6994BF25C1CC09209EA5
+ 3B6C04F15F9613C5D04646D151457A206F0B35C9A40CC3782B2F851759F93040CC0BC9
+ 75477C7228BD7D1435DA80607EAB07CAF93F3FAFC623B4458888F05949EA9B2972888F
+ D40F3E6E009F8C935E#)(s #8D204312E102743757098841C5E7661CD0469E50326578
+ B3F7324E3A303617BBBFB9022E03C61F69BAA89890C72AB82964B60F039786C9436A84
+ 7223E412A76F34B1AB913C44D0CC9682593BAB0738072748F2014A38A424DA91D629A4
+ 33DC33928C862BBC466B3166884010AD6A33E20A75D9870D3C07C183C01F410444D982
+ A15B8A377806136DB03A94EB5D2EF1311AE03884E92CED483365C582E5C60EE708AF20
+ 939E135A5EE45781448911DAA90EBCA6792F86C13579C4D6305816F493A35C4F8BC3C6
+ 4D516D901A38174701B33C597AA40953FAB5CF3C672C4B29220176BE370846AB24E216
+ B634F3A6ED26CA88757854883FFB203382DB609F473686E57AF1970B75724E8D339DF8
+ A7B614063556E72569447A81901705A319686125EE5480FB8C3C1B73CDE6DC8EFE7060
+ 2D8811C52C85DB834C06B57850AC742683C05451B27F8B47C7491A42B102D1A83AB114
+ 0D8EEA401C80467CC1826C59B572DCCA3B32880E07BC14C7A6A39015E7548197FC1BB8
+ C8203554312F170FC3916727E9A5A349B427EBCC2833C77F4C045E8C4F5B39708C4C61
+ FD2761BD824BCD5606992B1716C15EDE0155DD311238BC529C7838AD6B65BFB31915F6
+ 7248021410E6A4BA44817D307B0F713689186BA91C683EAA7C29B509D57A41BA0A09FA
+ 4427F5C806CD705039669A5871C4170BB3F7081A77E17BFB1455311650EEFCC35C8336
+ 03976CC8B34C9C23CA52641698DB36ED45AAABE15540A757F0BB09647B5D0943305FA7
+ 91C89C623EC941D24CA283A208EB8745F0B2049E196CB3CC032782BB44F01B94A89883
+ B5C52040806BFABA3143464677CF8540876E189FBE69BEA7B151D8106711F666929596
+ D61A7344D783A7A741BCC4C5F8A9064FB29C27D8A08EA009E13414523088361B52A6C8
+ 5C5FE88D54E4CAC784804F9C0E9B508E49DC7F1FD9574863C2B3B39EE74614BA60ABA6
+ 52777832175026613BD08EB979B66B92AFEE89341C30770601892172CC7A34587C04B3
+ FB901F0618CFEC0BA54874C95F833C99AC2ED7F20AB74C3719A27D7572CA6F8A99A221
+ 843CA939491B210703497CB3288F227680E54F0954B62B455650979250336509709280
+ 7BB80349A2B4F8576B4600225A8D059608C8173E78398C971A12940B14F414208ABA1D
+ 00343DA1332E2A813164D80C8E9B54D0212A481027EC0B81D3580E74982D63DA100265
+ 9EB600A9E096140938526AA3B709B9086F1A9E78076EF36B372EF3C4DC4C6A1BE015C6
+ 9B7DCFE960AAE67449424BD4448861520EE59008391243C9F508DBF22A7DF1C90DA9AB
+ FF663DD1D7BA02B2C21DE9BB5656BD935B639565982BE41805D5A2FE63646CB48A4A69
+ 26A8E11C3185BAEF032372B8687ECC71021BB44004ADA09A9B6AAC0F335C2A9B689BEB
+ 556D9C706CCFC480DA20695CD0C94807A05BCAB436B88911FBB36DD36E1F734E66F288
+ 5A325A06CCA6CEA3BECCE02E1E6B71FB9A5327A13B172C9E9D908327655A25DA8E6307
+ C9BC251C4C7CA11B211327C68783727AB7F280FB654EAE3883BA3B54D2EBB4564A7C23
+ 7AA44D3C15F48817A23B9A6D346D562560C34B41CAE117075857F9A6B23B717EBD719F
+ D4A75537D9420F411C0D9A75BF3160DA172FA9615579E4B0D4D965376B6F5006660330
+ 216DB476F2688B512A836D52A2CBDC4C56B690BE205D1C681EB1D55702BC0611B960F6
+ 558AACD22B41370C04B10BBC63B4EBE10BBDDC024C94BD7046C525F2767C6A55668A0B
+ EB47C0330C1A4E17493B15460DA6AD8084C304268DC0203013119017B59C5FE2C785A6
+ 6BD7E130004DCEC940BB422CCE6D434049385443E18FC832C0A2C60553A5B4C81B8708
+ 78AB143016F883A36A4742ED7B2BE8EAA13C817475F6B0A49462AD38A0993CB1A6F5A3
+ 88E0835A8164B5F9B53B8C7A41E419F9E42ABEA1921BC5154B293D00B05CD900323533
+ 1384E8AA789304F570249BF2B1D2AC7FD4AB93F76B41E49421ECBA3AA0D44A7A72296A
+ 5711069C94394270D03B3B13D99CAEB78B091C8C71924D5241ABD14C80FDE9C2E34346
+ 8A974A844B760BF80A2826249A22A20E26ABFB1486AAD01E815B54A2CB32E179946C04
+ 3F6F298DAB042FEEB65494376784F8424D73099910154F29CF1CC55A4D76BA3487C41F
+ 7A9E5B9C8FC324A49A9A0790782C7F4A7F51934777FBCF7E56C66C2A56158680E42419
+ 6D2749B7D624FDC9AC2B890BF8594CAE427E79DB4B0551A1512736ACA58CA8ACB41DDB
+ 854A08B546170190075051E91A85529795044500E239514A2ABAD1BE0D4C9F6512A887
+ C30903AABECFDB6A6708185641402473637C80806162BDF85C2133B0BBA05450E63886
+ 0784902616621FE7529A107A5A976FAC4985C3583BEFF33DC4A05E52767599D426C11B
+ BDE0A0C0BC45B6AEC3747A316E3119ADA71A5B6589927797483E3613E9F836C87685A2
+ 6BB35C93CCDCC8B2C3B8AF38C60B2B8BB54B21C9ADC91644D541E170AE1A97ABCAC321
+ BF41855A48B71CF9250A39948601100AD1B26DA8740E2AA117E60FB813848A126FDB87
+ C0F2856B0497104250590B9041919A0253ECA5F283688D25C7788B68A21450F388B1FF
+ DBB6736A9E38CCBC2A2B0F6A64AD45F882EDAC680AF060C0FA2B73921E7F053EDA5454
+ 9176BE0E65ABD94B490FE75681822B21B4784D849FB32C76DF08C817846A2628B11185
+ 80818740B637CEF7B31C1EC2B10FA7ADB8B4AEC9B541C568244E74A4D94817E7B294B7
+ 8A637A8AAD9F762028398B757063445C1BEE807B316788A3FCB7F8A288C5463975301E
+ 4EA27171738EA0983D5AE01CD38811E9699F5065101CCCA251C0524CB5B147B98FF659
+ 02243C98B1992475D297910383F6AAC31BE8A32D8A6341F436F5C4928826AD4628C51A
+ 443826C05314E11851BC41CA1571E43061BB730A5AA1CBC99636903745458851E28864
+ E54A885C0BC88C423670795777784A9607A3BD2004F1F51871000DC6F2578AC434D486
+ 871E665D14C76B1A416CC72676A96C4E6F79B75BE23FA31C2527D7C976133F4B211E07
+ E0BF4ED46C2FB77D81272290DB760DE8B567527B36316276AA9A3689AF4B3790C08820
+ A844258D139856944EB8C40C9EBB6D199A196D446FCF25B0E616884591950021AC9D5B
+ 62AFF2A46A955C36D21EC41B294EE3AC6235323BA1B3A3000B384B87813550FCAA470C
+ 6C6CBCC16D8732C5A3387C16C87B34B60D56B71797B177584C6A20804C9C145698A2AD
+ A0453ABB290B36124D2690124D1B4A0FB37BAA9A1BA0237690018B66E426B7F1400A8B
+ 3C0635A79689861F3B1A8041919F600B435179E375859591293E98209A2B07AFE02898
+ 2B5532275157E0545F844BC07C0491E55A2757A6DBDBB0A0817D580CA677E8C60823AE
+ FC76B42845AB54C40BF0D5733198C326AAA99F11667FD583A97195D4091BA790C419BB
+ 2312684479E15E09D94974B5BF747CCC99BB9F348C411178645555372EE08C514B6AF5
+ BC4EAF357FE0132AF374475BFC7754B29945D525E5E45561967002452C9B6A99B73405
+ 77D9AC5184911D3BA1E833C3FCD93B3401B3E8913721179D75AAB994A17FA6C9C62C2C
+ CE4194C563B4A78BABAE8CC82E177835AB351884FA1638898D54912A2E87A5BF14170F
+ D689E0E494DED76C43EBC23CE564B0EB94A5927DEC720C98346562D375E116AE6E00B5
+ 081B69CC45AB8263ADA4304C50615FCB3C78FA37961953560912B01D2C255541B94C92
+ 9E1F105B6F462A36436090461473ECAA05BCCCB26536DFC157EA60B37B49B4A17154AE
+ 6003A1C053E52329DF375F86A48300B7855C6865A7FAAA3F04A10D7821B18A978B8AA8
+ DD096C6C101015C4CF385422CD1105E765201A445D9D22B3C61525C4A09C692CA838E0
+ 1EEF6B45F82A25B9F15108441A5911C5529498BB629A22543239EA9765453633C51E75
+ B8C62F2C2D5E8A524E69097DF8480E8347F0F94ECC193DD9E2C86B7134066189C7FA6B
+ C12331D5622CCAC42D090553E1191186E6315165BAD5039AA3A75A777AB9057A59D463
+ C0E93B5DC34A058519848C78A607ECC631CC050554CEA61B29658A75D4D33DBC95AF5E
+ 587C1EC3B2B8E717FE85B8D3D41AE5C4700DCC449EA65491B55685791686CB9B51E97B
+ 18671D92F1A356A3060001CAB220707206B485EA6E0BD85F53719DFA3876C6534058C4
+ C291330244C19AC87B944B6945B77B44577772DDC8727C60260C5B17B4819A2C801A9D
+ 168D0EEA03BCA50236BC20F69886CEFB0C6994BF25C1CC09209EA53B6C04F15F9613C5
+ D04646D151457A206F0B35C9A40CC3782B2F851759F93040CC0BC975477C7228BD7D14
+ 35DA80607EAB07CAF93F3FAFC623B4458888F05949EA9B2972888FD40F3E6E009F8C93
+ 5EFC819BE888E45002962BD84C240119DDC2FB54BAC77FFB2D990600F53E83DA47DE79
+ 15EC78E7947BADC403546041E8985FB1BB2CEBE867C9D6E08C213E54992D#)))
diff --git a/tests/openpgp/privkeys/A1ABFD89944870D04039D40C218EE127254AEEE9.key b/tests/openpgp/privkeys/A1ABFD89944870D04039D40C218EE127254AEEE9.key
new file mode 100644
index 000000000..fd50945f6
--- /dev/null
+++ b/tests/openpgp/privkeys/A1ABFD89944870D04039D40C218EE127254AEEE9.key
@@ -0,0 +1,8 @@
+Created: 20240423T151629
+Key: (private-key (ecc (curve brainpoolP512r1)(q
+ #04681C6E8D70DEF5DED6097972643ECEA2538EA6CA3F9F87DC1E4B27D37CFDA3296A
+ F129D5EEB331D836EC7A215CC1CF4103184D21F7AA184C1EE9C338BAB19147438F4C30
+ 705E610E142C29F712C913D01132D862F5B65D7FADE1E145B4D9FB08E8B281DA94139D
+ 11D3FDC0A55B85D1AFF0DDFE052779115A72DE03BB098E03DD#)(d
+ #32C7C4790D709ADC404D85A791FB119C327FA8E88F835BE8C4076E250ABAD2858237
+ 6496ACC61573108A9518789BCF13FA7D33D6D4324D962895F6554DD6A129#)))
diff --git a/tests/openpgp/privkeys/A87B85D88DB8B2B5A62A9958C8F2878F49605D09.key b/tests/openpgp/privkeys/A87B85D88DB8B2B5A62A9958C8F2878F49605D09.key
new file mode 100644
index 000000000..b3898c99d
--- /dev/null
+++ b/tests/openpgp/privkeys/A87B85D88DB8B2B5A62A9958C8F2878F49605D09.key
@@ -0,0 +1,5 @@
+Created: 20240423T121404
+Key: (private-key (ecc (curve brainpoolP256r1)(q
+ #0431801CBE11209D65705872CDBED8E8718ADEBC8F4F44D69A71244F883EFFF54654
+ 7B31DCC0BC0D1BF5DE953DBE11A753DC3B9BD39DB955DCA30C1F2535F59CB4#)(d
+ #6418FBDFBCE6B9389971AF84468050995EC79FBCF42BE6AB5A5F96BF4A8000BE#)))
diff --git a/tests/openpgp/privkeys/D54E9B75C3541D95C45E430DAC9645E9FB62C668.key b/tests/openpgp/privkeys/D54E9B75C3541D95C45E430DAC9645E9FB62C668.key
new file mode 100644
index 000000000..835045265
--- /dev/null
+++ b/tests/openpgp/privkeys/D54E9B75C3541D95C45E430DAC9645E9FB62C668.key
@@ -0,0 +1,5 @@
+Created: 20240423T121447
+Key: (private-key (ecc (curve brainpoolP256r1)(q
+ #049A80F4C7499AE14056F51D49D9899D7B73DB1BE7EE62EEEAA477C7A1F96F55F118
+ CC0C0F89FF23E636C4F27AC51F6C571802606689A9FD9940D717EEDB0702ED#)(d
+ #409D4A1B8E6B0C8CB466BCD8D6C0B1D832A73FD8241C6EA65F01EA2D3BFFE1A4#)))
diff --git a/tests/openpgp/privkeys/EAD718DCE3D2F33A20BFC8BA617844DEF3FFAF3A.key b/tests/openpgp/privkeys/EAD718DCE3D2F33A20BFC8BA617844DEF3FFAF3A.key
new file mode 100644
index 000000000..bc1e024a4
--- /dev/null
+++ b/tests/openpgp/privkeys/EAD718DCE3D2F33A20BFC8BA617844DEF3FFAF3A.key
@@ -0,0 +1,104 @@
+Created: 20240423T121447
+Key: (private-key (kyber768 (p #46A3CE8B663A26CB4AFC5A5ED35A405AD3B3A66
+ B47A859A458AC6F1BF3518575209D91CD2311777DEA1A14D196E50388AC7A2E2599221
+ FF2697F5129FE6A5FC6B89AB1667AAD7179A99111CBD874E3D18699DBC03A601E82E16
+ 0547C2AA2962972B5AFD1EA74ECE3C67599693448760CB383FF044F8F716C37493865B
+ 419872B60173360AC74CCC31035C23A380D7660B536105ABA2CB2598F2434631C733E4
+ 952398A182C799C732BE69FBCC94796781AFA137D27080FCE314B9590A8F48261A3FC7
+ CB94A2094535E9712A29E02BA50C3BDA4D8B76F68BEE95907BB72A6913829245C7B9EB
+ C86778994FFC183EB423B9C48AF1A3A149621868CB984A67CAA8A8987DA6983337418E
+ 50217F0780C595BA39845A724A0400DA35FD0E308D2250F83723B009D41818C38156B9
+ 6C5C6ACDBD592BED95D67E939486A16565005EF105D55D7983E534F1E8767814B5C141
+ 867A0AC7CBE297CD39885BF837C9602BFD4344158F304B1BBC91F832B291214AAA0BE4
+ 7661168F16D9229A1A4B53B0BEC7C657729D0D6365A51C4A7919CC49B7D88DA619A5B4
+ 0FB082A33C26BE6100C5D183D2A72634AC46D314825B190B4AAF27573844E0CCCB21C6
+ 69513A5279AC0B5F1588D48C01167478E940BC62247A772065E80F339B708651740B8D
+ B2955F3BA78F50C2E5C645F995BCBCCFB466E0390DF2A595B69193FF3ADF1422D41A08
+ 33940355DDC48DD93960B0A4D9681A5BD54234DB94B24C44B2EFC9A90731978B9B4E2D
+ 12E4C253A58A947651658FA949A267323A79C4875488A7F9C0D22EB743EF6A39EE30E3
+ 423B6911677827BC3C2001154027381843E4C58B0B43B59EF317DEC146A27817664990
+ 2DC68245406C4835A56BB0174A30C50CD59598F873A979A6F15612B5044798C1AAEEB5
+ 40883A33766BB274A1B390BCB8AFC0C9C11A14A4D5568F237A9EC3232AA782BEFE6BBF
+ 2F8385B5C5E5133232907BAF963789F24B3EE989DC78C3AD0F59045B7ACF66340FDA65
+ 5442A19AADA6697175013FB0668C45153B61EF722C2CBF571AE3810801A3B1567CD797
+ 49C33955A4B413DCC989F24EA4AAFE80A2C3A24925574E422C11BA8735E9C96E9929DE
+ 9D263DA7ACD08A80A5BACA156C11161B3A7D0C56BADE20385C51A30941C16D4849EFCC
+ 89D479ADC6A9C06FC383AD900BA03750EC3BA81F97916D90FBC6582DE92CDF3E7C7DF5
+ C9621CA99CCA47D72A171C8C945E6891E421C87CAF523776874A3C441C2A824704ACDC
+ 5D384F65958D47A46DEB079A1B895F0D37D9CF36F6622A1D19109FB1933B52B4D9D271
+ 83496547B1794CDC675437C90C0C36C38F12A23D70B6D83540CC395BBA1B1885A3B8EB
+ 582D0A5C294E0048E7A94BC1A2E17F89DA710887C3B8241DB4D0891564D939932C047F
+ B28A2180B878F5BC142EB152E49411C8C1AB33A568E401BB827AD4E7B7B04A89236C40
+ 0B233006A89A77CC362F5F63D57715A85EC08BB175348FA305876CD3A282547A3502FD
+ 14AB85004BEB50EB6C8B2F5E217FF086C86473F1B34672FB9CBFA63BE1DB8A13DC76A9
+ CB30C6FEA0A25C828D8A6B6FB725569B888692B46DAEC53332A9AF9B61B1FC92851A02
+ 33384A9735B86F076A05F44C97DA491C69AAD8622CEC7530C0BD13CA98B91E58BC1229
+ 7CA2C2B37CDCE455EB5#)(s #0679BE74A86EB9B647B637B40EE3A26C4B3EA6D57024B
+ 56827C02017071E98052BCF0A4CA78036A672849D39975C06173FDAC73E6A732ACCB2E
+ FF887D77BA3D8E56A34643438A92ABCA1332F8C01AAB537AD721AD8E46DBB6742FE7C6
+ 9BDDA9D6FC3139081839AD702979018E41C60BCA2650C682DD003BD05C8AE3622761EB
+ 775BFCB89EEC67A12211FC67700DD0C62D499C0E75A7084E618F4B141461ACA2387380
+ 98947E8EBCDC86440426A8DB0F5737724218E75084E5C0A8CC96BFD842FEE6712B12B9
+ A1EF5A506577C7DDC1CD7905F7A853588452CD4A17707546DB6039C90AB82FA17026AD
+ B035C329AF7309BC560B3706919A2770337E008C14936F30900FCF541FAE7050A49A6B
+ CA80DDF0695D3A885EBD1AAA80009C9F06D72D10FAD4A1A8908832E151B668575678CC
+ FB67A4A34B6489CE3CC5865A5AD290853B12668401076A6355837359C27229B748E400
+ 2BC24506E42DA05DE5562107138DDBC13B99421C784CACA035766C040C9B8A5925666A
+ C218E7E1B300D3B5CC0D826F9E36871C860E7497804652F12D65BC2610FD5A3BD6AC4B
+ B0C38262C719EF66868CD4245F67C623CD5B7FAD28674E21564826A0D5B95E8125B03C
+ 9C99EB4ADD7B74AC16BC1CCF613453143F935A0AB34222FB6C079A4C372AC1632770FA
+ 8B30FA9B6399DCBAFA67723C760378DB081FCD8C825B18F45721550FBB86E747C1E7B0
+ 2A68A3796266C11FC3444D38CBE0B72D9D885C5C77F0AE48A038B7EE2CA97B1C126333
+ A9130DA1BDB94C6317660655C0F09D8C130A72ACA784176D4ADE57161B6801333A11C3
+ E119CA2757BBEB08B5112AF06965452C1CD797165E51209BF04C6F90B3373F2CC9E02B
+ 1A1594D4895B4C18C4774D222C0505BABEA18216642E7B7CBA796ACBC5C82354152ECB
+ 765B2D56F10E925F57BC45ED1421638AF87B8C125A74473B78D026295F341315D7A1A6
+ B1B2EA946817222338E8342C093CD5FC393F1A92931C92678766D2A465E5A4C5A90C35
+ 758C20BE0AAB6A8E0752250607DCB0603018CF4F07390AA328CF2C8364AA63FB25C815
+ 8A37A69716E743FE47303B498968E095226E59A24E4761676060504C5B0ABB5F0548DB
+ 3650854FB1AB66061426C3DBB177EFB1C0ADED3C142405C0CF7756A50776EBB76D4882
+ 44AD008ABB3766594071C38A835CC477C01A169E0C6AFA43AB81037A6E9606CB234590
+ 77DA8CC72F7C98732A56D557201B2C46E29460C37A3BD808C0E7BD346951292BFB51BE
+ 81C3A384B3277912CCCB521CB0BC81C76565CA65DF354680A1A243915655CA311374C4
+ E998977DB488A79B63864C440C8EC0E59F9C169A181AC48CCED41722FBC16D8DC22179
+ C3988237EF7E75467DC5AAC434AD6498690F28407A46015491BD0EC779AB079A9C0194
+ 5C0B3DF39036F3A0BFE80CB1DF898B24A1354B0BE35560EA0DBB12C30935BF047ACF36
+ 1CAC11B8D3C1D1C624B16F76EC3A80783C3742E195B031943C9A9A21702399BF9A4A25
+ 848A8951DFB047FCBC4ADBDD79C73EC27F6348281072BD670831BA5B414945CC12423F
+ 2D5C2CF34C9F4C841AD620B665623CD74CCFC162E0AD04BD0646F581C372EBA355DA8A
+ 64CA92BE1056B2CFA7B46A3CE8B663A26CB4AFC5A5ED35A405AD3B3A66B47A859A458A
+ C6F1BF3518575209D91CD2311777DEA1A14D196E50388AC7A2E2599221FF2697F5129F
+ E6A5FC6B89AB1667AAD7179A99111CBD874E3D18699DBC03A601E82E160547C2AA2962
+ 972B5AFD1EA74ECE3C67599693448760CB383FF044F8F716C37493865B419872B60173
+ 360AC74CCC31035C23A380D7660B536105ABA2CB2598F2434631C733E4952398A182C7
+ 99C732BE69FBCC94796781AFA137D27080FCE314B9590A8F48261A3FC7CB94A2094535
+ E9712A29E02BA50C3BDA4D8B76F68BEE95907BB72A6913829245C7B9EBC86778994FFC
+ 183EB423B9C48AF1A3A149621868CB984A67CAA8A8987DA6983337418E50217F0780C5
+ 95BA39845A724A0400DA35FD0E308D2250F83723B009D41818C38156B96C5C6ACDBD59
+ 2BED95D67E939486A16565005EF105D55D7983E534F1E8767814B5C141867A0AC7CBE2
+ 97CD39885BF837C9602BFD4344158F304B1BBC91F832B291214AAA0BE47661168F16D9
+ 229A1A4B53B0BEC7C657729D0D6365A51C4A7919CC49B7D88DA619A5B40FB082A33C26
+ BE6100C5D183D2A72634AC46D314825B190B4AAF27573844E0CCCB21C669513A5279AC
+ 0B5F1588D48C01167478E940BC62247A772065E80F339B708651740B8DB2955F3BA78F
+ 50C2E5C645F995BCBCCFB466E0390DF2A595B69193FF3ADF1422D41A0833940355DDC4
+ 8DD93960B0A4D9681A5BD54234DB94B24C44B2EFC9A90731978B9B4E2D12E4C253A58A
+ 947651658FA949A267323A79C4875488A7F9C0D22EB743EF6A39EE30E3423B69116778
+ 27BC3C2001154027381843E4C58B0B43B59EF317DEC146A278176649902DC68245406C
+ 4835A56BB0174A30C50CD59598F873A979A6F15612B5044798C1AAEEB540883A33766B
+ B274A1B390BCB8AFC0C9C11A14A4D5568F237A9EC3232AA782BEFE6BBF2F8385B5C5E5
+ 133232907BAF963789F24B3EE989DC78C3AD0F59045B7ACF66340FDA655442A19AADA6
+ 697175013FB0668C45153B61EF722C2CBF571AE3810801A3B1567CD79749C33955A4B4
+ 13DCC989F24EA4AAFE80A2C3A24925574E422C11BA8735E9C96E9929DE9D263DA7ACD0
+ 8A80A5BACA156C11161B3A7D0C56BADE20385C51A30941C16D4849EFCC89D479ADC6A9
+ C06FC383AD900BA03750EC3BA81F97916D90FBC6582DE92CDF3E7C7DF5C9621CA99CCA
+ 47D72A171C8C945E6891E421C87CAF523776874A3C441C2A824704ACDC5D384F65958D
+ 47A46DEB079A1B895F0D37D9CF36F6622A1D19109FB1933B52B4D9D27183496547B179
+ 4CDC675437C90C0C36C38F12A23D70B6D83540CC395BBA1B1885A3B8EB582D0A5C294E
+ 0048E7A94BC1A2E17F89DA710887C3B8241DB4D0891564D939932C047FB28A2180B878
+ F5BC142EB152E49411C8C1AB33A568E401BB827AD4E7B7B04A89236C400B233006A89A
+ 77CC362F5F63D57715A85EC08BB175348FA305876CD3A282547A3502FD14AB85004BEB
+ 50EB6C8B2F5E217FF086C86473F1B34672FB9CBFA63BE1DB8A13DC76A9CB30C6FEA0A2
+ 5C828D8A6B6FB725569B888692B46DAEC53332A9AF9B61B1FC92851A0233384A9735B8
+ 6F076A05F44C97DA491C69AAD8622CEC7530C0BD13CA98B91E58BC12297CA2C2B37CDC
+ E455EB5F45B962B404EC9D9ADA018C9243AA1C5DB1F39D31391BF62AB800F85D6BAB5C
+ 503A078F0E1B47A9475FF068E159DED6A0D5C8291D6054524B0A8064D9DA18A39#)))
diff --git a/tests/openpgp/samplekeys/README b/tests/openpgp/samplekeys/README
index 8e8b598b3..88361ee30 100644
--- a/tests/openpgp/samplekeys/README
+++ b/tests/openpgp/samplekeys/README
@@ -1,38 +1,41 @@
no-creation-time.gpg A key with a zero creation time.
ecc-sample-1-pub.asc A NIST P-256 ECC sample key.
ecc-sample-1-sec.asc Ditto, but the secret keyblock.
ecc-sample-2-pub.asc A NIST P-384 ECC sample key.
ecc-sample-2-sec.asc Ditto, but the secret keyblock.
ecc-sample-3-pub.asc A NIST P-521 ECC sample key.
ecc-sample-3-sec.asc Ditto, but the secret keyblock.
eddsa-sample-1-pub.asc An Ed25519 sample key.
eddsa-sample-1-sec.asc Ditto, but as protected secret keyblock.
dda252ebb8ebe1af-1.asc rsa4096 key 1
dda252ebb8ebe1af-2.asc rsa4096 key 2 with a long keyid collision.
whats-new-in-2.1.asc Collection of sample keys.
e2e-p256-1-clr.asc Google End-end-End test key (no protection)
e2e-p256-1-prt.asc Ditto, but protected with passphrase "a".
E657FB607BB4F21C90BB6651BC067AF28BC90111.asc Key with subkeys (no protection)
pgp-desktop-skr.asc Secret key with subkeys w/o signatures
rsa-rsa-sample-1.asc RSA+RSA sample key (no passphrase)
ed25519-cv25519-sample-1.asc Ed25519+CV25519 sample key (no passphrase)
ed25519-cv25519-sample-2.asc Ed25519+CV25519 sample key (no passphrase)
silent-running.asc Collection of sample secret keys (no passphrases)
rsa-primary-auth-only.pub.asc rsa2408 primary only, usage: cert,auth
rsa-primary-auth-only.sec.asc Ditto but the secret keyblock.
v5-sample-1-pub.asc A version 5 key (ed25519/cert,sign,v5+cv25519/v5)
v5-sample-1-sec.asc Ditto, but the secret keyblock (unprotected).
pqc-sample-1.key.asc ky768_cv25519 public key. [*]
pqc-sample-2.key.asc ky1024_cv448 public key. [*]
+pqc-sample-3.key.asc ky768_bp256 public key. [*]
+pqc-sample-4.key.asc ky1024_bp384 public key. [*]
+pqc-sample-5.key.asc ky1024_bp384 public key. [*]
Notes:
- A [*] marks public keys with their private parts in ../privkeys.
- pgp-desktop-skr.asc is a secret keyblock without the uid and subkey
binding signatures. When exporting a secret key from PGP desktop
such a file is created which is then directly followed by a separate
armored public key block. To create such a sample concatenate
pgp-desktop-skr.asc and E657FB607BB4F21C90BB6651BC067AF28BC90111.asc
- ecc-sample-2-sec.asc and ecc-sample-3-sec.asc do not have and
binding signatures either. ecc-sample-1-sec.asc has them, though.
diff --git a/tests/openpgp/samplekeys/pqc-sample-3.key.asc b/tests/openpgp/samplekeys/pqc-sample-3.key.asc
new file mode 100644
index 000000000..78f9a8f75
--- /dev/null
+++ b/tests/openpgp/samplekeys/pqc-sample-3.key.asc
@@ -0,0 +1,48 @@
+pub brainpoolP256r1 2024-04-23 [SC]
+ 9F7DCCABC11EFE248F48CECD6F6570B33D05BDF8
+ Keygrip = A87B85D88DB8B2B5A62A9958C8F2878F49605D09
+uid pqc-sample-3
+sub ky768_bp256 2024-04-23 [E]
+ B4707EA9BF0FF29F65190D779BE6064181208C59988A80BCD2B2177A9BBDFE22
+ Keygrip = D54E9B75C3541D95C45E430DAC9645E9FB62C668,
+ EAD718DCE3D2F33A20BFC8BA617844DEF3FFAF3A
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+x4ISI2U8yiITrTcSWAKs9A==
+=6QFw
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/openpgp/samplekeys/pqc-sample-4.key.asc b/tests/openpgp/samplekeys/pqc-sample-4.key.asc
new file mode 100644
index 000000000..e913f78da
--- /dev/null
+++ b/tests/openpgp/samplekeys/pqc-sample-4.key.asc
@@ -0,0 +1,58 @@
+pub brainpoolP384r1 2024-04-23 [SC]
+ A8237D19988A8255E70D2566EC280D0923FB2DF7
+ Keygrip = 702F599E35E6E0BE68E6FDF25D887229D42780F7
+uid pqc-sample-4
+sub ky1024_bp384 2024-04-23 [E]
+ FBCD76AC9908E094D22CAE2564C9CB50EC69AACF0E3AEC91AA115CE43C71DC81
+ Keygrip = 19C87B74004E9839F3D56992B0A9943BF90B56F7,
+ 7C31A4A632A49C4E8B1C8CBA53976ADFF714510F
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=Ztpi
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/openpgp/samplekeys/pqc-sample-5.key.asc b/tests/openpgp/samplekeys/pqc-sample-5.key.asc
new file mode 100644
index 000000000..6da585015
--- /dev/null
+++ b/tests/openpgp/samplekeys/pqc-sample-5.key.asc
@@ -0,0 +1,61 @@
+pub brainpoolP512r1 2024-04-23 [SC]
+ 7B3986A550E5DB116054B4B42CBE157D37FDEC1D
+ Keygrip = A1ABFD89944870D04039D40C218EE127254AEEE9
+uid pqc-sample-5
+sub ky1024_bp512 2024-04-23 [E]
+ CA44B5ED43D33290398C5D0983EE5EE4721EC5C680AEA12C2282451D3ED65F4F
+ Keygrip = 513906BEA5A40F25C9D6EBBCEF62D0784E7235A5,
+ 6EC551A7895031EE4543A1C789E16E6A6C229CFC
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=qQEJ
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/openpgp/samplemsgs/pqc-sample-3.enc.asc b/tests/openpgp/samplemsgs/pqc-sample-3.enc.asc
new file mode 100644
index 000000000..6ceed5f6b
--- /dev/null
+++ b/tests/openpgp/samplemsgs/pqc-sample-3.enc.asc
@@ -0,0 +1,32 @@
+-----BEGIN PGP MESSAGE-----
+
+hQS7A7Rwfqm/D/KfHQIDBHU3DvDIzFvwqYhmBHaN2qSe8cDi8rsj8Q4aJkioD9W/
+RJwG2ca+WWpOeBt1LBrZRE2+e1303lPxbWioK/osn+0AAARAZv6eblIHDvd1SgKP
+Ta+5aYdnfSSkUVwes2RrE3g9sa1LbkHACvpyYaA1ZsKjnueqw9q0tSr3IiSQ1Pnb
+yHbokFafgSViMJPbWtWv0d+Nr8piFa8bdNVCtTfwntnHqqHfsMhdBwoH8feq028K
+yqC2kDJq/f22puO3ANOp8cwzuVGRaalDToY7U1umNV1xrv70iyOL/ZRxYIgltV0a
+ZlxZ1xLC4vRcfcczRI/N3+A9w/rBinvxw2d5GYYjRlpjw9xFU6YskbuG3XExus+C
+F0WcJ0DMduSpFpCELuqnD2RuDQKd01IHajzkLuRcBrglUC49+IBNJVNtXkHu3TVH
+OllgHFMp2GaD28uil50oQYYj89Ok7JNir17aUChGNmKVaEkheOiDgeLQKJRpRwu+
+fL4LJYdI2sXnxSUv2uO7qUAvdk5xX5L1QCe4keAF//T14iSt9q00MX+wRfsmP6WC
+nhK7DJJwjN40RcyBrSU0IUe4oR+AIzWNVJRbb1QMA6mhKDICMGknOnKAgfjPYbhp
+kw579C6MS4XxwvwyaZaUQy3loKfOwN7ahp+Hdzn9ojpzDH8WHdgl/UZjj8A6+xC0
+626exdhGm0HSbsIW5ZUa5CZfbwlgsmRDlzovymtJl6Qvqp0845m4H969wwOxM3yC
+8NTUn2yNiZA1WBlM9hHvypU6l8M+cmvLd8aP2v54e47039f+dmeZfnF4N+7C59kA
+YrUyiFmYQEZfNLQAJ4cnggLqQIkS39aYjNVDitw3xhQw9J5uaoMN819pSI4w9rp+
+20JqzRszH7dUVsIFsAwB8kc6eIuJun7sd9Z9a16vRzSP39mM4xMnjyTF0EUSG849
+7n7Kwwgi92aA+VgYCIYc4aKyBeElMTLMdCS2Zlwz3/mMEAiDn9xLE3i0anZ4AoYj
+my1A9lmOB0Ms5F6NKc08riz8/ONerbqO6ArSDo1icrARWtzRsozarPdHTE29Aq9C
+kHYQLJ1aCnUs/f3KLbJEjReXNFimR1L9jsxuof82PeJO9LUAsdUTmkfdh0Ya0gjl
+jaYMdWm7bhoOjoFWPrhBfSlF5D7CCpiFyblio0SrE4FkCawDelhQG12HfcXi/UhF
+Y10g/xnvK86ntus1Gmk/M8BLgKQq3EzWgYvlS/+fI8oNPIxZWyZjW71TAkvjxR8L
+ctnh9rvCQtq38d0lQslZUvmgFZOdPK3y3H3KOZP7vldCW4giI8Dv9fgqqLK/crlT
+WQWgtI0KIFtFeKreLupeMOFRW/cxdZ8Hr5cO8nJkQXGkc7H0Fm2b4IJl3g6h+iLe
+qiw+aEFbNE0j80WV2skRoS+YhhaESoP+Q9S6O+o0g6M9hZpGszLGN7RgUScTq9oV
+N0zMUJ1phrVR9q0DwIaxm9bnuswO/qpmkIOcO/w/eIx49HAwlzSf8em34laGJNBC
+hofsGUNKy/8tNikiJClvZU9s4kwJKOHU/UlwTZyyqHewonFrqnuNxTWPcKe+3gP1
+sxTzLo9nqC1SsWIwn53UeAEJAhDorI7/L0xtmEijEyFL73ztomomHYQkCfh1i9+z
+9z0pzPXAFv8mWqKhj/jWOlQoGSO+o3yOcsfCcHG0fRwrCi88Gqe1r5AcWEjlm3Ru
+BfsI2CQsGk6vslxsAAUx5HMajVohSl+fb4ptpPoQi4UlafSJPCxJig==
+=Cv77
+-----END PGP MESSAGE-----
diff --git a/tests/openpgp/samplemsgs/pqc-sample-4.enc.asc b/tests/openpgp/samplemsgs/pqc-sample-4.enc.asc
new file mode 100644
index 000000000..f083a1248
--- /dev/null
+++ b/tests/openpgp/samplemsgs/pqc-sample-4.enc.asc
@@ -0,0 +1,43 @@
+-----BEGIN PGP MESSAGE-----
+
+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
+se4uGUbzOJfEhVQ7
+=rlF7
+-----END PGP MESSAGE-----
diff --git a/tests/openpgp/samplemsgs/pqc-sample-5.enc.asc b/tests/openpgp/samplemsgs/pqc-sample-5.enc.asc
new file mode 100644
index 000000000..eb20f9791
--- /dev/null
+++ b/tests/openpgp/samplemsgs/pqc-sample-5.enc.asc
@@ -0,0 +1,45 @@
+-----BEGIN PGP MESSAGE-----
+
+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
+MQHiPA1Y6KsYPhsQYg==
+=aAoH
+-----END PGP MESSAGE-----