Add Gatekeeper-based auth tests
Add tests that directly exercise Gatekeeper to get auth tokens for use
with auth-bound keys.
Test: VtsAidlKeyMintTargetTest
Change-Id: Ie668674d81ca487e8bbc18fdd9f36610bcab4c8c
diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp
index 26e91bd..ed3ca74 100644
--- a/security/keymint/aidl/vts/functional/Android.bp
+++ b/security/keymint/aidl/vts/functional/Android.bp
@@ -35,9 +35,12 @@
"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 @@
],
srcs: [
"AttestKeyTest.cpp",
+ "AuthTest.cpp",
"DeviceUniqueAttestationTest.cpp",
"KeyBlobUpgradeTest.cpp",
"KeyMintTest.cpp",
diff --git a/security/keymint/aidl/vts/functional/AuthTest.cpp b/security/keymint/aidl/vts/functional/AuthTest.cpp
new file mode 100644
index 0000000..a31ac01
--- /dev/null
+++ b/security/keymint/aidl/vts/functional/AuthTest.cpp
@@ -0,0 +1,412 @@
+/*
+ * 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
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
index 588a1d4..3ffb6ec 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -544,12 +544,13 @@
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;
@@ -603,8 +604,9 @@
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;
@@ -613,8 +615,8 @@
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());
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
index fae9459..2a0a19d 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
@@ -160,7 +160,8 @@
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);
@@ -168,7 +169,9 @@
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);
}