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

Reply via email to