diff --git a/security/keymint/aidl/vts/functional/AuthTest.cpp b/security/keymint/aidl/vts/functional/AuthTest.cpp index 78c88f4896..290e8fcf64 100644 --- a/security/keymint/aidl/vts/functional/AuthTest.cpp +++ b/security/keymint/aidl/vts/functional/AuthTest.cpp @@ -93,17 +93,21 @@ class AuthTest : public KeyMintAidlTestBase { void TearDown() { if (gk_ == nullptr) return; gk_->deleteUser(uid_); + if (alt_uid_ != 0) { + gk_->deleteUser(alt_uid_); + } } bool GatekeeperAvailable() { return (gk_ != nullptr) || (hidl_gk_ != nullptr); } - std::optional doEnroll(const std::vector& newPwd, + std::optional doEnroll(uint32_t uid, + const std::vector& newPwd, const std::vector& curHandle = {}, const std::vector& curPwd = {}) { if (gk_ != nullptr) { while (true) { GatekeeperEnrollResponse rsp; - Status status = gk_->enroll(uid_, curHandle, curPwd, newPwd, &rsp); + Status status = gk_->enroll(uid, curHandle, curPwd, newPwd, &rsp); if (!status.isOk() && status.getExceptionCode() == EX_SERVICE_SPECIFIC && status.getServiceSpecificError() == IGatekeeper::ERROR_RETRY_TIMEOUT) { sleep(1); @@ -120,7 +124,7 @@ class AuthTest : public KeyMintAidlTestBase { while (true) { HidlGatekeeperResponse rsp; auto status = hidl_gk_->enroll( - uid_, curHandle, curPwd, newPwd, + uid, curHandle, curPwd, newPwd, [&rsp](const HidlGatekeeperResponse& cbRsp) { rsp = cbRsp; }); if (!status.isOk()) { GTEST_LOG_(ERROR) << "doEnroll(HIDL) failed"; @@ -155,20 +159,23 @@ class AuthTest : public KeyMintAidlTestBase { } } - std::optional doEnroll(const string& newPwd, + std::optional doEnroll(uint32_t uid, const string& newPwd, const std::vector& curHandle = {}, const string& curPwd = {}) { - return doEnroll(std::vector(newPwd.begin(), newPwd.end()), curHandle, + return doEnroll(uid, std::vector(newPwd.begin(), newPwd.end()), curHandle, std::vector(curPwd.begin(), curPwd.end())); } + std::optional doEnroll(const string& newPwd) { + return doEnroll(uid_, newPwd); + } - std::optional doVerify(uint64_t challenge, + std::optional doVerify(uint32_t uid, uint64_t challenge, const std::vector& handle, const std::vector& pwd) { if (gk_ != nullptr) { while (true) { GatekeeperVerifyResponse rsp; - Status status = gk_->verify(uid_, challenge, handle, pwd, &rsp); + Status status = gk_->verify(uid, challenge, handle, pwd, &rsp); if (!status.isOk() && status.getExceptionCode() == EX_SERVICE_SPECIFIC && status.getServiceSpecificError() == IGatekeeper::ERROR_RETRY_TIMEOUT) { sleep(1); @@ -185,7 +192,7 @@ class AuthTest : public KeyMintAidlTestBase { while (true) { HidlGatekeeperResponse rsp; auto status = hidl_gk_->verify( - uid_, challenge, handle, pwd, + uid, challenge, handle, pwd, [&rsp](const HidlGatekeeperResponse& cbRsp) { rsp = cbRsp; }); if (!status.isOk()) { GTEST_LOG_(ERROR) << "doVerify(HIDL) failed"; @@ -220,10 +227,15 @@ class AuthTest : public KeyMintAidlTestBase { return std::nullopt; } } + std::optional doVerify(uint32_t uid, uint64_t challenge, + const std::vector& handle, + const string& pwd) { + return doVerify(uid, challenge, handle, std::vector(pwd.begin(), pwd.end())); + } std::optional doVerify(uint64_t challenge, const std::vector& handle, const string& pwd) { - return doVerify(challenge, handle, std::vector(pwd.begin(), pwd.end())); + return doVerify(uid_, challenge, handle, pwd); } // Variants of the base class methods but with authentication information included. @@ -268,6 +280,13 @@ class AuthTest : public KeyMintAidlTestBase { return plaintext; } + string SignMessage(const vector& key_blob, const string& message, + const AuthorizationSet& in_params, AuthorizationSet* out_params, + const HardwareAuthToken& hat) { + SCOPED_TRACE("SignMessage"); + return ProcessMessage(key_blob, KeyPurpose::SIGN, message, in_params, out_params, hat); + } + protected: std::shared_ptr gk_; sp hidl_gk_; @@ -275,6 +294,8 @@ class AuthTest : public KeyMintAidlTestBase { string password_; uint32_t uid_; int64_t sid_; + uint32_t alt_uid_; + int64_t alt_sid_; std::vector handle_; }; @@ -347,6 +368,116 @@ TEST_P(AuthTest, TimeoutAuthentication) { } } +// Test use of a key that requires user-authentication within recent history, but where +// the `TimestampToken` provided to the device is unrelated to the in-progress operation. +TEST_P(AuthTest, TimeoutAuthenticationIncorrectTimestampToken) { + if (!GatekeeperAvailable()) { + GTEST_SKIP() << "No Gatekeeper available"; + } + if (!timestamp_token_required_) { + GTEST_SKIP() << "Test only applies to devices with no secure clock"; + } + if (clock_ == nullptr) { + GTEST_SKIP() << "Device requires timestamps and no ISecureClock available"; + } + + // Create an AES key that requires authentication within the last 3 seconds. + const uint32_t timeout_secs = 3; + auto builder = AuthorizationSetBuilder() + .AesEncryptionKey(256) + .BlockMode(BlockMode::ECB) + .Padding(PaddingMode::PKCS7) + .Authorization(TAG_USER_SECURE_ID, sid_) + .Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::PASSWORD) + .Authorization(TAG_AUTH_TIMEOUT, timeout_secs); + vector keyblob; + vector key_characteristics; + vector cert_chain; + ASSERT_EQ(ErrorCode::OK, + GenerateKey(builder, std::nullopt, &keyblob, &key_characteristics, &cert_chain)); + + // Verify to get a HAT, arbitrary challenge. + const uint64_t challenge = 42; + const std::optional hat = doVerify(challenge, handle_, password_); + ASSERT_TRUE(hat.has_value()); + EXPECT_EQ(hat->userId, sid_); + + // KeyMint implementation has no clock, so only detects timeout via timestamp token provided + // on update()/finish(). However, for this test we ensure that that the timestamp token has a + // *different* challenge value. + const string message = "Hello World!"; + auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7); + AuthorizationSet out_params; + ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, hat)); + + secureclock::TimeStampToken time_token; + EXPECT_EQ(ErrorCode::OK, + GetReturnErrorCode(clock_->generateTimeStamp(challenge_ + 1, &time_token))); + string output; + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, + Finish(message, {} /* signature */, &output, hat, time_token)); +} + +// Test use of a key with multiple USER_SECURE_ID values. For variety, use an EC signing key +// generated with attestation. +TEST_P(AuthTest, TimeoutAuthenticationMultiSid) { + if (!GatekeeperAvailable()) { + GTEST_SKIP() << "No Gatekeeper available"; + } + if (timestamp_token_required_ && clock_ == nullptr) { + GTEST_SKIP() << "Device requires timestamps and no ISecureClock available"; + } + + // Enroll a password for a second user. + alt_uid_ = 20001; + const string alt_password = "correcthorsebatterystaple2"; + std::optional rsp = doEnroll(alt_uid_, alt_password); + ASSERT_TRUE(rsp.has_value()); + alt_sid_ = rsp->secureUserId; + const std::vector alt_handle = rsp->data; + + // Create an attested EC key that requires authentication within the last 3 seconds from either + // secure ID. Also allow any authenticator type. + const uint32_t timeout_secs = 3; + auto builder = AuthorizationSetBuilder() + .EcdsaSigningKey(EcCurve::P_256) + .Digest(Digest::NONE) + .Digest(Digest::SHA_2_256) + .SetDefaultValidity() + .AttestationChallenge("challenge") + .AttestationApplicationId("app_id") + .Authorization(TAG_USER_SECURE_ID, alt_sid_) + .Authorization(TAG_USER_SECURE_ID, sid_) + .Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::ANY) + .Authorization(TAG_AUTH_TIMEOUT, timeout_secs); + vector keyblob; + vector key_characteristics; + vector cert_chain; + ASSERT_EQ(ErrorCode::OK, + GenerateKey(builder, std::nullopt, &keyblob, &key_characteristics, &cert_chain)); + + // Verify first user to get a HAT that should work. + const uint64_t challenge = 42; + const std::optional hat = doVerify(uid_, challenge, handle_, password_); + ASSERT_TRUE(hat.has_value()); + EXPECT_EQ(hat->userId, sid_); + + const string message = "Hello World!"; + auto params = AuthorizationSetBuilder().Digest(Digest::SHA_2_256); + AuthorizationSet out_params; + const string signature = SignMessage(keyblob, message, params, &out_params, hat.value()); + + // Verify second user to get a HAT that should work. + const uint64_t alt_challenge = 43; + const std::optional alt_hat = + doVerify(alt_uid_, alt_challenge, alt_handle, alt_password); + ASSERT_TRUE(alt_hat.has_value()); + EXPECT_EQ(alt_hat->userId, alt_sid_); + + const string alt_signature = + SignMessage(keyblob, message, params, &out_params, alt_hat.value()); +} + // Test use of a key that requires an auth token for each action on the operation, with // a per-operation challenge value included. TEST_P(AuthTest, AuthPerOperation) { @@ -407,6 +538,93 @@ TEST_P(AuthTest, AuthPerOperation) { Finish(message, {} /* signature */, &ciphertext, hat.value())); } +// Test use of a key that requires an auth token for each action on the operation, with +// a per-operation challenge value included, with multiple secure IDs allowed. +TEST_P(AuthTest, AuthPerOperationMultiSid) { + if (!GatekeeperAvailable()) { + GTEST_SKIP() << "No Gatekeeper available"; + } + + // Enroll a password for a second user. + alt_uid_ = 20001; + const string alt_password = "correcthorsebatterystaple2"; + std::optional rsp = doEnroll(alt_uid_, alt_password); + ASSERT_TRUE(rsp.has_value()); + alt_sid_ = rsp->secureUserId; + const std::vector alt_handle = rsp->data; + + // Create an AES key that requires authentication per-action. + auto builder = AuthorizationSetBuilder() + .AesEncryptionKey(256) + .BlockMode(BlockMode::ECB) + .Padding(PaddingMode::PKCS7) + .Authorization(TAG_USER_SECURE_ID, sid_) + .Authorization(TAG_USER_SECURE_ID, alt_sid_) + .Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::ANY); + vector keyblob; + vector key_characteristics; + vector cert_chain; + ASSERT_EQ(ErrorCode::OK, + GenerateKey(builder, std::nullopt, &keyblob, &key_characteristics, &cert_chain)); + + // Get a HAT for first user with the challenge from an in-progress operation. + const string message = "Hello World!"; + auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7); + AuthorizationSet out_params; + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params)); + const std::optional hat = doVerify(uid_, challenge_, handle_, password_); + ASSERT_TRUE(hat.has_value()); + EXPECT_EQ(hat->userId, sid_); + string ciphertext; + EXPECT_EQ(ErrorCode::OK, Finish(message, {} /* signature */, &ciphertext, hat.value())); + + // Get a HAT for second user with the challenge from an in-progress operation. + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params)); + const std::optional alt_hat = + doVerify(alt_uid_, challenge_, alt_handle, alt_password); + ASSERT_TRUE(alt_hat.has_value()); + EXPECT_EQ(alt_hat->userId, alt_sid_); + string alt_ciphertext; + EXPECT_EQ(ErrorCode::OK, Finish(message, {} /* signature */, &ciphertext, alt_hat.value())); +} + +// Test use of a key that requires an auth token for each action on the operation, but +// which gets passed a HAT of the wrong type +TEST_P(AuthTest, AuthPerOperationWrongAuthType) { + if (!GatekeeperAvailable()) { + GTEST_SKIP() << "No Gatekeeper available"; + } + + // Create an AES key that requires authentication per-action, but with no valid authenticator + // types. + auto builder = + AuthorizationSetBuilder() + .AesEncryptionKey(256) + .BlockMode(BlockMode::ECB) + .Padding(PaddingMode::PKCS7) + .Authorization(TAG_USER_SECURE_ID, sid_) + .Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::FINGERPRINT); + vector keyblob; + vector key_characteristics; + vector cert_chain; + ASSERT_EQ(ErrorCode::OK, + GenerateKey(builder, std::nullopt, &keyblob, &key_characteristics, &cert_chain)); + + // Get a HAT with the challenge from an in-progress operation. + const string message = "Hello World!"; + auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7); + AuthorizationSet out_params; + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params)); + const std::optional hat = doVerify(challenge_, handle_, password_); + ASSERT_TRUE(hat.has_value()); + EXPECT_EQ(hat->userId, sid_); + + // Should fail because auth type doesn't (can't) match. + string ciphertext; + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, + Finish(message, {} /* signature */, &ciphertext, hat.value())); +} + INSTANTIATE_KEYMINT_AIDL_TEST(AuthTest); } // namespace aidl::android::hardware::security::keymint::test