Hello, In the task T6755, we introduced KEM API. ML-KEM is added.
Today, I'd like to propose adding ECC KEM implementation in the API. The intention of mine is use in gpg-agent to support PQC (task T7014). Attached is a patch adding ECC KEM for X25519. --
>From 5da6c63fed34f6027a9531780252f0f54087c379 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka <[email protected]> Date: Thu, 28 Mar 2024 11:52:55 +0900 Subject: [PATCH] cipher:kem: Add ECC KEM for X25519. * cipher/Makefile.am (libcipher_la_SOURCES): Add kem-ecc.{c,h}. * cipher/kem-ecc.c: New. * cipher/kem-ecc.h: New. * cipher/kem.c (_gcry_kem_keypair): Dispatch to _gcry_ecc_raw_keypair. (_gcry_kem_encap): Dispatch to _gcry_ecc_raw_encap, _gcry_ecc_dhkem_encap, _gcry_openpgp_kem_encap, and _gcry_cms_kem_encap. (_gcry_kem_decap): Dispatch to _gcry_ecc_raw_decap, _gcry_ecc_dhkem_decap, _gcry_openpgp_kem_decap, and _gcry_cms_kem_decap. * src/gcrypt.h.in: Add constants for ECC KEM. * tests/t-kem.c (test_kem_raw_x25519, test_kem_dhkem_x25519) (test_kem_openpgp_x25519, test_kem_cms_x25519): New. (check_kem, main): Add tests for ECC KEM. -- GnuPG-bug-id: 6755 Signed-off-by: NIIBE Yutaka <[email protected]> --- cipher/Makefile.am | 2 +- cipher/kem-ecc.c | 445 +++++++++++++++++++++++++++++++++++++++++++++ cipher/kem-ecc.h | 47 +++++ cipher/kem.c | 51 ++++-- src/gcrypt.h.in | 31 +++- tests/t-kem.c | 337 +++++++++++++++++++++++++++++++++- 6 files changed, 890 insertions(+), 23 deletions(-) create mode 100644 cipher/kem-ecc.c create mode 100644 cipher/kem-ecc.h diff --git a/cipher/Makefile.am b/cipher/Makefile.am index 760bdce5..6a533a25 100644 --- a/cipher/Makefile.am +++ b/cipher/Makefile.am @@ -60,7 +60,7 @@ libcipher_la_SOURCES = \ mac.c mac-internal.h \ mac-hmac.c mac-cmac.c mac-gmac.c mac-poly1305.c \ poly1305.c poly1305-internal.h \ - kem.c sntrup761.c sntrup761.h kyber.c kyber.h \ + kem.c sntrup761.c sntrup761.h kyber.c kyber.h kem-ecc.c kem-ecc.h \ kdf.c kdf-internal.h \ bithelp.h \ bufhelp.h \ diff --git a/cipher/kem-ecc.c b/cipher/kem-ecc.c new file mode 100644 index 00000000..3559f7f3 --- /dev/null +++ b/cipher/kem-ecc.c @@ -0,0 +1,445 @@ +/* kem-ecc.c - Key Encapsulation Mechanism with ECC + * Copyright (C) 2024 g10 Code GmbH + * + * This file is part of Libgcrypt. + * + * Libgcrypt is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser general Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * Libgcrypt 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "g10lib.h" +#include "cipher.h" + +#include "kem-ecc.h" + +static void +ecc_tweak_bits (int curveid, unsigned char *dst, const unsigned char *src) +{ + if (curveid == GCRY_ECC_CURVE25519) + { + memcpy (dst, src, 32); + dst[0] &= 0xf8; + dst[31] &= 0x7f; + dst[31] |= 0x40; + } + else if (curveid == GCRY_ECC_CURVE448) + { + memcpy (dst, src, 56); + dst[0] &= 0xfc; + dst[55] |= 0x80; + } +} + +static gpg_err_code_t +ecc_mul_point (int curveid, unsigned char *result, + const unsigned char *scalar, const unsigned char *point) +{ + unsigned char scalar_fixed[56]; /* Enough space for X448 */ + + /* Tweak the bits, so that it can be compatible to X25519/X448 + functions. */ + ecc_tweak_bits (curveid, scalar_fixed, scalar); + return _gcry_ecc_mul_point (curveid, result, scalar_fixed, point); +} + + +/* The generator of Curve25519. */ +static const unsigned char curve25519_G[32] = { 0x09 }; +/* The generator of Curve448. */ +static const unsigned char curve448_G[56] = { 0x05 }; + +gpg_err_code_t +_gcry_ecc_raw_keypair (int curveid, void *pubkey, void *seckey) +{ + unsigned char *seckey_byte = seckey; + unsigned int len = _gcry_ecc_get_algo_keylen (curveid); + const unsigned char *G; + + _gcry_randomize (seckey, len, GCRY_STRONG_RANDOM); + + /* Existing ECC applications with libgcrypt (like gpg-agent in + GnuPG) assumes that scalar is tweaked at key generation time. + For the possible use case where generated key with this routine + may be used with those, we put compatibile behavior here. */ + ecc_tweak_bits (curveid, seckey_byte, seckey_byte); + + if (curveid == GCRY_ECC_CURVE25519) + G = curve25519_G; + else + G = curve448_G; + + return ecc_mul_point (curveid, pubkey, seckey_byte, G); +} + +gpg_err_code_t +_gcry_ecc_raw_encap (int curveid, const void *pubkey, void *ciphertext, + void *shared) +{ + gpg_err_code_t err; + unsigned char seckey_ephemeral[32]; + void *pubkey_ephemeral = ciphertext; + + err = _gcry_ecc_raw_keypair (curveid, pubkey_ephemeral, seckey_ephemeral); + if (err) + return err; + + /* Do ECDH. */ + return ecc_mul_point (curveid, shared, seckey_ephemeral, pubkey); +} + +gpg_err_code_t +_gcry_ecc_raw_decap (int curveid, const void *seckey, const void *ciphertext, + void *shared) +{ + /* Do ECDH. */ + return ecc_mul_point (curveid, shared, seckey, ciphertext); +} + + +static gpg_err_code_t +ecc_dhkem_kdf (const unsigned char *ecdh, const unsigned char *ciphertext, + const unsigned char *pubkey, void *shared) +{ + gpg_err_code_t err; + unsigned char *p; + unsigned char labeled_ikm[7+5+7+32]; + unsigned char labeled_info[2+7+5+13+32+32]; + gcry_kdf_hd_t hd; + unsigned long param[1] = { 32 }; /* output-len */ + + p = labeled_ikm; + memcpy (p, "HPKE-v1", 7); + p += 7; + memcpy (p, "KEM\x00\x20", 5); /* suite_id */ + p += 5; + memcpy (p, "eae_prk", 7); + p += 7; + memcpy (p, ecdh, 32); + + p = labeled_info; + memcpy (p, "\x00\x20", 2); /* length */ + p += 2; + memcpy (p, "HPKE-v1", 7); + p += 7; + memcpy (p, "KEM\x00\x20", 5); /* suite_id */ + p += 5; + memcpy (p, "shared_secret", 13); + p += 13; + /* kem_context */ + memcpy (p, ciphertext, 32); + p += 32; + memcpy (p, pubkey, 32); + p += 32; + + err = _gcry_kdf_open (&hd, GCRY_KDF_HKDF, GCRY_MAC_HMAC_SHA256, param, 1, + labeled_ikm, sizeof (labeled_ikm), + NULL, 0, NULL, 0, labeled_info, sizeof (labeled_info)); + if (err) + return err; + + err = _gcry_kdf_compute (hd, NULL); + if (!err) + err = _gcry_kdf_final (hd, 32, shared); + _gcry_kdf_close (hd); + return err; +} + + +gpg_err_code_t +_gcry_ecc_dhkem_encap (int algo, const void *pubkey, void *ciphertext, + void *shared) +{ + gpg_err_code_t err; + int curveid; + unsigned char ecdh[32]; + unsigned char seckey_ephemeral[32]; + void *pubkey_ephemeral = ciphertext; + + if (algo != GCRY_KEM_DHKEM25519) + return GPG_ERR_UNKNOWN_ALGORITHM; + + /* From here, it's only for the DHKEM(X25519, HKDF-SHA256). */ + curveid = GCRY_ECC_CURVE25519; + + err = _gcry_ecc_raw_keypair (curveid, pubkey_ephemeral, seckey_ephemeral); + if (err) + return err; + + /* Do ECDH. */ + err = ecc_mul_point (curveid, ecdh, seckey_ephemeral, pubkey); + if (err) + return err; + + return ecc_dhkem_kdf (ecdh, ciphertext, pubkey, shared); +} + +gpg_err_code_t +_gcry_ecc_dhkem_decap (int algo, const void *seckey, const void *ciphertext, + void *shared, const void *optional) +{ + gpg_err_code_t err; + int curveid; + unsigned char ecdh[32]; + unsigned char pubkey_computed[32]; + const unsigned char *pubkey; + + if (algo != GCRY_KEM_DHKEM25519) + return GPG_ERR_UNKNOWN_ALGORITHM; + + /* From here, it's only for the DHKEM(X25519, HKDF-SHA256). */ + curveid = GCRY_ECC_CURVE25519; + + if (optional) + pubkey = optional; + else + { + err = ecc_mul_point (curveid, pubkey_computed, seckey, curve25519_G); + if (err) + return err; + + pubkey = pubkey_computed; + } + + /* Do ECDH. */ + err = ecc_mul_point (curveid, ecdh, seckey, ciphertext); + if (err) + return err; + + return ecc_dhkem_kdf (ecdh, ciphertext, pubkey, shared); +} + + +static gpg_err_code_t +openpgp_kem_kdf (const unsigned char *ecdh, size_t ecdh_len, + const unsigned char *kdf_param, void *shared) +{ + gpg_err_code_t err; + gcry_kdf_hd_t hd; + unsigned long param[1]; + int curve_oid_len; + int hash_id; + int kek_id; + size_t z_len; + size_t kdf_param_len; + + if (kdf_param == NULL) + return GPG_ERR_INV_VALUE; + + curve_oid_len = kdf_param[0]; + hash_id = kdf_param[1+curve_oid_len+3]; + kek_id = kdf_param[1+curve_oid_len+4]; + kdf_param_len = 1+curve_oid_len+5+20+20; + + err = _gcry_cipher_algo_info (kek_id, GCRYCTL_GET_KEYLEN, NULL, &z_len); + if (err) + return err; + + param[0] = z_len; + + err = _gcry_kdf_open (&hd, GCRY_KDF_ONESTEP_KDF, hash_id, param, 1, + ecdh, ecdh_len, + NULL, 0, NULL, 0, kdf_param, kdf_param_len); + if (err) + return err; + + err = _gcry_kdf_compute (hd, NULL); + if (!err) + err = _gcry_kdf_final (hd, z_len, shared); + _gcry_kdf_close (hd); + + return err; +} + +/* In OpenPGP v4, 0x40 is prepended to the native encoding of public + key. Here, PUBKEY and CIPHERTEXT are native representation sans + the prefix. */ +gpg_err_code_t +_gcry_openpgp_kem_encap (int algo, const void *pubkey, void *ciphertext, + void *shared, const void *optional) +{ + gpg_err_code_t err; + int curveid; + unsigned char ecdh[32]; + const unsigned char *kdf_param = optional; + unsigned char seckey_ephemeral[32]; + void *pubkey_ephemeral = ciphertext; + + if (algo != GCRY_KEM_OPENPGP_X25519) + return GPG_ERR_UNKNOWN_ALGORITHM; + + /* From here, it's only for the OpenPGP KEM(Curve25519, One-Step KDF). */ + curveid = GCRY_ECC_CURVE25519; + + err = _gcry_ecc_raw_keypair (curveid, pubkey_ephemeral, seckey_ephemeral); + if (err) + return err; + + /* Do ECDH. */ + err = ecc_mul_point (curveid, ecdh, seckey_ephemeral, pubkey); + if (err) + return err; + + return openpgp_kem_kdf (ecdh, sizeof (ecdh), kdf_param, shared); +} + +/* In OpenPGP v4, secret key is represented with big-endian MPI. + Here, SECKEY is native fixed size little-endian representation. + CIPHERTEXT is native representation sans the prefix 0x40. + */ +gpg_err_code_t +_gcry_openpgp_kem_decap (int algo, const void *seckey, const void *ciphertext, + void *shared, const void *optional) +{ + gpg_err_code_t err; + int curveid; + unsigned char ecdh[32]; + const unsigned char *kdf_param = optional; + + if (algo != GCRY_KEM_OPENPGP_X25519) + return GPG_ERR_UNKNOWN_ALGORITHM; + + /* From here, it's only for the OpenPGP KEM(Curve25519, One-Step KDF). */ + curveid = GCRY_ECC_CURVE25519; + + /* Do ECDH. */ + err = ecc_mul_point (curveid, ecdh, seckey, ciphertext); + if (err) + return err; + + return openpgp_kem_kdf (ecdh, sizeof (ecdh), kdf_param, shared); +} + + +static gpg_err_code_t +cms_kem_kdf (int kdf_id, int hash_id, + const unsigned char *ecdh, size_t ecdh_len, + const unsigned char *sharedinfo, void *shared) +{ + gpg_err_code_t err; + gcry_kdf_hd_t hd; + unsigned long param[1]; + unsigned int sharedinfolen; + const unsigned char *supppubinfo; + unsigned int keylen; + + /* + * ECC-CMS-SharedInfo ::= SEQUENCE { + * keyInfo AlgorithmIdentifier, + * entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, + * suppPubInfo [2] EXPLICIT OCTET STRING } + */ + if (!sharedinfo) + return GPG_ERR_INV_VALUE; + + if (sharedinfo[0] != 0x30 /* Constructed | SEQUENCE */ + || sharedinfo[1] >= 0x80) + return GPG_ERR_INV_VALUE; + + sharedinfolen = sharedinfo[1] + 2; + + /* Extract KEYLEN for keywrap from suppPubInfo. */ + supppubinfo = sharedinfo + sharedinfolen - 8; + if (supppubinfo[0] != 0xA2 /* CLASS_CONTEXT | Constructed | 2 */ + || supppubinfo[1] != 6 + || supppubinfo[2] != 0x04 /* OCTET STRING */ + || supppubinfo[3] != 4) + return GPG_ERR_INV_VALUE; + + keylen = ((((supppubinfo[4] << 24) | (supppubinfo[5] << 16) + | (supppubinfo[6] << 8) | supppubinfo[7])) + 7) / 8; + + param[0] = keylen; + + err = _gcry_kdf_open (&hd, kdf_id, hash_id, param, 1, + ecdh, ecdh_len, + NULL, 0, NULL, 0, sharedinfo, sharedinfolen); + if (err) + return err; + + err = _gcry_kdf_compute (hd, NULL); + if (!err) + err = _gcry_kdf_final (hd, keylen, shared); + _gcry_kdf_close (hd); + + return err; +} + +gpg_err_code_t +_gcry_cms_kem_encap (int algo, const void *pubkey, void *ciphertext, + void *shared, const void *optional) +{ + gpg_err_code_t err; + int curveid; + unsigned char ecdh[32]; + const unsigned char *sharedinfo = optional; + unsigned char seckey_ephemeral[32]; + void *pubkey_ephemeral = ciphertext; + int kdf_method; + + if (algo == GCRY_KEM_CMS_X25519_X963_SHA256) + kdf_method = GCRY_KDF_X963_KDF; + else if (algo == GCRY_KEM_CMS_X25519_HKDF_SHA256) + kdf_method = GCRY_KDF_HKDF; + else + return GPG_ERR_UNKNOWN_ALGORITHM; + + curveid = GCRY_ECC_CURVE25519; + + err = _gcry_ecc_raw_keypair (curveid, pubkey_ephemeral, seckey_ephemeral); + if (err) + return err; + + /* Do ECDH. */ + err = ecc_mul_point (curveid, ecdh, seckey_ephemeral, pubkey); + if (err) + return err; + + return cms_kem_kdf (kdf_method, GCRY_MD_SHA256, ecdh, sizeof (ecdh), + sharedinfo, shared); +} + +gpg_err_code_t +_gcry_cms_kem_decap (int algo, const void *seckey, const void *ciphertext, + void *shared, const void *optional) +{ + gpg_err_code_t err; + int curveid; + unsigned char ecdh[32]; + const unsigned char *sharedinfo = optional; + int kdf_method; + + if (algo == GCRY_KEM_CMS_X25519_X963_SHA256) + kdf_method = GCRY_KDF_X963_KDF; + else if (algo == GCRY_KEM_CMS_X25519_HKDF_SHA256) + kdf_method = GCRY_KDF_HKDF; + else + return GPG_ERR_UNKNOWN_ALGORITHM; + + curveid = GCRY_ECC_CURVE25519; + + /* Do ECDH. */ + err = ecc_mul_point (curveid, ecdh, seckey, ciphertext); + if (err) + return err; + + return cms_kem_kdf (kdf_method, GCRY_MD_SHA256, ecdh, sizeof (ecdh), + sharedinfo, shared); +} diff --git a/cipher/kem-ecc.h b/cipher/kem-ecc.h new file mode 100644 index 00000000..79a6925e --- /dev/null +++ b/cipher/kem-ecc.h @@ -0,0 +1,47 @@ +/* kem-ecc.h - Key Encapsulation Mechanism with ECC + * Copyright (C) 2024 g10 Code GmbH + * + * This file is part of Libgcrypt. + * + * Libgcrypt is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser general Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * Libgcrypt 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +gpg_err_code_t _gcry_ecc_raw_keypair (int algo, void *pubkey, void *seckey); +gpg_err_code_t _gcry_ecc_raw_encap (int curveid, const void *pubkey, + void *ciphertext, void *shared); +gpg_err_code_t _gcry_ecc_raw_decap (int curveid, const void *seckey, + const void *ciphertext, void *shared); + +gpg_err_code_t _gcry_ecc_dhkem_encap (int algo, const void *pubkey, + void *ciphertext, + void *shared); +gpg_err_code_t _gcry_ecc_dhkem_decap (int algo, const void *seckey, + const void *ciphertext, + void *shared, const void *optional); + +gpg_err_code_t _gcry_openpgp_kem_encap (int algo, const void *pubkey, + void *ciphertext, + void *shared, const void *optional); +gpg_err_code_t _gcry_openpgp_kem_decap (int algo, const void *seckey, + const void *ciphertext, + void *shared, const void *optional); + +gpg_err_code_t _gcry_cms_kem_encap (int algo, const void *pubkey, + void *ciphertext, + void *shared, const void *optional); +gpg_err_code_t _gcry_cms_kem_decap (int algo, const void *seckey, + const void *ciphertext, + void *shared, const void *optional); diff --git a/cipher/kem.c b/cipher/kem.c index 4511b230..c58123f9 100644 --- a/cipher/kem.c +++ b/cipher/kem.c @@ -29,6 +29,7 @@ #include "cipher.h" #include "sntrup761.h" #include "kyber.h" +#include "kem-ecc.h" /* Information about the the KEM algoithms for use by the s-expression @@ -114,9 +115,14 @@ _gcry_kem_keypair (int algo, kyber_keypair (algo, pubkey, seckey); return 0; + case GCRY_KEM_RAW_X25519: case GCRY_KEM_DHKEM25519: case GCRY_KEM_OPENPGP_X25519: - return GPG_ERR_NOT_IMPLEMENTED; + case GCRY_KEM_CMS_X25519_X963_SHA256: + case GCRY_KEM_CMS_X25519_HKDF_SHA256: + return _gcry_ecc_raw_keypair (GCRY_ECC_CURVE25519, pubkey, seckey); + default: + return GPG_ERR_UNKNOWN_ALGORITHM; } return GPG_ERR_UNKNOWN_ALGORITHM; @@ -140,7 +146,7 @@ _gcry_kem_encap (int algo, || shared_len != GCRY_KEM_SNTRUP761_SHARED_LEN) return GPG_ERR_INV_VALUE; sntrup761_enc (ciphertext, shared, pubkey, NULL, sntrup761_random); - return GPG_ERR_NO_ERROR; + return 0; case GCRY_KEM_MLKEM512: case GCRY_KEM_MLKEM768: @@ -148,16 +154,26 @@ _gcry_kem_encap (int algo, if (optional != NULL) return GPG_ERR_INV_VALUE; kyber_encap (algo, ciphertext, shared, pubkey); - return GPG_ERR_NO_ERROR; + return 0; + case GCRY_KEM_RAW_X25519: + if (optional != NULL) + return GPG_ERR_INV_VALUE; + return _gcry_ecc_raw_encap (GCRY_ECC_CURVE25519, pubkey, ciphertext, + shared); case GCRY_KEM_DHKEM25519: if (optional != NULL) return GPG_ERR_INV_VALUE; - return GPG_ERR_NOT_IMPLEMENTED; - + return _gcry_ecc_dhkem_encap (algo, pubkey, ciphertext, shared); case GCRY_KEM_OPENPGP_X25519: - return GPG_ERR_NOT_IMPLEMENTED; - + return _gcry_openpgp_kem_encap (algo, pubkey, ciphertext, shared, + optional); + case GCRY_KEM_CMS_X25519_X963_SHA256: + case GCRY_KEM_CMS_X25519_HKDF_SHA256: + return _gcry_cms_kem_encap (algo, pubkey, ciphertext, shared, + optional); + default: + return GPG_ERR_UNKNOWN_ALGORITHM; } return GPG_ERR_UNKNOWN_ALGORITHM; } @@ -180,7 +196,7 @@ _gcry_kem_decap (int algo, || shared_len != GCRY_KEM_SNTRUP761_SHARED_LEN) return GPG_ERR_INV_VALUE; sntrup761_dec (shared, ciphertext, seckey); - return GPG_ERR_NO_ERROR; + return 0; case GCRY_KEM_MLKEM512: case GCRY_KEM_MLKEM768: @@ -188,12 +204,25 @@ _gcry_kem_decap (int algo, if (optional != NULL) return GPG_ERR_INV_VALUE; kyber_decap (algo, shared, ciphertext, seckey); - return GPG_ERR_NO_ERROR; + return 0; + case GCRY_KEM_RAW_X25519: + if (optional != NULL) + return GPG_ERR_INV_VALUE; + return _gcry_ecc_raw_decap (GCRY_ECC_CURVE25519, seckey, ciphertext, + shared); case GCRY_KEM_DHKEM25519: + return _gcry_ecc_dhkem_decap (algo, seckey, ciphertext, shared, + optional); case GCRY_KEM_OPENPGP_X25519: - return GPG_ERR_NOT_IMPLEMENTED; - + return _gcry_openpgp_kem_decap (algo, seckey, ciphertext, shared, + optional); + case GCRY_KEM_CMS_X25519_X963_SHA256: + case GCRY_KEM_CMS_X25519_HKDF_SHA256: + return _gcry_cms_kem_decap (algo, seckey, ciphertext, shared, + optional); + default: + return GPG_ERR_UNKNOWN_ALGORITHM; } return GPG_ERR_UNKNOWN_ALGORITHM; } diff --git a/src/gcrypt.h.in b/src/gcrypt.h.in index a9d6b87f..0593b677 100644 --- a/src/gcrypt.h.in +++ b/src/gcrypt.h.in @@ -1714,8 +1714,15 @@ enum gcry_kem_algos GCRY_KEM_MLKEM512, /* aka Kyber512 */ GCRY_KEM_MLKEM768, /* aka Kyber768 */ GCRY_KEM_MLKEM1024, /* aka Kyber1024 */ - GCRY_KEM_DHKEM25519, /* DHKEM with X25519, HKDF, and SHA256 */ - GCRY_KEM_OPENPGP_X25519 /* For OpenPGP with Curve25519 */ + GCRY_KEM_RAW_X25519, /* keypair, encaps/decaps with Identity KDF */ + GCRY_KEM_DHKEM25519, /* DHKEM with X25519, HKDF, and SHA256 */ + GCRY_KEM_OPENPGP_X25519, /* For OpenPGP, OPTIONAL (KDF parameters) is + required, which defines hash and + outputlen (shared secret length) */ + GCRY_KEM_CMS_X25519_X963_SHA256, /* CMS with X25519, X963 and SHA256 */ + /* For CMS, OPTIONAL (sharedinfo) is required, + which defines keylen for keywrap. */ + GCRY_KEM_CMS_X25519_HKDF_SHA256, /* CMS with X25519, HKDF and SHA256 */ }; /* @@ -1730,26 +1737,40 @@ enum gcry_kem_algos #define GCRY_KEM_SNTRUP761_SECKEY_LEN 1763 #define GCRY_KEM_SNTRUP761_PUBKEY_LEN 1158 #define GCRY_KEM_SNTRUP761_ENCAPS_LEN 1039 +#define GCRY_KEM_SNTRUP761_CIPHER_LEN GCRY_KEM_SNTRUP761_ENCAPS_LEN #define GCRY_KEM_SNTRUP761_SHARED_LEN 32 #define GCRY_KEM_MLKEM512_SECKEY_LEN (2*384+2*384+32+2*32) #define GCRY_KEM_MLKEM512_PUBKEY_LEN (2*384+32) #define GCRY_KEM_MLKEM512_ENCAPS_LEN (128+2*320) +#define GCRY_KEM_MLKEM512_CIPHER_LEN GCRY_KEM_MLKEM512_ENCAPS_LEN #define GCRY_KEM_MLKEM512_SHARED_LEN 32 #define GCRY_KEM_MLKEM768_SECKEY_LEN (3*384+3*384+32+2*32) #define GCRY_KEM_MLKEM768_PUBKEY_LEN (3*384+32) #define GCRY_KEM_MLKEM768_ENCAPS_LEN (128+3*320) +#define GCRY_KEM_MLKEM768_CIPHER_LEN GCRY_KEM_MLKEM768_ENCAPS_LEN #define GCRY_KEM_MLKEM768_SHARED_LEN 32 #define GCRY_KEM_MLKEM1024_SECKEY_LEN (4*384+4*384+32+2*32) #define GCRY_KEM_MLKEM1024_PUBKEY_LEN (4*384+32) #define GCRY_KEM_MLKEM1024_ENCAPS_LEN (160+4*352) +#define GCRY_KEM_MLKEM1024_CIPHER_LEN GCRY_KEM_MLKEM1024_ENCAPS_LEN #define GCRY_KEM_MLKEM1024_SHARED_LEN 32 -#define GCRY_KEM_DHKEM25519_SECKEY_LEN 32 -#define GCRY_KEM_DHKEM25519_PUBKEY_LEN 32 -#define GCRY_KEM_DHKEM25519_ENCAPS_LEN 32 +/* For ECC, seckey, pubkey, and ciphertext is defined by the cureve. */ +#define GCRY_KEM_ECC_X25519_SECKEY_LEN 32 +#define GCRY_KEM_ECC_X25519_PUBKEY_LEN 32 +#define GCRY_KEM_ECC_X25519_ENCAPS_LEN 32 +#define GCRY_KEM_ECC_X25519_CIPHER_LEN GCRY_KEM_ECC_X25519_ENCAPS_LEN +/* And shared secret is specific to the protocol. For OpenPGP and + CMS, it is determined by the OPTIONAL argument. */ +#define GCRY_KEM_RAW_X25519_SHARED_LEN 32 + +#define GCRY_KEM_DHKEM25519_SECKEY_LEN GCRY_KEM_ECC_X25519_SECKEY_LEN +#define GCRY_KEM_DHKEM25519_PUBKEY_LEN GCRY_KEM_ECC_X25519_PUBKEY_LEN +#define GCRY_KEM_DHKEM25519_ENCAPS_LEN GCRY_KEM_ECC_X25519_ENCAPS_LEN +#define GCRY_KEM_DHKEM25519_CIPHER_LEN GCRY_KEM_DHKEM25519_ENCAPS_LEN #define GCRY_KEM_DHKEM25519_SHARED_LEN 32 /* Generate a new key pair with ALGO. */ diff --git a/tests/t-kem.c b/tests/t-kem.c index 3813d350..70f4d7ec 100644 --- a/tests/t-kem.c +++ b/tests/t-kem.c @@ -262,10 +262,279 @@ test_kem_mlkem1024 (int testno) } } -#define SELECTED_ALGO_SNTRUP761 (1 << 0) -#define SELECTED_ALGO_MLKEM512 (1 << 1) -#define SELECTED_ALGO_MLKEM768 (1 << 2) -#define SELECTED_ALGO_MLKEM1024 (1 << 3) + +static void +test_kem_raw_x25519 (int testno) +{ + gcry_error_t err; + uint8_t pubkey[GCRY_KEM_ECC_X25519_PUBKEY_LEN]; + uint8_t seckey[GCRY_KEM_ECC_X25519_SECKEY_LEN]; + uint8_t ciphertext[GCRY_KEM_ECC_X25519_ENCAPS_LEN]; + uint8_t key1[GCRY_KEM_RAW_X25519_SHARED_LEN]; + uint8_t key2[GCRY_KEM_RAW_X25519_SHARED_LEN]; + + err = gcry_kem_keypair (GCRY_KEM_RAW_X25519, + pubkey, GCRY_KEM_ECC_X25519_PUBKEY_LEN, + seckey, GCRY_KEM_ECC_X25519_SECKEY_LEN); + if (err) + { + fail ("gcry_kem_keypair %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_encap (GCRY_KEM_RAW_X25519, + pubkey, GCRY_KEM_ECC_X25519_PUBKEY_LEN, + ciphertext, GCRY_KEM_ECC_X25519_ENCAPS_LEN, + key1, GCRY_KEM_RAW_X25519_SHARED_LEN, + NULL, 0); + if (err) + { + fail ("gcry_kem_encap %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_decap (GCRY_KEM_RAW_X25519, + seckey, GCRY_KEM_ECC_X25519_SECKEY_LEN, + ciphertext, GCRY_KEM_ECC_X25519_ENCAPS_LEN, + key2, GCRY_KEM_RAW_X25519_SHARED_LEN, + NULL, 0); + if (err) + { + fail ("gcry_kem_decap %d: %s", testno, gpg_strerror (err)); + return; + } + + if (memcmp (key1, key2, GCRY_KEM_RAW_X25519_SHARED_LEN) != 0) + { + size_t i; + + fail ("raw-x25519 test %d failed: mismatch\n", testno); + fputs ("key1:", stderr); + for (i = 0; i < GCRY_KEM_RAW_X25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key1[i]); + putc ('\n', stderr); + fputs ("key2:", stderr); + for (i = 0; i < GCRY_KEM_RAW_X25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key2[i]); + putc ('\n', stderr); + } +} + + +static void +test_kem_dhkem_x25519 (int testno) +{ + gcry_error_t err; + uint8_t pubkey[GCRY_KEM_DHKEM25519_PUBKEY_LEN]; + uint8_t seckey[GCRY_KEM_DHKEM25519_SECKEY_LEN]; + uint8_t ciphertext[GCRY_KEM_DHKEM25519_ENCAPS_LEN]; + uint8_t key1[GCRY_KEM_DHKEM25519_SHARED_LEN]; + uint8_t key2[GCRY_KEM_DHKEM25519_SHARED_LEN]; + + err = gcry_kem_keypair (GCRY_KEM_DHKEM25519, + pubkey, GCRY_KEM_DHKEM25519_PUBKEY_LEN, + seckey, GCRY_KEM_DHKEM25519_SECKEY_LEN); + if (err) + { + fail ("gcry_kem_keypair %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_encap (GCRY_KEM_DHKEM25519, + pubkey, GCRY_KEM_DHKEM25519_PUBKEY_LEN, + ciphertext, GCRY_KEM_DHKEM25519_ENCAPS_LEN, + key1, GCRY_KEM_DHKEM25519_SHARED_LEN, + NULL, 0); + if (err) + { + fail ("gcry_kem_encap %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_decap (GCRY_KEM_DHKEM25519, + seckey, GCRY_KEM_DHKEM25519_SECKEY_LEN, + ciphertext, GCRY_KEM_DHKEM25519_ENCAPS_LEN, + key2, GCRY_KEM_DHKEM25519_SHARED_LEN, + pubkey, GCRY_KEM_DHKEM25519_PUBKEY_LEN); + if (err) + { + fail ("gcry_kem_decap %d: %s", testno, gpg_strerror (err)); + return; + } + + if (memcmp (key1, key2, GCRY_KEM_DHKEM25519_SHARED_LEN) != 0) + { + size_t i; + + fail ("dhkem-x25519 test %d failed: mismatch\n", testno); + fputs ("key1:", stderr); + for (i = 0; i < GCRY_KEM_DHKEM25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key1[i]); + putc ('\n', stderr); + fputs ("key2:", stderr); + for (i = 0; i < GCRY_KEM_DHKEM25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key2[i]); + putc ('\n', stderr); + } +} + +/* In the following case, with AES128-keywrap, shared secret length is 16. */ +#define MY_KEM_OPENPGP_X25519_SHARED_LEN 16 + +static void +test_kem_openpgp_x25519 (int testno) +{ + gcry_error_t err; + uint8_t pubkey[GCRY_KEM_ECC_X25519_PUBKEY_LEN]; + uint8_t seckey[GCRY_KEM_ECC_X25519_SECKEY_LEN]; + uint8_t ciphertext[GCRY_KEM_ECC_X25519_ENCAPS_LEN]; + uint8_t key1[MY_KEM_OPENPGP_X25519_SHARED_LEN]; + uint8_t key2[MY_KEM_OPENPGP_X25519_SHARED_LEN]; + const uint8_t kdf_param[56] = { + 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, + 0x01, 0x05, 0x01, + /**/ + 0x12, + /**/ + 0x03, 0x01, 0x08 /*SHA256*/, 0x07 /* AES128 */, + /**/ + 0x41, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75, + 0x73, 0x20, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x20, 0x20, 0x20, 0x20, /* "Anonymous Sender " */ + /**/ + 0x25, 0xd4, 0x45, 0xfa, 0xc1, 0x96, 0x49, 0xc4, + 0x6a, 0x6b, 0x2f, 0xb3, 0xcd, 0xfc, 0x22, 0x19, + 0xc5, 0x53, 0xd3, 0x92 /* public key fingerprint */ + }; + + err = gcry_kem_keypair (GCRY_KEM_OPENPGP_X25519, + pubkey, GCRY_KEM_ECC_X25519_PUBKEY_LEN, + seckey, GCRY_KEM_ECC_X25519_SECKEY_LEN); + if (err) + { + fail ("gcry_kem_keypair %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_encap (GCRY_KEM_OPENPGP_X25519, + pubkey, GCRY_KEM_ECC_X25519_PUBKEY_LEN, + ciphertext, GCRY_KEM_ECC_X25519_ENCAPS_LEN, + key1, MY_KEM_OPENPGP_X25519_SHARED_LEN, + kdf_param, sizeof (kdf_param)); + if (err) + { + fail ("gcry_kem_encap %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_decap (GCRY_KEM_OPENPGP_X25519, + seckey, GCRY_KEM_ECC_X25519_SECKEY_LEN, + ciphertext, GCRY_KEM_ECC_X25519_ENCAPS_LEN, + key2, MY_KEM_OPENPGP_X25519_SHARED_LEN, + kdf_param, sizeof (kdf_param)); + if (err) + { + fail ("gcry_kem_decap %d: %s", testno, gpg_strerror (err)); + return; + } + + if (memcmp (key1, key2, MY_KEM_OPENPGP_X25519_SHARED_LEN) != 0) + { + size_t i; + + fail ("openpgp-x25519 test %d failed: mismatch\n", testno); + fputs ("key1:", stderr); + for (i = 0; i < MY_KEM_OPENPGP_X25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key1[i]); + putc ('\n', stderr); + fputs ("key2:", stderr); + for (i = 0; i < MY_KEM_OPENPGP_X25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key2[i]); + putc ('\n', stderr); + } +} + + +/* In the following case, with AES128-keywrap, shared secret length is 16. */ +#define MY_KEM_CMS_X25519_SHARED_LEN 16 + +static void +test_kem_cms_x25519 (int testno) +{ + gcry_error_t err; + uint8_t pubkey[GCRY_KEM_ECC_X25519_PUBKEY_LEN]; + uint8_t seckey[GCRY_KEM_ECC_X25519_SECKEY_LEN]; + uint8_t ciphertext[GCRY_KEM_ECC_X25519_ENCAPS_LEN]; + uint8_t key1[MY_KEM_CMS_X25519_SHARED_LEN]; + uint8_t key2[MY_KEM_CMS_X25519_SHARED_LEN]; + const uint8_t sharedinfo[23] = { + 0x30, 0x15 , /* SEQUENCE */ + 0x30, 0x0B, /* SEQUENCE */ + /* OID */ + 0x06, 0x09, /* OBJECT_ID */ + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x05, + /* CONTEXT 2*/ + 0xA2, 0x06, + 0x04, 0x04, /* OCTET STRING */ + 0x00, 0x00, 0x00, 0x80 + }; + + err = gcry_kem_keypair (GCRY_KEM_CMS_X25519_X963_SHA256, + pubkey, GCRY_KEM_ECC_X25519_PUBKEY_LEN, + seckey, GCRY_KEM_ECC_X25519_SECKEY_LEN); + if (err) + { + fail ("gcry_kem_keypair %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_encap (GCRY_KEM_CMS_X25519_X963_SHA256, + pubkey, GCRY_KEM_ECC_X25519_PUBKEY_LEN, + ciphertext, GCRY_KEM_ECC_X25519_ENCAPS_LEN, + key1, MY_KEM_CMS_X25519_SHARED_LEN, + sharedinfo, sizeof (sharedinfo)); + if (err) + { + fail ("gcry_kem_encap %d: %s", testno, gpg_strerror (err)); + return; + } + + err = gcry_kem_decap (GCRY_KEM_CMS_X25519_X963_SHA256, + seckey, GCRY_KEM_ECC_X25519_SECKEY_LEN, + ciphertext, GCRY_KEM_ECC_X25519_ENCAPS_LEN, + key2, MY_KEM_CMS_X25519_SHARED_LEN, + sharedinfo, sizeof (sharedinfo)); + if (err) + { + fail ("gcry_kem_decap %d: %s", testno, gpg_strerror (err)); + return; + } + + if (memcmp (key1, key2, MY_KEM_CMS_X25519_SHARED_LEN) != 0) + { + size_t i; + + fail ("openpgp-x25519 test %d failed: mismatch\n", testno); + fputs ("key1:", stderr); + for (i = 0; i < MY_KEM_CMS_X25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key1[i]); + putc ('\n', stderr); + fputs ("key2:", stderr); + for (i = 0; i < MY_KEM_CMS_X25519_SHARED_LEN; i++) + fprintf (stderr, " %02x", key2[i]); + putc ('\n', stderr); + } +} + + +#define SELECTED_ALGO_SNTRUP761 (1 << 0) +#define SELECTED_ALGO_MLKEM512 (1 << 1) +#define SELECTED_ALGO_MLKEM768 (1 << 2) +#define SELECTED_ALGO_MLKEM1024 (1 << 3) +#define SELECTED_ALGO_RAW_X25519 (1 << 4) +#define SELECTED_ALGO_DHKEM25519 (1 << 5) +#define SELECTED_ALGO_OPENPGP_X25519 (1 << 6) +#define SELECTED_ALGO_CMS_X25519 (1 << 7) static unsigned int selected_algo; static void @@ -306,6 +575,34 @@ check_kem (int n_loops) ntests += n_loops; } + if ((selected_algo & SELECTED_ALGO_RAW_X25519)) + { + for (; testno < ntests + n_loops; testno++) + test_kem_raw_x25519 (testno); + ntests += n_loops; + } + + if ((selected_algo & SELECTED_ALGO_DHKEM25519)) + { + for (; testno < ntests + n_loops; testno++) + test_kem_dhkem_x25519 (testno); + ntests += n_loops; + } + + if ((selected_algo & SELECTED_ALGO_OPENPGP_X25519)) + { + for (; testno < ntests + n_loops; testno++) + test_kem_openpgp_x25519 (testno); + ntests += n_loops; + } + + if ((selected_algo & SELECTED_ALGO_CMS_X25519)) + { + for (; testno < ntests + n_loops; testno++) + test_kem_cms_x25519 (testno); + ntests += n_loops; + } + show_note ("%d tests done\n", ntests); } @@ -315,7 +612,6 @@ main (int argc, char **argv) int last_argc = -1; int n_loops = N_TESTS; - selected_algo = ~0; /* Default is all algos. */ if (argc) { @@ -323,6 +619,8 @@ main (int argc, char **argv) argv++; } + selected_algo = ~0; /* Default is all algos. */ + while (argc && last_argc != argc) { last_argc = argc; @@ -343,7 +641,10 @@ main (int argc, char **argv) " --sntrup761 select SNTRUP761 algo\n" " --mlkem512 select MLKEM512 algo\n" " --mlkem768 select MLKEM768 algo\n" - " --mlkem1024 select MLKEM1024 algo\n", + " --mlkem1024 select MLKEM1024 algo\n" + " --dhkem25519 select DHKEM25519 algo\n" + " --pgp-x25519 select PGP_X25519 algo\n" + " --cms-x25519 select CMS_X25519_X963 algo\n", stdout); exit (0); } @@ -392,6 +693,30 @@ main (int argc, char **argv) argc--; argv++; } + else if (!strcmp (*argv, "--raw-x25519")) + { + selected_algo = SELECTED_ALGO_RAW_X25519; + argc--; + argv++; + } + else if (!strcmp (*argv, "--dhkem25519")) + { + selected_algo = SELECTED_ALGO_DHKEM25519; + argc--; + argv++; + } + else if (!strcmp (*argv, "--pgp-x25519")) + { + selected_algo = SELECTED_ALGO_OPENPGP_X25519; + argc--; + argv++; + } + else if (!strcmp (*argv, "--cms-x25519")) + { + selected_algo = SELECTED_ALGO_CMS_X25519; + argc--; + argv++; + } else if (!strncmp (*argv, "--", 2)) die ("unknown option '%s'", *argv); } -- 2.39.2
_______________________________________________ Gcrypt-devel mailing list [email protected] https://lists.gnupg.org/mailman/listinfo/gcrypt-devel
