Unify the HPKE implementation for ML-KEM. ML-KEM-768 and ML-KEM-1024 HPKE implementations are roughly copy-pastes with "768" changed to "1024". C++ templates are able to do that sort of thing and remove the code duplication. Change-Id: I6133d1fe5017539c479c60482a465ef94a036ff4 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/82487 Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/hpke/hpke.cc b/crypto/hpke/hpke.cc index d49882a..38fba75 100644 --- a/crypto/hpke/hpke.cc +++ b/crypto/hpke/hpke.cc
@@ -720,239 +720,161 @@ return &kKEM; } -#define MLKEM768_PRIVATE_KEY_LEN MLKEM_SEED_BYTES -#define MLKEM768_PUBLIC_KEY_LEN MLKEM768_PUBLIC_KEY_BYTES -#define MLKEM768_PUBLIC_VALUE_LEN MLKEM768_CIPHERTEXT_BYTES -#define MLKEM768_SEED_LEN BCM_MLKEM_ENCAP_ENTROPY -#define MLKEM768_SHARED_KEY_LEN MLKEM_SHARED_SECRET_BYTES +namespace { -static int mlkem768_init_key(EVP_HPKE_KEY *key, const uint8_t *priv_key, - size_t priv_key_len) { - MLKEM768_private_key expanded_private_key; - if (!MLKEM768_private_key_from_seed(&expanded_private_key, priv_key, - priv_key_len)) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - MLKEM768_public_key public_key; - MLKEM768_public_from_private(&public_key, &expanded_private_key); - CBB cbb; - static_assert(sizeof(key->public_key) >= MLKEM768_PUBLIC_KEY_LEN, - "EVP_HPKE_KEY public_key is too small for ML-KEM-768."); - if (!CBB_init_fixed(&cbb, key->public_key, MLKEM768_PUBLIC_KEY_LEN) || - !MLKEM768_marshal_public_key(&cbb, &public_key)) { - return 0; +template <uint16_t KEM_ID, size_t PUBLIC_KEY_BYTES, size_t CIPHERTEXT_BYTES, + size_t ENCAP_ENTROPY_BYTES, + + typename PrivateKey, typename PublicKey, typename BCMPublicKey, + + int (*PrivateKeyFromSeed)(PrivateKey *, const uint8_t *, size_t), + void (*PublicFromPrivate)(PublicKey *, const PrivateKey *), + int (*MarshalPublicKey)(CBB *, const PublicKey *), + void (*GenerateKey)(uint8_t *, uint8_t *, PrivateKey *), + bcm_status (*BCMParsePublicKey)(BCMPublicKey *, CBS *), + bcm_infallible (*BCMEncapExternalEntropy)( + uint8_t *, uint8_t *, const BCMPublicKey *, const uint8_t *), + int (*Decap)(uint8_t *, const uint8_t *, size_t, const PrivateKey *)> +struct MLKEMHPKE { + // These sizes are common across both ML-KEM-768 and ML-KEM-1024. + static constexpr size_t PRIVATE_KEY_LEN = MLKEM_SEED_BYTES; + static constexpr size_t SHARED_KEY_LEN = MLKEM_SHARED_SECRET_BYTES; + + static constexpr uint16_t ID = KEM_ID; + static constexpr size_t PUBLIC_KEY_LEN = PUBLIC_KEY_BYTES; + static constexpr size_t SEED_LEN = ENCAP_ENTROPY_BYTES; + static constexpr size_t ENC_LEN = CIPHERTEXT_BYTES; + + static int InitKey(EVP_HPKE_KEY *key, const uint8_t *priv_key, + size_t priv_key_len) { + PrivateKey expanded_private_key; + if (!PrivateKeyFromSeed(&expanded_private_key, priv_key, priv_key_len)) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + PublicKey public_key; + PublicFromPrivate(&public_key, &expanded_private_key); + CBB cbb; + static_assert(sizeof(key->public_key) >= PUBLIC_KEY_BYTES, + "EVP_HPKE_KEY public_key is too small for ML-KEM."); + if (!CBB_init_fixed(&cbb, key->public_key, PUBLIC_KEY_BYTES) || + !MarshalPublicKey(&cbb, &public_key)) { + return 0; + } + + static_assert(sizeof(key->private_key) >= PRIVATE_KEY_LEN, + "EVP_HPKE_KEY private_key is too small for ML-KEM"); + OPENSSL_memcpy(key->private_key, priv_key, priv_key_len); + return 1; } - static_assert(sizeof(key->private_key) >= MLKEM768_PRIVATE_KEY_LEN, - "EVP_HPKE_KEY private_key is too small for ML-KEM-768"); - OPENSSL_memcpy(key->private_key, priv_key, priv_key_len); - return 1; -} + static int HpkeGenerateKey(EVP_HPKE_KEY *key) { + static_assert(sizeof(key->public_key) >= PUBLIC_KEY_BYTES, + "EVP_HPKE_KEY public_key is too small for ML-KEM."); + static_assert(sizeof(key->private_key) >= PRIVATE_KEY_LEN, + "EVP_HPKE_KEY private_key is too small for ML-KEM"); + PrivateKey expanded_private_key; + GenerateKey(key->public_key, key->private_key, &expanded_private_key); -static int mlkem768_generate_key(EVP_HPKE_KEY *key) { - static_assert(sizeof(key->public_key) >= MLKEM768_PUBLIC_KEY_LEN, - "EVP_HPKE_KEY public_key is too small for ML-KEM-768."); - static_assert(sizeof(key->private_key) >= MLKEM768_PRIVATE_KEY_LEN, - "EVP_HPKE_KEY private_key is too small for ML-KEM-768"); - MLKEM768_private_key expanded_private_key; - MLKEM768_generate_key(key->public_key, key->private_key, - &expanded_private_key); - - return 1; -} - -static int mlkem768_encap_with_seed( - const EVP_HPKE_KEM *kem, uint8_t *out_shared_secret, - size_t *out_shared_secret_len, uint8_t *out_enc, size_t *out_enc_len, - size_t max_enc, const uint8_t *peer_public_key, size_t peer_public_key_len, - const uint8_t *seed, size_t seed_len) { - if (max_enc < MLKEM768_PUBLIC_VALUE_LEN) { - OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE); - return 0; - } - if (peer_public_key_len != MLKEM768_PUBLIC_KEY_LEN || - seed_len != MLKEM768_SEED_LEN) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; + return 1; } - CBS cbs; - CBS_init(&cbs, peer_public_key, peer_public_key_len); - BCM_mlkem768_public_key public_key; - if (!bcm_success(BCM_mlkem768_parse_public_key(&public_key, &cbs))) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - // The public ML-KEM interface doesn't support providing the encap entropy so - // the BCM function is used here. - BCM_mlkem768_encap_external_entropy(out_enc, out_shared_secret, &public_key, - seed); + static int EncapWithSeed(const EVP_HPKE_KEM *kem, uint8_t *out_shared_secret, + size_t *out_shared_secret_len, uint8_t *out_enc, + size_t *out_enc_len, size_t max_enc, + const uint8_t *peer_public_key, + size_t peer_public_key_len, const uint8_t *seed, + size_t seed_len) { + if (max_enc < CIPHERTEXT_BYTES) { + OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE); + return 0; + } + if (peer_public_key_len != PUBLIC_KEY_BYTES || + seed_len != ENCAP_ENTROPY_BYTES) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } - *out_enc_len = MLKEM768_PUBLIC_VALUE_LEN; - *out_shared_secret_len = MLKEM768_SHARED_KEY_LEN; - return 1; -} + CBS cbs; + CBS_init(&cbs, peer_public_key, peer_public_key_len); + BCMPublicKey public_key; + if (!bcm_success(BCMParsePublicKey(&public_key, &cbs))) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + // The public ML-KEM interface doesn't support providing the encap entropy + // so the BCM function is used here. + BCMEncapExternalEntropy(out_enc, out_shared_secret, &public_key, seed); -static int mlkem768_decap(const EVP_HPKE_KEY *key, uint8_t *out_shared_secret, - size_t *out_shared_secret_len, const uint8_t *enc, - size_t enc_len) { - if (enc_len != MLKEM768_PUBLIC_VALUE_LEN) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; + *out_enc_len = CIPHERTEXT_BYTES; + *out_shared_secret_len = SHARED_KEY_LEN; + return 1; } - MLKEM768_private_key private_key; - if (!MLKEM768_private_key_from_seed(&private_key, key->private_key, - MLKEM768_PRIVATE_KEY_LEN)) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; + static int HpkeDecap(const EVP_HPKE_KEY *key, uint8_t *out_shared_secret, + size_t *out_shared_secret_len, const uint8_t *enc, + size_t enc_len) { + PrivateKey private_key; + if (!PrivateKeyFromSeed(&private_key, key->private_key, PRIVATE_KEY_LEN)) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + if (!Decap(out_shared_secret, enc, enc_len, &private_key)) { + OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR); + return 0; + } + + *out_shared_secret_len = SHARED_KEY_LEN; + return 1; } +}; - if (!MLKEM768_decap(out_shared_secret, enc, enc_len, &private_key)) { - OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR); - return 0; - } +using MLKEM768HPKE = + MLKEMHPKE<EVP_HPKE_MLKEM768, MLKEM768_PUBLIC_KEY_BYTES, + MLKEM768_CIPHERTEXT_BYTES, BCM_MLKEM_ENCAP_ENTROPY, - *out_shared_secret_len = MLKEM768_SHARED_KEY_LEN; - return 1; -} + MLKEM768_private_key, MLKEM768_public_key, + BCM_mlkem768_public_key, -const EVP_HPKE_KEM *EVP_hpke_mlkem768(void) { - static const EVP_HPKE_KEM kKEM = { - /*id=*/EVP_HPKE_MLKEM768, - /*public_key_len=*/MLKEM768_PUBLIC_KEY_LEN, - /*private_key_len=*/MLKEM768_PRIVATE_KEY_LEN, - /*seed_len=*/MLKEM768_SEED_LEN, - /*enc_len=*/MLKEM768_PUBLIC_VALUE_LEN, - mlkem768_init_key, - mlkem768_generate_key, - mlkem768_encap_with_seed, - mlkem768_decap, - // MLKEM768 doesn't support authenticated encapsulation/decapsulation: - // https://datatracker.ietf.org/doc/draft-ietf-hpke-pq/01/ - /* auth_encap_with_seed= */ nullptr, - /* auth_decap= */ nullptr, - }; - return &kKEM; -} + MLKEM768_private_key_from_seed, MLKEM768_public_from_private, + MLKEM768_marshal_public_key, MLKEM768_generate_key, + BCM_mlkem768_parse_public_key, + BCM_mlkem768_encap_external_entropy, MLKEM768_decap>; -#define MLKEM1024_PRIVATE_KEY_LEN MLKEM_SEED_BYTES -#define MLKEM1024_PUBLIC_KEY_LEN MLKEM1024_PUBLIC_KEY_BYTES -#define MLKEM1024_PUBLIC_VALUE_LEN MLKEM1024_CIPHERTEXT_BYTES -#define MLKEM1024_SEED_LEN BCM_MLKEM_ENCAP_ENTROPY -#define MLKEM1024_SHARED_KEY_LEN MLKEM_SHARED_SECRET_BYTES +using MLKEM1024HPKE = + MLKEMHPKE<EVP_HPKE_MLKEM1024, MLKEM1024_PUBLIC_KEY_BYTES, + MLKEM1024_CIPHERTEXT_BYTES, BCM_MLKEM_ENCAP_ENTROPY, -static int mlkem1024_init_key(EVP_HPKE_KEY *key, const uint8_t *priv_key, - size_t priv_key_len) { - MLKEM1024_private_key expanded_private_key; - if (!MLKEM1024_private_key_from_seed(&expanded_private_key, priv_key, - priv_key_len)) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - MLKEM1024_public_key public_key; - MLKEM1024_public_from_private(&public_key, &expanded_private_key); - CBB cbb; - static_assert(sizeof(key->public_key) >= MLKEM1024_PUBLIC_KEY_LEN, - "EVP_HPKE_KEY public_key is too small for ML-KEM-1024."); - if (!CBB_init_fixed(&cbb, key->public_key, MLKEM1024_PUBLIC_KEY_LEN) || - !MLKEM1024_marshal_public_key(&cbb, &public_key)) { - return 0; - } + MLKEM1024_private_key, MLKEM1024_public_key, + BCM_mlkem1024_public_key, - static_assert(sizeof(key->private_key) >= MLKEM1024_PRIVATE_KEY_LEN, - "EVP_HPKE_KEY private_key is too small for ML-KEM-1024"); - OPENSSL_memcpy(key->private_key, priv_key, priv_key_len); - return 1; -} + MLKEM1024_private_key_from_seed, MLKEM1024_public_from_private, + MLKEM1024_marshal_public_key, MLKEM1024_generate_key, + BCM_mlkem1024_parse_public_key, + BCM_mlkem1024_encap_external_entropy, MLKEM1024_decap>; -static int mlkem1024_generate_key(EVP_HPKE_KEY *key) { - static_assert(sizeof(key->public_key) >= MLKEM1024_PUBLIC_KEY_LEN, - "EVP_HPKE_KEY public_key is too small for ML-KEM-1024."); - static_assert(sizeof(key->private_key) >= MLKEM1024_PRIVATE_KEY_LEN, - "EVP_HPKE_KEY private_key is too small for ML-KEM-1024"); - MLKEM1024_private_key expanded_private_key; - MLKEM1024_generate_key(key->public_key, key->private_key, - &expanded_private_key); +template <typename MLKEM> +static const EVP_HPKE_KEM kMLKEM = { + /*id=*/MLKEM::ID, + /*public_key_len=*/MLKEM::PUBLIC_KEY_LEN, + /*private_key_len=*/MLKEM::PRIVATE_KEY_LEN, + /*seed_len=*/MLKEM::SEED_LEN, + /*enc_len=*/MLKEM::ENC_LEN, + MLKEM::InitKey, + MLKEM::HpkeGenerateKey, + MLKEM::EncapWithSeed, + MLKEM::HpkeDecap, + // ML-KEM doesn't support authenticated encapsulation/decapsulation: + // https://datatracker.ietf.org/doc/draft-ietf-hpke-pq/01/ + /*auth_encap_with_seed=*/nullptr, + /*auth_decap=*/nullptr, +}; - return 1; -} +} // namespace -static int mlkem1024_encap_with_seed( - const EVP_HPKE_KEM *kem, uint8_t *out_shared_secret, - size_t *out_shared_secret_len, uint8_t *out_enc, size_t *out_enc_len, - size_t max_enc, const uint8_t *peer_public_key, size_t peer_public_key_len, - const uint8_t *seed, size_t seed_len) { - if (max_enc < MLKEM1024_PUBLIC_VALUE_LEN) { - OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE); - return 0; - } - if (peer_public_key_len != MLKEM1024_PUBLIC_KEY_LEN || - seed_len != MLKEM1024_SEED_LEN) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - - CBS cbs; - CBS_init(&cbs, peer_public_key, peer_public_key_len); - BCM_mlkem1024_public_key public_key; - if (!bcm_success(BCM_mlkem1024_parse_public_key(&public_key, &cbs))) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - // The public ML-KEM interface doesn't support providing the encap entropy so - // the BCM function is used here. - BCM_mlkem1024_encap_external_entropy(out_enc, out_shared_secret, &public_key, - seed); - - *out_enc_len = MLKEM1024_PUBLIC_VALUE_LEN; - *out_shared_secret_len = MLKEM1024_SHARED_KEY_LEN; - return 1; -} - -static int mlkem1024_decap(const EVP_HPKE_KEY *key, uint8_t *out_shared_secret, - size_t *out_shared_secret_len, const uint8_t *enc, - size_t enc_len) { - if (enc_len != MLKEM1024_PUBLIC_VALUE_LEN) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - - MLKEM1024_private_key private_key; - if (!MLKEM1024_private_key_from_seed(&private_key, key->private_key, - MLKEM1024_PRIVATE_KEY_LEN)) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - - if (!MLKEM1024_decap(out_shared_secret, enc, enc_len, &private_key)) { - OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR); - return 0; - } - - *out_shared_secret_len = MLKEM1024_SHARED_KEY_LEN; - return 1; -} - -const EVP_HPKE_KEM *EVP_hpke_mlkem1024(void) { - static const EVP_HPKE_KEM kKEM = { - /*id=*/EVP_HPKE_MLKEM1024, - /*public_key_len=*/MLKEM1024_PUBLIC_KEY_LEN, - /*private_key_len=*/MLKEM1024_PRIVATE_KEY_LEN, - /*seed_len=*/MLKEM1024_SEED_LEN, - /*enc_len=*/MLKEM1024_PUBLIC_VALUE_LEN, - mlkem1024_init_key, - mlkem1024_generate_key, - mlkem1024_encap_with_seed, - mlkem1024_decap, - // MLKEM1024 doesn't support authenticated encapsulation/decapsulation: - // https://datatracker.ietf.org/doc/draft-ietf-hpke-pq/01/ - /* auth_encap_with_seed= */ nullptr, - /* auth_decap= */ nullptr, - }; - return &kKEM; -} +const EVP_HPKE_KEM *EVP_hpke_mlkem768(void) { return &kMLKEM<MLKEM768HPKE>; } +const EVP_HPKE_KEM *EVP_hpke_mlkem1024(void) { return &kMLKEM<MLKEM1024HPKE>; } uint16_t EVP_HPKE_KEM_id(const EVP_HPKE_KEM *kem) { return kem->id; }