diff --git a/gatekeeper/1.0/vts/Gatekeeper.vts b/gatekeeper/1.0/vts/Gatekeeper.vts new file mode 100644 index 0000000000..25dc32f21c --- /dev/null +++ b/gatekeeper/1.0/vts/Gatekeeper.vts @@ -0,0 +1,93 @@ +component_class: HAL_HIDL +component_type_version: 1.0 +component_name: "IGatekeeper" + +package: "android.hardware.gatekeeper" + +import: "android.hardware.gatekeeper@1.0::types" + +interface: { + api: { + name: "enroll" + return_type_hidl: { + type: TYPE_STRUCT + predefined_type: "::android::hardware::gatekeeper::V1_0::GatekeeperResponse" + } + arg: { + type: TYPE_SCALAR + scalar_type: "uint32_t" + } + arg: { + type: TYPE_VECTOR + vector_value: { + type: TYPE_SCALAR + scalar_type: "uint8_t" + } + } + arg: { + type: TYPE_VECTOR + vector_value: { + type: TYPE_SCALAR + scalar_type: "uint8_t" + } + } + arg: { + type: TYPE_VECTOR + vector_value: { + type: TYPE_SCALAR + scalar_type: "uint8_t" + } + } + } + + api: { + name: "verify" + return_type_hidl: { + type: TYPE_STRUCT + predefined_type: "::android::hardware::gatekeeper::V1_0::GatekeeperResponse" + } + arg: { + type: TYPE_SCALAR + scalar_type: "uint32_t" + } + arg: { + type: TYPE_SCALAR + scalar_type: "uint64_t" + } + arg: { + type: TYPE_VECTOR + vector_value: { + type: TYPE_SCALAR + scalar_type: "uint8_t" + } + } + arg: { + type: TYPE_VECTOR + vector_value: { + type: TYPE_SCALAR + scalar_type: "uint8_t" + } + } + } + + api: { + name: "deleteUser" + return_type_hidl: { + type: TYPE_STRUCT + predefined_type: "::android::hardware::gatekeeper::V1_0::GatekeeperResponse" + } + arg: { + type: TYPE_SCALAR + scalar_type: "uint32_t" + } + } + + api: { + name: "deleteAllUsers" + return_type_hidl: { + type: TYPE_STRUCT + predefined_type: "::android::hardware::gatekeeper::V1_0::GatekeeperResponse" + } + } + +} diff --git a/gatekeeper/1.0/vts/functional/Android.bp b/gatekeeper/1.0/vts/functional/Android.bp new file mode 100644 index 0000000000..9c72b81b29 --- /dev/null +++ b/gatekeeper/1.0/vts/functional/Android.bp @@ -0,0 +1,37 @@ +// +// 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. +// + +cc_test { + name: "gatekeeper_hidl_hal_test", + gtest: true, + srcs: ["gatekeeper_hidl_hal_test.cpp"], + shared_libs: [ + "libbase", + "liblog", + "libcutils", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "libnativehelper", + "libutils", + "android.hardware.gatekeeper@1.0", + ], + static_libs: ["libgtest"], + cflags: [ + "-O0", + "-g", + ], +} diff --git a/gatekeeper/1.0/vts/functional/gatekeeper_hidl_hal_test.cpp b/gatekeeper/1.0/vts/functional/gatekeeper_hidl_hal_test.cpp new file mode 100644 index 0000000000..4dd929401a --- /dev/null +++ b/gatekeeper/1.0/vts/functional/gatekeeper_hidl_hal_test.cpp @@ -0,0 +1,408 @@ +/* + * 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 +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include + +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 curPwdHandle; + hidl_vec curPwd; + hidl_vec 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(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::Test { + 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.getStatus().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.getStatus().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.getStatus().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.getStatus().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 &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 &password, GatekeeperResponse &rsp, + bool expectSuccess) { + GatekeeperRequest req; + req.newPwd.setToExternal(password.data(), password.size()); + doEnroll(req, rsp); + checkEnroll(rsp, expectSuccess); + } + + void verifyPassword(hidl_vec &password, + hidl_vec &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 gatekeeper_; + uint32_t uid_; + + public: + GatekeeperHidlTest() : uid_(0) {} + virtual void SetUp() override { + GatekeeperResponse rsp; + gatekeeper_ = IGatekeeper::getService("gatekeeper", false); + 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 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 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 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 password; + hidl_vec 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; + GatekeeperRequest reenrollReq; + GatekeeperResponse reenrollRsp; + GatekeeperResponse verifyRsp; + GatekeeperResponse reenrollVerifyRsp; + hidl_vec password; + hidl_vec 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 password; + hidl_vec 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 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 verify passwords after we enrolled them and then deleted + * all users + */ +TEST_F(GatekeeperHidlTest, DeleteAllUsersTest) { + struct UserData { + uint32_t userId; + hidl_vec 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; +} diff --git a/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/__init__.py b/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/__init__.py b/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/target/Android.mk b/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/target/Android.mk new file mode 100644 index 0000000000..59bfe172ee --- /dev/null +++ b/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/target/Android.mk @@ -0,0 +1,25 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(call all-subdir-makefiles) + +include $(CLEAR_VARS) + +LOCAL_MODULE := HalGatekeeperHidlTargetBasicTest +VTS_CONFIG_SRC_DIR := testcases/hal/gatekeeper/hidl/target +include test/vts/tools/build/Android.host_config.mk diff --git a/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/target/AndroidTest.xml b/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/target/AndroidTest.xml new file mode 100644 index 0000000000..92568239b8 --- /dev/null +++ b/gatekeeper/1.0/vts/functional/vts/testcases/hal/gatekeeper/hidl/target/AndroidTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/gatekeeper/1.0/vts/types.vts b/gatekeeper/1.0/vts/types.vts new file mode 100644 index 0000000000..3de9a148e6 --- /dev/null +++ b/gatekeeper/1.0/vts/types.vts @@ -0,0 +1,59 @@ +component_class: HAL_HIDL +component_type_version: 1.0 +component_name: "types" + +package: "android.hardware.gatekeeper" + + +attribute: { + name: "::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode" + type: TYPE_ENUM + enum_value: { + scalar_type: "int32_t" + + enumerator: "STATUS_REENROLL" + scalar_value: { + int32_t: 1 + } + enumerator: "STATUS_OK" + scalar_value: { + int32_t: 0 + } + enumerator: "ERROR_GENERAL_FAILURE" + scalar_value: { + int32_t: -1 + } + enumerator: "ERROR_RETRY_TIMEOUT" + scalar_value: { + int32_t: -2 + } + enumerator: "ERROR_NOT_IMPLEMENTED" + scalar_value: { + int32_t: -3 + } + } +} + +attribute: { + name: "::android::hardware::gatekeeper::V1_0::GatekeeperResponse" + type: TYPE_STRUCT + struct_value: { + name: "code" + type: TYPE_ENUM + predefined_type: "::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode" + } + struct_value: { + name: "timeout" + type: TYPE_SCALAR + scalar_type: "uint32_t" + } + struct_value: { + name: "data" + type: TYPE_VECTOR + vector_value: { + type: TYPE_SCALAR + scalar_type: "uint8_t" + } + } +} + diff --git a/gatekeeper/Android.bp b/gatekeeper/Android.bp index bbb3e4bac0..33f70ebae2 100644 --- a/gatekeeper/Android.bp +++ b/gatekeeper/Android.bp @@ -1,4 +1,5 @@ // This is an autogenerated file, do not edit. subdirs = [ "1.0", + "1.0/vts/functional", ]