[GnuPG Logo]    
· English ·        
Links  
   
 
authorNIIBE Yutaka <[email protected]>
Wed, 21 May 2025 05:44:16 +0000 (14:44 +0900)
committerNIIBE Yutaka <[email protected]>
Wed, 21 May 2025 05:49:56 +0000 (14:49 +0900)
* common/kem.c (gnupg_ecc_kem_kdf): Support traditional KDF of RFC
6637.
* common/util.h (gnupg_ecc_kem_kdf): Add FIXED_INFO argument.
* g10/pkglue.c (do_encrypt_kem): Follow the change.
* agent/pkdecrypt.c (ecc_pgp_kem_decap): Return ECC parameters.
(composite_pgp_kem_decrypt): Follow the changes.
(ecc_kem_decrypt): New.
(agent_kem_decrypt): Support ECC KEM.

--

GnuPG-bug-id: 7649
Signed-off-by: NIIBE Yutaka <[email protected]>
agent/pkdecrypt.c
common/kem.c
common/util.h
g10/pkglue.c

index af4a638158cd543f8895e489a4b62917198be56e..9b92938d1e3eff81678c225f5721353d74218b11 100644 (file)
  * find an entry.  */
 struct ecc_params
 {
-  const char *curve;            /* Canonical name of the curve.  */
-  size_t pubkey_len;            /* Pubkey in the SEXP representation.   */
+  const char *curve;  /* Canonical name of the curve.  */
+  size_t pubkey_len;  /* Pubkey length in the SEXP representation.  */
   size_t scalar_len;
   size_t point_len;
-  int hash_algo;
+  int hash_algo;      /* Hash algo when it's used for composite KEM.  */
   int kem_algo;
   int scalar_reverse;
 };
 
+/* FIXME: Add NIST curves for traditional ECC */
 static const struct ecc_params ecc_table[] =
   {
     {
@@ -425,18 +426,17 @@ ecc_get_curve (ctrl_t ctrl, gcry_sexp_t s_skey, const char **r_curve)
 /* Given a private key in SEXP by S_SKEY0 and a cipher text by ECC_CT
  * with length ECC_POINT_LEN, do ECC KEM decap (== raw ECDH)
  * operation.  Result is returned in the memory referred by ECC_ECDH.
- * Public key is extracted and put into ECC_PK.  The hash algorithm
- * which is used for following KDF operation is stored into
- * R_HASH_ALGO.  SHADOW_INFO0 is used to determine if the private key
- * is actually on smartcard.  CTRL is used to access smartcard,
- * internally.  */
+ * Public key is extracted and put into ECC_PK.  The pointer to ECC
+ * parameters is stored into R_ECC.  SHADOW_INFO0 is used to determine
+ * if the private key is actually on smartcard.  CTRL is used to
+ * access smartcard, internally.  */
 static gpg_error_t
 ecc_pgp_kem_decap (ctrl_t ctrl, gcry_sexp_t s_skey0,
                    const unsigned char *shadow_info0,
                    const unsigned char *ecc_ct, size_t ecc_point_len,
                    unsigned char ecc_ecdh[ECC_POINT_LEN_MAX],
                    unsigned char ecc_pk[ECC_POINT_LEN_MAX],
-                   int *r_hash_algo)
+                   const struct ecc_params **r_ecc)
 {
   gpg_error_t err;
   const char *curve;
@@ -465,8 +465,7 @@ ecc_pgp_kem_decap (ctrl_t ctrl, gcry_sexp_t s_skey0,
         log_info ("%s: curve '%s' not supported\n", __func__, curve);
       return gpg_error (GPG_ERR_BAD_SECKEY);
     }
-
-  *r_hash_algo = ecc->hash_algo;
+  *r_ecc = ecc;
 
   if (ecc->point_len != ecc_point_len)
     {
@@ -518,7 +517,7 @@ ecc_pgp_kem_decap (ctrl_t ctrl, gcry_sexp_t s_skey0,
    should follow the format of:
 
        (enc-val(pqc(c%d)(e%m)(k%m)(s%m)(fixed-info&)))
-        c: cipher identifier (symmetric)
+        c: cipher identifier (of session key (wrapped key))
         e: ECDH ciphertext
         k: ML-KEM ciphertext
         s: encrypted session key
@@ -552,6 +551,7 @@ composite_pgp_kem_decrypt (ctrl_t ctrl, const char *desc_text,
   unsigned char ecc_ss[ECC_HASH_LEN_MAX];
   int ecc_hashalgo;
   size_t ecc_shared_len, ecc_point_len;
+  const struct ecc_params *ecc;
 
   enum gcry_kem_algos mlkem_kem_algo;
   gcry_mpi_t mlkem_sk_mpi = NULL;
@@ -619,18 +619,19 @@ composite_pgp_kem_decrypt (ctrl_t ctrl, const char *desc_text,
   /* Firstly, ECC part.  */
   ecc_point_len = ecc_ct_len;
   err = ecc_pgp_kem_decap (ctrl, s_skey0, shadow_info0, ecc_ct, ecc_point_len,
-                           ecc_ecdh, ecc_pk, &ecc_hashalgo);
+                           ecc_ecdh, ecc_pk, &ecc);
   if (err)
     goto leave;
+  ecc_hashalgo = ecc->hash_algo;
   ecc_shared_len = gcry_md_get_algo_dlen (ecc_hashalgo);
   err = gnupg_ecc_kem_kdf (ecc_ss, ecc_shared_len, ecc_hashalgo,
                            ecc_ecdh, ecc_point_len, ecc_ct, ecc_point_len,
-                           ecc_pk, ecc_point_len);
+                           ecc_pk, ecc_point_len, NULL);
   if (err)
     {
       if (opt.verbose)
         log_info ("%s: kdf for ECC failed\n", __func__);
-      return err;
+      goto leave;
     }
   wipememory (ecc_ecdh, sizeof ecc_ecdh);
   if (DBG_CRYPTO)
@@ -766,44 +767,178 @@ composite_pgp_kem_decrypt (ctrl_t ctrl, const char *desc_text,
   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)
+/* For ECC PGP KEM, decrypt CIPHERTEXT using KEM API.  CIPHERTEXT
+   should follow the format of:
+
+       (enc-val(ecdh(c%d)(h%d)(e%m)(s%m)(fixed-info&)))
+        c: cipher identifier (of wrapping key)
+        h: hash identifier
+        e: ECDH ciphertext
+        s: encrypted session key
+        fixed-info: A buffer with the fixed info (the KDF parameters).
+
+  */
+static gpg_error_t
+ecc_kem_decrypt (ctrl_t ctrl, const char *desc_text,
+                 gcry_sexp_t s_cipher, membuf_t *outbuf)
 {
-  gcry_sexp_t s_cipher = NULL;
+  gcry_sexp_t s_skey = NULL;
+  unsigned char *shadow_info = 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);
+  unsigned int nbits;
 
-  (void)optionlen;
-  if (kemid == KEM_PQC_PGP && option)
+  int algo;
+  int hashalgo;
+  gcry_mpi_t encrypted_sessionkey_mpi = NULL;
+  const unsigned char *encrypted_sessionkey;
+  size_t encrypted_sessionkey_len;
+
+  gcry_mpi_t ecc_ct_mpi = NULL;
+  const unsigned char *ecc_ct;
+  size_t ecc_ct_len;
+  unsigned char ecc_ecdh[ECC_POINT_LEN_MAX];
+  unsigned char ecc_pk[ECC_POINT_LEN_MAX];
+  size_t ecc_point_len;
+  const struct ecc_params *ecc;
+
+  unsigned char *kek = NULL;
+  size_t kek_len;
+
+  gcry_cipher_hd_t hd;
+  unsigned char sessionkey[256];
+  size_t sessionkey_len;
+  gcry_buffer_t fixed_info = { 0, 0, 0, NULL };
+
+  err = agent_key_from_file (ctrl, NULL, desc_text,
+                             NULL, &shadow_info,
+                             CACHE_MODE_NORMAL, NULL, &s_skey, NULL, NULL);
+  if (err && gpg_err_code (err) != GPG_ERR_NO_SECKEY)
     {
-      log_error ("PQC-PGP requires no option\n");
-      return gpg_error (GPG_ERR_INV_ARG);
+      log_error ("failed to read the secret key\n");
+      goto leave;
     }
 
-  if (!ctrl->have_keygrip)
+  err = gcry_sexp_extract_param (s_cipher, NULL, "%dc%dh/es&'fixed-info'",
+                                 &algo, &hashalgo, &ecc_ct_mpi,
+                                 &encrypted_sessionkey_mpi, &fixed_info, NULL);
+  if (err)
     {
-      log_error ("speculative decryption not yet supported\n");
-      return gpg_error (GPG_ERR_NO_SECKEY);
+      if (opt.verbose)
+        log_info ("%s: extracting parameters failed\n", __func__);
+      goto leave;
     }
 
-  if (!ctrl->have_keygrip1)
+  if (!fixed_info.data)
     {
-      log_error ("Composite KEM requires two KEYGRIPs\n");
-      return gpg_error (GPG_ERR_NO_SECKEY);
+      if (opt.verbose)
+        log_info ("%s: the KDF parameters is required\n", __func__);
+      err = gpg_error (GPG_ERR_INV_DATA);
+      goto leave;
     }
 
+  ecc_ct = gcry_mpi_get_opaque (ecc_ct_mpi, &nbits);
+  ecc_ct_len = (nbits+7)/8;
+
+  encrypted_sessionkey = gcry_mpi_get_opaque (encrypted_sessionkey_mpi, &nbits);
+  encrypted_sessionkey_len = (nbits+7)/8;
+
+  kek_len = gcry_cipher_get_algo_keylen (algo);
+  if (kek_len == 0 || kek_len > gcry_md_get_algo_dlen (hashalgo))
+    {
+      err = gpg_error (GPG_ERR_INV_DATA);
+      goto leave;
+    }
+
+  kek = xtrymalloc (kek_len);
+  if (!kek)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  ecc_point_len = ecc_ct_len;
+  err = ecc_pgp_kem_decap (ctrl, s_skey, shadow_info,
+                           ecc_ct, ecc_point_len,
+                           ecc_ecdh, ecc_pk, &ecc);
+  if (err)
+    goto leave;
+  err = gnupg_ecc_kem_kdf (kek, kek_len, hashalgo,
+                           ecc->point_len > ecc->scalar_len ?
+                           /* For Weierstrass curve, extract
+                              x-component from the point.  */
+                           ecc_ecdh + 1 : ecc_ecdh,
+                           ecc->scalar_len, ecc_ct, ecc_point_len,
+                           ecc_pk, ecc_point_len, &fixed_info);
+  if (err)
+    {
+      if (opt.verbose)
+        log_info ("%s: kdf for ECC failed\n", __func__);
+      goto leave;
+    }
+  wipememory (ecc_ecdh, sizeof ecc_ecdh);
+  if (DBG_CRYPTO)
+    {
+      log_printhex (kek, kek_len, "KEK key: ");
+    }
+
+  err = gcry_cipher_open (&hd, algo, 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);
+
+  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 (sessionkey, sizeof sessionkey);
+  wipememory (kek, sizeof kek);
+  xfree (kek);
+  mpi_release (ecc_ct_mpi);
+  mpi_release (encrypted_sessionkey_mpi);
+  gcry_free (fixed_info.data);
+  gcry_sexp_release (s_skey);
+  xfree (shadow_info);
+  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.  For now,
+ * KEMID==KEM_CMS is _not_ yet supported.
+ */
+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;
+
+  (void)optionlen;
+
   err = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen);
   if (err)
     {
@@ -811,15 +946,40 @@ agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid,
       return gpg_error (GPG_ERR_INV_DATA);
     }
 
-  if (DBG_CRYPTO)
+  if (option)
     {
-      log_printhex (ctrl->keygrip, 20, "keygrip0:");
-      log_printhex (ctrl->keygrip1, 20, "keygrip1:");
-      gcry_log_debugsxp ("cipher", s_cipher);
+      log_error ("KEM (%d) requires no option\n", kemid);
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
     }
 
-  err = composite_pgp_kem_decrypt (ctrl, desc_text, s_cipher, outbuf);
+  if (kemid == KEM_PGP)
+    err = ecc_kem_decrypt  (ctrl, desc_text, s_cipher, outbuf);
+  else if (kemid == KEM_PQC_PGP)
+    {
+      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);
+        }
 
+      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);
+    }
+
+ leave:
   gcry_sexp_release (s_cipher);
   return err;
 }
index bbb450e1bb19d3d8074643177f4611b31b1039e5..fc5575f4fcea1035aaf7d19eb6537193943c9fd6 100644 (file)
@@ -150,24 +150,50 @@ gpg_error_t
 gnupg_ecc_kem_kdf (void *kek, size_t kek_len,
                    int hashalgo, const void *ecdh, size_t ecdh_len,
                    const void *ecc_ct, size_t ecc_ct_len,
-                   const void *ecc_pk, size_t ecc_pk_len)
+                   const void *ecc_pk, size_t ecc_pk_len,
+                   gcry_buffer_t *fixed_info)
 {
-  gcry_buffer_t iov[3];
-  unsigned int dlen;
-
-  dlen = gcry_md_get_algo_dlen (hashalgo);
-  if (kek_len != dlen)
-    return gpg_error (GPG_ERR_INV_LENGTH);
-
-  memset (iov, 0, sizeof (iov));
-
-  iov[0].data = (unsigned char *)ecdh;
-  iov[0].len = ecdh_len;
-  iov[1].data = (unsigned char *)ecc_ct;
-  iov[1].len = ecc_ct_len;
-  iov[2].data = (unsigned char *)ecc_pk;
-  iov[2].len = ecc_pk_len;
-  gcry_md_hash_buffers (hashalgo, 0, kek, iov, 3);
+  if (fixed_info)
+    {
+      /* Traditional ECC */
+      gpg_error_t err;
+      gcry_kdf_hd_t hd;
+      unsigned long param[1];
+
+      param[0] = kek_len;
+      err = gcry_kdf_open (&hd, GCRY_KDF_ONESTEP_KDF, hashalgo, param, 1,
+                           ecdh, ecdh_len, NULL, 0, NULL, 0,
+                           (char *)fixed_info->data+fixed_info->off,
+                           fixed_info->len);
+      if (!err)
+        {
+          gcry_kdf_compute (hd, NULL);
+          gcry_kdf_final (hd, kek_len, kek);
+          gcry_kdf_close (hd);
+        }
+
+      return err;
+    }
+  else
+    {
+      /* ECC in composite KEM */
+      gcry_buffer_t iov[3];
+      unsigned int dlen;
+
+      dlen = gcry_md_get_algo_dlen (hashalgo);
+      if (kek_len != dlen)
+        return gpg_error (GPG_ERR_INV_LENGTH);
+
+      memset (iov, 0, sizeof (iov));
+
+      iov[0].data = (unsigned char *)ecdh;
+      iov[0].len = ecdh_len;
+      iov[1].data = (unsigned char *)ecc_ct;
+      iov[1].len = ecc_ct_len;
+      iov[2].data = (unsigned char *)ecc_pk;
+      iov[2].len = ecc_pk_len;
+      gcry_md_hash_buffers (hashalgo, 0, kek, iov, 3);
+    }
 
   return 0;
 }
index 4564009ce72f00a2cb9601c0624ea458f4188275..cd5483a1a433d2b0ce777e95c01d1eae9a8e2407 100644 (file)
@@ -305,7 +305,8 @@ const char *gnupg_messages_locale_name (void);
 gpg_error_t gnupg_ecc_kem_kdf (void *kek, size_t kek_len,
                                int hashalgo, const void *ecdh, size_t ecdh_len,
                                const void *ecc_ct, size_t ecc_ct_len,
-                               const void *ecc_pk, size_t ecc_pk_len);
+                               const void *ecc_pk, size_t ecc_pk_len,
+                               gcry_buffer_t *fixed_info);
 
 gpg_error_t gnupg_kem_combiner  (void *kek, size_t kek_len,
                                  const void *ecc_ss, size_t ecc_ss_len,
index 307e39e0cc95855140ab313785cb6bca90931263..240f5084610dea432ac52e2db7443b88347e5a1b 100644 (file)
@@ -598,7 +598,7 @@ do_encrypt_kem (PKT_public_key *pk, gcry_mpi_t data, int seskey_algo,
                            ecc_hash_algo,
                            ecc_ecdh, ecc_ecdh_len,
                            ecc_ct, ecc_ct_len,
-                           ecc_pubkey, ecc_pubkey_len);
+                           ecc_pubkey, ecc_pubkey_len, NULL);
   if (err)
     {
       if (opt.verbose)
 
   
 
 
  Technical resources for this
service are sponsered by
 
  g10 Code GmbH  

Valid XHTML 1.0!     Peace!     Valid CSS!