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..16f546ad76 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 @@ -2780,6 +2777,156 @@ 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 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; + 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 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 + 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 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) + 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); + + // 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) ---- + 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 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())); + + // ---- 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())); + + // 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 7993293b3a..8a990894e9 100644 --- a/crypto/fipsmodule/kem/internal.h +++ b/crypto/fipsmodule/kem/internal.h @@ -109,6 +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 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: 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 575f0696a2..f2696ac761 100644 --- a/crypto/fipsmodule/kem/kem.c +++ b/crypto/fipsmodule/kem/kem.c @@ -356,3 +356,107 @@ int KEM_KEY_set_raw_keypair_from_seed(KEM_KEY *key, const CBS *seed) { return 1; } + +// 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; + } + + // 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; + } + + // Must have at least a public key + if (key->public_key == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); + return 0; + } + + // 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; + } + + // Call appropriate ML-KEM check functions based on KEM NID + switch (key->kem->nid) { + case NID_MLKEM512: + case NID_KYBER512_R3: + // Always validate public key + if (ml_kem_512_check_pk(key->public_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: + // Always validate public key + if (ml_kem_768_check_pk(key->public_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: + // Always validate public key + if (ml_kem_1024_check_pk(key->public_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; + + 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..97bc6459be 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem.c +++ b/crypto/fipsmodule/ml_kem/ml_kem.c @@ -394,3 +394,214 @@ 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); +} + +// 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 8da358e2a0..d2b8123fda 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem.h +++ b/crypto/fipsmodule/ml_kem/ml_kem.h @@ -175,6 +175,24 @@ 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 +int ml_kem_512_check_pk(const uint8_t *public_key /* IN */); +int ml_kem_512_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 */); + +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