From 1d332decb70caa85dcdb94770dc965e3fbcc46f9 Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Tue, 23 Sep 2025 23:50:49 -0700 Subject: [PATCH 1/7] Add EVP_PKEY_check and EVP_PKEY_public_check for KEMs --- crypto/evp_extra/evp_asn1.c | 4 ++ crypto/evp_extra/evp_extra_test.cc | 9 ++-- crypto/fipsmodule/kem/internal.h | 8 ++++ crypto/fipsmodule/kem/kem.c | 72 ++++++++++++++++++++++++++++++ crypto/fipsmodule/ml_kem/ml_kem.c | 51 +++++++++++++++++++++ crypto/fipsmodule/ml_kem/ml_kem.h | 10 +++++ 6 files changed, 148 insertions(+), 6 deletions(-) diff --git a/crypto/evp_extra/evp_asn1.c b/crypto/evp_extra/evp_asn1.c index a0cec91272..f688276a95 100644 --- a/crypto/evp_extra/evp_asn1.c +++ b/crypto/evp_extra/evp_asn1.c @@ -334,6 +334,8 @@ int EVP_PKEY_check(EVP_PKEY_CTX *ctx) { } case EVP_PKEY_RSA: return RSA_check_key(pkey->pkey.rsa); + case EVP_PKEY_KEM: + return KEM_check_key(pkey->pkey.kem_key); default: OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); return 0; @@ -357,6 +359,8 @@ int EVP_PKEY_public_check(EVP_PKEY_CTX *ctx) { return EC_KEY_check_key(pkey->pkey.ec); case EVP_PKEY_RSA: return RSA_check_key(pkey->pkey.rsa); + case EVP_PKEY_KEM: + return KEM_check_key(pkey->pkey.kem_key); default: OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); return 0; diff --git a/crypto/evp_extra/evp_extra_test.cc b/crypto/evp_extra/evp_extra_test.cc index 09ca98c069..ae5fc980a0 100644 --- a/crypto/evp_extra/evp_extra_test.cc +++ b/crypto/evp_extra/evp_extra_test.cc @@ -2584,15 +2584,12 @@ TEST_P(PerKEMTest, RawKeyOperations) { ASSERT_TRUE(pkey_new); ASSERT_TRUE(EVP_PKEY_kem_check_key(pkey_new.get())); - // Not supported for anything but EC and RSA keys + // Test EVP_PKEY_check and EVP_PKEY_public_check bssl::UniquePtr kem_key_ctx( EVP_PKEY_CTX_new(pkey_new.get(), NULL)); ASSERT_TRUE(kem_key_ctx); - EXPECT_FALSE(EVP_PKEY_check(kem_key_ctx.get())); - EXPECT_FALSE(EVP_PKEY_public_check((kem_key_ctx.get()))); - ASSERT_EQ((uint16_t)ERR_get_error(), - (uint16_t)EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); - ERR_clear_error(); + EXPECT_TRUE(EVP_PKEY_check(kem_key_ctx.get())); + EXPECT_TRUE(EVP_PKEY_public_check((kem_key_ctx.get()))); // ---- 5. Test encaps/decaps with new keys ---- // Create Alice's context with the new key that has both diff --git a/crypto/fipsmodule/kem/internal.h b/crypto/fipsmodule/kem/internal.h index bbe9afa48b..9eaad16f4f 100644 --- a/crypto/fipsmodule/kem/internal.h +++ b/crypto/fipsmodule/kem/internal.h @@ -98,6 +98,14 @@ 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_check_key function validates a KEM key pair using the ML-KEM specific +// validation functions crypto_kem_check_pk and crypto_kem_check_sk. +// When ML-KEM is used as the KEM, it performs modulus check for the public +// key and hash check for the secret key as mandated by FIPS 203. +// +// Returns 1 on success, 0 on failure. +int KEM_check_key(const KEM_KEY *key); + #if defined(__cplusplus) } // extern C #endif diff --git a/crypto/fipsmodule/kem/kem.c b/crypto/fipsmodule/kem/kem.c index 65c8440842..21995f5d51 100644 --- a/crypto/fipsmodule/kem/kem.c +++ b/crypto/fipsmodule/kem/kem.c @@ -306,3 +306,75 @@ int KEM_KEY_set_raw_key(KEM_KEY *key, const uint8_t *in_public, return 1; } + +int KEM_check_key(const KEM_KEY *key) { + if (key == NULL) { + OPENSSL_PUT_ERROR(EVP, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + // Check that the KEM method and parameters are valid + if (key->kem == NULL || key->kem->method == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); + return 0; + } + + // Check that at least the public key exists + if (key->public_key == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); + return 0; + } + + // Call appropriate ML-KEM check functions based on KEM NID + switch (key->kem->nid) { + case NID_MLKEM512: + case NID_KYBER512_R3: + // Check public key validity + if (ml_kem_512_check_pk(key->public_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + // Check secret key validity if present + if (key->secret_key != NULL && ml_kem_512_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + break; + + case NID_MLKEM768: + case NID_KYBER768_R3: + // Check public key validity + if (ml_kem_768_check_pk(key->public_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + // Check secret key validity if present + if (key->secret_key != NULL && ml_kem_768_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + break; + + case NID_MLKEM1024: + case NID_KYBER1024_R3: + // Check public key validity + if (ml_kem_1024_check_pk(key->public_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + // Check secret key validity if present + if (key->secret_key != NULL && ml_kem_1024_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + break; + + default: + // For unsupported KEM variants + OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); + return 0; + } + + return 1; +} + diff --git a/crypto/fipsmodule/ml_kem/ml_kem.c b/crypto/fipsmodule/ml_kem/ml_kem.c index e2227e0465..94b787df4c 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem.c +++ b/crypto/fipsmodule/ml_kem/ml_kem.c @@ -394,3 +394,54 @@ int ml_kem_common_decapsulate(int (*decapsulate)(uint8_t *shared_secret, const u set_written_len_on_success(res, shared_secret); return res; } + +// ML-KEM key validation functions +// These functions perform FIPS 203 compliant validation of ML-KEM public and secret keys + +int ml_kem_512_check_pk(const uint8_t *public_key) { + if (public_key == NULL) { + return -1; // Invalid input + } + // Call the underlying ML-KEM-512 check function + return mlkem512_check_pk(public_key); +} + +int ml_kem_512_check_sk(const uint8_t *secret_key) { + if (secret_key == NULL) { + return -1; // Invalid input + } + // Call the underlying ML-KEM-512 check function + return mlkem512_check_sk(secret_key); +} + +int ml_kem_768_check_pk(const uint8_t *public_key) { + if (public_key == NULL) { + return -1; // Invalid input + } + // Call the underlying ML-KEM-768 check function + return mlkem768_check_pk(public_key); +} + +int ml_kem_768_check_sk(const uint8_t *secret_key) { + if (secret_key == NULL) { + return -1; // Invalid input + } + // Call the underlying ML-KEM-768 check function + return mlkem768_check_sk(secret_key); +} + +int ml_kem_1024_check_pk(const uint8_t *public_key) { + if (public_key == NULL) { + return -1; // Invalid input + } + // Call the underlying ML-KEM-1024 check function + return mlkem1024_check_pk(public_key); +} + +int ml_kem_1024_check_sk(const uint8_t *secret_key) { + if (secret_key == NULL) { + return -1; // Invalid input + } + // Call the underlying ML-KEM-1024 check function + return mlkem1024_check_sk(secret_key); +} diff --git a/crypto/fipsmodule/ml_kem/ml_kem.h b/crypto/fipsmodule/ml_kem/ml_kem.h index 8da358e2a0..91006b8e0a 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem.h +++ b/crypto/fipsmodule/ml_kem/ml_kem.h @@ -175,6 +175,16 @@ int ml_kem_1024_decapsulate_no_self_test(uint8_t *shared_secret /* OUT */, const uint8_t *ciphertext /* IN */, const uint8_t *secret_key /* IN */); +// ML-KEM key validation functions +OPENSSL_EXPORT int ml_kem_512_check_pk(const uint8_t *public_key /* IN */); +OPENSSL_EXPORT int ml_kem_512_check_sk(const uint8_t *secret_key /* IN */); + +OPENSSL_EXPORT int ml_kem_768_check_pk(const uint8_t *public_key /* IN */); +OPENSSL_EXPORT int ml_kem_768_check_sk(const uint8_t *secret_key /* IN */); + +OPENSSL_EXPORT int ml_kem_1024_check_pk(const uint8_t *public_key /* IN */); +OPENSSL_EXPORT int ml_kem_1024_check_sk(const uint8_t *secret_key /* IN */); + #if defined(__cplusplus) } #endif From a09e674f3b15b44d2c6a621f368558effd308c5e Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Wed, 24 Sep 2025 00:03:19 -0700 Subject: [PATCH 2/7] remove extern and fix up documentation --- crypto/fipsmodule/kem/internal.h | 2 +- crypto/fipsmodule/ml_kem/ml_kem.h | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crypto/fipsmodule/kem/internal.h b/crypto/fipsmodule/kem/internal.h index 9eaad16f4f..17baa0d632 100644 --- a/crypto/fipsmodule/kem/internal.h +++ b/crypto/fipsmodule/kem/internal.h @@ -98,7 +98,7 @@ 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_check_key function validates a KEM key pair using the ML-KEM specific +// KEM_check_key function validates a KEM key pair using the specific // validation functions crypto_kem_check_pk and crypto_kem_check_sk. // When ML-KEM is used as the KEM, it performs modulus check for the public // key and hash check for the secret key as mandated by FIPS 203. diff --git a/crypto/fipsmodule/ml_kem/ml_kem.h b/crypto/fipsmodule/ml_kem/ml_kem.h index 91006b8e0a..5d1663fb95 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem.h +++ b/crypto/fipsmodule/ml_kem/ml_kem.h @@ -176,14 +176,14 @@ int ml_kem_1024_decapsulate_no_self_test(uint8_t *shared_secret /* OUT */, const uint8_t *secret_key /* IN */); // ML-KEM key validation functions -OPENSSL_EXPORT int ml_kem_512_check_pk(const uint8_t *public_key /* IN */); -OPENSSL_EXPORT int ml_kem_512_check_sk(const uint8_t *secret_key /* IN */); +int ml_kem_512_check_pk(const uint8_t *public_key /* IN */); +int ml_kem_512_check_sk(const uint8_t *secret_key /* IN */); -OPENSSL_EXPORT int ml_kem_768_check_pk(const uint8_t *public_key /* IN */); -OPENSSL_EXPORT int ml_kem_768_check_sk(const uint8_t *secret_key /* IN */); +int ml_kem_768_check_pk(const uint8_t *public_key /* IN */); +int ml_kem_768_check_sk(const uint8_t *secret_key /* IN */); -OPENSSL_EXPORT int ml_kem_1024_check_pk(const uint8_t *public_key /* IN */); -OPENSSL_EXPORT int ml_kem_1024_check_sk(const uint8_t *secret_key /* IN */); +int ml_kem_1024_check_pk(const uint8_t *public_key /* IN */); +int ml_kem_1024_check_sk(const uint8_t *secret_key /* IN */); #if defined(__cplusplus) } From d24a1168a75b991abd09d61616607293ee48cc50 Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Mon, 6 Oct 2025 15:34:34 -0700 Subject: [PATCH 3/7] add PCT tests to KEM check --- crypto/fipsmodule/kem/internal.h | 9 ++ crypto/fipsmodule/kem/kem.c | 31 ++++-- crypto/fipsmodule/ml_kem/ml_kem.c | 160 ++++++++++++++++++++++++++++++ crypto/fipsmodule/ml_kem/ml_kem.h | 8 ++ 4 files changed, 200 insertions(+), 8 deletions(-) diff --git a/crypto/fipsmodule/kem/internal.h b/crypto/fipsmodule/kem/internal.h index 7993293b3a..cdd42993f7 100644 --- a/crypto/fipsmodule/kem/internal.h +++ b/crypto/fipsmodule/kem/internal.h @@ -109,6 +109,15 @@ int KEM_KEY_set_raw_key(KEM_KEY *key, const uint8_t *in_public, // |key->secret_key| must both be NULL. int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed); +// KEM_check_key function validates a KEM keypair by performing: +// 1. Public key validity checks +// 2. Secret key validity checks +// 3. Pairwise Consistency Test (PCT) - encapsulation/decapsulation round-trip +// +// NOTE: Both public and secret keys must be present. +// Returns 1 on success, 0 on failure. +int KEM_check_key(const KEM_KEY *key); + #if defined(__cplusplus) } // extern C #endif diff --git a/crypto/fipsmodule/kem/kem.c b/crypto/fipsmodule/kem/kem.c index af0741447c..9dda891f06 100644 --- a/crypto/fipsmodule/kem/kem.c +++ b/crypto/fipsmodule/kem/kem.c @@ -369,8 +369,8 @@ int KEM_check_key(const KEM_KEY *key) { return 0; } - // Check that at least the public key exists - if (key->public_key == NULL) { + // Check that both public and private keys exist + if (key->public_key == NULL || key->secret_key == NULL) { OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); return 0; } @@ -384,8 +384,13 @@ int KEM_check_key(const KEM_KEY *key) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } - // Check secret key validity if present - if (key->secret_key != NULL && ml_kem_512_check_sk(key->secret_key) != 0) { + // Check secret key validity + if (ml_kem_512_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + // Perform Pairwise Consistency Test (PCT) + if (ml_kem_512_check_pct(key->public_key, key->secret_key) != 0) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } @@ -398,8 +403,13 @@ int KEM_check_key(const KEM_KEY *key) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } - // Check secret key validity if present - if (key->secret_key != NULL && ml_kem_768_check_sk(key->secret_key) != 0) { + // Check secret key validity + if (ml_kem_768_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + // Perform Pairwise Consistency Test (PCT) + if (ml_kem_768_check_pct(key->public_key, key->secret_key) != 0) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } @@ -412,8 +422,13 @@ int KEM_check_key(const KEM_KEY *key) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } - // Check secret key validity if present - if (key->secret_key != NULL && ml_kem_1024_check_sk(key->secret_key) != 0) { + // Check secret key validity + if (ml_kem_1024_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + // Perform Pairwise Consistency Test (PCT) + if (ml_kem_1024_check_pct(key->public_key, key->secret_key) != 0) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } diff --git a/crypto/fipsmodule/ml_kem/ml_kem.c b/crypto/fipsmodule/ml_kem/ml_kem.c index 94b787df4c..97bc6459be 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem.c +++ b/crypto/fipsmodule/ml_kem/ml_kem.c @@ -445,3 +445,163 @@ int ml_kem_1024_check_sk(const uint8_t *secret_key) { // Call the underlying ML-KEM-1024 check function return mlkem1024_check_sk(secret_key); } + +// ML-KEM Pairwise Consistency Test (PCT) functions +// These functions perform FIPS 203 compliant PCT by performing encapsulation +// and decapsulation operations and comparing the resulting shared secrets + +int ml_kem_512_check_pct(const uint8_t *public_key, const uint8_t *secret_key) { + if (public_key == NULL || secret_key == NULL) { + return -1; // Invalid input + } + + uint8_t ciphertext[MLKEM512_CIPHERTEXT_BYTES]; + uint8_t shared_secret_enc[MLKEM512_SHARED_SECRET_LEN]; + uint8_t shared_secret_dec[MLKEM512_SHARED_SECRET_LEN]; + size_t ciphertext_len = MLKEM512_CIPHERTEXT_BYTES; + size_t shared_secret_len_enc = MLKEM512_SHARED_SECRET_LEN; + size_t shared_secret_len_dec = MLKEM512_SHARED_SECRET_LEN; + + // Perform encapsulation + int res = ml_kem_512_encapsulate(ciphertext, &ciphertext_len, + shared_secret_enc, &shared_secret_len_enc, + public_key); + if (res != 0) { + goto cleanup; + } + + // Perform decapsulation + res = ml_kem_512_decapsulate(shared_secret_dec, &shared_secret_len_dec, + ciphertext, secret_key); + if (res != 0) { + goto cleanup; + } + + // Compare shared secrets - they should be identical for a valid keypair + if (shared_secret_len_enc != shared_secret_len_dec || + shared_secret_len_enc != MLKEM512_SHARED_SECRET_LEN) { + res = -1; + goto cleanup; + } + + // Use constant-time comparison + for (size_t i = 0; i < MLKEM512_SHARED_SECRET_LEN; i++) { + if (shared_secret_enc[i] != shared_secret_dec[i]) { + res = -1; + goto cleanup; + } + } + + res = 0; // PCT passed + +cleanup: + // Clear sensitive data + OPENSSL_cleanse(ciphertext, sizeof(ciphertext)); + OPENSSL_cleanse(shared_secret_enc, sizeof(shared_secret_enc)); + OPENSSL_cleanse(shared_secret_dec, sizeof(shared_secret_dec)); + return res; +} + +int ml_kem_768_check_pct(const uint8_t *public_key, const uint8_t *secret_key) { + if (public_key == NULL || secret_key == NULL) { + return -1; // Invalid input + } + + uint8_t ciphertext[MLKEM768_CIPHERTEXT_BYTES]; + uint8_t shared_secret_enc[MLKEM768_SHARED_SECRET_LEN]; + uint8_t shared_secret_dec[MLKEM768_SHARED_SECRET_LEN]; + size_t ciphertext_len = MLKEM768_CIPHERTEXT_BYTES; + size_t shared_secret_len_enc = MLKEM768_SHARED_SECRET_LEN; + size_t shared_secret_len_dec = MLKEM768_SHARED_SECRET_LEN; + + // Perform encapsulation + int res = ml_kem_768_encapsulate(ciphertext, &ciphertext_len, + shared_secret_enc, &shared_secret_len_enc, + public_key); + if (res != 0) { + goto cleanup; + } + + // Perform decapsulation + res = ml_kem_768_decapsulate(shared_secret_dec, &shared_secret_len_dec, + ciphertext, secret_key); + if (res != 0) { + goto cleanup; + } + + // Compare shared secrets - they should be identical for a valid keypair + if (shared_secret_len_enc != shared_secret_len_dec || + shared_secret_len_enc != MLKEM768_SHARED_SECRET_LEN) { + res = -1; + goto cleanup; + } + + // Use constant-time comparison + for (size_t i = 0; i < MLKEM768_SHARED_SECRET_LEN; i++) { + if (shared_secret_enc[i] != shared_secret_dec[i]) { + res = -1; + goto cleanup; + } + } + + res = 0; // PCT passed + +cleanup: + // Clear sensitive data + OPENSSL_cleanse(ciphertext, sizeof(ciphertext)); + OPENSSL_cleanse(shared_secret_enc, sizeof(shared_secret_enc)); + OPENSSL_cleanse(shared_secret_dec, sizeof(shared_secret_dec)); + return res; +} + +int ml_kem_1024_check_pct(const uint8_t *public_key, const uint8_t *secret_key) { + if (public_key == NULL || secret_key == NULL) { + return -1; // Invalid input + } + + uint8_t ciphertext[MLKEM1024_CIPHERTEXT_BYTES]; + uint8_t shared_secret_enc[MLKEM1024_SHARED_SECRET_LEN]; + uint8_t shared_secret_dec[MLKEM1024_SHARED_SECRET_LEN]; + size_t ciphertext_len = MLKEM1024_CIPHERTEXT_BYTES; + size_t shared_secret_len_enc = MLKEM1024_SHARED_SECRET_LEN; + size_t shared_secret_len_dec = MLKEM1024_SHARED_SECRET_LEN; + + // Perform encapsulation + int res = ml_kem_1024_encapsulate(ciphertext, &ciphertext_len, + shared_secret_enc, &shared_secret_len_enc, + public_key); + if (res != 0) { + goto cleanup; + } + + // Perform decapsulation + res = ml_kem_1024_decapsulate(shared_secret_dec, &shared_secret_len_dec, + ciphertext, secret_key); + if (res != 0) { + goto cleanup; + } + + // Compare shared secrets - they should be identical for a valid keypair + if (shared_secret_len_enc != shared_secret_len_dec || + shared_secret_len_enc != MLKEM1024_SHARED_SECRET_LEN) { + res = -1; + goto cleanup; + } + + // Use constant-time comparison + for (size_t i = 0; i < MLKEM1024_SHARED_SECRET_LEN; i++) { + if (shared_secret_enc[i] != shared_secret_dec[i]) { + res = -1; + goto cleanup; + } + } + + res = 0; // PCT passed + +cleanup: + // Clear sensitive data + OPENSSL_cleanse(ciphertext, sizeof(ciphertext)); + OPENSSL_cleanse(shared_secret_enc, sizeof(shared_secret_enc)); + OPENSSL_cleanse(shared_secret_dec, sizeof(shared_secret_dec)); + return res; +} diff --git a/crypto/fipsmodule/ml_kem/ml_kem.h b/crypto/fipsmodule/ml_kem/ml_kem.h index 5d1663fb95..d2b8123fda 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem.h +++ b/crypto/fipsmodule/ml_kem/ml_kem.h @@ -185,6 +185,14 @@ int ml_kem_768_check_sk(const uint8_t *secret_key /* IN */); int ml_kem_1024_check_pk(const uint8_t *public_key /* IN */); int ml_kem_1024_check_sk(const uint8_t *secret_key /* IN */); +// ML-KEM Pairwise Consistency Test (PCT) functions +int ml_kem_512_check_pct(const uint8_t *public_key /* IN */, + const uint8_t *secret_key /* IN */); +int ml_kem_768_check_pct(const uint8_t *public_key /* IN */, + const uint8_t *secret_key /* IN */); +int ml_kem_1024_check_pct(const uint8_t *public_key /* IN */, + const uint8_t *secret_key /* IN */); + #if defined(__cplusplus) } #endif From 0616873c1b6dd9f025ad3b86e59d816138478e93 Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Tue, 7 Oct 2025 12:59:09 -0700 Subject: [PATCH 4/7] drafted public key check function --- crypto/evp_extra/evp_asn1.c | 2 +- crypto/evp_extra/evp_extra_test.cc | 167 +++++++++++++++++++++++++++++ crypto/fipsmodule/kem/internal.h | 8 ++ crypto/fipsmodule/kem/kem.c | 54 ++++++++++ 4 files changed, 230 insertions(+), 1 deletion(-) diff --git a/crypto/evp_extra/evp_asn1.c b/crypto/evp_extra/evp_asn1.c index f688276a95..7afca55330 100644 --- a/crypto/evp_extra/evp_asn1.c +++ b/crypto/evp_extra/evp_asn1.c @@ -360,7 +360,7 @@ int EVP_PKEY_public_check(EVP_PKEY_CTX *ctx) { case EVP_PKEY_RSA: return RSA_check_key(pkey->pkey.rsa); case EVP_PKEY_KEM: - return KEM_check_key(pkey->pkey.kem_key); + return KEM_check_public_key(pkey->pkey.kem_key); default: OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); return 0; diff --git a/crypto/evp_extra/evp_extra_test.cc b/crypto/evp_extra/evp_extra_test.cc index ae5fc980a0..f916983559 100644 --- a/crypto/evp_extra/evp_extra_test.cc +++ b/crypto/evp_extra/evp_extra_test.cc @@ -2777,6 +2777,173 @@ TEST_P(PerKEMTest, RawKeyOperations) { ASSERT_FALSE(EVP_PKEY_kem_check_key(pkey_new.get())); } +TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { + // ---- 1. Setup phase: generate a valid key for reference ---- + bssl::UniquePtr ctx; + ctx = setup_ctx_and_generate_key(GetParam().nid, nullptr, nullptr); + ASSERT_TRUE(ctx); + + EVP_PKEY *valid_pkey = EVP_PKEY_CTX_get0_pkey(ctx.get()); + ASSERT_TRUE(valid_pkey); + + // Test EVP_PKEY_check and EVP_PKEY_public_check on valid key + bssl::UniquePtr kem_key_ctx(EVP_PKEY_CTX_new(valid_pkey, nullptr)); + ASSERT_TRUE(kem_key_ctx); + EXPECT_TRUE(EVP_PKEY_check(kem_key_ctx.get())); + EXPECT_TRUE(EVP_PKEY_public_check(kem_key_ctx.get())); + + // ---- 2. Test with corrupted public key ---- + // Extract the raw keys from the valid key + size_t pk_len = GetParam().public_key_len; + size_t sk_len = GetParam().secret_key_len; + std::vector pk_copy(pk_len); + std::vector sk_copy(sk_len); + + ASSERT_TRUE(EVP_PKEY_get_raw_public_key(valid_pkey, pk_copy.data(), &pk_len)); + ASSERT_TRUE(EVP_PKEY_get_raw_private_key(valid_pkey, sk_copy.data(), &sk_len)); + + // Create a corrupted public key + std::vector corrupted_pk = pk_copy; + corrupted_pk[0] = 0xFF; + corrupted_pk[1] = 0xFF; + if (pk_len > 2) { + corrupted_pk[2] = 0xFF; + } + + // Create EVP_PKEY with corrupted public key but valid secret key + bssl::UniquePtr corrupted_pk_pkey( + EVP_PKEY_kem_new_raw_key(GetParam().nid, corrupted_pk.data(), pk_len, + sk_copy.data(), sk_len)); + ASSERT_TRUE(corrupted_pk_pkey); + + // This should fail EVP_PKEY_check due to invalid public key + bssl::UniquePtr corrupted_pk_ctx( + EVP_PKEY_CTX_new(corrupted_pk_pkey.get(), nullptr)); + ASSERT_TRUE(corrupted_pk_ctx); + EXPECT_FALSE(EVP_PKEY_check(corrupted_pk_ctx.get())); + EXPECT_FALSE(EVP_PKEY_public_check(corrupted_pk_ctx.get())); + + // ---- 3. Test with corrupted secret key ---- + // Create a corrupted secret key + std::vector corrupted_sk = sk_copy; + + // Corrupt the hash portion of the secret key (last 64 bytes contain hash) + // This should cause the secret key validation to fail + if (sk_len >= 64) { + corrupted_sk[sk_len - 64] ^= 1; + corrupted_sk[sk_len - 63] ^= 1; + } + + // Create EVP_PKEY with valid public key but corrupted secret key + bssl::UniquePtr corrupted_sk_pkey( + EVP_PKEY_kem_new_raw_key(GetParam().nid, pk_copy.data(), pk_len, + corrupted_sk.data(), sk_len)); + ASSERT_TRUE(corrupted_sk_pkey); + + // This should fail EVP_PKEY_check due to invalid secret key + bssl::UniquePtr corrupted_sk_ctx( + EVP_PKEY_CTX_new(corrupted_sk_pkey.get(), nullptr)); + ASSERT_TRUE(corrupted_sk_ctx); + EXPECT_FALSE(EVP_PKEY_check(corrupted_sk_ctx.get())); + // Public key check should still pass since only secret key is corrupted + EXPECT_TRUE(EVP_PKEY_public_check(corrupted_sk_ctx.get())); + + // ---- 4. Test mismatched keypair (PCT failure) ---- + // Generate a second key pair to create a mismatch + bssl::UniquePtr ctx2; + ctx2 = setup_ctx_and_generate_key(GetParam().nid, nullptr, nullptr); + ASSERT_TRUE(ctx2); + + EVP_PKEY *second_pkey = EVP_PKEY_CTX_get0_pkey(ctx2.get()); + ASSERT_TRUE(second_pkey); + + // Extract secret key from second keypair + std::vector second_sk_copy(sk_len); + size_t second_sk_len = sk_len; + ASSERT_TRUE(EVP_PKEY_get_raw_private_key(second_pkey, second_sk_copy.data(), &second_sk_len)); + + // Create EVP_PKEY with public key from first keypair and secret key from second keypair + bssl::UniquePtr mismatched_pkey( + EVP_PKEY_kem_new_raw_key(GetParam().nid, pk_copy.data(), pk_len, + second_sk_copy.data(), sk_len)); + ASSERT_TRUE(mismatched_pkey); + + // This should fail EVP_PKEY_check due to mismatched keypair (PCT failure) + bssl::UniquePtr mismatched_ctx( + EVP_PKEY_CTX_new(mismatched_pkey.get(), nullptr)); + ASSERT_TRUE(mismatched_ctx); + EXPECT_FALSE(EVP_PKEY_check(mismatched_ctx.get())); + // Public key check should still pass since the public key itself is valid + EXPECT_TRUE(EVP_PKEY_public_check(mismatched_ctx.get())); + + // ---- 5. Test with public key only ---- + // Create EVP_PKEY with only public key (no secret key) + bssl::UniquePtr public_only_pkey( + EVP_PKEY_kem_new_raw_public_key(GetParam().nid, pk_copy.data(), pk_len)); + ASSERT_TRUE(public_only_pkey); + + bssl::UniquePtr public_only_ctx( + EVP_PKEY_CTX_new(public_only_pkey.get(), nullptr)); + ASSERT_TRUE(public_only_ctx); + + // EVP_PKEY_check should fail because there's no private key + EXPECT_FALSE(EVP_PKEY_check(public_only_ctx.get())); + // But EVP_PKEY_public_check should pass + EXPECT_TRUE(EVP_PKEY_public_check(public_only_ctx.get())); + + // ---- 6. Test with corrupted public key (public key only) ---- + bssl::UniquePtr corrupted_public_only_pkey( + EVP_PKEY_kem_new_raw_public_key(GetParam().nid, corrupted_pk.data(), pk_len)); + ASSERT_TRUE(corrupted_public_only_pkey); + + bssl::UniquePtr corrupted_public_only_ctx( + EVP_PKEY_CTX_new(corrupted_public_only_pkey.get(), nullptr)); + ASSERT_TRUE(corrupted_public_only_ctx); + + // Both checks should fail due to invalid public key + EXPECT_FALSE(EVP_PKEY_check(corrupted_public_only_ctx.get())); + EXPECT_FALSE(EVP_PKEY_public_check(corrupted_public_only_ctx.get())); + + // ---- 7. Test with invalid key sizes ---- + // Test with public key that's too small + if (pk_len > 1) { + std::vector short_pk(pk_len - 1, 0x00); + + // This should fail during key creation due to wrong size + bssl::UniquePtr short_pk_pkey( + EVP_PKEY_kem_new_raw_public_key(GetParam().nid, short_pk.data(), pk_len - 1)); + EXPECT_FALSE(short_pk_pkey); + + // Clear any errors from the failed key creation + ERR_clear_error(); + } + + // ---- 8. Test with invalid secret key size ---- + if (sk_len > 1) { + std::vector short_sk(sk_len - 1, 0x00); + + // This should fail during key creation due to wrong size + bssl::UniquePtr short_sk_pkey( + EVP_PKEY_kem_new_raw_key(GetParam().nid, pk_copy.data(), pk_len, + short_sk.data(), sk_len - 1)); + EXPECT_FALSE(short_sk_pkey); + + // Clear any errors from the failed key creation + ERR_clear_error(); + } + + // ---- 9. Verify original valid key still works ---- + // Make sure our tests didn't affect the original valid key + EXPECT_TRUE(EVP_PKEY_check(kem_key_ctx.get())); + EXPECT_TRUE(EVP_PKEY_public_check(kem_key_ctx.get())); + + // Clear any remaining errors + ERR_clear_error(); +} + + + + // Perform Known Answer Test (KAT) on known KEMs. // These tests access the deterministic EVP APIs for KeyGen and Encapsulation. // To perform KATs in KEMs we use a DRBG seeded with a given state "seed". diff --git a/crypto/fipsmodule/kem/internal.h b/crypto/fipsmodule/kem/internal.h index cdd42993f7..939201eb51 100644 --- a/crypto/fipsmodule/kem/internal.h +++ b/crypto/fipsmodule/kem/internal.h @@ -118,6 +118,14 @@ int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed); // Returns 1 on success, 0 on failure. int KEM_check_key(const KEM_KEY *key); +// KEM_check_public_key function validates only the public key portion of a KEM key. +// This function performs public key validity checks without requiring the secret key +// or performing the Pairwise Consistency Test (PCT). +// +// NOTE: Only the public key needs to be present. +// Returns 1 on success, 0 on failure. +int KEM_check_public_key(const KEM_KEY *key); + #if defined(__cplusplus) } // extern C #endif diff --git a/crypto/fipsmodule/kem/kem.c b/crypto/fipsmodule/kem/kem.c index 9dda891f06..44ebe3e4f2 100644 --- a/crypto/fipsmodule/kem/kem.c +++ b/crypto/fipsmodule/kem/kem.c @@ -357,6 +357,60 @@ int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed) { return 1; } +// Internal function for public key validation only +int KEM_check_public_key(const KEM_KEY *key) { + if (key == NULL) { + OPENSSL_PUT_ERROR(EVP, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + // Check that the KEM method and parameters are valid + if (key->kem == NULL || key->kem->method == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); + return 0; + } + + // Check that the public key exists + if (key->public_key == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); + return 0; + } + + // Call appropriate KEM public key check functions based on KEM NID + switch (key->kem->nid) { + case NID_MLKEM512: + case NID_KYBER512_R3: + if (ml_kem_512_check_pk(key->public_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + break; + + case NID_MLKEM768: + case NID_KYBER768_R3: + if (ml_kem_768_check_pk(key->public_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + break; + + case NID_MLKEM1024: + case NID_KYBER1024_R3: + if (ml_kem_1024_check_pk(key->public_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + break; + + default: + // For unsupported KEM variants + OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); + return 0; + } + + return 1; +} + int KEM_check_key(const KEM_KEY *key) { if (key == NULL) { OPENSSL_PUT_ERROR(EVP, ERR_R_PASSED_NULL_PARAMETER); From f30bfd4ac9d3aaf7d39de2ade0cef255495e3e24 Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Tue, 7 Oct 2025 13:54:59 -0700 Subject: [PATCH 5/7] cleaner solution and more negative testing --- crypto/evp_extra/evp_asn1.c | 2 +- crypto/evp_extra/evp_extra_test.cc | 29 +++++-- crypto/fipsmodule/kem/internal.h | 18 +--- crypto/fipsmodule/kem/kem.c | 128 +++++++++++------------------ 4 files changed, 72 insertions(+), 105 deletions(-) diff --git a/crypto/evp_extra/evp_asn1.c b/crypto/evp_extra/evp_asn1.c index 7afca55330..f688276a95 100644 --- a/crypto/evp_extra/evp_asn1.c +++ b/crypto/evp_extra/evp_asn1.c @@ -360,7 +360,7 @@ int EVP_PKEY_public_check(EVP_PKEY_CTX *ctx) { case EVP_PKEY_RSA: return RSA_check_key(pkey->pkey.rsa); case EVP_PKEY_KEM: - return KEM_check_public_key(pkey->pkey.kem_key); + return KEM_check_key(pkey->pkey.kem_key); default: OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); return 0; diff --git a/crypto/evp_extra/evp_extra_test.cc b/crypto/evp_extra/evp_extra_test.cc index f916983559..d21d517e56 100644 --- a/crypto/evp_extra/evp_extra_test.cc +++ b/crypto/evp_extra/evp_extra_test.cc @@ -2845,8 +2845,8 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { EVP_PKEY_CTX_new(corrupted_sk_pkey.get(), nullptr)); ASSERT_TRUE(corrupted_sk_ctx); EXPECT_FALSE(EVP_PKEY_check(corrupted_sk_ctx.get())); - // Public key check should still pass since only secret key is corrupted - EXPECT_TRUE(EVP_PKEY_public_check(corrupted_sk_ctx.get())); + // Public key check will fail PCT since secret key is present, and corrupted + EXPECT_FALSE(EVP_PKEY_public_check(corrupted_sk_ctx.get())); // ---- 4. Test mismatched keypair (PCT failure) ---- // Generate a second key pair to create a mismatch @@ -2873,8 +2873,8 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { EVP_PKEY_CTX_new(mismatched_pkey.get(), nullptr)); ASSERT_TRUE(mismatched_ctx); EXPECT_FALSE(EVP_PKEY_check(mismatched_ctx.get())); - // Public key check should still pass since the public key itself is valid - EXPECT_TRUE(EVP_PKEY_public_check(mismatched_ctx.get())); + // Public key check will fail PCT + EXPECT_FALSE(EVP_PKEY_public_check(mismatched_ctx.get())); // ---- 5. Test with public key only ---- // Create EVP_PKEY with only public key (no secret key) @@ -2886,9 +2886,8 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { EVP_PKEY_CTX_new(public_only_pkey.get(), nullptr)); ASSERT_TRUE(public_only_ctx); - // EVP_PKEY_check should fail because there's no private key - EXPECT_FALSE(EVP_PKEY_check(public_only_ctx.get())); - // But EVP_PKEY_public_check should pass + // Both checks should pass + EXPECT_TRUE(EVP_PKEY_check(public_only_ctx.get())); EXPECT_TRUE(EVP_PKEY_public_check(public_only_ctx.get())); // ---- 6. Test with corrupted public key (public key only) ---- @@ -2932,7 +2931,21 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { ERR_clear_error(); } - // ---- 9. Verify original valid key still works ---- + // ---- 9. Test with secret key only (no public key) ---- + // Create EVP_PKEY with only secret key (no public key) + bssl::UniquePtr secret_only_pkey( + EVP_PKEY_kem_new_raw_secret_key(GetParam().nid, sk_copy.data(), sk_len)); + ASSERT_TRUE(secret_only_pkey); + + bssl::UniquePtr secret_only_ctx( + EVP_PKEY_CTX_new(secret_only_pkey.get(), nullptr)); + ASSERT_TRUE(secret_only_ctx); + + // Both checks should fail because public key is required + EXPECT_FALSE(EVP_PKEY_check(secret_only_ctx.get())); + EXPECT_FALSE(EVP_PKEY_public_check(secret_only_ctx.get())); + + // ---- 10. Verify original valid key still works ---- // Make sure our tests didn't affect the original valid key EXPECT_TRUE(EVP_PKEY_check(kem_key_ctx.get())); EXPECT_TRUE(EVP_PKEY_public_check(kem_key_ctx.get())); diff --git a/crypto/fipsmodule/kem/internal.h b/crypto/fipsmodule/kem/internal.h index 939201eb51..8a990894e9 100644 --- a/crypto/fipsmodule/kem/internal.h +++ b/crypto/fipsmodule/kem/internal.h @@ -109,23 +109,13 @@ int KEM_KEY_set_raw_key(KEM_KEY *key, const uint8_t *in_public, // |key->secret_key| must both be NULL. int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed); -// KEM_check_key function validates a KEM keypair by performing: -// 1. Public key validity checks -// 2. Secret key validity checks -// 3. Pairwise Consistency Test (PCT) - encapsulation/decapsulation round-trip +// KEM_check_key function validates a KEM key based on available key material: +// - If only public key: validates public key only +// - If secret key is present: requires public key and validates public key, secret key, and PCT // -// NOTE: Both public and secret keys must be present. -// Returns 1 on success, 0 on failure. +// NOTE: Returns 1 on success, 0 on failure. int KEM_check_key(const KEM_KEY *key); -// KEM_check_public_key function validates only the public key portion of a KEM key. -// This function performs public key validity checks without requiring the secret key -// or performing the Pairwise Consistency Test (PCT). -// -// NOTE: Only the public key needs to be present. -// Returns 1 on success, 0 on failure. -int KEM_check_public_key(const KEM_KEY *key); - #if defined(__cplusplus) } // extern C #endif diff --git a/crypto/fipsmodule/kem/kem.c b/crypto/fipsmodule/kem/kem.c index 44ebe3e4f2..f2696ac761 100644 --- a/crypto/fipsmodule/kem/kem.c +++ b/crypto/fipsmodule/kem/kem.c @@ -357,8 +357,10 @@ int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed) { return 1; } -// Internal function for public key validation only -int KEM_check_public_key(const KEM_KEY *key) { +// KEM_check_key: validates the key based on available key material +// - If only public key: validates public key only +// - If secret key is present: requires public key and validates public key, secret key, and PCT +int KEM_check_key(const KEM_KEY *key) { if (key == NULL) { OPENSSL_PUT_ERROR(EVP, ERR_R_PASSED_NULL_PARAMETER); return 0; @@ -370,61 +372,14 @@ int KEM_check_public_key(const KEM_KEY *key) { return 0; } - // Check that the public key exists + // Must have at least a public key if (key->public_key == NULL) { OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); return 0; } - // Call appropriate KEM public key check functions based on KEM NID - switch (key->kem->nid) { - case NID_MLKEM512: - case NID_KYBER512_R3: - if (ml_kem_512_check_pk(key->public_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - break; - - case NID_MLKEM768: - case NID_KYBER768_R3: - if (ml_kem_768_check_pk(key->public_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - break; - - case NID_MLKEM1024: - case NID_KYBER1024_R3: - if (ml_kem_1024_check_pk(key->public_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - break; - - default: - // For unsupported KEM variants - OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); - return 0; - } - - return 1; -} - -int KEM_check_key(const KEM_KEY *key) { - if (key == NULL) { - OPENSSL_PUT_ERROR(EVP, ERR_R_PASSED_NULL_PARAMETER); - return 0; - } - - // Check that the KEM method and parameters are valid - if (key->kem == NULL || key->kem->method == NULL) { - OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); - return 0; - } - - // Check that both public and private keys exist - if (key->public_key == NULL || key->secret_key == NULL) { + // If we have a secret key, we must also have a public key for PCT + if (key->secret_key != NULL && key->public_key == NULL) { OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); return 0; } @@ -433,58 +388,67 @@ int KEM_check_key(const KEM_KEY *key) { switch (key->kem->nid) { case NID_MLKEM512: case NID_KYBER512_R3: - // Check public key validity + // Always validate public key if (ml_kem_512_check_pk(key->public_key) != 0) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } - // Check secret key validity - if (ml_kem_512_check_sk(key->secret_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - // Perform Pairwise Consistency Test (PCT) - if (ml_kem_512_check_pct(key->public_key, key->secret_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; + + // If secret key is present, validate it and perform PCT + if (key->secret_key != NULL) { + if (ml_kem_512_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + if (ml_kem_512_check_pct(key->public_key, key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } } break; case NID_MLKEM768: case NID_KYBER768_R3: - // Check public key validity + // Always validate public key if (ml_kem_768_check_pk(key->public_key) != 0) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } - // Check secret key validity - if (ml_kem_768_check_sk(key->secret_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - // Perform Pairwise Consistency Test (PCT) - if (ml_kem_768_check_pct(key->public_key, key->secret_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; + + // If secret key is present, validate it and perform PCT + if (key->secret_key != NULL) { + if (ml_kem_768_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + if (ml_kem_768_check_pct(key->public_key, key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } } break; case NID_MLKEM1024: case NID_KYBER1024_R3: - // Check public key validity + // Always validate public key if (ml_kem_1024_check_pk(key->public_key) != 0) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; } - // Check secret key validity - if (ml_kem_1024_check_sk(key->secret_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - // Perform Pairwise Consistency Test (PCT) - if (ml_kem_1024_check_pct(key->public_key, key->secret_key) != 0) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; + + // If secret key is present, validate it and perform PCT + if (key->secret_key != NULL) { + if (ml_kem_1024_check_sk(key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + if (ml_kem_1024_check_pct(key->public_key, key->secret_key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } } break; From 5b01330b05ea18cf8ccb6e5d3cdaecd9582ed785 Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Tue, 7 Oct 2025 14:04:40 -0700 Subject: [PATCH 6/7] doc nit --- crypto/evp_extra/evp_extra_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/evp_extra/evp_extra_test.cc b/crypto/evp_extra/evp_extra_test.cc index d21d517e56..cec45d09fd 100644 --- a/crypto/evp_extra/evp_extra_test.cc +++ b/crypto/evp_extra/evp_extra_test.cc @@ -2827,7 +2827,7 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { // Create a corrupted secret key std::vector corrupted_sk = sk_copy; - // Corrupt the hash portion of the secret key (last 64 bytes contain hash) + // Corrupt the last 64 bytes of secret key (in ML-KEM this is the hash) // This should cause the secret key validation to fail if (sk_len >= 64) { corrupted_sk[sk_len - 64] ^= 1; From 416f603496bb5463267aeceef40c0c61d3515f12 Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Tue, 7 Oct 2025 14:15:51 -0700 Subject: [PATCH 7/7] remove unnecessary tests --- crypto/evp_extra/evp_extra_test.cc | 34 ++---------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/crypto/evp_extra/evp_extra_test.cc b/crypto/evp_extra/evp_extra_test.cc index cec45d09fd..16f546ad76 100644 --- a/crypto/evp_extra/evp_extra_test.cc +++ b/crypto/evp_extra/evp_extra_test.cc @@ -2903,35 +2903,7 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { EXPECT_FALSE(EVP_PKEY_check(corrupted_public_only_ctx.get())); EXPECT_FALSE(EVP_PKEY_public_check(corrupted_public_only_ctx.get())); - // ---- 7. Test with invalid key sizes ---- - // Test with public key that's too small - if (pk_len > 1) { - std::vector short_pk(pk_len - 1, 0x00); - - // This should fail during key creation due to wrong size - bssl::UniquePtr short_pk_pkey( - EVP_PKEY_kem_new_raw_public_key(GetParam().nid, short_pk.data(), pk_len - 1)); - EXPECT_FALSE(short_pk_pkey); - - // Clear any errors from the failed key creation - ERR_clear_error(); - } - - // ---- 8. Test with invalid secret key size ---- - if (sk_len > 1) { - std::vector short_sk(sk_len - 1, 0x00); - - // This should fail during key creation due to wrong size - bssl::UniquePtr short_sk_pkey( - EVP_PKEY_kem_new_raw_key(GetParam().nid, pk_copy.data(), pk_len, - short_sk.data(), sk_len - 1)); - EXPECT_FALSE(short_sk_pkey); - - // Clear any errors from the failed key creation - ERR_clear_error(); - } - - // ---- 9. Test with secret key only (no public key) ---- + // ---- 7. Test with secret key only (no public key) ---- // Create EVP_PKEY with only secret key (no public key) bssl::UniquePtr secret_only_pkey( EVP_PKEY_kem_new_raw_secret_key(GetParam().nid, sk_copy.data(), sk_len)); @@ -2945,7 +2917,7 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { EXPECT_FALSE(EVP_PKEY_check(secret_only_ctx.get())); EXPECT_FALSE(EVP_PKEY_public_check(secret_only_ctx.get())); - // ---- 10. Verify original valid key still works ---- + // ---- 8. Verify original valid key still works ---- // Make sure our tests didn't affect the original valid key EXPECT_TRUE(EVP_PKEY_check(kem_key_ctx.get())); EXPECT_TRUE(EVP_PKEY_public_check(kem_key_ctx.get())); @@ -2955,8 +2927,6 @@ TEST_P(PerKEMTest, KEMCheckKeyNegativeTests) { } - - // Perform Known Answer Test (KAT) on known KEMs. // These tests access the deterministic EVP APIs for KeyGen and Encapsulation. // To perform KATs in KEMs we use a DRBG seeded with a given state "seed".