Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F26446067
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
138 KB
Subscribers
None
View Options
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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>
#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 <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#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))
{
buf[buflen++] = 0x80 | (value >> 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 <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#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 <https://www.gnu.org/licenses/>.
# 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-----
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 17, 12:26 AM (12 h, 41 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
38/6d/b7fc087c0017dc4a4f1a1a6e06fc
Attached To
rG GnuPG
Event Timeline
Log In to Comment