Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 36 additions & 17 deletions crypto/evp_extra/p_kem_asn1.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,26 +178,45 @@ static int kem_priv_decode(EVP_PKEY *out, CBS *oid, CBS *params, CBS *key,
return 0;
}

// At the moment, we only support expandedKey format from
// https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates.
// TODO(awslc): add support for "seed" and "both" formats.
if (!CBS_peek_asn1_tag(key, CBS_ASN1_OCTETSTRING)) {
// Support multiple ML-KEM private key formats from
// https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates/
// Case 1: seed [0] OCTET STRING
// Case 2: expandedKey OCTET STRING
// Case 3: TODO: both SEQUENCE {seed, expandedKey}

if (CBS_peek_asn1_tag(key, CBS_ASN1_CONTEXT_SPECIFIC | 0)) {
// Case 1: seed [0] OCTET STRING
CBS seed;
if (!CBS_get_asn1(key, &seed, CBS_ASN1_CONTEXT_SPECIFIC | 0)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}

if (CBS_len(&seed) != out->pkey.kem_key->kem->keygen_seed_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}

return KEM_KEY_set_raw_keypair_from_seed(out->pkey.kem_key, &seed);
} else if (CBS_peek_asn1_tag(key, CBS_ASN1_OCTETSTRING)) {
// Case 2: expandedKey OCTET STRING
CBS expanded_key;
if (!CBS_get_asn1(key, &expanded_key, CBS_ASN1_OCTETSTRING)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}

if (CBS_len(&expanded_key) != out->pkey.kem_key->kem->secret_key_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}

return KEM_KEY_set_raw_secret_key(out->pkey.kem_key, CBS_data(&expanded_key));
} else {
// Case 3: both SEQUENCE {seed, expandedKey} - not implemented yet
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}

CBS expanded_key;
if (!CBS_get_asn1(key, &expanded_key, CBS_ASN1_OCTETSTRING)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}

if (CBS_len(&expanded_key) != out->pkey.kem_key->kem->secret_key_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}

return KEM_KEY_set_raw_secret_key(out->pkey.kem_key, CBS_data(&expanded_key));
}

static int kem_priv_encode(CBB *out, const EVP_PKEY *pkey) {
Expand Down
70 changes: 64 additions & 6 deletions crypto/evp_extra/p_kem_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,28 @@ const char *mlkem_1024_pub_pem_str =
"uYOILhF1\n"
"-----END PUBLIC KEY-----\n";

// https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates/
// C.1.1.1. ML-KEM-512 Private Key Examples: Seed Format
const char *mlkem_512_seed_pem_str =
"-----BEGIN PRIVATE KEY-----\n"
"MFQCAQAwCwYJYIZIAWUDBAQBBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ\n"
"GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=\n"
"-----END PRIVATE KEY-----\n";

// C.1.2.1. ML-KEM-768 Private Key Examples: Seed Format
const char *mlkem_768_seed_pem_str =
"-----BEGIN PRIVATE KEY-----\n"
"MFQCAQAwCwYJYIZIAWUDBAQCBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ\n"
"GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=\n"
"-----END PRIVATE KEY-----\n";

// C.1.3.1. ML-KEM-1024 Private Key Examples: Seed Format
const char *mlkem_1024_seed_pem_str =
"-----BEGIN PRIVATE KEY-----\n"
"MFQCAQAwCwYJYIZIAWUDBAQDBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ\n"
"GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=\n"
"-----END PRIVATE KEY-----\n";

const char *mlkem_512_priv_expanded_pem_str =
"-----BEGIN PRIVATE KEY-----\n"
"MIIGeAIBADALBglghkgBZQMEBAEEggZkBIIGYHBVT9Q2NE8nhbGzsbrBhLZnkAMz\n"
Expand Down Expand Up @@ -432,24 +454,25 @@ struct KEMTestVector {
int nid;
const char *public_pem_str;
const char *private_pem_expanded_str;
const char *private_pem_seed_str;
const char *expected_deterministic_pub_pem;
const char *expected_deterministic_priv_pem;
size_t public_key_len;
size_t secret_key_len;
};

static const KEMTestVector kemParameters[] = {
{NID_MLKEM512, mlkem_512_pub_pem_str, mlkem_512_priv_expanded_pem_str,
mlkem_512_pub_pem_str, mlkem_512_priv_expanded_pem_str, 800, 1632},
{NID_MLKEM512, mlkem_512_pub_pem_str, mlkem_512_priv_expanded_pem_str,
mlkem_512_seed_pem_str, mlkem_512_pub_pem_str, mlkem_512_priv_expanded_pem_str, 800, 1632},
{NID_MLKEM768, mlkem_768_pub_pem_str, mlkem_768_priv_expanded_pem_str,
mlkem_768_pub_pem_str, mlkem_768_priv_expanded_pem_str, 1184, 2400},
mlkem_768_seed_pem_str, mlkem_768_pub_pem_str, mlkem_768_priv_expanded_pem_str, 1184, 2400},
{NID_MLKEM1024, mlkem_1024_pub_pem_str, mlkem_1024_priv_expanded_pem_str,
mlkem_1024_pub_pem_str, mlkem_1024_priv_expanded_pem_str, 1568, 3168},
mlkem_1024_seed_pem_str, mlkem_1024_pub_pem_str, mlkem_1024_priv_expanded_pem_str, 1568, 3168},
{NID_MLKEM512, bouncy_castle_mlkem_512_pub_pem_str,
bouncy_castle_mlkem_512_priv_expanded_pem_str,
bouncy_castle_mlkem_512_priv_expanded_pem_str, nullptr,
mlkem_512_pub_pem_str, mlkem_512_priv_expanded_pem_str, 800, 1632},
{NID_MLKEM768, bouncy_castle_mlkem_768_pub_pem_str,
bouncy_castle_mlkem_768_priv_expanded_str,
bouncy_castle_mlkem_768_priv_expanded_str, nullptr,
mlkem_768_pub_pem_str, mlkem_768_priv_expanded_pem_str, 1184, 2400},
};

Expand Down Expand Up @@ -831,3 +854,38 @@ TEST_P(KEMTest, ASN1_Methods_Cross_Compatibility) {
Bytes(decoded_priv_from_marshal->pkey.kem_key->secret_key, test.secret_key_len));
}

TEST_P(KEMTest, ParsePrivateKeySeed) {
// Skip BouncyCastle entries and only test vectors from IETF standard
if (GetParam().public_pem_str == bouncy_castle_mlkem_512_pub_pem_str ||
GetParam().public_pem_str == bouncy_castle_mlkem_768_pub_pem_str) {
return;
}
// ---- 1. Setup phase: parse provided public/private from PEM strings ----
CBS cbs_pub, cbs_priv;
uint8_t *der_pub = nullptr, *der_priv = nullptr;
long der_pub_len = 0, der_priv_len = 0;

ASSERT_TRUE(PEM_to_DER(GetParam().public_pem_str, &der_pub, &der_pub_len));
ASSERT_TRUE(PEM_to_DER(GetParam().private_pem_seed_str, &der_priv, &der_priv_len));

CBS_init(&cbs_pub, der_pub, der_pub_len);
CBS_init(&cbs_priv, der_priv, der_priv_len);

// ---- 2. Attempt to parse private key ----
bssl::UniquePtr<EVP_PKEY> pkey1(EVP_parse_private_key(&cbs_priv));
ASSERT_TRUE(pkey1);

// ---- 3. Attempt to parse public key ----
bssl::UniquePtr<EVP_PKEY> pkey2(EVP_parse_public_key(&cbs_pub));
ASSERT_TRUE(pkey2);

// ---- 4. Compare public keys ----
// EVP_parse_private_key will populate both public and private key, we verify
// that the public key calculated by EVP_parse_private_key is equivalent to
// the public key that was parsed from PEM.
ASSERT_EQ(1, EVP_PKEY_cmp(pkey1.get(), pkey2.get()));

// Clean up
OPENSSL_free(der_pub);
OPENSSL_free(der_priv);
}
9 changes: 9 additions & 0 deletions crypto/fipsmodule/kem/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ int KEM_KEY_set_raw_secret_key(KEM_KEY *key, const uint8_t *in);
int KEM_KEY_set_raw_key(KEM_KEY *key, const uint8_t *in_public,
const uint8_t *in_secret);

// KEM_KEY_set_raw_keypair_from_seed function generates a keypair from the
// given seed using the appropriate key generation function based on the
// KEM variant, then allocates and sets both public and secret key buffers
// within the given |key|.
//
// NOTE: The seed must be exactly 64 bytes for all ML-KEM variants.
// The caller must ensure the seed CBS contains valid data.
int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed);

#if defined(__cplusplus)
} // extern C
#endif
Expand Down
44 changes: 44 additions & 0 deletions crypto/fipsmodule/kem/kem.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "../delocate.h"
#include "../ml_kem/ml_kem.h"
#include "internal.h"
#include <openssl/bytestring.h>
#include <openssl/err.h>

// https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration
// 2.16.840.1.101.3.4.4.1
Expand Down Expand Up @@ -306,3 +308,45 @@ int KEM_KEY_set_raw_key(KEM_KEY *key, const uint8_t *in_public,

return 1;
}

int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed) {
if (key == NULL || seed == NULL || key->kem == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PARAMETERS);
return 0;
}

// Validate seed length - all ML-KEM variants use 64-byte seeds
if (CBS_len(seed) != key->kem->keygen_seed_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}

// Allocate temporary buffers for key generation
uint8_t *public_key = OPENSSL_malloc(key->kem->public_key_len);
uint8_t *secret_key = OPENSSL_malloc(key->kem->secret_key_len);
if (public_key == NULL || secret_key == NULL) {
OPENSSL_free(public_key);
OPENSSL_free(secret_key);
OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
return 0;
}

size_t public_len = key->kem->public_key_len;
size_t secret_len = key->kem->secret_key_len;

// Generate keypair from seed using the KEM method
if (!key->kem->method->keygen_deterministic(public_key, &public_len,
secret_key, &secret_len,
CBS_data(seed))) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
OPENSSL_free(public_key);
OPENSSL_free(secret_key);
return 0;
}

// Set public and secret key
key->public_key = public_key;
key->secret_key = secret_key;

return 1;
}
Loading