From 28fa931f005035686fa14a880e0aa1eca1489380 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Wed, 1 Feb 2023 14:53:01 +0000 Subject: [PATCH] Add Gatekeeper-based auth tests Add tests that directly exercise Gatekeeper to get auth tokens for use with auth-bound keys. Test: VtsAidlKeyMintTargetTest Change-Id: Ie668674d81ca487e8bbc18fdd9f36610bcab4c8c --- .../keymint/aidl/vts/functional/Android.bp | 4 + .../keymint/aidl/vts/functional/AuthTest.cpp | 412 ++++++++++++++++++ .../vts/functional/KeyMintAidlTestBase.cpp | 14 +- .../aidl/vts/functional/KeyMintAidlTestBase.h | 7 +- 4 files changed, 429 insertions(+), 8 deletions(-) create mode 100644 security/keymint/aidl/vts/functional/AuthTest.cpp diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp index 26e91bd091..ed3ca74b5b 100644 --- a/security/keymint/aidl/vts/functional/Android.bp +++ b/security/keymint/aidl/vts/functional/Android.bp @@ -35,9 +35,12 @@ cc_defaults { "libbinder_ndk", "libcrypto", "libbase", + "libgatekeeper", "packagemanager_aidl-cpp", ], static_libs: [ + "android.hardware.gatekeeper@1.0", + "android.hardware.gatekeeper-V1-ndk", "android.hardware.security.rkp-V3-ndk", "android.hardware.security.secureclock-V1-ndk", "libcppbor_external", @@ -59,6 +62,7 @@ cc_test { ], srcs: [ "AttestKeyTest.cpp", + "AuthTest.cpp", "DeviceUniqueAttestationTest.cpp", "KeyBlobUpgradeTest.cpp", "KeyMintTest.cpp", diff --git a/security/keymint/aidl/vts/functional/AuthTest.cpp b/security/keymint/aidl/vts/functional/AuthTest.cpp new file mode 100644 index 0000000000..a31ac01174 --- /dev/null +++ b/security/keymint/aidl/vts/functional/AuthTest.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "keymint_1_test" +#include + +#include +#include + +#include "KeyMintAidlTestBase.h" + +#include +#include +#include +#include +#include +#include + +using aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse; +using aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse; +using aidl::android::hardware::gatekeeper::IGatekeeper; +using aidl::android::hardware::security::keymint::HardwareAuthToken; +using aidl::android::hardware::security::secureclock::ISecureClock; + +#include +#include +#include // for password_handle_t +#include + +using ::android::sp; +using IHidlGatekeeper = ::android::hardware::gatekeeper::V1_0::IGatekeeper; +using HidlGatekeeperResponse = ::android::hardware::gatekeeper::V1_0::GatekeeperResponse; +using HidlGatekeeperStatusCode = ::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode; + +namespace aidl::android::hardware::security::keymint::test { + +class AuthTest : public KeyMintAidlTestBase { + public: + void SetUp() { + KeyMintAidlTestBase::SetUp(); + + // Find the default Gatekeeper instance. + string gk_name = string(IGatekeeper::descriptor) + "/default"; + if (AServiceManager_isDeclared(gk_name.c_str())) { + // Enroll a user with AIDL Gatekeeper. + ::ndk::SpAIBinder binder(AServiceManager_waitForService(gk_name.c_str())); + gk_ = IGatekeeper::fromBinder(binder); + } else { + // Prior to Android U, Gatekeeper was HIDL not AIDL and so may not be present. + // Try to enroll user with HIDL Gatekeeper instead. + string gk_name = "default"; + hidl_gk_ = IHidlGatekeeper::getService(gk_name.c_str()); + if (hidl_gk_ == nullptr) { + std::cerr << "No HIDL Gatekeeper instance for '" << gk_name << "' found.\n"; + return; + } + std::cerr << "No AIDL Gatekeeper instance for '" << gk_name << "' found, using HIDL.\n"; + } + + // If the device needs timestamps, find the default ISecureClock instance. + if (timestamp_token_required_) { + string clock_name = string(ISecureClock::descriptor) + "/default"; + if (AServiceManager_isDeclared(clock_name.c_str())) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(clock_name.c_str())); + clock_ = ISecureClock::fromBinder(binder); + } else { + std::cerr << "No ISecureClock instance for '" << clock_name << "' found.\n"; + } + } + + // Enroll a password for a user. + uid_ = 10001; + password_ = "correcthorsebatterystaple"; + std::optional rsp = doEnroll(password_); + ASSERT_TRUE(rsp.has_value()); + sid_ = rsp->secureUserId; + handle_ = rsp->data; + } + + void TearDown() { + if (gk_ == nullptr) return; + gk_->deleteUser(uid_); + } + + bool GatekeeperAvailable() { return (gk_ != nullptr) || (hidl_gk_ != nullptr); } + + std::optional doEnroll(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); + if (!status.isOk() && status.getExceptionCode() == EX_SERVICE_SPECIFIC && + status.getServiceSpecificError() == IGatekeeper::ERROR_RETRY_TIMEOUT) { + sleep(1); + continue; + } + if (status.isOk()) { + return std::move(rsp); + } else { + GTEST_LOG_(ERROR) << "doEnroll(AIDL) failed: " << status; + return std::nullopt; + } + } + } else if (hidl_gk_ != nullptr) { + while (true) { + HidlGatekeeperResponse rsp; + auto status = hidl_gk_->enroll( + uid_, curHandle, curPwd, newPwd, + [&rsp](const HidlGatekeeperResponse& cbRsp) { rsp = cbRsp; }); + if (!status.isOk()) { + GTEST_LOG_(ERROR) << "doEnroll(HIDL) failed"; + return std::nullopt; + } + if (rsp.code == HidlGatekeeperStatusCode::ERROR_RETRY_TIMEOUT) { + sleep(1); + continue; + } + if (rsp.code != HidlGatekeeperStatusCode::STATUS_OK) { + GTEST_LOG_(ERROR) << "doEnroll(HIDL) failed with " << int(rsp.code); + return std::nullopt; + } + // "Parse" the returned data to get at the secure user ID. + if (rsp.data.size() != sizeof(::gatekeeper::password_handle_t)) { + GTEST_LOG_(ERROR) + << "HAL returned password handle of invalid length " << rsp.data.size(); + return std::nullopt; + } + const ::gatekeeper::password_handle_t* handle = + reinterpret_cast(rsp.data.data()); + + // Translate HIDL response to look like an AIDL response. + GatekeeperEnrollResponse aidl_rsp; + aidl_rsp.statusCode = IGatekeeper::STATUS_OK; + aidl_rsp.data = rsp.data; + aidl_rsp.secureUserId = handle->user_id; + return aidl_rsp; + } + } else { + return std::nullopt; + } + } + + std::optional doEnroll(const string& newPwd, + const std::vector& curHandle = {}, + const string& curPwd = {}) { + return doEnroll(std::vector(newPwd.begin(), newPwd.end()), curHandle, + std::vector(curPwd.begin(), curPwd.end())); + } + + std::optional doVerify(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); + if (!status.isOk() && status.getExceptionCode() == EX_SERVICE_SPECIFIC && + status.getServiceSpecificError() == IGatekeeper::ERROR_RETRY_TIMEOUT) { + sleep(1); + continue; + } + if (status.isOk()) { + return rsp.hardwareAuthToken; + } else { + GTEST_LOG_(ERROR) << "doVerify(AIDL) failed: " << status; + return std::nullopt; + } + } + } else if (hidl_gk_ != nullptr) { + while (true) { + HidlGatekeeperResponse rsp; + auto status = hidl_gk_->verify( + uid_, challenge, handle, pwd, + [&rsp](const HidlGatekeeperResponse& cbRsp) { rsp = cbRsp; }); + if (!status.isOk()) { + GTEST_LOG_(ERROR) << "doVerify(HIDL) failed"; + return std::nullopt; + } + if (rsp.code == HidlGatekeeperStatusCode::ERROR_RETRY_TIMEOUT) { + sleep(1); + continue; + } + if (rsp.code != HidlGatekeeperStatusCode::STATUS_OK) { + GTEST_LOG_(ERROR) << "doVerify(HIDL) failed with " << int(rsp.code); + return std::nullopt; + } + // "Parse" the returned data to get auth token contents. + if (rsp.data.size() != sizeof(hw_auth_token_t)) { + GTEST_LOG_(ERROR) << "Incorrect size of AuthToken payload."; + return std::nullopt; + } + const hw_auth_token_t* hwAuthToken = + reinterpret_cast(rsp.data.data()); + HardwareAuthToken authToken; + authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp); + authToken.challenge = hwAuthToken->challenge; + authToken.userId = hwAuthToken->user_id; + authToken.authenticatorId = hwAuthToken->authenticator_id; + authToken.authenticatorType = static_cast( + betoh32(hwAuthToken->authenticator_type)); + authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]); + return authToken; + } + } else { + return std::nullopt; + } + } + std::optional doVerify(uint64_t challenge, + const std::vector& handle, + const string& pwd) { + return doVerify(challenge, handle, std::vector(pwd.begin(), pwd.end())); + } + + // Variants of the base class methods but with authentication information included. + string ProcessMessage(const vector& key_blob, KeyPurpose operation, + const string& message, const AuthorizationSet& in_params, + AuthorizationSet* out_params, const HardwareAuthToken& hat) { + AuthorizationSet begin_out_params; + ErrorCode result = Begin(operation, key_blob, in_params, out_params, hat); + EXPECT_EQ(ErrorCode::OK, result); + if (result != ErrorCode::OK) { + return ""; + } + + std::optional time_token = std::nullopt; + if (timestamp_token_required_ && clock_ != nullptr) { + // Ask a secure clock instance for a timestamp, including the per-op challenge. + secureclock::TimeStampToken token; + EXPECT_EQ(ErrorCode::OK, + GetReturnErrorCode(clock_->generateTimeStamp(challenge_, &token))); + time_token = token; + } + + string output; + EXPECT_EQ(ErrorCode::OK, Finish(message, {} /* signature */, &output, hat, time_token)); + return output; + } + + string EncryptMessage(const vector& key_blob, const string& message, + const AuthorizationSet& in_params, AuthorizationSet* out_params, + const HardwareAuthToken& hat) { + SCOPED_TRACE("EncryptMessage"); + return ProcessMessage(key_blob, KeyPurpose::ENCRYPT, message, in_params, out_params, hat); + } + + string DecryptMessage(const vector& key_blob, const string& ciphertext, + const AuthorizationSet& params, const HardwareAuthToken& hat) { + SCOPED_TRACE("DecryptMessage"); + AuthorizationSet out_params; + string plaintext = + ProcessMessage(key_blob, KeyPurpose::DECRYPT, ciphertext, params, &out_params, hat); + EXPECT_TRUE(out_params.empty()); + return plaintext; + } + + protected: + std::shared_ptr gk_; + sp hidl_gk_; + std::shared_ptr clock_; + string password_; + uint32_t uid_; + long sid_; + std::vector handle_; +}; + +// Test use of a key that requires user-authentication within recent history. +TEST_P(AuthTest, TimeoutAuthentication) { + if (!GatekeeperAvailable()) { + GTEST_SKIP() << "No Gatekeeper available"; + } + if (timestamp_token_required_ && 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)); + + // Attempt to use the AES key without authentication. + const string message = "Hello World!"; + AuthorizationSet out_params; + auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7); + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, + Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params)); + + // 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_); + + // Adding the auth token makes it possible to use the AES key. + const string ciphertext = EncryptMessage(keyblob, message, params, &out_params, hat.value()); + const string plaintext = DecryptMessage(keyblob, ciphertext, params, hat.value()); + EXPECT_EQ(message, plaintext); + + // Altering a single bit in the MAC means no auth. + HardwareAuthToken dodgy_hat = hat.value(); + ASSERT_GT(dodgy_hat.mac.size(), 0); + dodgy_hat.mac[0] ^= 0x01; + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, + Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, dodgy_hat)); + + // Wait for long enough that the hardware auth token expires. + sleep(timeout_secs + 1); + if (!timestamp_token_required_) { + // KeyMint implementation has its own clock, and can immediately detect timeout. + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, + Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, hat)); + } else { + // KeyMint implementation has no clock, so only detects timeout via timestamp token provided + // on update()/finish(). + ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, hat)); + secureclock::TimeStampToken time_token; + EXPECT_EQ(ErrorCode::OK, + GetReturnErrorCode(clock_->generateTimeStamp(challenge_, &time_token))); + + string output; + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, + Finish(message, {} /* signature */, &output, hat, time_token)); + } +} + +// 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) { + if (!GatekeeperAvailable()) { + GTEST_SKIP() << "No Gatekeeper available"; + } + + // 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_AUTH_TYPE, HardwareAuthenticatorType::PASSWORD); + vector keyblob; + vector key_characteristics; + vector cert_chain; + ASSERT_EQ(ErrorCode::OK, + GenerateKey(builder, std::nullopt, &keyblob, &key_characteristics, &cert_chain)); + + // Attempt to use the AES key without authentication fails after begin. + const string message = "Hello World!"; + AuthorizationSet out_params; + auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7); + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params)); + string output; + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, Finish(message, {} /* signature */, &output)); + + // Verify to get a HAT, but with an arbitrary challenge. + const uint64_t unrelated_challenge = 42; + const std::optional unrelated_hat = + doVerify(unrelated_challenge, handle_, password_); + ASSERT_TRUE(unrelated_hat.has_value()); + EXPECT_EQ(unrelated_hat->userId, sid_); + + // Attempt to use the AES key with an unrelated authentication fails after begin. + EXPECT_EQ(ErrorCode::OK, + Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, unrelated_hat.value())); + EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, + Finish(message, {} /* signature */, &output, unrelated_hat.value())); + + // Now get a HAT with the challenge from an in-progress operation. + 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_); + string ciphertext; + EXPECT_EQ(ErrorCode::OK, Finish(message, {} /* signature */, &ciphertext, hat.value())); + + // Altering a single bit in the MAC means no auth. + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params)); + std::optional dodgy_hat = doVerify(challenge_, handle_, password_); + ASSERT_TRUE(dodgy_hat.has_value()); + EXPECT_EQ(dodgy_hat->userId, sid_); + ASSERT_GT(dodgy_hat->mac.size(), 0); + dodgy_hat->mac[0] ^= 0x01; + 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 diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp index 588a1d44a0..3ffb6eccbf 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp @@ -544,12 +544,13 @@ ErrorCode KeyMintAidlTestBase::Begin(KeyPurpose purpose, const vector& ErrorCode KeyMintAidlTestBase::Begin(KeyPurpose purpose, const vector& key_blob, const AuthorizationSet& in_params, - AuthorizationSet* out_params) { + AuthorizationSet* out_params, + std::optional hat) { SCOPED_TRACE("Begin"); Status result; BeginResult out; - result = keymint_->begin(purpose, key_blob, in_params.vector_data(), std::nullopt, &out); + result = keymint_->begin(purpose, key_blob, in_params.vector_data(), hat, &out); if (result.isOk()) { *out_params = out.params; @@ -603,8 +604,9 @@ ErrorCode KeyMintAidlTestBase::Update(const string& input, string* output) { return GetReturnErrorCode(result); } -ErrorCode KeyMintAidlTestBase::Finish(const string& input, const string& signature, - string* output) { +ErrorCode KeyMintAidlTestBase::Finish(const string& input, const string& signature, string* output, + std::optional hat, + std::optional time_token) { SCOPED_TRACE("Finish"); Status result; @@ -613,8 +615,8 @@ ErrorCode KeyMintAidlTestBase::Finish(const string& input, const string& signatu vector oPut; result = op_->finish(vector(input.begin(), input.end()), - vector(signature.begin(), signature.end()), {} /* authToken */, - {} /* timestampToken */, {} /* confirmationToken */, &oPut); + vector(signature.begin(), signature.end()), hat, time_token, + {} /* confirmationToken */, &oPut); if (result.isOk()) output->append(oPut.begin(), oPut.end()); diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h index fae9459171..2a0a19ddde 100644 --- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h +++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h @@ -160,7 +160,8 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam { const AuthorizationSet& in_params, AuthorizationSet* out_params, std::shared_ptr& op); ErrorCode Begin(KeyPurpose purpose, const vector& key_blob, - const AuthorizationSet& in_params, AuthorizationSet* out_params); + const AuthorizationSet& in_params, AuthorizationSet* out_params, + std::optional hat = std::nullopt); ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params, AuthorizationSet* out_params); ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params); @@ -168,7 +169,9 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam { ErrorCode UpdateAad(const string& input); ErrorCode Update(const string& input, string* output); - ErrorCode Finish(const string& message, const string& signature, string* output); + ErrorCode Finish(const string& message, const string& signature, string* output, + std::optional hat = std::nullopt, + std::optional time_token = std::nullopt); ErrorCode Finish(const string& message, string* output) { return Finish(message, {} /* signature */, output); }