Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
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
4 changes: 4 additions & 0 deletions crypto/evp_extra/evp_asn1.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
9 changes: 3 additions & 6 deletions crypto/evp_extra/evp_extra_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<EVP_PKEY_CTX> 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
Expand Down
8 changes: 8 additions & 0 deletions crypto/fipsmodule/kem/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
Expand Down
72 changes: 72 additions & 0 deletions crypto/fipsmodule/kem/kem.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NP: It's currently possible (although I wish it weren't) for the secret_key to be set but not the public_key. For those, this would always return an error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently intended behaviour, my idea is to merge the PRs first that ensure we can populate both. Then we fail this check if pub_key isn't set when secrect_key is? How does that sound?

Copy link
Collaborator Author

@jakemas jakemas Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once caveat is that EVP_PKEY_public_check also calls the same function e.g. EC_KEY_check_key that just handles it. See here. Little bit strange, but if we follow the pattern, we'd have to make KEM_CHECK ok with pub only. Or we could implement two functions, the second KEM_CHECK_PUBLIC_KEY and call the it from EVP_PKEY_public_check: drafted that idea out here 0616873, but I don't think I like the anti-pattern. Looking at EC check for inspo, I will implement similar to how they do it, If only public key: validates public key only, if secret key is present: requires public key and validates public key, secret key, and PCT.

Copy link
Collaborator Author

@jakemas jakemas Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've landed on f30bfd4 that preserves functionality with other EVP_PKEY_public_check/EVP_PKEY_check functions. As documented, it

// 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

I added negative testing for a bunch of cases, including the one you mention:

  // ---- 7. Test with secret key only (no public key) ----
  // Create EVP_PKEY with only secret key (no public key)


// Call appropriate ML-KEM check functions based on KEM NID
switch (key->kem->nid) {
case NID_MLKEM512:
case NID_KYBER512_R3:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should mix-up Kyber with ML-KEM, technically they are not the same algorithms. I'd drop Kyber NIDs from here.

// 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;
}

51 changes: 51 additions & 0 deletions crypto/fipsmodule/ml_kem/ml_kem.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
10 changes: 10 additions & 0 deletions crypto/fipsmodule/ml_kem/ml_kem.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 */);

#if defined(__cplusplus)
}
#endif
Expand Down
Loading