From df8f52ee8dded3b1e2fae3ead6e47e02a8a55006 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Thu, 6 May 2021 08:10:58 +0100 Subject: [PATCH] KeyMint VTS: local asymmetric verification Change verification of ECDSA and RSA signatures so it happens locally in the test, rather than by invoking a VERIFY operation against KeyMint. Test: VtsAidlKeyMintTargetTest Change-Id: I0efc30f3c96cd70ac636d34718eff53cc23f1480 --- .../vts/functional/KeyMintAidlTestBase.cpp | 109 +++++++ .../aidl/vts/functional/KeyMintAidlTestBase.h | 2 + .../aidl/vts/functional/KeyMintTest.cpp | 292 +++++------------- 3 files changed, 194 insertions(+), 209 deletions(-) diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp index 47892042e7..1a05ac8b11 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp @@ -59,6 +59,11 @@ using ::testing::MatchesRegex; namespace test { namespace { + +// Overhead for PKCS#1 v1.5 signature padding of undigested messages. Digested messages have +// additional overhead, for the digest algorithmIdentifier required by PKCS#1. +const size_t kPkcs1UndigestedSignaturePaddingOverhead = 11; + typedef KeyMintAidlTestBase::KeyData KeyData; // Predicate for testing basic characteristics validity in generation or import. bool KeyCharacteristicsBasicallyValid(SecurityLevel secLevel, @@ -590,6 +595,110 @@ void KeyMintAidlTestBase::VerifyMessage(const string& message, const string& sig VerifyMessage(key_blob_, message, signature, params); } +void KeyMintAidlTestBase::LocalVerifyMessage(const string& message, const string& signature, + const AuthorizationSet& params) { + SCOPED_TRACE("LocalVerifyMessage"); + + // Retrieve the public key from the leaf certificate. + ASSERT_GT(cert_chain_.size(), 0); + X509_Ptr key_cert(parse_cert_blob(cert_chain_[0].encodedCertificate)); + ASSERT_TRUE(key_cert.get()); + EVP_PKEY_Ptr pub_key(X509_get_pubkey(key_cert.get())); + ASSERT_TRUE(pub_key.get()); + + Digest digest = params.GetTagValue(TAG_DIGEST).value(); + PaddingMode padding = PaddingMode::NONE; + auto tag = params.GetTagValue(TAG_PADDING); + if (tag.has_value()) { + padding = tag.value(); + } + + if (digest == Digest::NONE) { + switch (EVP_PKEY_id(pub_key.get())) { + case EVP_PKEY_EC: { + vector data((EVP_PKEY_bits(pub_key.get()) + 7) / 8); + size_t data_size = std::min(data.size(), message.size()); + memcpy(data.data(), message.data(), data_size); + EC_KEY_Ptr ecdsa(EVP_PKEY_get1_EC_KEY(pub_key.get())); + ASSERT_TRUE(ecdsa.get()); + ASSERT_EQ(1, + ECDSA_verify(0, reinterpret_cast(data.data()), data_size, + reinterpret_cast(signature.data()), + signature.size(), ecdsa.get())); + break; + } + case EVP_PKEY_RSA: { + vector data(EVP_PKEY_size(pub_key.get())); + size_t data_size = std::min(data.size(), message.size()); + memcpy(data.data(), message.data(), data_size); + + RSA_Ptr rsa(EVP_PKEY_get1_RSA(const_cast(pub_key.get()))); + ASSERT_TRUE(rsa.get()); + + size_t key_len = RSA_size(rsa.get()); + int openssl_padding = RSA_NO_PADDING; + switch (padding) { + case PaddingMode::NONE: + ASSERT_TRUE(data_size <= key_len); + ASSERT_EQ(key_len, signature.size()); + openssl_padding = RSA_NO_PADDING; + break; + case PaddingMode::RSA_PKCS1_1_5_SIGN: + ASSERT_TRUE(data_size + kPkcs1UndigestedSignaturePaddingOverhead <= + key_len); + openssl_padding = RSA_PKCS1_PADDING; + break; + default: + ADD_FAILURE() << "Unsupported RSA padding mode " << padding; + } + + vector decrypted_data(key_len); + int bytes_decrypted = RSA_public_decrypt( + signature.size(), reinterpret_cast(signature.data()), + decrypted_data.data(), rsa.get(), openssl_padding); + ASSERT_GE(bytes_decrypted, 0); + + const uint8_t* compare_pos = decrypted_data.data(); + size_t bytes_to_compare = bytes_decrypted; + uint8_t zero_check_result = 0; + if (padding == PaddingMode::NONE && data_size < bytes_to_compare) { + // If the data is short, for "unpadded" signing we zero-pad to the left. So + // during verification we should have zeros on the left of the decrypted data. + // Do a constant-time check. + const uint8_t* zero_end = compare_pos + bytes_to_compare - data_size; + while (compare_pos < zero_end) zero_check_result |= *compare_pos++; + ASSERT_EQ(0, zero_check_result); + bytes_to_compare = data_size; + } + ASSERT_EQ(0, memcmp(compare_pos, data.data(), bytes_to_compare)); + break; + } + default: + ADD_FAILURE() << "Unknown public key type"; + } + } else { + EVP_MD_CTX digest_ctx; + EVP_MD_CTX_init(&digest_ctx); + EVP_PKEY_CTX* pkey_ctx; + const EVP_MD* md = openssl_digest(digest); + ASSERT_NE(md, nullptr); + ASSERT_EQ(1, EVP_DigestVerifyInit(&digest_ctx, &pkey_ctx, md, nullptr, pub_key.get())); + + if (padding == PaddingMode::RSA_PSS) { + EXPECT_GT(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING), 0); + EXPECT_GT(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, EVP_MD_size(md)), 0); + } + + ASSERT_EQ(1, EVP_DigestVerifyUpdate(&digest_ctx, + reinterpret_cast(message.data()), + message.size())); + ASSERT_EQ(1, EVP_DigestVerifyFinal(&digest_ctx, + reinterpret_cast(signature.data()), + signature.size())); + EVP_MD_CTX_cleanup(&digest_ctx); + } +} + string KeyMintAidlTestBase::EncryptMessage(const vector& key_blob, const string& message, const AuthorizationSet& in_params, AuthorizationSet* out_params) { diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h index cb38938684..1a14d47904 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h @@ -162,6 +162,8 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam { const string& signature, const AuthorizationSet& params); void VerifyMessage(const string& message, const string& signature, const AuthorizationSet& params); + void LocalVerifyMessage(const string& message, const string& signature, + const AuthorizationSet& params); string EncryptMessage(const vector& key_blob, const string& message, const AuthorizationSet& in_params, AuthorizationSet* out_params); diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp index cd7d603a09..2c0c459a2a 100644 --- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp @@ -482,7 +482,6 @@ class NewKeyGenerationTest : public KeyMintAidlTestBase { void CheckBaseParams(const vector& keyCharacteristics) { AuthorizationSet auths = CheckCommonParams(keyCharacteristics); EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::SIGN)); - EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::VERIFY)); // Check that some unexpected tags/values are NOT present. EXPECT_FALSE(auths.Contains(TAG_PURPOSE, KeyPurpose::ENCRYPT)); @@ -495,7 +494,6 @@ class NewKeyGenerationTest : public KeyMintAidlTestBase { EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::DECRYPT)); EXPECT_FALSE(auths.Contains(TAG_PURPOSE, KeyPurpose::SIGN)); - EXPECT_FALSE(auths.Contains(TAG_PURPOSE, KeyPurpose::VERIFY)); } AuthorizationSet CheckCommonParams(const vector& keyCharacteristics) { @@ -1986,6 +1984,50 @@ TEST_P(SigningOperationsTest, RsaSuccess) { string message = "12345678901234567890123456789012"; string signature = SignMessage( message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)); + LocalVerifyMessage(message, signature, + AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)); +} + +/* + * SigningOperationsTest.RsaAllPaddingsAndDigests + * + * Verifies RSA signature/verification for all padding modes and digests. + */ +TEST_P(SigningOperationsTest, RsaAllPaddingsAndDigests) { + auto authorizations = AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .RsaSigningKey(2048, 65537) + .Digest(ValidDigests(true /* withNone */, true /* withMD5 */)) + .Padding(PaddingMode::NONE) + .Padding(PaddingMode::RSA_PSS) + .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) + .SetDefaultValidity(); + + ASSERT_EQ(ErrorCode::OK, GenerateKey(authorizations)); + + string message(128, 'a'); + string corrupt_message(message); + ++corrupt_message[corrupt_message.size() / 2]; + + for (auto padding : + {PaddingMode::NONE, PaddingMode::RSA_PSS, PaddingMode::RSA_PKCS1_1_5_SIGN}) { + for (auto digest : ValidDigests(true /* withNone */, true /* withMD5 */)) { + if (padding == PaddingMode::NONE && digest != Digest::NONE) { + // Digesting only makes sense with padding. + continue; + } + + if (padding == PaddingMode::RSA_PSS && digest == Digest::NONE) { + // PSS requires digesting. + continue; + } + + string signature = + SignMessage(message, AuthorizationSetBuilder().Digest(digest).Padding(padding)); + LocalVerifyMessage(message, signature, + AuthorizationSetBuilder().Digest(digest).Padding(padding)); + } + } } /* @@ -2431,6 +2473,39 @@ TEST_P(SigningOperationsTest, EcdsaAllSizesAndHashes) { } } +/* + * SigningOperationsTest.EcdsaAllDigestsAndCurves + * + * Verifies ECDSA signature/verification for all digests and curves. + */ +TEST_P(SigningOperationsTest, EcdsaAllDigestsAndCurves) { + auto digests = ValidDigests(true /* withNone */, false /* withMD5 */); + + string message = "1234567890"; + string corrupt_message = "2234567890"; + for (auto curve : ValidCurves()) { + SCOPED_TRACE(testing::Message() << "Curve::" << curve); + ErrorCode error = GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .EcdsaSigningKey(curve) + .Digest(digests) + .SetDefaultValidity()); + EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate key for EC curve " << curve; + if (error != ErrorCode::OK) { + continue; + } + + for (auto digest : digests) { + SCOPED_TRACE(testing::Message() << "Digest::" << digest); + string signature = SignMessage(message, AuthorizationSetBuilder().Digest(digest)); + LocalVerifyMessage(message, signature, AuthorizationSetBuilder().Digest(digest)); + } + + auto rc = DeleteKey(); + ASSERT_TRUE(rc == ErrorCode::OK || rc == ErrorCode::UNIMPLEMENTED); + } +} + /* * SigningOperationsTest.EcdsaAllCurves * @@ -2698,207 +2773,6 @@ INSTANTIATE_KEYMINT_AIDL_TEST(SigningOperationsTest); typedef KeyMintAidlTestBase VerificationOperationsTest; -/* - * VerificationOperationsTest.RsaSuccess - * - * Verifies that a simple RSA signature/verification sequence succeeds. - */ -TEST_P(VerificationOperationsTest, RsaSuccess) { - ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() - .Authorization(TAG_NO_AUTH_REQUIRED) - .RsaSigningKey(2048, 65537) - .Digest(Digest::NONE) - .Padding(PaddingMode::NONE) - .SetDefaultValidity())); - string message = "12345678901234567890123456789012"; - string signature = SignMessage( - message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)); - VerifyMessage(message, signature, - AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)); -} - -/* - * VerificationOperationsTest.RsaAllPaddingsAndDigests - * - * Verifies RSA signature/verification for all padding modes and digests. - */ -TEST_P(VerificationOperationsTest, RsaAllPaddingsAndDigests) { - auto authorizations = AuthorizationSetBuilder() - .Authorization(TAG_NO_AUTH_REQUIRED) - .RsaSigningKey(2048, 65537) - .Digest(ValidDigests(true /* withNone */, true /* withMD5 */)) - .Padding(PaddingMode::NONE) - .Padding(PaddingMode::RSA_PSS) - .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN) - .SetDefaultValidity(); - - ASSERT_EQ(ErrorCode::OK, GenerateKey(authorizations)); - - string message(128, 'a'); - string corrupt_message(message); - ++corrupt_message[corrupt_message.size() / 2]; - - for (auto padding : - {PaddingMode::NONE, PaddingMode::RSA_PSS, PaddingMode::RSA_PKCS1_1_5_SIGN}) { - for (auto digest : ValidDigests(true /* withNone */, true /* withMD5 */)) { - if (padding == PaddingMode::NONE && digest != Digest::NONE) { - // Digesting only makes sense with padding. - continue; - } - - if (padding == PaddingMode::RSA_PSS && digest == Digest::NONE) { - // PSS requires digesting. - continue; - } - - string signature = - SignMessage(message, AuthorizationSetBuilder().Digest(digest).Padding(padding)); - VerifyMessage(message, signature, - AuthorizationSetBuilder().Digest(digest).Padding(padding)); - - /* TODO(seleneh) add exportkey tests back later when we have decided on - * the new api. - if (digest != Digest::NONE) { - // Verify with OpenSSL. - vector pubkey; - ASSERT_EQ(ErrorCode::OK, ExportKey(KeyFormat::X509, &pubkey)); - - const uint8_t* p = pubkey.data(); - EVP_PKEY_Ptr pkey(d2i_PUBKEY(nullptr, &p, pubkey.size())); - ASSERT_TRUE(pkey.get()); - - EVP_MD_CTX digest_ctx; - EVP_MD_CTX_init(&digest_ctx); - EVP_PKEY_CTX* pkey_ctx; - const EVP_MD* md = openssl_digest(digest); - ASSERT_NE(md, nullptr); - EXPECT_EQ(1, EVP_DigestVerifyInit(&digest_ctx, &pkey_ctx, md, - nullptr, pkey.get())); - - switch (padding) { - case PaddingMode::RSA_PSS: - EXPECT_GT(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, - RSA_PKCS1_PSS_PADDING), 0); EXPECT_GT(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, - EVP_MD_size(md)), 0); break; case PaddingMode::RSA_PKCS1_1_5_SIGN: - // PKCS1 is the default; don't need to set anything. - break; - default: - FAIL(); - break; - } - - EXPECT_EQ(1, EVP_DigestVerifyUpdate(&digest_ctx, message.data(), - message.size())); EXPECT_EQ(1, EVP_DigestVerifyFinal(&digest_ctx, - reinterpret_cast(signature.data()), signature.size())); EVP_MD_CTX_cleanup(&digest_ctx); - } - */ - - // Corrupt signature shouldn't verify. - string corrupt_signature(signature); - ++corrupt_signature[corrupt_signature.size() / 2]; - - EXPECT_EQ(ErrorCode::OK, - Begin(KeyPurpose::VERIFY, - AuthorizationSetBuilder().Digest(digest).Padding(padding))); - string result; - EXPECT_EQ(ErrorCode::VERIFICATION_FAILED, Finish(message, corrupt_signature, &result)); - - // Corrupt message shouldn't verify - EXPECT_EQ(ErrorCode::OK, - Begin(KeyPurpose::VERIFY, - AuthorizationSetBuilder().Digest(digest).Padding(padding))); - EXPECT_EQ(ErrorCode::VERIFICATION_FAILED, Finish(corrupt_message, signature, &result)); - } - } -} - -/* - * VerificationOperationsTest.RsaAllDigestsAndCurves - * - * Verifies ECDSA signature/verification for all digests and curves. - */ -TEST_P(VerificationOperationsTest, EcdsaAllDigestsAndCurves) { - auto digests = ValidDigests(true /* withNone */, false /* withMD5 */); - - string message = "1234567890"; - string corrupt_message = "2234567890"; - for (auto curve : ValidCurves()) { - ErrorCode error = GenerateKey(AuthorizationSetBuilder() - .Authorization(TAG_NO_AUTH_REQUIRED) - .EcdsaSigningKey(curve) - .Digest(digests) - .SetDefaultValidity()); - EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate key for EC curve " << curve; - if (error != ErrorCode::OK) { - continue; - } - - for (auto digest : digests) { - string signature = SignMessage(message, AuthorizationSetBuilder().Digest(digest)); - VerifyMessage(message, signature, AuthorizationSetBuilder().Digest(digest)); - - /* TODO(seleneh) add exportkey tests back later when we have decided on - * the new api. - - // Verify with OpenSSL - if (digest != Digest::NONE) { - vector pubkey; - ASSERT_EQ(ErrorCode::OK, ExportKey(KeyFormat::X509, &pubkey)) - << curve << ' ' << digest; - - const uint8_t* p = pubkey.data(); - EVP_PKEY_Ptr pkey(d2i_PUBKEY(nullptr, &p, pubkey.size())); - ASSERT_TRUE(pkey.get()); - - EVP_MD_CTX digest_ctx; - EVP_MD_CTX_init(&digest_ctx); - EVP_PKEY_CTX* pkey_ctx; - const EVP_MD* md = openssl_digest(digest); - - EXPECT_EQ(1, EVP_DigestVerifyInit(&digest_ctx, &pkey_ctx, md, - nullptr, pkey.get())) - << curve << ' ' << digest; - - EXPECT_EQ(1, EVP_DigestVerifyUpdate(&digest_ctx, message.data(), - message.size())) - << curve << ' ' << digest; - - EXPECT_EQ(1, - EVP_DigestVerifyFinal(&digest_ctx, - reinterpret_cast(signature.data()), signature.size())) - << curve << ' ' << digest; - - EVP_MD_CTX_cleanup(&digest_ctx); - } - */ - // Corrupt signature shouldn't verify. - string corrupt_signature(signature); - ++corrupt_signature[corrupt_signature.size() / 2]; - - EXPECT_EQ(ErrorCode::OK, - Begin(KeyPurpose::VERIFY, AuthorizationSetBuilder().Digest(digest))) - << curve << ' ' << digest; - - string result; - EXPECT_EQ(ErrorCode::VERIFICATION_FAILED, Finish(message, corrupt_signature, &result)) - << curve << ' ' << digest; - - // Corrupt message shouldn't verify - EXPECT_EQ(ErrorCode::OK, - Begin(KeyPurpose::VERIFY, AuthorizationSetBuilder().Digest(digest))) - << curve << ' ' << digest; - - EXPECT_EQ(ErrorCode::VERIFICATION_FAILED, Finish(corrupt_message, signature, &result)) - << curve << ' ' << digest; - } - - auto rc = DeleteKey(); - ASSERT_TRUE(rc == ErrorCode::OK || rc == ErrorCode::UNIMPLEMENTED); - } -} - /* * VerificationOperationsTest.HmacSigningKeyCannotVerify * @@ -3016,7 +2890,7 @@ TEST_P(ImportKeyTest, RsaSuccess) { string message(1024 / 8, 'a'); auto params = AuthorizationSetBuilder().Digest(Digest::SHA_2_256).Padding(PaddingMode::RSA_PSS); string signature = SignMessage(message, params); - VerifyMessage(message, signature, params); + LocalVerifyMessage(message, signature, params); } /* @@ -3058,7 +2932,7 @@ TEST_P(ImportKeyTest, RsaSuccessWithoutParams) { string message(1024 / 8, 'a'); auto params = AuthorizationSetBuilder().Digest(Digest::SHA_2_256).Padding(PaddingMode::RSA_PSS); string signature = SignMessage(message, params); - VerifyMessage(message, signature, params); + LocalVerifyMessage(message, signature, params); } /* @@ -3116,7 +2990,7 @@ TEST_P(ImportKeyTest, EcdsaSuccess) { string message(32, 'a'); auto params = AuthorizationSetBuilder().Digest(Digest::SHA_2_256); string signature = SignMessage(message, params); - VerifyMessage(message, signature, params); + LocalVerifyMessage(message, signature, params); } /* @@ -3143,7 +3017,7 @@ TEST_P(ImportKeyTest, EcdsaP256RFC5915Success) { string message(32, 'a'); auto params = AuthorizationSetBuilder().Digest(Digest::SHA_2_256); string signature = SignMessage(message, params); - VerifyMessage(message, signature, params); + LocalVerifyMessage(message, signature, params); } /* @@ -3169,7 +3043,7 @@ TEST_P(ImportKeyTest, EcdsaP256SEC1Success) { string message(32, 'a'); auto params = AuthorizationSetBuilder().Digest(Digest::SHA_2_256); string signature = SignMessage(message, params); - VerifyMessage(message, signature, params); + LocalVerifyMessage(message, signature, params); } /* @@ -3195,7 +3069,7 @@ TEST_P(ImportKeyTest, Ecdsa521Success) { string message(32, 'a'); auto params = AuthorizationSetBuilder().Digest(Digest::SHA_2_256); string signature = SignMessage(message, params); - VerifyMessage(message, signature, params); + LocalVerifyMessage(message, signature, params); } /*