Skip to content

Commit

Permalink
ML-KEM Service Indicator for EVP_PKEY_keygen, EVP_PKEY_encapsulate, E…
Browse files Browse the repository at this point in the history
…VP_PKEY_decapsulate (#1844)

### Description of changes:
* Fixes `EVP_DigestSign` to not set the indicator on size checks
* Adds service indicator for ML-KEM-{5127,768,1024} for
`EVP_PKEY_keygen`, `EVP_PKEY_encapsulate`, `EVP_PKEY_decapsulate`
functions.
* Adjusts the internal `pkey_kem_keygen` function to use `kem_key` union
member for consistency when setting the generated keypair on the
EVP_PKEY.

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license and the ISC license.
  • Loading branch information
skmcgrail authored Sep 11, 2024
1 parent 51d9a8d commit 9a644b2
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 19 deletions.
4 changes: 3 additions & 1 deletion crypto/fipsmodule/evp/digestsign.c
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ int EVP_DigestSign(EVP_MD_CTX *ctx, uint8_t *out_sig, size_t *out_sig_len,
data_len);
end:
FIPS_service_indicator_unlock_state();
if (ret > 0) {
if (ret > 0 && out_sig != NULL) {
// Indicator should only be set if we performed crypto, don't set if we only
// performed a size check.
EVP_DigestSign_verify_service_indicator(ctx);
}
return ret;
Expand Down
54 changes: 40 additions & 14 deletions crypto/fipsmodule/evp/evp_ctx.c
Original file line number Diff line number Diff line change
Expand Up @@ -591,30 +591,56 @@ int EVP_PKEY_encapsulate_deterministic(EVP_PKEY_CTX *ctx,
seed, seed_len);
}

int EVP_PKEY_encapsulate(EVP_PKEY_CTX *ctx,
uint8_t *ciphertext, size_t *ciphertext_len,
uint8_t *shared_secret, size_t *shared_secret_len) {
int EVP_PKEY_encapsulate(EVP_PKEY_CTX *ctx, uint8_t *ciphertext,
size_t *ciphertext_len, uint8_t *shared_secret,
size_t *shared_secret_len) {
// We have to avoid potential underlying services updating the indicator
// state, so we lock the state here.
FIPS_service_indicator_lock_state();
SET_DIT_AUTO_DISABLE;
int ret = 0;
if (ctx == NULL || ctx->pmeth == NULL || ctx->pmeth->encapsulate == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
return 0;
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
goto end;
}

return ctx->pmeth->encapsulate(ctx, ciphertext, ciphertext_len,
shared_secret, shared_secret_len);
if (!ctx->pmeth->encapsulate(ctx, ciphertext, ciphertext_len, shared_secret,
shared_secret_len)) {
goto end;
}
ret = 1;
end:
FIPS_service_indicator_unlock_state();
if (ret && ciphertext != NULL && shared_secret != NULL) {
EVP_PKEY_encapsulate_verify_service_indicator(ctx);
}
return ret;
}

int EVP_PKEY_decapsulate(EVP_PKEY_CTX *ctx,
uint8_t *shared_secret, size_t *shared_secret_len,
const uint8_t *ciphertext, size_t ciphertext_len) {
int EVP_PKEY_decapsulate(EVP_PKEY_CTX *ctx, uint8_t *shared_secret,
size_t *shared_secret_len, const uint8_t *ciphertext,
size_t ciphertext_len) {
// We have to avoid potential underlying services updating the indicator
// state, so we lock the state here.
FIPS_service_indicator_lock_state();
SET_DIT_AUTO_DISABLE;
int ret = 0;
if (ctx == NULL || ctx->pmeth == NULL || ctx->pmeth->decapsulate == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
return 0;
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
goto end;
}

return ctx->pmeth->decapsulate(ctx, shared_secret, shared_secret_len,
ciphertext, ciphertext_len);
if (!ctx->pmeth->decapsulate(ctx, shared_secret, shared_secret_len,
ciphertext, ciphertext_len)) {
goto end;
}
ret = 1;
end:
FIPS_service_indicator_unlock_state();
if (ret && shared_secret != NULL) {
EVP_PKEY_decapsulate_verify_service_indicator(ctx);
}
return ret;
}

// Deprecated keygen NO-OP functions
Expand Down
3 changes: 2 additions & 1 deletion crypto/fipsmodule/evp/p_kem.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ static int pkey_kem_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) {
if (key == NULL ||
!KEM_KEY_init(key, kem) ||
!kem->method->keygen(key->public_key, key->secret_key) ||
!EVP_PKEY_assign(pkey, EVP_PKEY_KEM, key)) {
!EVP_PKEY_set_type(pkey, EVP_PKEY_KEM)) {
KEM_KEY_free(key);
return 0;
}
pkey->pkey.kem_key = key;

return 1;
}
Expand Down
6 changes: 6 additions & 0 deletions crypto/fipsmodule/service_indicator/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ void TLSKDF_verify_service_indicator(const EVP_MD *dgst, const char *label,
void SSKDF_digest_verify_service_indicator(const EVP_MD *dgst);
void SSKDF_hmac_verify_service_indicator(const EVP_MD *dgst);
void KBKDF_ctr_hmac_verify_service_indicator(const EVP_MD *dgst);
void EVP_PKEY_encapsulate_verify_service_indicator(const EVP_PKEY_CTX* ctx);
void EVP_PKEY_decapsulate_verify_service_indicator(const EVP_PKEY_CTX* ctx);

#else

Expand Down Expand Up @@ -127,6 +129,10 @@ OPENSSL_INLINE void SSKDF_hmac_verify_service_indicator(

OPENSSL_INLINE void KBKDF_ctr_hmac_verify_service_indicator(OPENSSL_UNUSED const EVP_MD *dgst) {}

OPENSSL_INLINE void EVP_PKEY_encapsulate_verify_service_indicator(OPENSSL_UNUSED const EVP_PKEY_CTX* ctx) {}

OPENSSL_INLINE void EVP_PKEY_decapsulate_verify_service_indicator(OPENSSL_UNUSED const EVP_PKEY_CTX* ctx) {}

#endif // AWSLC_FIPS

// is_fips_build is similar to |FIPS_mode| but returns 1 including in the case
Expand Down
41 changes: 41 additions & 0 deletions crypto/fipsmodule/service_indicator/service_indicator.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,17 @@ void EVP_PKEY_keygen_verify_service_indicator(const EVP_PKEY *pkey) {
if (is_ec_fips_approved(curve_nid)) {
FIPS_service_indicator_update_state();
}
} else if (pkey->type == EVP_PKEY_KEM) {
const KEM *kem = KEM_KEY_get0_kem(pkey->pkey.kem_key);
switch (kem->nid) {
case NID_MLKEM512:
case NID_MLKEM768:
case NID_MLKEM1024:
FIPS_service_indicator_update_state();
break;
default:
break;
}
}
}

Expand Down Expand Up @@ -571,6 +582,36 @@ void KBKDF_ctr_hmac_verify_service_indicator(const EVP_MD *dgst) {
}
}

void EVP_PKEY_encapsulate_verify_service_indicator(const EVP_PKEY_CTX* ctx) {
if (ctx->pkey->type == EVP_PKEY_KEM) {
const KEM *kem = KEM_KEY_get0_kem(ctx->pkey->pkey.kem_key);
switch (kem->nid) {
case NID_MLKEM512:
case NID_MLKEM768:
case NID_MLKEM1024:
FIPS_service_indicator_update_state();
break;
default:
break;
}
}
}

void EVP_PKEY_decapsulate_verify_service_indicator(const EVP_PKEY_CTX* ctx) {
if (ctx->pkey->type == EVP_PKEY_KEM) {
const KEM *kem = KEM_KEY_get0_kem(ctx->pkey->pkey.kem_key);
switch (kem->nid) {
case NID_MLKEM512:
case NID_MLKEM768:
case NID_MLKEM1024:
FIPS_service_indicator_update_state();
break;
default:
break;
}
}
}

#else

uint64_t FIPS_service_indicator_before_call(void) { return 0; }
Expand Down
71 changes: 68 additions & 3 deletions crypto/fipsmodule/service_indicator/service_indicator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2297,21 +2297,31 @@ TEST_P(RSAServiceIndicatorTest, RSASigGen) {
&sig_len)));
EXPECT_EQ(approved, test.sig_gen_expect_approved);

// Test using the one-shot |EVP_DigestSign| function for approval.
md_ctx.Reset();
std::vector<uint8_t> oneshot_output(sig_len);
CALL_SERVICE_AND_CHECK_APPROVED(
approved, ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pctx, test.func(),
nullptr, pkey.get())));
EXPECT_EQ(approved, AWSLC_NOT_APPROVED);

if (test.use_pss) {
CALL_SERVICE_AND_CHECK_APPROVED(approved,
ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING)));
CALL_SERVICE_AND_CHECK_APPROVED(
approved,
ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING)));
EXPECT_EQ(approved, AWSLC_NOT_APPROVED);
CALL_SERVICE_AND_CHECK_APPROVED(
approved, ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1)));
EXPECT_EQ(approved, AWSLC_NOT_APPROVED);
}

// Test the one-shot |EVP_DigestSign| function to determine size.
// This should not set the indicator.
CALL_SERVICE_AND_CHECK_APPROVED(
approved,
ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), nullptr, &sig_len, nullptr, 0)));
EXPECT_EQ(approved, AWSLC_NOT_APPROVED);

// Now test using the one-shot |EVP_DigestSign| function for approval.
CALL_SERVICE_AND_CHECK_APPROVED(approved,
ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), oneshot_output.data(), &sig_len,
kPlaintext, sizeof(kPlaintext))));
Expand Down Expand Up @@ -4557,6 +4567,61 @@ TEST_P(KBKDFCtrHmacIndicatorTest, KBKDF) {
ASSERT_EQ(vector.expectation, approved);
}

TEST(ServiceIndicatorTest, ML_KEM) {
for (int nid : {NID_MLKEM512, NID_MLKEM768, NID_MLKEM1024}) {
bssl::UniquePtr<EVP_PKEY_CTX> ctx(
EVP_PKEY_CTX_new_id(EVP_PKEY_KEM, nullptr));
ASSERT_TRUE(EVP_PKEY_CTX_kem_set_params(ctx.get(), nid));
ASSERT_TRUE(EVP_PKEY_keygen_init(ctx.get()));

FIPSStatus approved = AWSLC_NOT_APPROVED;
EVP_PKEY *raw = nullptr;
// keygen for ML-KEM algorithms should be approved
CALL_SERVICE_AND_CHECK_APPROVED(approved, EVP_PKEY_keygen(ctx.get(), &raw));
bssl::UniquePtr<EVP_PKEY> pkey(raw);
ASSERT_EQ(approved, AWSLC_APPROVED);

size_t ciphertext_len = 0;
size_t shared_secret_len = 0;

ctx.reset(EVP_PKEY_CTX_new(pkey.get(), nullptr));

approved = AWSLC_NOT_APPROVED;
// encapsulate size check should not set indicator
CALL_SERVICE_AND_CHECK_APPROVED(
approved, EVP_PKEY_encapsulate(ctx.get(), nullptr, &ciphertext_len,
nullptr, &shared_secret_len));
ASSERT_EQ(approved, AWSLC_NOT_APPROVED);

std::vector<uint8_t> ciphertext(ciphertext_len);
std::vector<uint8_t> encap_shared_secret(shared_secret_len);

// encapsulate should set indicator
CALL_SERVICE_AND_CHECK_APPROVED(
approved,
EVP_PKEY_encapsulate(ctx.get(), ciphertext.data(), &ciphertext_len,
encap_shared_secret.data(), &shared_secret_len));
ASSERT_EQ(approved, AWSLC_APPROVED);

shared_secret_len = 0;
approved = AWSLC_NOT_APPROVED;
// decapsulate size check should not set indicator
CALL_SERVICE_AND_CHECK_APPROVED(
approved, EVP_PKEY_decapsulate(ctx.get(), nullptr, &shared_secret_len,
ciphertext.data(), ciphertext.size()));
ASSERT_EQ(approved, AWSLC_NOT_APPROVED);

std::vector<uint8_t> decap_shared_secret(shared_secret_len);
// decapsulate should set indicator
CALL_SERVICE_AND_CHECK_APPROVED(
approved, EVP_PKEY_decapsulate(ctx.get(), decap_shared_secret.data(),
&shared_secret_len, ciphertext.data(),
ciphertext.size()));
ASSERT_EQ(approved, AWSLC_APPROVED);
ASSERT_EQ(encap_shared_secret, decap_shared_secret);
}
}

// Verifies that the awslc_version_string is as expected.
// Since this is running in FIPS mode it should end in FIPS
// Update this when the AWS-LC version number is modified
Expand Down

0 comments on commit 9a644b2

Please sign in to comment.