From c736052e9ccaca8fc4662af43820090910e88122 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 15 Apr 2024 12:16:40 +0200 Subject: [PATCH] gpg: Implement Kyber encryption. * g10/build-packet.c (do_pubkey_enc): Support Kyber. * g10/pkglue.c (do_encrypt_kem): Implement. -- Note that the code does only work for ky768_cv25519 for now. GnuPG-bug-id: 6815 --- g10/build-packet.c | 9 +- g10/pkglue.c | 209 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 214 insertions(+), 4 deletions(-) diff --git a/g10/build-packet.c b/g10/build-packet.c index fd315b313..606c5c2d8 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -987,9 +987,14 @@ do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc ) if (enc->pubkey_algo == PUBKEY_ALGO_KYBER && i == 2) iobuf_put (a, enc->seskey_algo); - if (enc->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1) + if (i == 1 && enc->pubkey_algo == PUBKEY_ALGO_ECDH) rc = gpg_mpi_write_opaque_nohdr (a, enc->data[i]); - else if (enc->pubkey_algo == PUBKEY_ALGO_ECDH) + else if (i == 1 && enc->pubkey_algo == PUBKEY_ALGO_KYBER) + rc = gpg_mpi_write_opaque_32 (a, enc->data[i], NULL); + else if (i == 2 && enc->pubkey_algo == PUBKEY_ALGO_KYBER) + rc = gpg_mpi_write_opaque_nohdr (a, enc->data[i]); + else if (enc->pubkey_algo == PUBKEY_ALGO_ECDH + || enc->pubkey_algo == PUBKEY_ALGO_KYBER) rc = sos_write (a, enc->data[i], NULL); else rc = gpg_mpi_write (a, enc->data[i], NULL); diff --git a/g10/pkglue.c b/g10/pkglue.c index 52d8c1012..0167e80e3 100644 --- a/g10/pkglue.c +++ b/g10/pkglue.c @@ -423,9 +423,214 @@ 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; - log_debug ("Implement Kyber encryption\n"); - return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + 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; + + unsigned char ecc_ct[GCRY_KEM_ECC_X25519_ENCAPS_LEN]; + size_t ecc_ct_len = GCRY_KEM_ECC_X25519_ENCAPS_LEN; + unsigned char ecc_ecdh[GCRY_KEM_RAW_X25519_SHARED_LEN]; + size_t ecc_ecdh_len = GCRY_KEM_RAW_X25519_SHARED_LEN; + unsigned char ecc_ss[GCRY_KEM_RAW_X25519_SHARED_LEN]; + size_t ecc_ss_len = GCRY_KEM_RAW_X25519_SHARED_LEN; + + unsigned char kyber_ct[GCRY_KEM_MLKEM768_ENCAPS_LEN]; + size_t kyber_ct_len = GCRY_KEM_MLKEM768_ENCAPS_LEN; + unsigned char kyber_ss[GCRY_KEM_MLKEM768_SHARED_LEN]; + size_t kyber_ss_len = GCRY_KEM_MLKEM768_SHARED_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. */ + + /* FIXME: This is only for cv25519 */ + ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits); + ecc_pubkey_len = (nbits+7)/8; + if (ecc_pubkey_len != 33) + { + 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_pubkey++; /* Remove the 0x40 prefix. */ + ecc_pubkey_len--; + if (DBG_CRYPTO) + log_printhex (ecc_pubkey, ecc_pubkey_len, "ECC pubkey:"); + + err = gcry_kem_encap (GCRY_KEM_RAW_X25519, + 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 failed\n", __func__); + 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, + GCRY_MD_SHA3_256, + 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:"); + + /* FIXME: This is only for kyber768 */ + 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) + { + 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 (GCRY_KEM_MLKEM768, + 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, ecc_ct_len); + wipememory (ecc_ecdh, ecc_ecdh_len); + wipememory (ecc_ss, ecc_ss_len); + wipememory (kyber_ct, kyber_ct_len); + wipememory (kyber_ss, kyber_ss_len); + wipememory (kek, kek_len); + xfree (enc_seskey); + gcry_cipher_close (hd); + return err; }