platform_hardware_interfaces/gatekeeper/1.0/vts/functional/VtsHalGatekeeperV1_0TargetTest.cpp
Eric Biggers 1396f237d1 Test that the password isn't truncated
Test that Gatekeeper doesn't truncate passwords, either due to them
containing NUL bytes or being long.

Bug: 238919794
Test: atest VtsHalGatekeeperV1_0TargetTest # on Cuttlefish
Change-Id: I8dd40c66bbe5cd8d309a6461053389b479fa0745
2022-08-01 03:49:07 +00:00

485 lines
16 KiB
C++

/*
* Copyright (C) 2016 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 "gatekeeper_hidl_hal_test"
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include <inttypes.h>
#include <unistd.h>
#include <gtest/gtest.h>
#include <hardware/hw_auth_token.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
#include <android/log.h>
#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
#include <android/hardware/gatekeeper/1.0/types.h>
#include <log/log.h>
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::gatekeeper::V1_0::IGatekeeper;
using ::android::hardware::gatekeeper::V1_0::GatekeeperResponse;
using ::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;
struct GatekeeperRequest {
uint32_t uid;
uint64_t challenge;
hidl_vec<uint8_t> curPwdHandle;
hidl_vec<uint8_t> curPwd;
hidl_vec<uint8_t> newPwd;
GatekeeperRequest() : uid(0), challenge(0) {}
};
// ASSERT_* macros generate return "void" internally
// we have to use EXPECT_* if we return anything but "void"
static const hw_auth_token_t *toAuthToken(GatekeeperResponse &rsp) {
const hw_auth_token_t *auth_token =
reinterpret_cast<hw_auth_token_t *>(rsp.data.data());
const size_t auth_token_size = rsp.data.size();
EXPECT_NE(nullptr, auth_token);
EXPECT_EQ(sizeof(hw_auth_token_t), auth_token_size);
if (auth_token != nullptr && auth_token_size >= sizeof(*auth_token)) {
// these are in network order: translate to host
uint32_t auth_type = ntohl(auth_token->authenticator_type);
uint64_t auth_tstamp = ntohq(auth_token->timestamp);
EXPECT_EQ(HW_AUTH_PASSWORD, auth_type);
EXPECT_NE(UINT64_C(~0), auth_tstamp);
EXPECT_EQ(HW_AUTH_TOKEN_VERSION, auth_token->version);
// EXPECT_NE(UINT64_C(0), auth_token->authenticator_id);
ALOGI("Authenticator ID: %016" PRIX64, auth_token->authenticator_id);
EXPECT_NE(UINT32_C(0), auth_token->user_id);
}
return auth_token;
}
// The main test class for Gatekeeper HIDL HAL.
class GatekeeperHidlTest : public ::testing::TestWithParam<std::string> {
protected:
void setUid(uint32_t uid) { uid_ = uid; }
void doEnroll(GatekeeperRequest &req, GatekeeperResponse &rsp) {
while (true) {
auto ret = gatekeeper_->enroll(
uid_, req.curPwdHandle, req.curPwd, req.newPwd,
[&rsp](const GatekeeperResponse &cbRsp) { rsp = cbRsp; });
ASSERT_TRUE(ret.isOk());
if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
ALOGI("%s: got retry code; retrying in 1 sec", __func__);
sleep(1);
}
}
void doVerify(GatekeeperRequest &req, GatekeeperResponse &rsp) {
while (true) {
auto ret = gatekeeper_->verify(
uid_, req.challenge, req.curPwdHandle, req.newPwd,
[&rsp](const GatekeeperResponse &cb_rsp) { rsp = cb_rsp; });
ASSERT_TRUE(ret.isOk());
if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
ALOGI("%s: got retry code; retrying in 1 sec", __func__);
sleep(1);
}
}
void doDeleteUser(GatekeeperResponse &rsp) {
while (true) {
auto ret = gatekeeper_->deleteUser(
uid_, [&rsp](const GatekeeperResponse &cb_rsp) { rsp = cb_rsp; });
ASSERT_TRUE(ret.isOk());
if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
ALOGI("%s: got retry code; retrying in 1 sec", __func__);
sleep(1);
}
}
void doDeleteAllUsers(GatekeeperResponse &rsp) {
while (true) {
auto ret = gatekeeper_->deleteAllUsers(
[&rsp](const GatekeeperResponse &cb_rsp) { rsp = cb_rsp; });
ASSERT_TRUE(ret.isOk());
if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
ALOGI("%s: got retry code; retrying in 1 sec", __func__);
sleep(1);
}
}
void generatePassword(hidl_vec<uint8_t> &password, uint8_t seed) {
password.resize(16);
memset(password.data(), seed, password.size());
}
void checkEnroll(GatekeeperResponse &rsp, bool expectSuccess) {
if (expectSuccess) {
EXPECT_EQ(GatekeeperStatusCode::STATUS_OK, rsp.code);
EXPECT_NE(nullptr, rsp.data.data());
EXPECT_GT(rsp.data.size(), UINT32_C(0));
} else {
EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, rsp.code);
EXPECT_EQ(UINT32_C(0), rsp.data.size());
}
}
void checkVerify(GatekeeperResponse &rsp, uint64_t challenge,
bool expectSuccess) {
if (expectSuccess) {
EXPECT_GE(rsp.code, GatekeeperStatusCode::STATUS_OK);
EXPECT_LE(rsp.code, GatekeeperStatusCode::STATUS_REENROLL);
const hw_auth_token_t *auth_token = toAuthToken(rsp);
ASSERT_NE(nullptr, auth_token);
EXPECT_EQ(challenge, auth_token->challenge);
} else {
EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, rsp.code);
EXPECT_EQ(UINT32_C(0), rsp.data.size());
}
}
void enrollNewPassword(hidl_vec<uint8_t> &password, GatekeeperResponse &rsp,
bool expectSuccess) {
GatekeeperRequest req;
req.newPwd.setToExternal(password.data(), password.size());
doEnroll(req, rsp);
checkEnroll(rsp, expectSuccess);
}
void verifyPassword(hidl_vec<uint8_t> &password,
hidl_vec<uint8_t> &passwordHandle, uint64_t challenge,
GatekeeperResponse &verifyRsp, bool expectSuccess) {
GatekeeperRequest verifyReq;
// build verify request for the same password (we want it to succeed)
verifyReq.newPwd = password;
// use enrolled password handle we've got
verifyReq.curPwdHandle = passwordHandle;
verifyReq.challenge = challenge;
doVerify(verifyReq, verifyRsp);
checkVerify(verifyRsp, challenge, expectSuccess);
}
protected:
sp<IGatekeeper> gatekeeper_;
uint32_t uid_;
public:
GatekeeperHidlTest() : uid_(0) {}
virtual void SetUp() override {
GatekeeperResponse rsp;
gatekeeper_ = IGatekeeper::getService(GetParam());
ASSERT_NE(nullptr, gatekeeper_.get());
doDeleteAllUsers(rsp);
}
virtual void TearDown() override {
GatekeeperResponse rsp;
doDeleteAllUsers(rsp);
}
};
/**
* Ensure we can enroll new password
*/
TEST_P(GatekeeperHidlTest, EnrollSuccess) {
hidl_vec<uint8_t> password;
GatekeeperResponse rsp;
ALOGI("Testing Enroll (expected success)");
generatePassword(password, 0);
enrollNewPassword(password, rsp, true);
ALOGI("Testing Enroll done");
}
/**
* Ensure we can not enroll empty password
*/
TEST_P(GatekeeperHidlTest, EnrollNoPassword) {
hidl_vec<uint8_t> password;
GatekeeperResponse rsp;
ALOGI("Testing Enroll (expected failure)");
enrollNewPassword(password, rsp, false);
ALOGI("Testing Enroll done");
}
/**
* Ensure we can successfully verify previously enrolled password
*/
TEST_P(GatekeeperHidlTest, VerifySuccess) {
GatekeeperResponse enrollRsp;
GatekeeperResponse verifyRsp;
hidl_vec<uint8_t> password;
ALOGI("Testing Enroll+Verify (expected success)");
generatePassword(password, 0);
enrollNewPassword(password, enrollRsp, true);
verifyPassword(password, enrollRsp.data, 1, verifyRsp, true);
ALOGI("Testing unenrolled password doesn't verify");
generatePassword(password, 1);
verifyPassword(password, enrollRsp.data, 1, verifyRsp, false);
ALOGI("Testing Enroll+Verify done");
}
/**
* Ensure that passwords containing a NUL byte aren't truncated
*/
TEST_P(GatekeeperHidlTest, PasswordIsBinaryData) {
GatekeeperResponse enrollRsp;
GatekeeperResponse verifyRsp;
hidl_vec<uint8_t> rightPassword = {'A', 'B', 'C', '\0', 'D', 'E', 'F'};
hidl_vec<uint8_t> wrongPassword = {'A', 'B', 'C', '\0', '\0', '\0', '\0'};
ALOGI("Testing Enroll+Verify of password with embedded NUL (expected success)");
enrollNewPassword(rightPassword, enrollRsp, true);
verifyPassword(rightPassword, enrollRsp.data, 1, verifyRsp, true);
ALOGI("Testing Verify of wrong password (expected failure)");
verifyPassword(wrongPassword, enrollRsp.data, 1, verifyRsp, false);
ALOGI("PasswordIsBinaryData test done");
}
/**
* Ensure that long passwords aren't truncated
*/
TEST_P(GatekeeperHidlTest, LongPassword) {
GatekeeperResponse enrollRsp;
GatekeeperResponse verifyRsp;
hidl_vec<uint8_t> password;
password.resize(64); // maximum length used by Android
memset(password.data(), 'A', password.size());
ALOGI("Testing Enroll+Verify of long password (expected success)");
enrollNewPassword(password, enrollRsp, true);
verifyPassword(password, enrollRsp.data, 1, verifyRsp, true);
ALOGI("Testing Verify of wrong password (expected failure)");
password[password.size() - 1] ^= 1;
verifyPassword(password, enrollRsp.data, 1, verifyRsp, false);
ALOGI("LongPassword test done");
}
/**
* Ensure we can securely update password (keep the same
* secure user_id) if we prove we know old password
*/
TEST_P(GatekeeperHidlTest, TrustedReenroll) {
GatekeeperResponse enrollRsp;
GatekeeperRequest reenrollReq;
GatekeeperResponse reenrollRsp;
GatekeeperResponse verifyRsp;
GatekeeperResponse reenrollVerifyRsp;
hidl_vec<uint8_t> password;
hidl_vec<uint8_t> newPassword;
generatePassword(password, 0);
ALOGI("Testing Trusted Reenroll (expected success)");
enrollNewPassword(password, enrollRsp, true);
verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
ALOGI("Primary Enroll+Verify done");
generatePassword(newPassword, 1);
reenrollReq.newPwd.setToExternal(newPassword.data(), newPassword.size());
reenrollReq.curPwd.setToExternal(password.data(), password.size());
reenrollReq.curPwdHandle.setToExternal(enrollRsp.data.data(),
enrollRsp.data.size());
doEnroll(reenrollReq, reenrollRsp);
checkEnroll(reenrollRsp, true);
verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
ALOGI("Trusted ReEnroll+Verify done");
const hw_auth_token_t *first = toAuthToken(verifyRsp);
const hw_auth_token_t *second = toAuthToken(reenrollVerifyRsp);
if (first != nullptr && second != nullptr) {
EXPECT_EQ(first->user_id, second->user_id);
}
ALOGI("Testing Trusted Reenroll done");
}
/**
* Ensure we can update password (and get new
* secure user_id) if we don't know old password
*/
TEST_P(GatekeeperHidlTest, UntrustedReenroll) {
GatekeeperResponse enrollRsp;
GatekeeperResponse reenrollRsp;
GatekeeperResponse verifyRsp;
GatekeeperResponse reenrollVerifyRsp;
hidl_vec<uint8_t> password;
hidl_vec<uint8_t> newPassword;
ALOGI("Testing Untrusted Reenroll (expected success)");
generatePassword(password, 0);
enrollNewPassword(password, enrollRsp, true);
verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
ALOGI("Primary Enroll+Verify done");
generatePassword(newPassword, 1);
enrollNewPassword(newPassword, reenrollRsp, true);
verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
ALOGI("Untrusted ReEnroll+Verify done");
const hw_auth_token_t *first = toAuthToken(verifyRsp);
const hw_auth_token_t *second = toAuthToken(reenrollVerifyRsp);
if (first != nullptr && second != nullptr) {
EXPECT_NE(first->user_id, second->user_id);
}
ALOGI("Testing Untrusted Reenroll done");
}
/**
* Ensure we dont get successful verify with invalid data
*/
TEST_P(GatekeeperHidlTest, VerifyNoData) {
hidl_vec<uint8_t> password;
hidl_vec<uint8_t> passwordHandle;
GatekeeperResponse verifyRsp;
ALOGI("Testing Verify (expected failure)");
verifyPassword(password, passwordHandle, 0, verifyRsp, false);
EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, verifyRsp.code);
ALOGI("Testing Verify done");
}
/**
* Ensure we can not verify password after we enrolled it and then deleted user
*/
TEST_P(GatekeeperHidlTest, DeleteUserTest) {
hidl_vec<uint8_t> password;
GatekeeperResponse enrollRsp;
GatekeeperResponse verifyRsp;
GatekeeperResponse delRsp;
ALOGI("Testing deleteUser (expected success)");
setUid(10001);
generatePassword(password, 0);
enrollNewPassword(password, enrollRsp, true);
verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
ALOGI("Enroll+Verify done");
doDeleteUser(delRsp);
EXPECT_EQ(UINT32_C(0), delRsp.data.size());
EXPECT_TRUE(delRsp.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
delRsp.code == GatekeeperStatusCode::STATUS_OK);
ALOGI("DeleteUser done");
if (delRsp.code == GatekeeperStatusCode::STATUS_OK) {
verifyPassword(password, enrollRsp.data, 0, verifyRsp, false);
EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, verifyRsp.code);
ALOGI("Verify after Delete done (must fail)");
}
ALOGI("Testing deleteUser done: rsp=%" PRIi32, delRsp.code);
}
/**
* Ensure we can not delete a user that does not exist
*/
TEST_P(GatekeeperHidlTest, DeleteInvalidUserTest) {
hidl_vec<uint8_t> password;
GatekeeperResponse enrollRsp;
GatekeeperResponse verifyRsp;
GatekeeperResponse delRsp1;
GatekeeperResponse delRsp2;
ALOGI("Testing deleteUser (expected failure)");
setUid(10002);
generatePassword(password, 0);
enrollNewPassword(password, enrollRsp, true);
verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
ALOGI("Enroll+Verify done");
// Delete the user
doDeleteUser(delRsp1);
EXPECT_EQ(UINT32_C(0), delRsp1.data.size());
EXPECT_TRUE(delRsp1.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
delRsp1.code == GatekeeperStatusCode::STATUS_OK);
// Delete the user again
doDeleteUser(delRsp2);
EXPECT_EQ(UINT32_C(0), delRsp2.data.size());
EXPECT_TRUE(delRsp2.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
delRsp2.code == GatekeeperStatusCode::ERROR_GENERAL_FAILURE);
ALOGI("DeleteUser done");
ALOGI("Testing deleteUser done: rsp=%" PRIi32, delRsp2.code);
}
/**
* Ensure we can not verify passwords after we enrolled them and then deleted
* all users
*/
TEST_P(GatekeeperHidlTest, DeleteAllUsersTest) {
struct UserData {
uint32_t userId;
hidl_vec<uint8_t> password;
GatekeeperResponse enrollRsp;
GatekeeperResponse verifyRsp;
UserData(int id) { userId = id; }
} users[3]{10001, 10002, 10003};
GatekeeperResponse delAllRsp;
ALOGI("Testing deleteAllUsers (expected success)");
// enroll multiple users
for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
setUid(users[i].userId);
generatePassword(users[i].password, (i % 255) + 1);
enrollNewPassword(users[i].password, users[i].enrollRsp, true);
}
ALOGI("Multiple users enrolled");
// verify multiple users
for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
setUid(users[i].userId);
verifyPassword(users[i].password, users[i].enrollRsp.data, 0,
users[i].verifyRsp, true);
}
ALOGI("Multiple users verified");
doDeleteAllUsers(delAllRsp);
EXPECT_EQ(UINT32_C(0), delAllRsp.data.size());
EXPECT_TRUE(delAllRsp.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
delAllRsp.code == GatekeeperStatusCode::STATUS_OK);
ALOGI("All users deleted");
if (delAllRsp.code == GatekeeperStatusCode::STATUS_OK) {
// verify multiple users after they are deleted; all must fail
for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
setUid(users[i].userId);
verifyPassword(users[i].password, users[i].enrollRsp.data, 0,
users[i].verifyRsp, false);
EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE,
users[i].verifyRsp.code);
}
ALOGI("Multiple users verified after delete (all must fail)");
}
ALOGI("Testing deleteAllUsers done: rsp=%" PRIi32, delAllRsp.code);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GatekeeperHidlTest);
INSTANTIATE_TEST_SUITE_P(
PerInstance, GatekeeperHidlTest,
testing::ValuesIn(android::hardware::getAllHalInstanceNames(IGatekeeper::descriptor)),
android::hardware::PrintInstanceNameToString);