KeyMint VTS: local RSA encryption
Change RSA encryption (with public key) so it happens locally in the
test, rather than by invoking an ENCRYPT operation against KeyMint.
- Specify MGF1 digest for OAEP mode as (now) required by AIDL spec.
- Drop tests for too-long encryption inputs.
- Adjust test comments to reflect decryption-only nature.
- Change parameter checking tests to do so on DECRYPT rather than ENCRYPT.
Test: VtsAidlKeyMintTargetTest
Change-Id: I10c4beea28387eecfd0bc7c5dfd59a1b66fec21e
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
index 1a05ac8..8c4e0c3 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -699,6 +699,101 @@
}
}
+string KeyMintAidlTestBase::LocalRsaEncryptMessage(const string& message,
+ const AuthorizationSet& params) {
+ SCOPED_TRACE("LocalRsaEncryptMessage");
+
+ // Retrieve the public key from the leaf certificate.
+ if (cert_chain_.empty()) {
+ ADD_FAILURE() << "No public key available";
+ return "Failure";
+ }
+ X509_Ptr key_cert(parse_cert_blob(cert_chain_[0].encodedCertificate));
+ EVP_PKEY_Ptr pub_key(X509_get_pubkey(key_cert.get()));
+ RSA_Ptr rsa(EVP_PKEY_get1_RSA(const_cast<EVP_PKEY*>(pub_key.get())));
+
+ // Retrieve relevant tags.
+ Digest digest = Digest::NONE;
+ Digest mgf_digest = Digest::NONE;
+ PaddingMode padding = PaddingMode::NONE;
+
+ auto digest_tag = params.GetTagValue(TAG_DIGEST);
+ if (digest_tag.has_value()) digest = digest_tag.value();
+ auto pad_tag = params.GetTagValue(TAG_PADDING);
+ if (pad_tag.has_value()) padding = pad_tag.value();
+ auto mgf_tag = params.GetTagValue(TAG_RSA_OAEP_MGF_DIGEST);
+ if (mgf_tag.has_value()) mgf_digest = mgf_tag.value();
+
+ const EVP_MD* md = openssl_digest(digest);
+ const EVP_MD* mgf_md = openssl_digest(mgf_digest);
+
+ // Set up encryption context.
+ EVP_PKEY_CTX_Ptr ctx(EVP_PKEY_CTX_new(pub_key.get(), /* engine= */ nullptr));
+ if (EVP_PKEY_encrypt_init(ctx.get()) <= 0) {
+ ADD_FAILURE() << "Encryption init failed: " << ERR_peek_last_error();
+ return "Failure";
+ }
+
+ int rc = -1;
+ switch (padding) {
+ case PaddingMode::NONE:
+ rc = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_NO_PADDING);
+ break;
+ case PaddingMode::RSA_PKCS1_1_5_ENCRYPT:
+ rc = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PADDING);
+ break;
+ case PaddingMode::RSA_OAEP:
+ rc = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING);
+ break;
+ default:
+ break;
+ }
+ if (rc <= 0) {
+ ADD_FAILURE() << "Set padding failed: " << ERR_peek_last_error();
+ return "Failure";
+ }
+ if (padding == PaddingMode::RSA_OAEP) {
+ if (!EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), md)) {
+ ADD_FAILURE() << "Set digest failed: " << ERR_peek_last_error();
+ return "Failure";
+ }
+ if (!EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), mgf_md)) {
+ ADD_FAILURE() << "Set MGF digest failed: " << ERR_peek_last_error();
+ return "Failure";
+ }
+ }
+
+ // Determine output size.
+ size_t outlen;
+ if (EVP_PKEY_encrypt(ctx.get(), nullptr /* out */, &outlen,
+ reinterpret_cast<const uint8_t*>(message.data()), message.size()) <= 0) {
+ ADD_FAILURE() << "Determine output size failed: " << ERR_peek_last_error();
+ return "Failure";
+ }
+
+ // Left-zero-pad the input if necessary.
+ const uint8_t* to_encrypt = reinterpret_cast<const uint8_t*>(message.data());
+ size_t to_encrypt_len = message.size();
+
+ std::unique_ptr<string> zero_padded_message;
+ if (padding == PaddingMode::NONE && to_encrypt_len < outlen) {
+ zero_padded_message.reset(new string(outlen, '\0'));
+ memcpy(zero_padded_message->data() + (outlen - to_encrypt_len), message.data(),
+ message.size());
+ to_encrypt = reinterpret_cast<const uint8_t*>(zero_padded_message->data());
+ to_encrypt_len = outlen;
+ }
+
+ // Do the encryption.
+ string output(outlen, '\0');
+ if (EVP_PKEY_encrypt(ctx.get(), reinterpret_cast<uint8_t*>(output.data()), &outlen, to_encrypt,
+ to_encrypt_len) <= 0) {
+ ADD_FAILURE() << "Encryption failed: " << ERR_peek_last_error();
+ return "Failure";
+ }
+ return output;
+}
+
string KeyMintAidlTestBase::EncryptMessage(const vector<uint8_t>& 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 1a14d47..d8f1bb3 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
@@ -165,6 +165,7 @@
void LocalVerifyMessage(const string& message, const string& signature,
const AuthorizationSet& params);
+ string LocalRsaEncryptMessage(const string& message, const AuthorizationSet& params);
string EncryptMessage(const vector<uint8_t>& key_blob, const string& message,
const AuthorizationSet& in_params, AuthorizationSet* out_params);
string EncryptMessage(const string& message, const AuthorizationSet& params,
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index 2c0c459..7d366f1 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -3627,7 +3627,7 @@
/*
* EncryptionOperationsTest.RsaNoPaddingSuccess
*
- * Verifies that raw RSA encryption works.
+ * Verifies that raw RSA decryption works.
*/
TEST_P(EncryptionOperationsTest, RsaNoPaddingSuccess) {
for (uint64_t exponent : {3, 65537}) {
@@ -3639,10 +3639,10 @@
string message = string(2048 / 8, 'a');
auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
- string ciphertext1 = EncryptMessage(message, params);
+ string ciphertext1 = LocalRsaEncryptMessage(message, params);
EXPECT_EQ(2048U / 8, ciphertext1.size());
- string ciphertext2 = EncryptMessage(message, params);
+ string ciphertext2 = LocalRsaEncryptMessage(message, params);
EXPECT_EQ(2048U / 8, ciphertext2.size());
// Unpadded RSA is deterministic
@@ -3655,7 +3655,7 @@
/*
* EncryptionOperationsTest.RsaNoPaddingShortMessage
*
- * Verifies that raw RSA encryption of short messages works.
+ * Verifies that raw RSA decryption of short messages works.
*/
TEST_P(EncryptionOperationsTest, RsaNoPaddingShortMessage) {
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
@@ -3667,76 +3667,47 @@
string message = "1";
auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
- string ciphertext = EncryptMessage(message, params);
+ string ciphertext = LocalRsaEncryptMessage(message, params);
EXPECT_EQ(2048U / 8, ciphertext.size());
string expected_plaintext = string(2048U / 8 - 1, 0) + message;
string plaintext = DecryptMessage(ciphertext, params);
EXPECT_EQ(expected_plaintext, plaintext);
-
- // Degenerate case, encrypting a numeric 1 yields 0x00..01 as the ciphertext.
- message = static_cast<char>(1);
- ciphertext = EncryptMessage(message, params);
- EXPECT_EQ(2048U / 8, ciphertext.size());
- EXPECT_EQ(ciphertext, string(2048U / 8 - 1, 0) + message);
}
/*
- * EncryptionOperationsTest.RsaNoPaddingTooLong
- *
- * Verifies that raw RSA encryption of too-long messages fails in the expected way.
- */
-TEST_P(EncryptionOperationsTest, RsaNoPaddingTooLong) {
- ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
- .Authorization(TAG_NO_AUTH_REQUIRED)
- .RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::NONE)
- .SetDefaultValidity()));
-
- string message(2048 / 8 + 1, 'a');
-
- auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
- EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params));
-
- string result;
- EXPECT_EQ(ErrorCode::INVALID_INPUT_LENGTH, Finish(message, &result));
-}
-
-/*
- * EncryptionOperationsTest.RsaNoPaddingTooLarge
- *
- * Verifies that raw RSA encryption of too-large (numerically) messages fails in the expected
- * way.
- */
-// TODO(seleneh) add RsaNoPaddingTooLarge test back after decided and implemented new
-// version of ExportKey inside generateKey
-
-/*
* EncryptionOperationsTest.RsaOaepSuccess
*
- * Verifies that RSA-OAEP encryption operations work, with all digests.
+ * Verifies that RSA-OAEP decryption operations work, with all digests.
*/
TEST_P(EncryptionOperationsTest, RsaOaepSuccess) {
auto digests = ValidDigests(false /* withNone */, true /* withMD5 */);
size_t key_size = 2048; // Need largish key for SHA-512 test.
- ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
- .Authorization(TAG_NO_AUTH_REQUIRED)
- .RsaEncryptionKey(key_size, 65537)
- .Padding(PaddingMode::RSA_OAEP)
- .Digest(digests)
- .SetDefaultValidity()));
+ ASSERT_EQ(ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaEncryptionKey(key_size, 65537)
+ .Padding(PaddingMode::RSA_OAEP)
+ .Digest(digests)
+ .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA1)
+ .SetDefaultValidity()));
string message = "Hello";
for (auto digest : digests) {
- auto params = AuthorizationSetBuilder().Digest(digest).Padding(PaddingMode::RSA_OAEP);
- string ciphertext1 = EncryptMessage(message, params);
+ SCOPED_TRACE(testing::Message() << "digest-" << digest);
+
+ auto params = AuthorizationSetBuilder()
+ .Digest(digest)
+ .Padding(PaddingMode::RSA_OAEP)
+ .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA1);
+ string ciphertext1 = LocalRsaEncryptMessage(message, params);
if (HasNonfatalFailure()) std::cout << "-->" << digest << std::endl;
EXPECT_EQ(key_size / 8, ciphertext1.size());
- string ciphertext2 = EncryptMessage(message, params);
+ string ciphertext2 = LocalRsaEncryptMessage(message, params);
EXPECT_EQ(key_size / 8, ciphertext2.size());
// OAEP randomizes padding so every result should be different (with astronomically high
@@ -3766,7 +3737,7 @@
/*
* EncryptionOperationsTest.RsaOaepInvalidDigest
*
- * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate
+ * Verifies that RSA-OAEP decryption operations fail in the correct way when asked to operate
* without a digest.
*/
TEST_P(EncryptionOperationsTest, RsaOaepInvalidDigest) {
@@ -3778,13 +3749,13 @@
.SetDefaultValidity()));
auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_OAEP).Digest(Digest::NONE);
- EXPECT_EQ(ErrorCode::INCOMPATIBLE_DIGEST, Begin(KeyPurpose::ENCRYPT, params));
+ EXPECT_EQ(ErrorCode::INCOMPATIBLE_DIGEST, Begin(KeyPurpose::DECRYPT, params));
}
/*
* EncryptionOperationsTest.RsaOaepInvalidPadding
*
- * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate
+ * Verifies that RSA-OAEP decryption operations fail in the correct way when asked to operate
* with a padding value that is only suitable for signing/verifying.
*/
TEST_P(EncryptionOperationsTest, RsaOaepInvalidPadding) {
@@ -3796,13 +3767,13 @@
.SetDefaultValidity()));
auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PSS).Digest(Digest::NONE);
- EXPECT_EQ(ErrorCode::UNSUPPORTED_PADDING_MODE, Begin(KeyPurpose::ENCRYPT, params));
+ EXPECT_EQ(ErrorCode::UNSUPPORTED_PADDING_MODE, Begin(KeyPurpose::DECRYPT, params));
}
/*
* EncryptionOperationsTest.RsaOaepDecryptWithWrongDigest
*
- * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to decrypt
+ * Verifies that RSA-OAEP decryption operations fail in the correct way when asked to decrypt
* with a different digest than was used to encrypt.
*/
TEST_P(EncryptionOperationsTest, RsaOaepDecryptWithWrongDigest) {
@@ -3815,7 +3786,7 @@
.Digest(Digest::SHA_2_224, Digest::SHA_2_256)
.SetDefaultValidity()));
string message = "Hello World!";
- string ciphertext = EncryptMessage(
+ string ciphertext = LocalRsaEncryptMessage(
message,
AuthorizationSetBuilder().Digest(Digest::SHA_2_224).Padding(PaddingMode::RSA_OAEP));
@@ -3828,34 +3799,9 @@
}
/*
- * EncryptionOperationsTest.RsaOaepTooLarge
- *
- * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to encrypt a
- * too-large message.
- */
-TEST_P(EncryptionOperationsTest, RsaOaepTooLarge) {
- ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
- .Authorization(TAG_NO_AUTH_REQUIRED)
- .RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::RSA_OAEP)
- .Digest(Digest::SHA_2_256)
- .SetDefaultValidity()));
- constexpr size_t digest_size = 256 /* SHA_2_256 */ / 8;
- constexpr size_t oaep_overhead = 2 * digest_size + 2;
- string message(2048 / 8 - oaep_overhead + 1, 'a');
- EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, AuthorizationSetBuilder()
- .Padding(PaddingMode::RSA_OAEP)
- .Digest(Digest::SHA_2_256)));
- string result;
- ErrorCode error = Finish(message, &result);
- EXPECT_TRUE(error == ErrorCode::INVALID_INPUT_LENGTH || error == ErrorCode::INVALID_ARGUMENT);
- EXPECT_EQ(0U, result.size());
-}
-
-/*
* EncryptionOperationsTest.RsaOaepWithMGFDigestSuccess
*
- * Verifies that RSA-OAEP encryption operations work, with all SHA 256 digests and all type of MGF1
+ * Verifies that RSA-OAEP decryption operations work, with all SHA 256 digests and all type of MGF1
* digests.
*/
TEST_P(EncryptionOperationsTest, RsaOaepWithMGFDigestSuccess) {
@@ -3877,11 +3823,11 @@
.Authorization(TAG_RSA_OAEP_MGF_DIGEST, digest)
.Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP);
- string ciphertext1 = EncryptMessage(message, params);
+ string ciphertext1 = LocalRsaEncryptMessage(message, params);
if (HasNonfatalFailure()) std::cout << "-->" << digest << std::endl;
EXPECT_EQ(key_size / 8, ciphertext1.size());
- string ciphertext2 = EncryptMessage(message, params);
+ string ciphertext2 = LocalRsaEncryptMessage(message, params);
EXPECT_EQ(key_size / 8, ciphertext2.size());
// OAEP randomizes padding so every result should be different (with astronomically high
@@ -3911,7 +3857,7 @@
/*
* EncryptionOperationsTest.RsaOaepWithMGFIncompatibleDigest
*
- * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate
+ * Verifies that RSA-OAEP decryption operations fail in the correct way when asked to operate
* with incompatible MGF digest.
*/
TEST_P(EncryptionOperationsTest, RsaOaepWithMGFIncompatibleDigest) {
@@ -3929,7 +3875,7 @@
.Padding(PaddingMode::RSA_OAEP)
.Digest(Digest::SHA_2_256)
.Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_224);
- EXPECT_EQ(ErrorCode::INCOMPATIBLE_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params));
+ EXPECT_EQ(ErrorCode::INCOMPATIBLE_MGF_DIGEST, Begin(KeyPurpose::DECRYPT, params));
}
/*
@@ -3953,7 +3899,7 @@
.Padding(PaddingMode::RSA_OAEP)
.Digest(Digest::SHA_2_256)
.Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::NONE);
- EXPECT_EQ(ErrorCode::UNSUPPORTED_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params));
+ EXPECT_EQ(ErrorCode::UNSUPPORTED_MGF_DIGEST, Begin(KeyPurpose::DECRYPT, params));
}
/*
@@ -3970,10 +3916,10 @@
string message = "Hello World!";
auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT);
- string ciphertext1 = EncryptMessage(message, params);
+ string ciphertext1 = LocalRsaEncryptMessage(message, params);
EXPECT_EQ(2048U / 8, ciphertext1.size());
- string ciphertext2 = EncryptMessage(message, params);
+ string ciphertext2 = LocalRsaEncryptMessage(message, params);
EXPECT_EQ(2048U / 8, ciphertext2.size());
// PKCS1 v1.5 randomizes padding so every result should be different.
@@ -3997,27 +3943,6 @@
}
/*
- * EncryptionOperationsTest.RsaPkcs1TooLarge
- *
- * Verifies that RSA PKCS encryption fails in the correct way when the message is too large.
- */
-TEST_P(EncryptionOperationsTest, RsaPkcs1TooLarge) {
- ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
- .Authorization(TAG_NO_AUTH_REQUIRED)
- .RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)
- .SetDefaultValidity()));
- string message(2048 / 8 - 10, 'a');
-
- auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT);
- EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params));
- string result;
- ErrorCode error = Finish(message, &result);
- EXPECT_TRUE(error == ErrorCode::INVALID_INPUT_LENGTH || error == ErrorCode::INVALID_ARGUMENT);
- EXPECT_EQ(0U, result.size());
-}
-
-/*
* EncryptionOperationsTest.EcdsaEncrypt
*
* Verifies that attempting to use ECDSA keys to encrypt fails in the correct way.