-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rehaul PQDSA Test Suite #2062
Rehaul PQDSA Test Suite #2062
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #2062 +/- ##
==========================================
+ Coverage 78.77% 78.78% +0.01%
==========================================
Files 598 598
Lines 103744 103744
Branches 14734 14732 -2
==========================================
+ Hits 81720 81736 +16
+ Misses 21371 21357 -14
+ Partials 653 651 -2 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for documenting these tests so thoroughly, Jake, and for the detailed PR description. Most comments are Nits. One return value is to be discussed.
crypto/dilithium/p_pqdsa_test.cc
Outdated
bssl::UniquePtr<EVP_PKEY> public_pkey(EVP_PKEY_pqdsa_new_raw_public_key(nid, pkey->pkey.pqdsa_key->public_key, pk_len)); | ||
bssl::UniquePtr<EVP_PKEY> private_pkey(EVP_PKEY_pqdsa_new_raw_private_key(nid, pkey->pkey.pqdsa_key->private_key, sk_len)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to move the second line for private_key
right before the checks so it's more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually disagree here, I was inspired by Dusan's extremely clean and readable tests for ML-KEM that order the code systematically, calling similar functionality together, for example see:
https://github.com/jakemas/aws-lc/blob/ab8953b7478f72a2b80497cf54ce0b72ed719cfc/crypto/evp_extra/evp_extra_test.cc#L2528-L2533
(Also, it's used just 7 lines down from where it's defined, which seems more readable personally to me)
crypto/dilithium/p_pqdsa_test.cc
Outdated
ASSERT_FALSE(EVP_PKEY_CTX_pqdsa_set_params(ctx.get(), nid)); | ||
err = ERR_get_error(); | ||
EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err)); | ||
EXPECT_EQ(ERR_R_PASSED_NULL_PARAMETER, ERR_GET_REASON(err)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED
in this case because there is not passed in null parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, we set ctx->data
to NULL, then pass in ctx
, expecting a failure because of a null value. This is consistant with how we use the error code in ML-KEM at https://github.com/jakemas/aws-lc/blob/ab8953b7478f72a2b80497cf54ce0b72ed719cfc/crypto/evp_extra/evp_extra_test.cc#L2229
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good to be consistent, I agree. We can later revisit it to see if we want to return another error message. I believe the data would be null if the context wasn't initialised or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we leave as is for now then? In my opinion the most useful such a generic error message can be is when it is consistent with other failures cases in the library.
crypto/dilithium/p_pqdsa_test.cc
Outdated
// 1. Creates a |EVP_PKEY_CTX| object of type: EVP_PKEY_PQDSA. | ||
// 2. Sets the specific PQDSA parameters according to the |pqdsa_nid| provided. | ||
// 3. Generates a key pair. | ||
// 4. Creates an EVP PKEY object from the generated key (as a bssl::UniquePtr). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 4. Creates an EVP PKEY object from the generated key (as a bssl::UniquePtr). | |
// 4. Creates an EVP_PKEY object from the generated key (as a bssl::UniquePtr). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in f7d1dad
crypto/dilithium/p_pqdsa_test.cc
Outdated
} | ||
|
||
TEST_P(PQDSAParameterTest, KeyCmp) { | ||
// Generate two MLDSA keys are check that they are not equal. | ||
// Generate two PQDSA keys are check that they are not equal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Generate two PQDSA keys are check that they are not equal. | |
// Generate two PQDSA keys and check that they are not equal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in f7d1dad
crypto/dilithium/p_pqdsa_test.cc
Outdated
bssl::UniquePtr<EVP_PKEY> public_pkey(EVP_PKEY_pqdsa_new_raw_public_key(nid, pkey->pkey.pqdsa_key->public_key, pk_len)); | ||
bssl::UniquePtr<EVP_PKEY> private_pkey(EVP_PKEY_pqdsa_new_raw_private_key(nid, pkey->pkey.pqdsa_key->private_key, sk_len)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines length
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in f7d1dad
crypto/dilithium/p_pqdsa_test.cc
Outdated
uint32_t err = ERR_get_error(); | ||
EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err)); | ||
EXPECT_EQ(EVP_R_BUFFER_TOO_SMALL, ERR_GET_REASON(err)); | ||
OPENSSL_free(buf); | ||
buf = nullptr; | ||
EXPECT_EQ(ERR_R_PASSED_NULL_PARAMETER, ERR_GET_REASON(err)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want, these three lines that repeat can be made into a macro. I think all of them are ERR_LIB_EVP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand exactly what you mean here. Open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 3 lines
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
EXPECT_EQ(ERR_R_PASSED_NULL_PARAMETER, ERR_GET_REASON(err));
repeat throughout the tests and can be replaced by a single-line macro that takes the reason as a parameter (similar to the CMP macros).
It will condense the code a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
16f48d6 done
crypto/dilithium/p_pqdsa_test.cc
Outdated
ASSERT_FALSE(EVP_DigestVerify(md_ctx.get(), signature1.data(), sig_len, | ||
msg2.data(), msg2.size())); | ||
// ---- 3. Test signature failure modes: incompatible messages/signatures ---- | ||
// Check that the verification of signature1 failes for a different message; message2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Check that the verification of signature1 failes for a different message; message2 | |
// Check that the verification of signature1 fails for a different message; message2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in f7d1dad
msg1.data(), msg1.size())); | ||
// ---- 2. Test signature round trip (sign + verify) ---- | ||
|
||
// Initalize the signing context |md_ctx| with the |pkey| we generated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want, you can replace in the comments
message1 -> msg1
message2 -> msg2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in f7d1dad
crypto/dilithium/p_pqdsa_test.cc
Outdated
md_ctx.Reset(); | ||
md_ctx_verify.Reset(); | ||
|
||
// PQDSA signature schemes can be in either randomized (every signature on a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// PQDSA signature schemes can be in either randomized (every signature on a | |
// PQDSA signature schemes can be either in randomized (every signature on a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in f7d1dad
crypto/dilithium/p_pqdsa_test.cc
Outdated
bssl::UniquePtr<EVP_PKEY>public_pkey( | ||
EVP_PKEY_pqdsa_new_raw_public_key(nid, pkey->pkey.pqdsa_key->public_key, pk_len)); | ||
bssl::UniquePtr<EVP_PKEY> private_pkey( | ||
EVP_PKEY_pqdsa_new_raw_private_key(nid, pkey->pkey.pqdsa_key->private_key, sk_len)); | ||
|
||
// check that public key is present and private key is not present |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I suggest then to add to this comment "in public_key" and to the one on l. 1282 "in private_key" because I got a bit confused about what the comment meant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crypto/dilithium/p_pqdsa_test.cc
Outdated
uint32_t err = ERR_get_error(); | ||
EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err)); | ||
EXPECT_EQ(EVP_R_BUFFER_TOO_SMALL, ERR_GET_REASON(err)); | ||
OPENSSL_free(buf); | ||
buf = nullptr; | ||
EXPECT_EQ(ERR_R_PASSED_NULL_PARAMETER, ERR_GET_REASON(err)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 3 lines
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
EXPECT_EQ(ERR_R_PASSED_NULL_PARAMETER, ERR_GET_REASON(err));
repeat throughout the tests and can be replaced by a single-line macro that takes the reason as a parameter (similar to the CMP macros).
It will condense the code a bit.
Issues:
Resolves #CryptoAlg-2800
Description of changes:
FIPS 204 states:
This description outlines how these checks are performed within our implementation of PQDSA in AWS-LC.
Signature Length
When attempting to verify a signature, the user will call:
This will call into the PKEY function method for ML-DSA verify
pkey_pqdsa_verify_signature
, defined withinp_pqdsa.c
. This function performs the check that the signature length provided matches the expected signature length defined for the particular algorithm:We are able to explicitly prove these failure modes within the PQDSA test suite
PQDSAParameterTest
. TheSIGOperations
test has been modified to include a check that signature verification fails when providing a signature of invalid length. For example:Public Key Length
As the length of the public key is not provided to the verify API, we are not able to validate the size in the same way as signature length. However, as the public key is provided via a PKEY structure, we can enforce the requirement that prevents public keys of the incorrect length by checking the size of the public key during it’s addition to the PKEY structure.
How do ML-DSA keys enter EVP Verify?
The EVP API for verify init is as follows:
EVP_DigestVerifyInit(&md_ctx_verify, nullptr, nullptr, nullptr, key);
Where the public key either enters explicitly as a PKEY argument
key
or through theEVP_MD_CTX
argumentmd_ctx_verify
as apctx
.Thus, to show that verify will only accept public key inputs of the correct length, we must evidence that:
we cannot construct a PQDSA PKEY such that the public/private key is not of the size required for that parameter set.
We now verify for every method to construct an PQDSA PKEY that the size of the public/private key are exactly the size required for that parameter set. The methods to construct an PQDSA PKEY are as follows:
EVP_PKEY_pqdsa_new_raw_public_key
EVP_PKEY_pqdsa_new_raw_private_key
EVP_parse_public_key
EVP_parse_private_key
PQDSA
keypair, then set the parameters to a different parameter set usingEVP_PKEY_CTX_pqdsa_set_params
We now discuss each method, outlining any testing that has been added to provide evidence that this cannot occur (where necessary).
(1)
EVP_PKEY_pqdsa_new_raw_public_key
This function takes three inputs;
nid, in, len,
where:nid
is the algorithm identifierin
is the contents of the public key (raw)len
is the length ofin
We test failure modes for all three input parameters within the
RawFunctions
tests as part of the PQDSA test suitePQDSAParameterTest
. In particular, we verify that the length of the input public keylen
must be exactly equal to the expected length of the associated public key for that parameter set. Explicitly, this is verified within the functionEVP_PKEY_pqdsa_new_raw_public_key
by the check:p_pqdsa.c lines 217-220
(2)
EVP_PKEY_pqdsa_new_raw_private_key
Similarly to above, this function takes three inputs;
nid, in, len,
where:nid
is the algorithm identifierin
is the contents of the private key (raw)len
is the length ofin
Failure modes are tested within the
RawFunctions
tests as part of the PQDSA test suitePQDSAParameterTest
. In particular, we verify that the length of the input private keylen
must be exactly equal to the expected length of the associated private key for that parameter set. Explicitly, this is verified within the functionEVP_PKEY_pqdsa_new_raw_private_key
by the check:p_pqdsa.c lines 247-250
(3)
EVP_parse_public_key
EVP_parse_public_key
will call the specific PQDSA ASN.1 decode functionpqdsa_pub_decode
, which in turn will callPQDSA_KEY_set_raw_public_key
. This function performs a lookup of the expected public key size from the parameter set, and duplicates exactly that many bytes from the input buffer to the PKEY’s public key. Disallowing any other size value.pqdsa.c line 71
key->public_key = OPENSSL_memdup(in, key->pqdsa->public_key_len);
(4)
EVP_parse_private_key
EVP_parse_private_key
will call the specific PQDSA ASN.1 decode functionpqdsa_priv_decode
, which in turn will callPQDSA_KEY_set_raw_private_key
. This function performs a lookup of the expected private key size from the parameter set, and duplicates exactly that many bytes from the input buffer to the PKEY’s private key. Disallowing any other size value.pqdsa.c line 80
key->private_key = OPENSSL_memdup(in, key->pqdsa->private_key_len);
(5)
EVP_PKEY_CTX_pqdsa_set_params
Theoretically, we could create bad keypairs by misaligning a PKEY with the associated parameter set. The function that sets PQDSA parameter sets is
EVP_PKEY_CTX_pqdsa_set_params
, that takes the arguments:ctx
the inputEVP_PKEY_CTX
that we want to set the parameter set onnid
the algorithm identifierFailure modes are tested within the
KeyGen
tests as part of the PQDSA test suitePQDSAParameterTest
. In particular, we verify that:ERR_R_PASSED_NULL_PARAMETER
whenctx
is null.ERR_R_PASSED_NULL_PARAMETER
whenctx->data
is null.EVP_R_INVALID_OPERATION
whenctx->pkey
is not null.EVP_R_UNSUPPORTED_ALGORITHM
whennid
is not a PQDSA.EVP_PKEY_pqdsa_set_params
on an initalized PKEY will null the public/private keysCall-outs:
As we prepare to remove the
enable_dilithium
flag, I am pre-empting missing test coverage.The
SIGOperations
test has been improved to the following flow:EVP_DigestSignInit
EVP_DigestSign
with signature length as null to get the expected size of the signatureEVP_DigestSign
with correct signature lengthEVP_VerifyInit
EVP_DigestVerify
to verify signatureBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.