From 8960aaefeaedd1a31b088c618d4dece7a0ca1cc6 Mon Sep 17 00:00:00 2001 From: Chirag Pathak Date: Mon, 25 Jan 2021 21:37:06 +0000 Subject: [PATCH] The implementation of vts and default implementation to support ISecureClock and ISharedSecret AIDLs. Test: atest VtsAidlSecureClockTargetTest, atest VtsAidlSharedSecretTargetTest Bug: b/175136979, b/175141176 Change-Id: I4a0d25981d0172c0e2c8defc61b325eca6d6a029 --- security/keymint/aidl/default/Android.bp | 8 +- ....hardware.security.secureclock-service.xml | 6 + ...hardware.security.sharedsecret-service.xml | 6 + security/keymint/aidl/default/service.cpp | 31 +- .../security/secureclock/ISecureClock.aidl | 17 +- .../security/secureclock/TimeStampToken.aidl | 16 +- .../security/secureclock/Timestamp.aidl | 16 +- .../security/secureclock/ISecureClock.aidl | 2 +- .../security/secureclock/TimeStampToken.aidl | 8 +- .../aidl/vts/functional/Android.bp | 43 +++ .../aidl/vts/functional/AndroidTest.xml | 34 +++ .../vts/functional/SecureClockAidlTest.cpp | 193 +++++++++++++ .../aidl/vts/functional/Android.bp | 43 +++ .../aidl/vts/functional/AndroidTest.xml | 34 +++ .../vts/functional/SharedSecretAidlTest.cpp | 268 ++++++++++++++++++ 15 files changed, 707 insertions(+), 18 deletions(-) create mode 100644 security/keymint/aidl/default/android.hardware.security.secureclock-service.xml create mode 100644 security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml create mode 100644 security/secureclock/aidl/vts/functional/Android.bp create mode 100644 security/secureclock/aidl/vts/functional/AndroidTest.xml create mode 100644 security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp create mode 100644 security/sharedsecret/aidl/vts/functional/Android.bp create mode 100644 security/sharedsecret/aidl/vts/functional/AndroidTest.xml create mode 100644 security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp diff --git a/security/keymint/aidl/default/Android.bp b/security/keymint/aidl/default/Android.bp index b2758adcb1..9b7e081314 100644 --- a/security/keymint/aidl/default/Android.bp +++ b/security/keymint/aidl/default/Android.bp @@ -2,7 +2,11 @@ cc_binary { name: "android.hardware.security.keymint-service", relative_install_path: "hw", init_rc: ["android.hardware.security.keymint-service.rc"], - vintf_fragments: ["android.hardware.security.keymint-service.xml"], + vintf_fragments: [ + "android.hardware.security.keymint-service.xml", + "android.hardware.security.sharedsecret-service.xml", + "android.hardware.security.secureclock-service.xml", + ], vendor: true, cflags: [ "-Wall", @@ -10,6 +14,8 @@ cc_binary { ], shared_libs: [ "android.hardware.security.keymint-V1-ndk_platform", + "android.hardware.security.sharedsecret-unstable-ndk_platform", + "android.hardware.security.secureclock-unstable-ndk_platform", "libbase", "libbinder_ndk", "libcppbor", diff --git a/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml new file mode 100644 index 0000000000..c0ff7752ba --- /dev/null +++ b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml @@ -0,0 +1,6 @@ + + + android.hardware.security.secureclock + ISecureClock/default + + diff --git a/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml new file mode 100644 index 0000000000..d37981fe4d --- /dev/null +++ b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml @@ -0,0 +1,6 @@ + + + android.hardware.security.sharedsecret + ISharedSecret/default + + diff --git a/security/keymint/aidl/default/service.cpp b/security/keymint/aidl/default/service.cpp index a710535fac..75b394e187 100644 --- a/security/keymint/aidl/default/service.cpp +++ b/security/keymint/aidl/default/service.cpp @@ -21,25 +21,38 @@ #include #include +#include +#include #include using aidl::android::hardware::security::keymint::AndroidKeyMintDevice; using aidl::android::hardware::security::keymint::SecurityLevel; +using aidl::android::hardware::security::secureclock::AndroidSecureClock; +using aidl::android::hardware::security::sharedsecret::AndroidSharedSecret; + +template +std::shared_ptr addService(Args&&... args) { + std::shared_ptr ser = ndk::SharedRefBase::make(std::forward(args)...); + auto instanceName = std::string(T::descriptor) + "/default"; + LOG(INFO) << "adding keymint service instance: " << instanceName; + binder_status_t status = + AServiceManager_addService(ser->asBinder().get(), instanceName.c_str()); + CHECK(status == STATUS_OK); + return ser; +} int main() { // Zero threads seems like a useless pool, but below we'll join this thread to it, increasing // the pool size to 1. ABinderProcess_setThreadPoolMaxThreadCount(0); + + // Add Keymint Service std::shared_ptr keyMint = - ndk::SharedRefBase::make(SecurityLevel::SOFTWARE); - - keymaster::SoftKeymasterLogger logger; - const auto instanceName = std::string(AndroidKeyMintDevice::descriptor) + "/default"; - LOG(INFO) << "instance: " << instanceName; - binder_status_t status = - AServiceManager_addService(keyMint->asBinder().get(), instanceName.c_str()); - CHECK(status == STATUS_OK); - + addService(SecurityLevel::SOFTWARE); + // Add Secure Clock Service + addService(keyMint); + // Add Shared Secret Service + addService(keyMint); ABinderProcess_joinThreadPool(); return EXIT_FAILURE; // should not reach } diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl index c16b312cda..377889716a 100644 --- a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl @@ -1,4 +1,17 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * Copyright (C) 2020 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. + * limitations under the License. + *//////////////////////////////////////////////////////////////////////////////// // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// @@ -20,5 +33,5 @@ package android.hardware.security.secureclock; @VintfStability interface ISecureClock { android.hardware.security.secureclock.TimeStampToken generateTimeStamp(in long challenge); - const String TIME_STAMP_MAC_LABEL = "Time Verification"; + const String TIME_STAMP_MAC_LABEL = "Auth Verification"; } diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl index 21eeb74519..00a8bb256d 100644 --- a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl @@ -1,4 +1,18 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * Copyright (C) 2020 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. + *//////////////////////////////////////////////////////////////////////////////// // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl index f01fdc74c0..bebeb5cb9c 100644 --- a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl +++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl @@ -1,4 +1,18 @@ -/////////////////////////////////////////////////////////////////////////////// +/* + * Copyright 2020 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. + *//////////////////////////////////////////////////////////////////////////////// // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // /////////////////////////////////////////////////////////////////////////////// diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl index 7d416dda7a..577dd8f231 100644 --- a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl +++ b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl @@ -33,7 +33,7 @@ interface ISecureClock { * String used as context in the HMAC computation signing the generated time stamp. * See TimeStampToken.mac for details. */ - const String TIME_STAMP_MAC_LABEL = "Time Verification"; + const String TIME_STAMP_MAC_LABEL = "Auth Verification"; /** * Generates an authenticated timestamp. diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl index 3fb586078a..dd957325e9 100644 --- a/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl +++ b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl @@ -39,18 +39,20 @@ parcelable TimeStampToken { * 32-byte HMAC-SHA256 of the above values, computed as: * * HMAC(H, - * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp) + * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp || securityLevel ) * * where: * * ``ISecureClock.TIME_STAMP_MAC_LABEL'' is a sting constant defined in ISecureClock.aidl. * - * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedHmacSecret). + * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedSecret). * * ``||'' represents concatenation * * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian - * order. securityLevel is represented as a 32-bit unsigned integer in big-endian order. + * order. SecurityLevel is represented as a 32-bit unsigned integer in big-endian order as + * described in android.hardware.security.keymint.SecurityLevel. It represents the security + * level of the secure clock environment. */ byte[] mac; } diff --git a/security/secureclock/aidl/vts/functional/Android.bp b/security/secureclock/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..30244eb137 --- /dev/null +++ b/security/secureclock/aidl/vts/functional/Android.bp @@ -0,0 +1,43 @@ +// +// Copyright (C) 2020 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: "VtsAidlSecureClockTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + cflags: [ + "-Wall", + "-Wextra", + ], + srcs: [ + "SecureClockAidlTest.cpp", + ], + shared_libs: [ + "libbinder_ndk", + "libcrypto", + "libkeymint", + ], + static_libs: [ + "android.hardware.security.keymint-unstable-ndk_platform", + "android.hardware.security.secureclock-unstable-ndk_platform", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/security/secureclock/aidl/vts/functional/AndroidTest.xml b/security/secureclock/aidl/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..4861c7c7af --- /dev/null +++ b/security/secureclock/aidl/vts/functional/AndroidTest.xml @@ -0,0 +1,34 @@ + + + + diff --git a/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp new file mode 100644 index 0000000000..9ca1ee8af1 --- /dev/null +++ b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2020 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 "secureclock_test" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace aidl::android::hardware::security::secureclock::test { +using Status = ::ndk::ScopedAStatus; +using ::aidl::android::hardware::security::keymint::ErrorCode; +using ::std::shared_ptr; +using ::std::string; +using ::std::vector; + +class SecureClockAidlTest : public ::testing::TestWithParam { + public: + struct TimestampTokenResult { + ErrorCode error; + TimeStampToken token; + }; + + TimestampTokenResult getTimestampToken(int64_t in_challenge) { + TimestampTokenResult result; + result.error = + GetReturnErrorCode(secureClock_->generateTimeStamp(in_challenge, &result.token)); + return result; + } + + uint64_t getTime() { + struct timespec timespec; + EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, ×pec)); + return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000; + } + + int sleep_ms(uint32_t milliseconds) { + struct timespec sleep_time = {static_cast(milliseconds / 1000), + static_cast(milliseconds % 1000) * 1000000}; + while (sleep_time.tv_sec || sleep_time.tv_nsec) { + if (nanosleep(&sleep_time /* to wait */, + &sleep_time /* remaining (on interrruption) */) == 0) { + sleep_time = {}; + } else { + if (errno != EINTR) return errno; + } + } + return 0; + } + + ErrorCode GetReturnErrorCode(const Status& result) { + if (result.isOk()) return ErrorCode::OK; + + if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast(result.getServiceSpecificError()); + } + + return ErrorCode::UNKNOWN_ERROR; + } + + void InitializeSecureClock(std::shared_ptr secureClock) { + ASSERT_NE(secureClock, nullptr); + secureClock_ = secureClock; + } + + ISecureClock& secureClock() { return *secureClock_; } + + static vector build_params() { + auto params = ::android::getAidlHalInstanceNames(ISecureClock::descriptor); + return params; + } + + void SetUp() override { + if (AServiceManager_isDeclared(GetParam().c_str())) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str())); + InitializeSecureClock(ISecureClock::fromBinder(binder)); + } else { + InitializeSecureClock(nullptr); + } + } + + void TearDown() override {} + + private: + std::shared_ptr secureClock_; +}; + +/* + * The precise capabilities required to generate TimeStampToken will vary depending on the specific + * vendor implementations. The only thing we really can test is that tokens can be created by + * secureclock services, and that the timestamps increase as expected. + */ +TEST_P(SecureClockAidlTest, TestCreation) { + auto result1 = getTimestampToken(1 /* challenge */); + auto result1_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result1.error); + EXPECT_EQ(1U, result1.token.challenge); + EXPECT_GT(result1.token.timestamp.milliSeconds, 0U); + + unsigned long time_to_sleep = 200; + sleep_ms(time_to_sleep); + + auto result2 = getTimestampToken(2 /* challenge */); + auto result2_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result2.error); + EXPECT_EQ(2U, result2.token.challenge); + EXPECT_GT(result2.token.timestamp.milliSeconds, 0U); + + auto host_time_delta = result2_time - result1_time; + + EXPECT_GE(host_time_delta, time_to_sleep) + << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much"; + EXPECT_LE(host_time_delta, time_to_sleep + 100) + << "The getTimestampToken call took " << (host_time_delta - time_to_sleep) + << " ms? That's awful!"; + EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds); + unsigned long km_time_delta = + result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds; + // 20 ms of slop just to avoid test flakiness. + EXPECT_LE(host_time_delta, km_time_delta + 20); + EXPECT_LE(km_time_delta, host_time_delta + 20); + ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size()); + ASSERT_NE(0, + memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size())); +} + +/* + * Test that the mac changes when the time stamp changes. This is does not guarantee that the time + * stamp is included in the mac but on failure we know that it is not. Other than in the test + * case above we call getTimestampToken with the exact same set of parameters. + */ +TEST_P(SecureClockAidlTest, MacChangesOnChangingTimestamp) { + auto result1 = getTimestampToken(0 /* challenge */); + auto result1_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result1.error); + EXPECT_EQ(0U, result1.token.challenge); + EXPECT_GT(result1.token.timestamp.milliSeconds, 0U); + + unsigned long time_to_sleep = 200; + sleep_ms(time_to_sleep); + + auto result2 = getTimestampToken(1 /* challenge */); + auto result2_time = getTime(); + EXPECT_EQ(ErrorCode::OK, result2.error); + EXPECT_EQ(1U, result2.token.challenge); + EXPECT_GT(result2.token.timestamp.milliSeconds, 0U); + + auto host_time_delta = result2_time - result1_time; + + EXPECT_GE(host_time_delta, time_to_sleep) + << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much"; + EXPECT_LE(host_time_delta, time_to_sleep + 100) + << "The getTimestampToken call took " << (host_time_delta - time_to_sleep) + << " ms? That's awful!"; + + EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds); + unsigned long km_time_delta = + result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds; + + EXPECT_LE(host_time_delta, km_time_delta + 20); + EXPECT_LE(km_time_delta, host_time_delta + 20); + ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size()); + ASSERT_NE(0, + memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size())); +} + +INSTANTIATE_TEST_SUITE_P(PerInstance, SecureClockAidlTest, + testing::ValuesIn(SecureClockAidlTest::build_params()), + ::android::PrintInstanceNameToString); +} // namespace aidl::android::hardware::security::secureclock::test + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/security/sharedsecret/aidl/vts/functional/Android.bp b/security/sharedsecret/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..56ab317cf2 --- /dev/null +++ b/security/sharedsecret/aidl/vts/functional/Android.bp @@ -0,0 +1,43 @@ +// +// Copyright (C) 2020 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: "VtsAidlSharedSecretTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "SharedSecretAidlTest.cpp", + ], + cflags: [ + "-Wall", + "-Wextra", + ], + shared_libs: [ + "libbinder_ndk", + "libcrypto", + "libkeymint", + ], + static_libs: [ + "android.hardware.security.keymint-unstable-ndk_platform", + "android.hardware.security.sharedsecret-unstable-ndk_platform", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/security/sharedsecret/aidl/vts/functional/AndroidTest.xml b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..c6697bcda5 --- /dev/null +++ b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml @@ -0,0 +1,34 @@ + + + + diff --git a/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp new file mode 100644 index 0000000000..83f6ef39db --- /dev/null +++ b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2020 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 "sharedsecret_test" +#include + +#include +#include +#include +#include +#include +#include + +namespace aidl::android::hardware::security::sharedsecret::test { +using ::aidl::android::hardware::security::keymint::ErrorCode; +using ::std::shared_ptr; +using ::std::vector; +using Status = ::ndk::ScopedAStatus; + +class SharedSecretAidlTest : public ::testing::Test { + public: + struct GetParamsResult { + ErrorCode error; + SharedSecretParameters params; + auto tie() { return std::tie(error, params); } + }; + + struct ComputeResult { + ErrorCode error; + vector sharing_check; + auto tie() { return std::tie(error, sharing_check); } + }; + + GetParamsResult getSharedSecretParameters(shared_ptr& sharedSecret) { + SharedSecretParameters params; + auto error = GetReturnErrorCode(sharedSecret->getSharedSecretParameters(¶ms)); + EXPECT_EQ(ErrorCode::OK, error); + GetParamsResult result; + result.tie() = std::tie(error, params); + return result; + } + + vector getAllSharedSecretParameters() { + vector paramsVec; + for (auto& sharedSecret : allSharedSecrets_) { + auto result = getSharedSecretParameters(sharedSecret); + EXPECT_EQ(ErrorCode::OK, result.error); + if (result.error == ErrorCode::OK) paramsVec.push_back(std::move(result.params)); + } + return paramsVec; + } + + ComputeResult computeSharedSecret(shared_ptr& sharedSecret, + const vector& params) { + std::vector sharingCheck; + auto error = GetReturnErrorCode(sharedSecret->computeSharedSecret(params, &sharingCheck)); + ComputeResult result; + result.tie() = std::tie(error, sharingCheck); + return result; + } + + vector computeAllSharedSecrets(const vector& params) { + vector result; + for (auto& sharedSecret : allSharedSecrets_) { + result.push_back(computeSharedSecret(sharedSecret, params)); + } + return result; + } + + vector> copyNonces(const vector& paramsVec) { + vector> nonces; + for (auto& param : paramsVec) { + nonces.push_back(param.nonce); + } + return nonces; + } + + void verifyResponses(const vector& expected, const vector& responses) { + for (auto& response : responses) { + EXPECT_EQ(ErrorCode::OK, response.error); + EXPECT_EQ(expected, response.sharing_check) << "Sharing check values should match."; + } + } + + ErrorCode GetReturnErrorCode(const Status& result) { + if (result.isOk()) return ErrorCode::OK; + if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast(result.getServiceSpecificError()); + } + return ErrorCode::UNKNOWN_ERROR; + } + + static shared_ptr getSharedSecretService(const char* name) { + if (AServiceManager_isDeclared(name)) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(name)); + return ISharedSecret::fromBinder(binder); + } + return nullptr; + } + + const vector>& allSharedSecrets() { return allSharedSecrets_; } + + static void SetUpTestCase() { + if (allSharedSecrets_.empty()) { + auto names = ::android::getAidlHalInstanceNames(ISharedSecret::descriptor); + for (const auto& name : names) { + auto servicePtr = getSharedSecretService(name.c_str()); + if (servicePtr != nullptr) allSharedSecrets_.push_back(std::move(servicePtr)); + } + } + } + static void TearDownTestCase() {} + void SetUp() override {} + void TearDown() override {} + + private: + static vector> allSharedSecrets_; +}; + +vector> SharedSecretAidlTest::allSharedSecrets_; + +TEST_F(SharedSecretAidlTest, GetParameters) { + auto sharedSecrets = allSharedSecrets(); + for (auto sharedSecret : sharedSecrets) { + auto result1 = getSharedSecretParameters(sharedSecret); + EXPECT_EQ(ErrorCode::OK, result1.error); + auto result2 = getSharedSecretParameters(sharedSecret); + EXPECT_EQ(ErrorCode::OK, result2.error); + ASSERT_EQ(result1.params.seed, result2.params.seed) + << "A given shared secret service should always return the same seed."; + ASSERT_EQ(result1.params.nonce, result2.params.nonce) + << "A given shared secret service should always return the same nonce until " + "restart."; + } +} + +TEST_F(SharedSecretAidlTest, ComputeSharedSecret) { + auto params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret services failed to provide parameters."; + auto nonces = copyNonces(params); + EXPECT_EQ(allSharedSecrets().size(), nonces.size()); + std::sort(nonces.begin(), nonces.end()); + std::unique(nonces.begin(), nonces.end()); + EXPECT_EQ(allSharedSecrets().size(), nonces.size()); + + auto responses = computeAllSharedSecrets(params); + ASSERT_GT(responses.size(), 0U); + verifyResponses(responses[0].sharing_check, responses); + + // Do it a second time. Should get the same answers. + params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret services failed to provide parameters."; + + responses = computeAllSharedSecrets(params); + ASSERT_GT(responses.size(), 0U); + ASSERT_EQ(32U, responses[0].sharing_check.size()); + verifyResponses(responses[0].sharing_check, responses); +} + +template +class final_action { + public: + explicit final_action(F f) : f_(std::move(f)) {} + ~final_action() { f_(); } + + private: + F f_; +}; + +template +inline final_action finally(const F& f) { + return final_action(f); +} + +TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptNonce) { + auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); }); + + auto params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret services failed to provide parameters."; + + // All should be well in the normal case + auto responses = computeAllSharedSecrets(params); + + ASSERT_GT(responses.size(), 0U); + vector correct_response = responses[0].sharing_check; + verifyResponses(correct_response, responses); + + // Pick a random param, a random byte within the param's nonce, and a random bit within + // the byte. Flip that bit. + size_t param_to_tweak = rand() % params.size(); + uint8_t byte_to_tweak = rand() % sizeof(params[param_to_tweak].nonce); + uint8_t bit_to_tweak = rand() % 8; + params[param_to_tweak].nonce[byte_to_tweak] ^= (1 << bit_to_tweak); + + responses = computeAllSharedSecrets(params); + for (size_t i = 0; i < responses.size(); ++i) { + if (i == param_to_tweak) { + EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error) + << "Shared secret service that provided tweaked param should fail to compute " + "shared secret"; + } else { + EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed"; + EXPECT_NE(correct_response, responses[i].sharing_check) + << "Others should calculate a different shared secret, due to the tweaked " + "nonce."; + } + } +} + +TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptSeed) { + auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); }); + auto params = getAllSharedSecretParameters(); + ASSERT_EQ(allSharedSecrets().size(), params.size()) + << "One or more shared secret service failed to provide parameters."; + + // All should be well in the normal case + auto responses = computeAllSharedSecrets(params); + + ASSERT_GT(responses.size(), 0U); + vector correct_response = responses[0].sharing_check; + verifyResponses(correct_response, responses); + + // Pick a random param and modify the seed. We just increase the seed length by 1. It doesn't + // matter what value is in the additional byte; it changes the seed regardless. + auto param_to_tweak = rand() % params.size(); + auto& to_tweak = params[param_to_tweak].seed; + ASSERT_TRUE(to_tweak.size() == 32 || to_tweak.size() == 0); + if (!to_tweak.size()) { + to_tweak.resize(32); // Contents don't matter; a little randomization is nice. + } + to_tweak[0]++; + + responses = computeAllSharedSecrets(params); + for (size_t i = 0; i < responses.size(); ++i) { + if (i == param_to_tweak) { + EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error) + << "Shared secret service that provided tweaked param should fail to compute " + "shared secret"; + } else { + EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed"; + EXPECT_NE(correct_response, responses[i].sharing_check) + << "Others should calculate a different shared secret, due to the tweaked " + "nonce."; + } + } +} +} // namespace aidl::android::hardware::security::sharedsecret::test + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}