Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e23b4bf authored by David Drysdale's avatar David Drysdale Committed by Automerger Merge Worker
Browse files

Merge "Add Gatekeeper-based auth tests" am: e85498a9

parents be2e9bbb e85498a9
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -35,9 +35,12 @@ cc_defaults {
        "libbinder_ndk",
        "libcrypto",
        "libbase",
        "libgatekeeper",
        "packagemanager_aidl-cpp",
    ],
    static_libs: [
        "android.hardware.gatekeeper@1.0",
        "android.hardware.gatekeeper-V1-ndk",
        "android.hardware.security.rkp-V3-ndk",
        "android.hardware.security.secureclock-V1-ndk",
        "libcppbor_external",
@@ -59,6 +62,7 @@ cc_test {
    ],
    srcs: [
        "AttestKeyTest.cpp",
        "AuthTest.cpp",
        "DeviceUniqueAttestationTest.cpp",
        "KeyBlobUpgradeTest.cpp",
        "KeyMintTest.cpp",
+412 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 "keymint_1_test"
#include <cutils/log.h>

#include <iostream>
#include <optional>

#include "KeyMintAidlTestBase.h"

#include <aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.h>
#include <aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.h>
#include <aidl/android/hardware/gatekeeper/IGatekeeper.h>
#include <aidl/android/hardware/security/secureclock/ISecureClock.h>
#include <android-base/logging.h>
#include <android/binder_manager.h>

using aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse;
using aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse;
using aidl::android::hardware::gatekeeper::IGatekeeper;
using aidl::android::hardware::security::keymint::HardwareAuthToken;
using aidl::android::hardware::security::secureclock::ISecureClock;

#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
#include <android/hardware/gatekeeper/1.0/types.h>
#include <gatekeeper/password_handle.h>  // for password_handle_t
#include <hardware/hw_auth_token.h>

using ::android::sp;
using IHidlGatekeeper = ::android::hardware::gatekeeper::V1_0::IGatekeeper;
using HidlGatekeeperResponse = ::android::hardware::gatekeeper::V1_0::GatekeeperResponse;
using HidlGatekeeperStatusCode = ::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;

namespace aidl::android::hardware::security::keymint::test {

class AuthTest : public KeyMintAidlTestBase {
  public:
    void SetUp() {
        KeyMintAidlTestBase::SetUp();

        // Find the default Gatekeeper instance.
        string gk_name = string(IGatekeeper::descriptor) + "/default";
        if (AServiceManager_isDeclared(gk_name.c_str())) {
            // Enroll a user with AIDL Gatekeeper.
            ::ndk::SpAIBinder binder(AServiceManager_waitForService(gk_name.c_str()));
            gk_ = IGatekeeper::fromBinder(binder);
        } else {
            // Prior to Android U, Gatekeeper was HIDL not AIDL and so may not be present.
            // Try to enroll user with HIDL Gatekeeper instead.
            string gk_name = "default";
            hidl_gk_ = IHidlGatekeeper::getService(gk_name.c_str());
            if (hidl_gk_ == nullptr) {
                std::cerr << "No HIDL Gatekeeper instance for '" << gk_name << "' found.\n";
                return;
            }
            std::cerr << "No AIDL Gatekeeper instance for '" << gk_name << "' found, using HIDL.\n";
        }

        // If the device needs timestamps, find the default ISecureClock instance.
        if (timestamp_token_required_) {
            string clock_name = string(ISecureClock::descriptor) + "/default";
            if (AServiceManager_isDeclared(clock_name.c_str())) {
                ::ndk::SpAIBinder binder(AServiceManager_waitForService(clock_name.c_str()));
                clock_ = ISecureClock::fromBinder(binder);
            } else {
                std::cerr << "No ISecureClock instance for '" << clock_name << "' found.\n";
            }
        }

        // Enroll a password for a user.
        uid_ = 10001;
        password_ = "correcthorsebatterystaple";
        std::optional<GatekeeperEnrollResponse> rsp = doEnroll(password_);
        ASSERT_TRUE(rsp.has_value());
        sid_ = rsp->secureUserId;
        handle_ = rsp->data;
    }

    void TearDown() {
        if (gk_ == nullptr) return;
        gk_->deleteUser(uid_);
    }

    bool GatekeeperAvailable() { return (gk_ != nullptr) || (hidl_gk_ != nullptr); }

    std::optional<GatekeeperEnrollResponse> doEnroll(const std::vector<uint8_t>& newPwd,
                                                     const std::vector<uint8_t>& curHandle = {},
                                                     const std::vector<uint8_t>& curPwd = {}) {
        if (gk_ != nullptr) {
            while (true) {
                GatekeeperEnrollResponse rsp;
                Status status = gk_->enroll(uid_, curHandle, curPwd, newPwd, &rsp);
                if (!status.isOk() && status.getExceptionCode() == EX_SERVICE_SPECIFIC &&
                    status.getServiceSpecificError() == IGatekeeper::ERROR_RETRY_TIMEOUT) {
                    sleep(1);
                    continue;
                }
                if (status.isOk()) {
                    return std::move(rsp);
                } else {
                    GTEST_LOG_(ERROR) << "doEnroll(AIDL) failed: " << status;
                    return std::nullopt;
                }
            }
        } else if (hidl_gk_ != nullptr) {
            while (true) {
                HidlGatekeeperResponse rsp;
                auto status = hidl_gk_->enroll(
                        uid_, curHandle, curPwd, newPwd,
                        [&rsp](const HidlGatekeeperResponse& cbRsp) { rsp = cbRsp; });
                if (!status.isOk()) {
                    GTEST_LOG_(ERROR) << "doEnroll(HIDL) failed";
                    return std::nullopt;
                }
                if (rsp.code == HidlGatekeeperStatusCode::ERROR_RETRY_TIMEOUT) {
                    sleep(1);
                    continue;
                }
                if (rsp.code != HidlGatekeeperStatusCode::STATUS_OK) {
                    GTEST_LOG_(ERROR) << "doEnroll(HIDL) failed with " << int(rsp.code);
                    return std::nullopt;
                }
                // "Parse" the returned data to get at the secure user ID.
                if (rsp.data.size() != sizeof(::gatekeeper::password_handle_t)) {
                    GTEST_LOG_(ERROR)
                            << "HAL returned password handle of invalid length " << rsp.data.size();
                    return std::nullopt;
                }
                const ::gatekeeper::password_handle_t* handle =
                        reinterpret_cast<const ::gatekeeper::password_handle_t*>(rsp.data.data());

                // Translate HIDL response to look like an AIDL response.
                GatekeeperEnrollResponse aidl_rsp;
                aidl_rsp.statusCode = IGatekeeper::STATUS_OK;
                aidl_rsp.data = rsp.data;
                aidl_rsp.secureUserId = handle->user_id;
                return aidl_rsp;
            }
        } else {
            return std::nullopt;
        }
    }

    std::optional<GatekeeperEnrollResponse> doEnroll(const string& newPwd,
                                                     const std::vector<uint8_t>& curHandle = {},
                                                     const string& curPwd = {}) {
        return doEnroll(std::vector<uint8_t>(newPwd.begin(), newPwd.end()), curHandle,
                        std::vector<uint8_t>(curPwd.begin(), curPwd.end()));
    }

    std::optional<HardwareAuthToken> doVerify(uint64_t challenge,
                                              const std::vector<uint8_t>& handle,
                                              const std::vector<uint8_t>& pwd) {
        if (gk_ != nullptr) {
            while (true) {
                GatekeeperVerifyResponse rsp;
                Status status = gk_->verify(uid_, challenge, handle, pwd, &rsp);
                if (!status.isOk() && status.getExceptionCode() == EX_SERVICE_SPECIFIC &&
                    status.getServiceSpecificError() == IGatekeeper::ERROR_RETRY_TIMEOUT) {
                    sleep(1);
                    continue;
                }
                if (status.isOk()) {
                    return rsp.hardwareAuthToken;
                } else {
                    GTEST_LOG_(ERROR) << "doVerify(AIDL) failed: " << status;
                    return std::nullopt;
                }
            }
        } else if (hidl_gk_ != nullptr) {
            while (true) {
                HidlGatekeeperResponse rsp;
                auto status = hidl_gk_->verify(
                        uid_, challenge, handle, pwd,
                        [&rsp](const HidlGatekeeperResponse& cbRsp) { rsp = cbRsp; });
                if (!status.isOk()) {
                    GTEST_LOG_(ERROR) << "doVerify(HIDL) failed";
                    return std::nullopt;
                }
                if (rsp.code == HidlGatekeeperStatusCode::ERROR_RETRY_TIMEOUT) {
                    sleep(1);
                    continue;
                }
                if (rsp.code != HidlGatekeeperStatusCode::STATUS_OK) {
                    GTEST_LOG_(ERROR) << "doVerify(HIDL) failed with " << int(rsp.code);
                    return std::nullopt;
                }
                // "Parse" the returned data to get auth token contents.
                if (rsp.data.size() != sizeof(hw_auth_token_t)) {
                    GTEST_LOG_(ERROR) << "Incorrect size of AuthToken payload.";
                    return std::nullopt;
                }
                const hw_auth_token_t* hwAuthToken =
                        reinterpret_cast<const hw_auth_token_t*>(rsp.data.data());
                HardwareAuthToken authToken;
                authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp);
                authToken.challenge = hwAuthToken->challenge;
                authToken.userId = hwAuthToken->user_id;
                authToken.authenticatorId = hwAuthToken->authenticator_id;
                authToken.authenticatorType = static_cast<HardwareAuthenticatorType>(
                        betoh32(hwAuthToken->authenticator_type));
                authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]);
                return authToken;
            }
        } else {
            return std::nullopt;
        }
    }
    std::optional<HardwareAuthToken> doVerify(uint64_t challenge,
                                              const std::vector<uint8_t>& handle,
                                              const string& pwd) {
        return doVerify(challenge, handle, std::vector<uint8_t>(pwd.begin(), pwd.end()));
    }

    // Variants of the base class methods but with authentication information included.
    string ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
                          const string& message, const AuthorizationSet& in_params,
                          AuthorizationSet* out_params, const HardwareAuthToken& hat) {
        AuthorizationSet begin_out_params;
        ErrorCode result = Begin(operation, key_blob, in_params, out_params, hat);
        EXPECT_EQ(ErrorCode::OK, result);
        if (result != ErrorCode::OK) {
            return "";
        }

        std::optional<secureclock::TimeStampToken> time_token = std::nullopt;
        if (timestamp_token_required_ && clock_ != nullptr) {
            // Ask a secure clock instance for a timestamp, including the per-op challenge.
            secureclock::TimeStampToken token;
            EXPECT_EQ(ErrorCode::OK,
                      GetReturnErrorCode(clock_->generateTimeStamp(challenge_, &token)));
            time_token = token;
        }

        string output;
        EXPECT_EQ(ErrorCode::OK, Finish(message, {} /* signature */, &output, hat, time_token));
        return output;
    }

    string EncryptMessage(const vector<uint8_t>& key_blob, const string& message,
                          const AuthorizationSet& in_params, AuthorizationSet* out_params,
                          const HardwareAuthToken& hat) {
        SCOPED_TRACE("EncryptMessage");
        return ProcessMessage(key_blob, KeyPurpose::ENCRYPT, message, in_params, out_params, hat);
    }

    string DecryptMessage(const vector<uint8_t>& key_blob, const string& ciphertext,
                          const AuthorizationSet& params, const HardwareAuthToken& hat) {
        SCOPED_TRACE("DecryptMessage");
        AuthorizationSet out_params;
        string plaintext =
                ProcessMessage(key_blob, KeyPurpose::DECRYPT, ciphertext, params, &out_params, hat);
        EXPECT_TRUE(out_params.empty());
        return plaintext;
    }

  protected:
    std::shared_ptr<IGatekeeper> gk_;
    sp<IHidlGatekeeper> hidl_gk_;
    std::shared_ptr<ISecureClock> clock_;
    string password_;
    uint32_t uid_;
    long sid_;
    std::vector<uint8_t> handle_;
};

// Test use of a key that requires user-authentication within recent history.
TEST_P(AuthTest, TimeoutAuthentication) {
    if (!GatekeeperAvailable()) {
        GTEST_SKIP() << "No Gatekeeper available";
    }
    if (timestamp_token_required_ && clock_ == nullptr) {
        GTEST_SKIP() << "Device requires timestamps and no ISecureClock available";
    }

    // Create an AES key that requires authentication within the last 3 seconds.
    const uint32_t timeout_secs = 3;
    auto builder = AuthorizationSetBuilder()
                           .AesEncryptionKey(256)
                           .BlockMode(BlockMode::ECB)
                           .Padding(PaddingMode::PKCS7)
                           .Authorization(TAG_USER_SECURE_ID, sid_)
                           .Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::PASSWORD)
                           .Authorization(TAG_AUTH_TIMEOUT, timeout_secs);
    vector<uint8_t> keyblob;
    vector<KeyCharacteristics> key_characteristics;
    vector<Certificate> cert_chain;
    ASSERT_EQ(ErrorCode::OK,
              GenerateKey(builder, std::nullopt, &keyblob, &key_characteristics, &cert_chain));

    // Attempt to use the AES key without authentication.
    const string message = "Hello World!";
    AuthorizationSet out_params;
    auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7);
    EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED,
              Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params));

    // Verify to get a HAT, arbitrary challenge.
    const uint64_t challenge = 42;
    const std::optional<HardwareAuthToken> hat = doVerify(challenge, handle_, password_);
    ASSERT_TRUE(hat.has_value());
    EXPECT_EQ(hat->userId, sid_);

    // Adding the auth token makes it possible to use the AES key.
    const string ciphertext = EncryptMessage(keyblob, message, params, &out_params, hat.value());
    const string plaintext = DecryptMessage(keyblob, ciphertext, params, hat.value());
    EXPECT_EQ(message, plaintext);

    // Altering a single bit in the MAC means no auth.
    HardwareAuthToken dodgy_hat = hat.value();
    ASSERT_GT(dodgy_hat.mac.size(), 0);
    dodgy_hat.mac[0] ^= 0x01;
    EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED,
              Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, dodgy_hat));

    // Wait for long enough that the hardware auth token expires.
    sleep(timeout_secs + 1);
    if (!timestamp_token_required_) {
        // KeyMint implementation has its own clock, and can immediately detect timeout.
        EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED,
                  Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, hat));
    } else {
        // KeyMint implementation has no clock, so only detects timeout via timestamp token provided
        // on update()/finish().
        ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, hat));
        secureclock::TimeStampToken time_token;
        EXPECT_EQ(ErrorCode::OK,
                  GetReturnErrorCode(clock_->generateTimeStamp(challenge_, &time_token)));

        string output;
        EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED,
                  Finish(message, {} /* signature */, &output, hat, time_token));
    }
}

// Test use of a key that requires an auth token for each action on the operation, with
// a per-operation challenge value included.
TEST_P(AuthTest, AuthPerOperation) {
    if (!GatekeeperAvailable()) {
        GTEST_SKIP() << "No Gatekeeper available";
    }

    // Create an AES key that requires authentication per-action.
    auto builder = AuthorizationSetBuilder()
                           .AesEncryptionKey(256)
                           .BlockMode(BlockMode::ECB)
                           .Padding(PaddingMode::PKCS7)
                           .Authorization(TAG_USER_SECURE_ID, sid_)
                           .Authorization(TAG_USER_AUTH_TYPE, HardwareAuthenticatorType::PASSWORD);
    vector<uint8_t> keyblob;
    vector<KeyCharacteristics> key_characteristics;
    vector<Certificate> cert_chain;
    ASSERT_EQ(ErrorCode::OK,
              GenerateKey(builder, std::nullopt, &keyblob, &key_characteristics, &cert_chain));

    // Attempt to use the AES key without authentication fails after begin.
    const string message = "Hello World!";
    AuthorizationSet out_params;
    auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7);
    EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params));
    string output;
    EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED, Finish(message, {} /* signature */, &output));

    // Verify to get a HAT, but with an arbitrary challenge.
    const uint64_t unrelated_challenge = 42;
    const std::optional<HardwareAuthToken> unrelated_hat =
            doVerify(unrelated_challenge, handle_, password_);
    ASSERT_TRUE(unrelated_hat.has_value());
    EXPECT_EQ(unrelated_hat->userId, sid_);

    // Attempt to use the AES key with an unrelated authentication fails after begin.
    EXPECT_EQ(ErrorCode::OK,
              Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params, unrelated_hat.value()));
    EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED,
              Finish(message, {} /* signature */, &output, unrelated_hat.value()));

    // Now get a HAT with the challenge from an in-progress operation.
    EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params));
    const std::optional<HardwareAuthToken> hat = doVerify(challenge_, handle_, password_);
    ASSERT_TRUE(hat.has_value());
    EXPECT_EQ(hat->userId, sid_);
    string ciphertext;
    EXPECT_EQ(ErrorCode::OK, Finish(message, {} /* signature */, &ciphertext, hat.value()));

    // Altering a single bit in the MAC means no auth.
    EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, keyblob, params, &out_params));
    std::optional<HardwareAuthToken> dodgy_hat = doVerify(challenge_, handle_, password_);
    ASSERT_TRUE(dodgy_hat.has_value());
    EXPECT_EQ(dodgy_hat->userId, sid_);
    ASSERT_GT(dodgy_hat->mac.size(), 0);
    dodgy_hat->mac[0] ^= 0x01;
    EXPECT_EQ(ErrorCode::KEY_USER_NOT_AUTHENTICATED,
              Finish(message, {} /* signature */, &ciphertext, hat.value()));
}

INSTANTIATE_KEYMINT_AIDL_TEST(AuthTest);

}  // namespace aidl::android::hardware::security::keymint::test
+8 −6
Original line number Diff line number Diff line
@@ -552,12 +552,13 @@ ErrorCode KeyMintAidlTestBase::Begin(KeyPurpose purpose, const vector<uint8_t>&

ErrorCode KeyMintAidlTestBase::Begin(KeyPurpose purpose, const vector<uint8_t>& key_blob,
                                     const AuthorizationSet& in_params,
                                     AuthorizationSet* out_params) {
                                     AuthorizationSet* out_params,
                                     std::optional<HardwareAuthToken> hat) {
    SCOPED_TRACE("Begin");
    Status result;
    BeginResult out;

    result = keymint_->begin(purpose, key_blob, in_params.vector_data(), std::nullopt, &out);
    result = keymint_->begin(purpose, key_blob, in_params.vector_data(), hat, &out);

    if (result.isOk()) {
        *out_params = out.params;
@@ -611,8 +612,9 @@ ErrorCode KeyMintAidlTestBase::Update(const string& input, string* output) {
    return GetReturnErrorCode(result);
}

ErrorCode KeyMintAidlTestBase::Finish(const string& input, const string& signature,
                                      string* output) {
ErrorCode KeyMintAidlTestBase::Finish(const string& input, const string& signature, string* output,
                                      std::optional<HardwareAuthToken> hat,
                                      std::optional<secureclock::TimeStampToken> time_token) {
    SCOPED_TRACE("Finish");
    Status result;

@@ -621,8 +623,8 @@ ErrorCode KeyMintAidlTestBase::Finish(const string& input, const string& signatu

    vector<uint8_t> oPut;
    result = op_->finish(vector<uint8_t>(input.begin(), input.end()),
                         vector<uint8_t>(signature.begin(), signature.end()), {} /* authToken */,
                         {} /* timestampToken */, {} /* confirmationToken */, &oPut);
                         vector<uint8_t>(signature.begin(), signature.end()), hat, time_token,
                         {} /* confirmationToken */, &oPut);

    if (result.isOk()) output->append(oPut.begin(), oPut.end());

+5 −2
Original line number Diff line number Diff line
@@ -161,7 +161,8 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> {
                    const AuthorizationSet& in_params, AuthorizationSet* out_params,
                    std::shared_ptr<IKeyMintOperation>& op);
    ErrorCode Begin(KeyPurpose purpose, const vector<uint8_t>& key_blob,
                    const AuthorizationSet& in_params, AuthorizationSet* out_params);
                    const AuthorizationSet& in_params, AuthorizationSet* out_params,
                    std::optional<HardwareAuthToken> hat = std::nullopt);
    ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params,
                    AuthorizationSet* out_params);
    ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params);
@@ -169,7 +170,9 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> {
    ErrorCode UpdateAad(const string& input);
    ErrorCode Update(const string& input, string* output);

    ErrorCode Finish(const string& message, const string& signature, string* output);
    ErrorCode Finish(const string& message, const string& signature, string* output,
                     std::optional<HardwareAuthToken> hat = std::nullopt,
                     std::optional<secureclock::TimeStampToken> time_token = std::nullopt);
    ErrorCode Finish(const string& message, string* output) {
        return Finish(message, {} /* signature */, output);
    }