| /* |
| * 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 <android-base/logging.h> |
| |
| #include <aidl/Vintf.h> |
| #include <aidl/android/hardware/security/keymint/ErrorCode.h> |
| #include <aidl/android/hardware/security/sharedsecret/ISharedSecret.h> |
| #include <android/binder_manager.h> |
| #include <gtest/gtest.h> |
| #include <vector> |
| |
| 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<uint8_t> sharing_check; |
| auto tie() { return std::tie(error, sharing_check); } |
| }; |
| |
| GetParamsResult getSharedSecretParameters(shared_ptr<ISharedSecret>& 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<SharedSecretParameters> getAllSharedSecretParameters() { |
| vector<SharedSecretParameters> 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<ISharedSecret>& sharedSecret, |
| const vector<SharedSecretParameters>& params) { |
| std::vector<uint8_t> sharingCheck; |
| auto error = GetReturnErrorCode(sharedSecret->computeSharedSecret(params, &sharingCheck)); |
| ComputeResult result; |
| result.tie() = std::tie(error, sharingCheck); |
| return result; |
| } |
| |
| vector<ComputeResult> computeAllSharedSecrets(const vector<SharedSecretParameters>& params) { |
| vector<ComputeResult> result; |
| for (auto& sharedSecret : allSharedSecrets_) { |
| result.push_back(computeSharedSecret(sharedSecret, params)); |
| } |
| return result; |
| } |
| |
| vector<vector<uint8_t>> copyNonces(const vector<SharedSecretParameters>& paramsVec) { |
| vector<vector<uint8_t>> nonces; |
| for (auto& param : paramsVec) { |
| nonces.push_back(param.nonce); |
| } |
| return nonces; |
| } |
| |
| void verifyResponses(const vector<uint8_t>& expected, const vector<ComputeResult>& 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<ErrorCode>(result.getServiceSpecificError()); |
| } |
| return ErrorCode::UNKNOWN_ERROR; |
| } |
| |
| static shared_ptr<ISharedSecret> getSharedSecretService(const char* name) { |
| if (AServiceManager_isDeclared(name)) { |
| ::ndk::SpAIBinder binder(AServiceManager_waitForService(name)); |
| return ISharedSecret::fromBinder(binder); |
| } |
| return nullptr; |
| } |
| |
| const vector<shared_ptr<ISharedSecret>>& allSharedSecrets() { return allSharedSecrets_; } |
| |
| static void SetUpTestCase() { |
| ASSERT_TRUE(allSharedSecrets_.empty()) << "The Shared Secret vector is not 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<shared_ptr<ISharedSecret>> allSharedSecrets_; |
| }; |
| |
| vector<shared_ptr<ISharedSecret>> SharedSecretAidlTest::allSharedSecrets_; |
| |
| TEST_F(SharedSecretAidlTest, GetParameters) { |
| auto sharedSecrets = allSharedSecrets(); |
| if (sharedSecrets.empty()) { |
| GTEST_SKIP() << "Skipping the test because no shared secret service is found."; |
| } |
| 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 sharedSecrets = allSharedSecrets(); |
| if (sharedSecrets.empty()) { |
| GTEST_SKIP() << "Skipping the test as no shared secret service is found."; |
| } |
| auto params = getAllSharedSecretParameters(); |
| ASSERT_EQ(sharedSecrets.size(), params.size()) |
| << "One or more shared secret services failed to provide parameters."; |
| auto nonces = copyNonces(params); |
| EXPECT_EQ(sharedSecrets.size(), nonces.size()); |
| std::sort(nonces.begin(), nonces.end()); |
| std::unique(nonces.begin(), nonces.end()); |
| EXPECT_EQ(sharedSecrets.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(sharedSecrets.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 F> |
| class final_action { |
| public: |
| explicit final_action(F f) : f_(std::move(f)) {} |
| ~final_action() { f_(); } |
| |
| private: |
| F f_; |
| }; |
| |
| template <class F> |
| inline final_action<F> finally(const F& f) { |
| return final_action<F>(f); |
| } |
| |
| TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptNonce) { |
| auto sharedSecrets = allSharedSecrets(); |
| if (sharedSecrets.empty()) { |
| GTEST_SKIP() << "Skipping the test as no shared secret service is found."; |
| } |
| auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); }); |
| |
| auto params = getAllSharedSecretParameters(); |
| ASSERT_EQ(sharedSecrets.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<uint8_t> 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 sharedSecrets = allSharedSecrets(); |
| if (sharedSecrets.empty()) { |
| GTEST_SKIP() << "Skipping the test as no shared secret service is found."; |
| } |
| auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); }); |
| auto params = getAllSharedSecretParameters(); |
| ASSERT_EQ(sharedSecrets.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<uint8_t> 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(); |
| } |