You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
15 KiB
440 lines
15 KiB
/* |
|
* 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 <hardware/hw_auth_token.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> |
|
|
|
#include <VtsHalHidlTargetTestBase.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::VtsHalHidlTargetTestBase { |
|
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_ = ::testing::VtsHalHidlTargetTestBase::getService<IGatekeeper>(); |
|
ASSERT_NE(nullptr, gatekeeper_.get()); |
|
doDeleteAllUsers(rsp); |
|
} |
|
|
|
virtual void TearDown() override { |
|
GatekeeperResponse rsp; |
|
doDeleteAllUsers(rsp); |
|
} |
|
}; |
|
|
|
/** |
|
* Ensure we can enroll new password |
|
*/ |
|
TEST_F(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_F(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_F(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 Enroll+Verify done"); |
|
} |
|
|
|
/** |
|
* Ensure we can securely update password (keep the same |
|
* secure user_id) if we prove we know old password |
|
*/ |
|
TEST_F(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_F(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_F(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_F(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_F(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_F(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); |
|
} |
|
|
|
int main(int argc, char **argv) { |
|
::testing::InitGoogleTest(&argc, argv); |
|
int status = RUN_ALL_TESTS(); |
|
ALOGI("Test result = %d", status); |
|
return status; |
|
}
|
|
|