mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
Merge "KeyMint: more authentication tests"
This commit is contained in:
@@ -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<GatekeeperEnrollResponse> doEnroll(const std::vector<uint8_t>& newPwd,
|
||||
std::optional<GatekeeperEnrollResponse> doEnroll(uint32_t uid,
|
||||
const std::vector<uint8_t>& newPwd,
|
||||
const std::vector<uint8_t>& curHandle = {},
|
||||
const std::vector<uint8_t>& 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<GatekeeperEnrollResponse> doEnroll(const string& newPwd,
|
||||
std::optional<GatekeeperEnrollResponse> doEnroll(uint32_t uid, const string& newPwd,
|
||||
const std::vector<uint8_t>& curHandle = {},
|
||||
const string& curPwd = {}) {
|
||||
return doEnroll(std::vector<uint8_t>(newPwd.begin(), newPwd.end()), curHandle,
|
||||
return doEnroll(uid, std::vector<uint8_t>(newPwd.begin(), newPwd.end()), curHandle,
|
||||
std::vector<uint8_t>(curPwd.begin(), curPwd.end()));
|
||||
}
|
||||
std::optional<GatekeeperEnrollResponse> doEnroll(const string& newPwd) {
|
||||
return doEnroll(uid_, newPwd);
|
||||
}
|
||||
|
||||
std::optional<HardwareAuthToken> doVerify(uint64_t challenge,
|
||||
std::optional<HardwareAuthToken> doVerify(uint32_t uid, uint64_t challenge,
|
||||
const std::vector<uint8_t>& handle,
|
||||
const std::vector<uint8_t>& 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<HardwareAuthToken> doVerify(uint32_t uid, uint64_t challenge,
|
||||
const std::vector<uint8_t>& handle,
|
||||
const string& pwd) {
|
||||
return doVerify(uid, challenge, handle, std::vector<uint8_t>(pwd.begin(), pwd.end()));
|
||||
}
|
||||
std::optional<HardwareAuthToken> doVerify(uint64_t challenge,
|
||||
const std::vector<uint8_t>& handle,
|
||||
const string& pwd) {
|
||||
return doVerify(challenge, handle, std::vector<uint8_t>(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<uint8_t>& 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<IGatekeeper> gk_;
|
||||
sp<IHidlGatekeeper> 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<uint8_t> 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<uint8_t> keyblob;
|
||||
vector<KeyCharacteristics> key_characteristics;
|
||||
vector<Certificate> 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<HardwareAuthToken> 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<GatekeeperEnrollResponse> rsp = doEnroll(alt_uid_, alt_password);
|
||||
ASSERT_TRUE(rsp.has_value());
|
||||
alt_sid_ = rsp->secureUserId;
|
||||
const std::vector<uint8_t> 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<uint8_t> keyblob;
|
||||
vector<KeyCharacteristics> key_characteristics;
|
||||
vector<Certificate> 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<HardwareAuthToken> 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<HardwareAuthToken> 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<GatekeeperEnrollResponse> rsp = doEnroll(alt_uid_, alt_password);
|
||||
ASSERT_TRUE(rsp.has_value());
|
||||
alt_sid_ = rsp->secureUserId;
|
||||
const std::vector<uint8_t> 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<uint8_t> keyblob;
|
||||
vector<KeyCharacteristics> key_characteristics;
|
||||
vector<Certificate> 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<HardwareAuthToken> 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<HardwareAuthToken> 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<uint8_t> keyblob;
|
||||
vector<KeyCharacteristics> key_characteristics;
|
||||
vector<Certificate> 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<HardwareAuthToken> 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
|
||||
|
||||
Reference in New Issue
Block a user