Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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;
}
Comment on lines +322 to +326
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?


// 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;
Comment on lines +333 to +342
Copy link
Contributor

Choose a reason for hiding this comment

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

We would also need a pair-consistency check, otherwise the secret and public keys might be unrelated. (?)

Copy link
Collaborator Author

@jakemas jakemas Oct 3, 2025

Choose a reason for hiding this comment

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

Good idea -- I'll have to make changes upstream in mlkem-native to support this at it would require exposing/namspacing the pct tests, i.e. would need to add

#define crypto_kem_check_pct MLK_NAMESPACE_K(check_pct)

to /crypto/fipsmodule/ml_kem/mlkem/kem.h and then I can hook it up just like I did the check_{pk/sk} tests that become ml_kem_512_check_pk etc.

and some changes to make sure they are available, as currently the PCTs are only defined in FIPS builds (when MLK_CONFIG_KEYGEN_PCT)

ooor we avoid all of that, and could just do a cheeky little encap decap in this function itself? I'll go check out the other algorithm variants of this function.


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