[DBSC] Add TokenBindingHelper class for managing token binding keys
TokenBindingHelper allows generating binding key assertions from
wrapped unexportable keys that can be stored on disk.
This CL
1. Adds a new TokenBindingHelper class.
2. Adds support for generating assertion JWT to
components/signin/public/base/session_binding_utils.h
3. Adds code for registering binding keys to
MutableProfileOAuth2TokenServiceDelegate
4. Propagates all necessary dependencies to create TokenBindingHelper
from identity_manager_factory.cc to
profile_oauth2_token_service_builder.h
Bug: b/274463812
Change-Id: I7df5aced994d21ee8bf17c118dc8b43e643dd103
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4584432
Reviewed-by: Monica Basta <msalama@chromium.org>
Commit-Queue: Alex Ilin <alexilin@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1154869}
diff --git a/chrome/browser/signin/bound_session_credentials/registration_token_helper_unittest.cc b/chrome/browser/signin/bound_session_credentials/registration_token_helper_unittest.cc
index 48174cf..8cdf824d 100644
--- a/chrome/browser/signin/bound_session_credentials/registration_token_helper_unittest.cc
+++ b/chrome/browser/signin/bound_session_credentials/registration_token_helper_unittest.cc
@@ -53,7 +53,7 @@
*unexportable_key_service().GetSubjectPublicKeyInfo(key_id);
EXPECT_TRUE(
- signin::VefiryJwtSingature(registration_token, algorithm, pubkey));
+ signin::VerifyJwtSignature(registration_token, algorithm, pubkey));
const auto& wrapped_key = future.Get()->wrapped_binding_key;
EXPECT_EQ(wrapped_key, unexportable_key_service().GetWrappedKey(key_id));
diff --git a/chrome/browser/signin/identity_manager_factory.cc b/chrome/browser/signin/identity_manager_factory.cc
index fd58815c0..9cf4c48 100644
--- a/chrome/browser/signin/identity_manager_factory.cc
+++ b/chrome/browser/signin/identity_manager_factory.cc
@@ -34,6 +34,10 @@
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
#include "chrome/browser/web_data_service_factory.h"
#include "components/keyed_service/core/service_access_type.h"
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+#include "chrome/browser/signin/bound_session_credentials/unexportable_key_service_factory.h"
+#include "components/unexportable_keys/unexportable_key_service.h" // nogncheck
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -69,6 +73,9 @@
.Build()) {
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
DependsOn(WebDataServiceFactory::GetInstance());
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ DependsOn(UnexportableKeyServiceFactory::GetInstance());
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_CHROMEOS_LACROS)
DependsOn(ProfileAccountManagerFactory::GetInstance());
@@ -143,6 +150,10 @@
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
params.token_web_data = WebDataServiceFactory::GetTokenWebDataForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS);
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ params.unexportable_key_service =
+ UnexportableKeyServiceFactory::GetForProfile(profile);
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif // #if BUILDFLAG(ENABLE_DICE_SUPPORT)
#if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/components/signin/internal/identity_manager/BUILD.gn b/components/signin/internal/identity_manager/BUILD.gn
index 86967d64..7824db2 100644
--- a/components/signin/internal/identity_manager/BUILD.gn
+++ b/components/signin/internal/identity_manager/BUILD.gn
@@ -107,6 +107,14 @@
deps += [ "//components/account_manager_core" ]
}
+ if (enable_bound_session_credentials) {
+ sources += [
+ "token_binding_helper.cc",
+ "token_binding_helper.h",
+ ]
+ deps += [ "//components/unexportable_keys" ]
+ }
+
if (is_chromeos_ash) {
deps += [ "//ash/constants" ]
}
@@ -208,6 +216,16 @@
if (enable_dice_support) {
sources += [ "mutable_profile_oauth2_token_service_delegate_unittest.cc" ]
}
+
+ if (enable_bound_session_credentials) {
+ sources += [ "token_binding_helper_unittest.cc" ]
+
+ deps += [
+ "//components/unexportable_keys",
+ "//components/unexportable_keys:test_support",
+ "//crypto:test_support",
+ ]
+ }
}
# This target contains test support that backs the test support for
diff --git a/components/signin/internal/identity_manager/DEPS b/components/signin/internal/identity_manager/DEPS
index dcf37e5..f7f7f07 100644
--- a/components/signin/internal/identity_manager/DEPS
+++ b/components/signin/internal/identity_manager/DEPS
@@ -6,6 +6,7 @@
"+components/signin/public/android/jni_headers",
"+components/signin/public/android/test_support_jni_headers",
"+components/supervised_user/core/common",
+ "+components/unexportable_keys",
"+google_apis",
"+mojo/public",
]
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
index c188d0ec..0bdb430 100644
--- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
+++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
@@ -29,6 +29,10 @@
#include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+#include "components/signin/internal/identity_manager/token_binding_helper.h"
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+
namespace {
const char kAccountIdPrefix[] = "AccountId-";
@@ -194,6 +198,9 @@
scoped_refptr<TokenWebData> token_web_data,
signin::AccountConsistencyMethod account_consistency,
bool revoke_all_tokens_on_load,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ std::unique_ptr<TokenBindingHelper> token_binding_helper,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
FixRequestErrorCallback fix_request_error_callback)
: ProfileOAuth2TokenServiceDelegate(/*use_backoff=*/true),
web_data_service_request_(0),
@@ -203,6 +210,9 @@
token_web_data_(token_web_data),
account_consistency_(account_consistency),
revoke_all_tokens_on_load_(revoke_all_tokens_on_load),
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ token_binding_helper_(std::move(token_binding_helper)),
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
fix_request_error_callback_(fix_request_error_callback) {
VLOG(1) << "MutablePO2TS::MutablePO2TS";
DCHECK(client);
@@ -279,9 +289,11 @@
std::vector<uint8_t>
MutableProfileOAuth2TokenServiceDelegate::GetWrappedBindingKey(
const CoreAccountId& account_id) const {
- // TODO(b/274463812): retrieve a wrapped binding key from memory cache once
- // implemented.
- return {};
+ if (!token_binding_helper_) {
+ return {};
+ }
+
+ return token_binding_helper_->GetWrappedBindingKey(account_id);
}
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
@@ -330,6 +342,11 @@
refresh_tokens_.clear();
ClearAuthError(absl::nullopt);
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ if (token_binding_helper_) {
+ token_binding_helper_->ClearAllKeys();
+ }
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
if (!token_web_data_) {
// This case only exists in unit tests that do not care about loading
@@ -547,8 +564,12 @@
RevokeCredentialsOnServer(refresh_tokens_[account_id]);
refresh_tokens_[account_id] = refresh_token;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ if (token_binding_helper_) {
+ token_binding_helper_->SetBindingKey(account_id, wrapped_binding_key);
+ }
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
UpdateAuthError(account_id, error);
- // TODO(b/274463812): save wrapped_binding_key in memory.
} else {
VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was absent. "
<< "account_id=" << account_id;
@@ -663,6 +684,11 @@
server_revokes_.clear();
CancelWebTokenFetch();
refresh_tokens_.clear();
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ if (token_binding_helper_) {
+ token_binding_helper_->ClearAllKeys();
+ }
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
ProfileOAuth2TokenServiceDelegate::Shutdown();
}
@@ -688,8 +714,12 @@
const GoogleServiceAuthError& error) {
DCHECK_EQ(0u, refresh_tokens_.count(account_id));
refresh_tokens_[account_id] = refresh_token;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ if (token_binding_helper_) {
+ token_binding_helper_->SetBindingKey(account_id, wrapped_binding_key);
+ }
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
UpdateAuthError(account_id, error, /*fire_auth_error_changed=*/false);
- // TODO(b/274463812): save wrapped_binding_key in memory.
FireAuthErrorChanged(account_id, error);
}
@@ -705,10 +735,16 @@
if (refresh_tokens_.count(account_id) > 0) {
VLOG(1) << "MutablePO2TS::RevokeCredentials for account_id=" << account_id;
- if (revoke_on_server)
+ if (revoke_on_server) {
RevokeCredentialsOnServer(refresh_tokens_[account_id]);
+ }
refresh_tokens_.erase(account_id);
ClearAuthError(account_id);
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ if (token_binding_helper_) {
+ token_binding_helper_->SetBindingKey(account_id, {});
+ }
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
ClearPersistedCredentials(account_id);
FireRefreshTokenRevoked(account_id);
}
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h
index 4bc7f0b..a9862da2 100644
--- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h
+++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h
@@ -26,6 +26,9 @@
class SigninClient;
class TokenWebData;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+class TokenBindingHelper;
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
class MutableProfileOAuth2TokenServiceDelegate
: public ProfileOAuth2TokenServiceDelegate,
@@ -41,6 +44,9 @@
scoped_refptr<TokenWebData> token_web_data,
signin::AccountConsistencyMethod account_consistency,
bool revoke_all_tokens_on_load,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ std::unique_ptr<TokenBindingHelper> token_binding_helper,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
FixRequestErrorCallback fix_request_error_callback);
MutableProfileOAuth2TokenServiceDelegate(
@@ -135,6 +141,12 @@
InvalidateTokensForMultilogin);
FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest,
ExtractCredentials);
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest,
+ UpdateBoundToken);
+ FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest,
+ RevokeBoundToken);
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
// WebDataServiceConsumer implementation:
void OnWebDataServiceRequestDone(
@@ -231,6 +243,11 @@
// error state.
bool revoke_all_tokens_on_load_;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ // This is null if token binding is disabled.
+ const std::unique_ptr<TokenBindingHelper> token_binding_helper_;
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+
// Callback function that attempts to correct request errors. Best effort
// only. Returns true if the error was fixed and retry should be reattempted.
FixRequestErrorCallback fix_request_error_callback_;
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc
index aaa418f..8d09a12 100644
--- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc
+++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc
@@ -45,6 +45,11 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+#include "components/signin/internal/identity_manager/token_binding_helper.h" // nogncheck
+#include "components/unexportable_keys/fake_unexportable_key_service.h" // nogncheck
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+
class MutableProfileOAuth2TokenServiceDelegateTest
: public testing::Test,
public OAuth2AccessTokenConsumer,
@@ -106,11 +111,19 @@
std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate>
CreateOAuth2ServiceDelegate(
- signin::AccountConsistencyMethod account_consistency) {
+ signin::AccountConsistencyMethod account_consistency
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ ,
+ std::unique_ptr<TokenBindingHelper> token_binding_helper = nullptr
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ ) {
return std::make_unique<MutableProfileOAuth2TokenServiceDelegate>(
client_.get(), &account_tracker_service_,
network::TestNetworkConnectionTracker::GetInstance(), token_web_data_,
account_consistency, revoke_all_tokens_on_load_,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ std::move(token_binding_helper),
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorCallback());
}
@@ -1439,3 +1452,57 @@
EXPECT_TRUE(other_delegate->RefreshTokenIsAvailable(account_id));
EXPECT_EQ("token", other_delegate->GetRefreshToken(account_id));
}
+
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, UpdateBoundToken) {
+ unexportable_keys::FakeUnexportableKeyService fake_unexportable_key_service;
+ auto token_binding_helper =
+ std::make_unique<TokenBindingHelper>(fake_unexportable_key_service);
+ std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate> delegate =
+ CreateOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled,
+ std::move(token_binding_helper));
+ const CoreAccountId account_id = CoreAccountId::FromGaiaId("account_id");
+ EXPECT_TRUE(delegate->GetWrappedBindingKey(account_id).empty());
+
+ // Set bound refresh token.
+ const std::vector<uint8_t> kFakeWrappedBindingKey = {1, 2, 3};
+ delegate->UpdateCredentials(account_id, "refresh_token",
+ kFakeWrappedBindingKey);
+ EXPECT_EQ(delegate->GetWrappedBindingKey(account_id), kFakeWrappedBindingKey);
+
+ // Update bound refresh token.
+ const std::vector<uint8_t> kFakeWrappedBindingKey2 = {4, 5, 6};
+ delegate->UpdateCredentials(account_id, "refresh_token2",
+ kFakeWrappedBindingKey2);
+ EXPECT_EQ(delegate->GetWrappedBindingKey(account_id),
+ kFakeWrappedBindingKey2);
+
+ // Invalidate bound refresh token.
+ delegate->UpdateCredentials(account_id, GaiaConstants::kInvalidRefreshToken);
+ EXPECT_TRUE(delegate->GetWrappedBindingKey(account_id).empty());
+ delegate->Shutdown();
+}
+
+TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, RevokeBoundToken) {
+ unexportable_keys::FakeUnexportableKeyService fake_unexportable_key_service;
+ auto token_binding_helper =
+ std::make_unique<TokenBindingHelper>(fake_unexportable_key_service);
+ std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate> delegate =
+ CreateOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled,
+ std::move(token_binding_helper));
+ const CoreAccountId account_id = CoreAccountId::FromGaiaId("account_id");
+ const CoreAccountId account_id2 = CoreAccountId::FromGaiaId("account_id2");
+ const std::vector<uint8_t> kFakeWrappedBindingKey = {1, 2, 3};
+ const std::vector<uint8_t> kFakeWrappedBindingKey2 = {4, 5, 6};
+ delegate->UpdateCredentials(account_id, "refresh_token",
+ kFakeWrappedBindingKey);
+ delegate->UpdateCredentials(account_id2, "refresh_token2",
+ kFakeWrappedBindingKey2);
+
+ delegate->RevokeCredentials(account_id);
+ EXPECT_TRUE(delegate->GetWrappedBindingKey(account_id).empty());
+ EXPECT_EQ(delegate->GetWrappedBindingKey(account_id2),
+ kFakeWrappedBindingKey2);
+ delegate->Shutdown();
+}
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
index 1bd5a2a..e2aecf8 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
@@ -23,6 +23,10 @@
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
#include "components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h"
#include "components/signin/public/webdata/token_web_data.h"
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+#include "components/signin/internal/identity_manager/token_binding_helper.h"
+#include "components/unexportable_keys/unexportable_key_service.h" // nogncheck
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_CHROMEOS)
@@ -80,6 +84,9 @@
bool delete_signin_cookies_on_exit,
scoped_refptr<TokenWebData> token_web_data,
SigninClient* signin_client,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ unexportable_keys::UnexportableKeyService* unexportable_key_service,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#if BUILDFLAG(IS_WIN)
MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorCallback
reauth_callback,
@@ -91,9 +98,20 @@
(account_consistency == signin::AccountConsistencyMethod::kDice) &&
delete_signin_cookies_on_exit;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ std::unique_ptr<TokenBindingHelper> token_binding_helper;
+ if (unexportable_key_service) {
+ token_binding_helper =
+ std::make_unique<TokenBindingHelper>(*unexportable_key_service);
+ }
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+
return std::make_unique<MutableProfileOAuth2TokenServiceDelegate>(
signin_client, account_tracker_service, network_connection_tracker,
token_web_data, account_consistency, revoke_all_tokens_on_load,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ std::move(token_binding_helper),
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#if BUILDFLAG(IS_WIN)
reauth_callback
#else
@@ -117,6 +135,9 @@
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
scoped_refptr<TokenWebData> token_web_data,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ unexportable_keys::UnexportableKeyService* unexportable_key_service,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_IOS)
std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
@@ -146,6 +167,9 @@
return CreateMutableProfileOAuthDelegate(
account_tracker_service, account_consistency,
delete_signin_cookies_on_exit, token_web_data, signin_client,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ unexportable_key_service,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#if BUILDFLAG(IS_WIN)
reauth_callback,
#endif // BUILDFLAG(IS_WIN)
@@ -172,6 +196,9 @@
#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
scoped_refptr<TokenWebData> token_web_data,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ unexportable_keys::UnexportableKeyService* unexportable_key_service,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT)
#if BUILDFLAG(IS_IOS)
std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
@@ -202,6 +229,9 @@
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
token_web_data,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ unexportable_key_service,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_IOS)
std::move(device_accounts_provider),
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
index e5ffd19..f68a895 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
@@ -39,6 +39,11 @@
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
class TokenWebData;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+namespace unexportable_keys {
+class UnexportableKeyService;
+}
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_CHROMEOS)
@@ -61,6 +66,9 @@
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
scoped_refptr<TokenWebData> token_web_data,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ unexportable_keys::UnexportableKeyService* unexportable_key_service,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT)
#if BUILDFLAG(IS_IOS)
std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
diff --git a/components/signin/internal/identity_manager/token_binding_helper.cc b/components/signin/internal/identity_manager/token_binding_helper.cc
new file mode 100644
index 0000000..77065470
--- /dev/null
+++ b/components/signin/internal/identity_manager/token_binding_helper.cc
@@ -0,0 +1,145 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/signin/internal/identity_manager/token_binding_helper.h"
+
+#include "base/containers/contains.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/span.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/notreached.h"
+#include "base/strings/string_piece.h"
+#include "components/signin/public/base/session_binding_utils.h"
+#include "components/unexportable_keys/background_task_priority.h"
+#include "components/unexportable_keys/service_error.h"
+#include "components/unexportable_keys/unexportable_key_loader.h"
+#include "components/unexportable_keys/unexportable_key_service.h"
+#include "google_apis/gaia/core_account_id.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "url/gurl.h"
+
+namespace {
+
+unexportable_keys::BackgroundTaskPriority kTokenBindingPriority =
+ unexportable_keys::BackgroundTaskPriority::kBestEffort;
+
+std::string CreateAssertionToken(
+ const std::string& header_and_payload,
+ unexportable_keys::ServiceErrorOr<std::vector<uint8_t>> signature) {
+ if (!signature.has_value()) {
+ // TODO(alexilin): Record a histogram.
+ return std::string();
+ }
+
+ return signin::AppendSignatureToHeaderAndPayload(header_and_payload,
+ *signature);
+}
+
+} // namespace
+
+TokenBindingHelper::TokenBindingHelper(
+ unexportable_keys::UnexportableKeyService& unexportable_key_service)
+ : unexportable_key_service_(unexportable_key_service) {}
+
+TokenBindingHelper::~TokenBindingHelper() = default;
+
+void TokenBindingHelper::SetBindingKey(
+ const CoreAccountId& account_id,
+ base::span<const uint8_t> wrapped_binding_key) {
+ if (wrapped_binding_key.empty()) {
+ // No need in storing an empty key, just remove the entry if any.
+ binding_keys_.erase(account_id);
+ return;
+ }
+
+ binding_keys_.insert_or_assign(
+ account_id, BindingKeyData(std::vector<uint8_t>(
+ wrapped_binding_key.begin(), wrapped_binding_key.end())));
+}
+
+bool TokenBindingHelper::HasBindingKey(const CoreAccountId& account_id) const {
+ return base::Contains(binding_keys_, account_id);
+}
+
+void TokenBindingHelper::ClearAllKeys() {
+ binding_keys_.clear();
+}
+
+void TokenBindingHelper::GenerateBindingKeyAssertion(
+ const CoreAccountId& account_id,
+ base::StringPiece challenge,
+ const GURL& destination_url,
+ base::OnceCallback<void(std::string)> callback) {
+ CHECK(callback);
+ auto it = binding_keys_.find(account_id);
+ if (it == binding_keys_.end()) {
+ std::move(callback).Run(std::string());
+ return;
+ }
+
+ auto& binding_key_data = it->second;
+ if (!binding_key_data.key_loader) {
+ binding_key_data.key_loader =
+ unexportable_keys::UnexportableKeyLoader::CreateFromWrappedKey(
+ *unexportable_key_service_, binding_key_data.wrapped_key,
+ kTokenBindingPriority);
+ }
+
+ // `base::Unretained(this)` is safe because `this` owns the
+ // `UnexportableKeyLoader`.
+ binding_key_data.key_loader->InvokeCallbackAfterKeyLoaded(base::BindOnce(
+ &TokenBindingHelper::SignAssertionToken, base::Unretained(this),
+ challenge, destination_url, std::move(callback)));
+}
+
+std::vector<uint8_t> TokenBindingHelper::GetWrappedBindingKey(
+ const CoreAccountId& account_id) const {
+ auto it = binding_keys_.find(account_id);
+ if (it == binding_keys_.end()) {
+ return {};
+ }
+
+ return it->second.wrapped_key;
+}
+
+TokenBindingHelper::BindingKeyData::BindingKeyData(
+ std::vector<uint8_t> in_wrapped_key)
+ : wrapped_key(in_wrapped_key) {}
+TokenBindingHelper::BindingKeyData::BindingKeyData(BindingKeyData&& other) =
+ default;
+TokenBindingHelper::BindingKeyData&
+TokenBindingHelper::BindingKeyData::operator=(BindingKeyData&& other) = default;
+TokenBindingHelper::BindingKeyData::~BindingKeyData() = default;
+
+void TokenBindingHelper::SignAssertionToken(
+ base::StringPiece challenge,
+ const GURL& destination_url,
+ base::OnceCallback<void(std::string)> callback,
+ unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>
+ binding_key) {
+ if (!binding_key.has_value()) {
+ std::move(callback).Run(std::string());
+ return;
+ }
+
+ absl::optional<std::string> header_and_payload =
+ signin::CreateKeyAssertionHeaderAndPayload(
+ *unexportable_key_service_->GetAlgorithm(*binding_key),
+ *unexportable_key_service_->GetSubjectPublicKeyInfo(*binding_key),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(), challenge,
+ destination_url);
+
+ if (!header_and_payload.has_value()) {
+ // TODO(alexilin): Record a histogram.
+ std::move(callback).Run(std::string());
+ return;
+ }
+
+ unexportable_key_service_->SignSlowlyAsync(
+ *binding_key, base::as_bytes(base::make_span(*header_and_payload)),
+ kTokenBindingPriority,
+ base::BindOnce(&CreateAssertionToken, *header_and_payload)
+ .Then(std::move(callback)));
+}
diff --git a/components/signin/internal/identity_manager/token_binding_helper.h b/components/signin/internal/identity_manager/token_binding_helper.h
new file mode 100644
index 0000000..25e2f5a
--- /dev/null
+++ b/components/signin/internal/identity_manager/token_binding_helper.h
@@ -0,0 +1,100 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SIGNIN_INTERNAL_IDENTITY_MANAGER_TOKEN_BINDING_HELPER_H_
+#define COMPONENTS_SIGNIN_INTERNAL_IDENTITY_MANAGER_TOKEN_BINDING_HELPER_H_
+
+#include "base/containers/flat_map.h"
+#include "base/containers/span.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ref.h"
+#include "base/strings/string_piece_forward.h"
+#include "components/unexportable_keys/service_error.h"
+#include "components/unexportable_keys/unexportable_key_id.h"
+
+namespace unexportable_keys {
+class UnexportableKeyService;
+class UnexportableKeyLoader;
+} // namespace unexportable_keys
+
+class GURL;
+
+struct CoreAccountId;
+
+// `TokenBindingHelper` manages in-memory cache of refresh token binding keys
+// and provides an asynchronous method of creating a binding key assertion.
+//
+// Keys needs to be loaded into the helper on every startup.
+class TokenBindingHelper {
+ public:
+ explicit TokenBindingHelper(
+ unexportable_keys::UnexportableKeyService& unexportable_key_service);
+
+ TokenBindingHelper(const TokenBindingHelper&) = delete;
+ TokenBindingHelper& operator=(const TokenBindingHelper&) = delete;
+
+ ~TokenBindingHelper();
+
+ // Adds a key associated with `account_id` to the in-memory cache.
+ // If `wrapped_binding_key` is empty, removes any existing key instead.
+ // The key is not loaded with `unexportable_key_service_` immediately but
+ // stored as a wrapped key until an attestation is requested for the first
+ // time.
+ void SetBindingKey(const CoreAccountId& account_id,
+ base::span<const uint8_t> wrapped_binding_key);
+
+ // Returns whether the helper has a non-empty key associated with
+ // `account_id`.
+ // This method returns `true` even if the key has failed to load.
+ bool HasBindingKey(const CoreAccountId& account_id) const;
+
+ // Removes all keys from the helper.
+ // To remove a key for a specific account, use `SetBindingKey()` with an empty
+ // key parameter.
+ void ClearAllKeys();
+
+ // Asynchronously generates a binding key assertion with a key associated with
+ // `account_id`. The result is returned through `callback`. Returns an empty
+ // string if the generation fails.
+ void GenerateBindingKeyAssertion(
+ const CoreAccountId& account_id,
+ base::StringPiece challenge,
+ const GURL& destination_url,
+ base::OnceCallback<void(std::string)> callback);
+
+ // Returns a wrapped key associated with `account_id`. Returns an empty vector
+ // if no key is found.
+ std::vector<uint8_t> GetWrappedBindingKey(
+ const CoreAccountId& account_id) const;
+
+ private:
+ struct BindingKeyData {
+ explicit BindingKeyData(std::vector<uint8_t> wrapped_key);
+
+ BindingKeyData(const BindingKeyData&) = delete;
+ BindingKeyData& operator=(const BindingKeyData&) = delete;
+
+ BindingKeyData(BindingKeyData&& other);
+ BindingKeyData& operator=(BindingKeyData&& other);
+
+ ~BindingKeyData();
+
+ std::vector<uint8_t> wrapped_key;
+ std::unique_ptr<unexportable_keys::UnexportableKeyLoader> key_loader;
+ };
+
+ void SignAssertionToken(
+ base::StringPiece challenge,
+ const GURL& destination_url,
+ base::OnceCallback<void(std::string)> callback,
+ unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>
+ binding_key);
+
+ const raw_ref<unexportable_keys::UnexportableKeyService>
+ unexportable_key_service_;
+
+ base::flat_map<CoreAccountId, BindingKeyData> binding_keys_;
+};
+
+#endif // COMPONENTS_SIGNIN_INTERNAL_IDENTITY_MANAGER_TOKEN_BINDING_HELPER_H_
diff --git a/components/signin/internal/identity_manager/token_binding_helper_unittest.cc b/components/signin/internal/identity_manager/token_binding_helper_unittest.cc
new file mode 100644
index 0000000..f7c594e
--- /dev/null
+++ b/components/signin/internal/identity_manager/token_binding_helper_unittest.cc
@@ -0,0 +1,149 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/signin/internal/identity_manager/token_binding_helper.h"
+
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "components/signin/public/base/session_binding_test_utils.h"
+#include "components/unexportable_keys/service_error.h"
+#include "components/unexportable_keys/unexportable_key_id.h"
+#include "components/unexportable_keys/unexportable_key_service_impl.h"
+#include "components/unexportable_keys/unexportable_key_task_manager.h"
+#include "crypto/scoped_mock_unexportable_key_provider.h"
+#include "crypto/signature_verifier.h"
+#include "google_apis/gaia/core_account_id.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+constexpr crypto::SignatureVerifier::SignatureAlgorithm
+ kAcceptableAlgorithms[] = {crypto::SignatureVerifier::ECDSA_SHA256};
+constexpr unexportable_keys::BackgroundTaskPriority kTaskPriority =
+ unexportable_keys::BackgroundTaskPriority::kUserVisible;
+} // namespace
+
+class TokenBindingHelperTest : public testing::Test {
+ public:
+ TokenBindingHelperTest()
+ : unexportable_key_service_(unexportable_key_task_manager_),
+ helper_(unexportable_key_service_) {}
+
+ void RunBackgroundTasks() { task_environment_.RunUntilIdle(); }
+
+ TokenBindingHelper& helper() { return helper_; }
+
+ unexportable_keys::UnexportableKeyService& unexportable_key_service() {
+ return unexportable_key_service_;
+ }
+
+ unexportable_keys::UnexportableKeyId GenerateNewKey() {
+ base::test::TestFuture<
+ unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>>
+ generate_future;
+ unexportable_key_service_.GenerateSigningKeySlowlyAsync(
+ kAcceptableAlgorithms, kTaskPriority, generate_future.GetCallback());
+ RunBackgroundTasks();
+ unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>
+ key_id = generate_future.Get();
+ CHECK(key_id.has_value());
+ return *key_id;
+ }
+
+ std::vector<uint8_t> GetWrappedKey(
+ const unexportable_keys::UnexportableKeyId& key_id) {
+ unexportable_keys::ServiceErrorOr<std::vector<uint8_t>> wrapped_key =
+ unexportable_key_service_.GetWrappedKey(key_id);
+ CHECK(wrapped_key.has_value());
+ return *wrapped_key;
+ }
+
+ private:
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::ThreadPoolExecutionMode::
+ QUEUED}; // QUEUED - tasks don't run until `RunUntilIdle()` is
+ // called.
+ crypto::ScopedMockUnexportableKeyProvider scoped_key_provider_;
+ unexportable_keys::UnexportableKeyTaskManager unexportable_key_task_manager_;
+ unexportable_keys::UnexportableKeyServiceImpl unexportable_key_service_;
+ TokenBindingHelper helper_;
+};
+
+TEST_F(TokenBindingHelperTest, SetBindingKey) {
+ CoreAccountId account_id = CoreAccountId::FromGaiaId("test_gaia_id");
+ std::vector<uint8_t> wrapped_key = GetWrappedKey(GenerateNewKey());
+ EXPECT_FALSE(helper().HasBindingKey(account_id));
+
+ helper().SetBindingKey(account_id, wrapped_key);
+
+ EXPECT_TRUE(helper().HasBindingKey(account_id));
+ EXPECT_EQ(helper().GetWrappedBindingKey(account_id), wrapped_key);
+}
+
+TEST_F(TokenBindingHelperTest, SetBindingKeyToEmpty) {
+ CoreAccountId account_id = CoreAccountId::FromGaiaId("test_gaia_id");
+ std::vector<uint8_t> wrapped_key = GetWrappedKey(GenerateNewKey());
+ helper().SetBindingKey(account_id, wrapped_key);
+
+ helper().SetBindingKey(account_id, {});
+ EXPECT_FALSE(helper().HasBindingKey(account_id));
+}
+
+TEST_F(TokenBindingHelperTest, ClearAllKeys) {
+ CoreAccountId account_id = CoreAccountId::FromGaiaId("test_gaia_id");
+ CoreAccountId account_id2 = CoreAccountId::FromGaiaId("test_gaia_id2");
+ std::vector<uint8_t> wrapped_key = GetWrappedKey(GenerateNewKey());
+ std::vector<uint8_t> wrapped_key2 = GetWrappedKey(GenerateNewKey());
+ helper().SetBindingKey(account_id, wrapped_key);
+ helper().SetBindingKey(account_id2, wrapped_key2);
+
+ helper().ClearAllKeys();
+ EXPECT_FALSE(helper().HasBindingKey(account_id));
+ EXPECT_FALSE(helper().HasBindingKey(account_id2));
+}
+
+TEST_F(TokenBindingHelperTest, GenerateBindingKeyAssertion) {
+ CoreAccountId account_id = CoreAccountId::FromGaiaId("test_gaia_id");
+ unexportable_keys::UnexportableKeyId key_id = GenerateNewKey();
+ std::vector<uint8_t> wrapped_key = GetWrappedKey(key_id);
+ helper().SetBindingKey(account_id, wrapped_key);
+
+ base::test::TestFuture<std::string> sign_future;
+ helper().GenerateBindingKeyAssertion(
+ account_id, "challenge", GURL("https://oauth.example.com/IssueToken"),
+ sign_future.GetCallback());
+ RunBackgroundTasks();
+ std::string assertion = sign_future.Get();
+ EXPECT_FALSE(assertion.empty());
+
+ EXPECT_TRUE(signin::VerifyJwtSignature(
+ assertion, *unexportable_key_service().GetAlgorithm(key_id),
+ *unexportable_key_service().GetSubjectPublicKeyInfo(key_id)));
+}
+
+TEST_F(TokenBindingHelperTest, GenerateBindingKeyAssertionNoBindingKey) {
+ CoreAccountId account_id = CoreAccountId::FromGaiaId("test_gaia_id");
+
+ base::test::TestFuture<std::string> sign_future;
+ helper().GenerateBindingKeyAssertion(
+ account_id, "challenge", GURL("https://oauth.example.com/IssueToken"),
+ sign_future.GetCallback());
+ RunBackgroundTasks();
+ std::string assertion = sign_future.Get();
+ EXPECT_TRUE(assertion.empty());
+}
+
+TEST_F(TokenBindingHelperTest, GenerateBindingKeyAssertionInvalidBindingKey) {
+ CoreAccountId account_id = CoreAccountId::FromGaiaId("test_gaia_id");
+ const std::vector<uint8_t> kInvalidWrappedKey = {1, 2, 3};
+ helper().SetBindingKey(account_id, kInvalidWrappedKey);
+
+ base::test::TestFuture<std::string> sign_future;
+ helper().GenerateBindingKeyAssertion(
+ account_id, "challenge", GURL("https://oauth.example.com/IssueToken"),
+ sign_future.GetCallback());
+ RunBackgroundTasks();
+ std::string assertion = sign_future.Get();
+ EXPECT_TRUE(assertion.empty());
+}
diff --git a/components/signin/public/base/session_binding_test_utils.cc b/components/signin/public/base/session_binding_test_utils.cc
index bffdbd6..c4e394a 100644
--- a/components/signin/public/base/session_binding_test_utils.cc
+++ b/components/signin/public/base/session_binding_test_utils.cc
@@ -13,7 +13,7 @@
namespace signin {
-bool VefiryJwtSingature(base::StringPiece jwt,
+bool VerifyJwtSignature(base::StringPiece jwt,
crypto::SignatureVerifier::SignatureAlgorithm algorithm,
base::span<const uint8_t> public_key) {
std::vector<base::StringPiece> parts = base::SplitStringPiece(
diff --git a/components/signin/public/base/session_binding_test_utils.h b/components/signin/public/base/session_binding_test_utils.h
index 3149390..5369c7cf 100644
--- a/components/signin/public/base/session_binding_test_utils.h
+++ b/components/signin/public/base/session_binding_test_utils.h
@@ -12,7 +12,7 @@
namespace signin {
// Verifies that `jwt` is well-formed and properly signed.
-[[nodiscard]] bool VefiryJwtSingature(
+[[nodiscard]] bool VerifyJwtSignature(
base::StringPiece jwt,
crypto::SignatureVerifier::SignatureAlgorithm algorithm,
base::span<const uint8_t> public_key);
diff --git a/components/signin/public/base/session_binding_utils.cc b/components/signin/public/base/session_binding_utils.cc
index 0f5977c..b572e950 100644
--- a/components/signin/public/base/session_binding_utils.cc
+++ b/components/signin/public/base/session_binding_utils.cc
@@ -49,6 +49,13 @@
reinterpret_cast<const char*>(data.data()), data.size()));
}
+base::Value::Dict CreateHeader(
+ crypto::SignatureVerifier::SignatureAlgorithm algorithm) {
+ return base::Value::Dict()
+ .Set("alg", SignatureAlgorithmToString(algorithm))
+ .Set("typ", "jwt");
+}
+
} // namespace
absl::optional<std::string> CreateKeyRegistrationHeaderAndPayload(
@@ -58,10 +65,7 @@
base::StringPiece auth_code,
const GURL& registration_url,
base::Time timestamp) {
- base::Value::Dict header =
- base::Value::Dict()
- .Set("alg", SignatureAlgorithmToString(algorithm))
- .Set("typ", "jwt");
+ base::Value::Dict header = CreateHeader(algorithm);
std::string header_serialized;
if (!base::JSONWriter::Write(header, &header_serialized)) {
DVLOG(1) << "Unexpected JSONWriter error while serializing a registration "
@@ -98,6 +102,39 @@
Base64UrlEncode(payload_serialized)});
}
+absl::optional<std::string> CreateKeyAssertionHeaderAndPayload(
+ crypto::SignatureVerifier::SignatureAlgorithm algorithm,
+ base::span<const uint8_t> pubkey,
+ base::StringPiece client_id,
+ base::StringPiece challenge,
+ const GURL& destination_url) {
+ base::Value::Dict header = CreateHeader(algorithm);
+ std::string header_serialized;
+ if (!base::JSONWriter::Write(header, &header_serialized)) {
+ DVLOG(1) << "Unexpected JSONWriter error while serializing a registration "
+ "token header";
+ return absl::nullopt;
+ }
+
+ base::Value::Dict payload =
+ base::Value::Dict()
+ .Set("sub", client_id)
+ .Set("aud", destination_url.spec())
+ .Set("jti", challenge)
+ .Set("iss", Base64UrlEncode(crypto::SHA256Hash(pubkey)));
+ std::string payload_serialized;
+ if (!base::JSONWriter::WriteWithOptions(
+ payload, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
+ &payload_serialized)) {
+ DVLOG(1) << "Unexpected JSONWriter error while serializing a registration "
+ "token payload";
+ return absl::nullopt;
+ }
+
+ return base::StrCat({Base64UrlEncode(header_serialized), ".",
+ Base64UrlEncode(payload_serialized)});
+}
+
std::string AppendSignatureToHeaderAndPayload(
base::StringPiece header_and_payload,
base::span<const uint8_t> signature) {
diff --git a/components/signin/public/base/session_binding_utils.h b/components/signin/public/base/session_binding_utils.h
index 158684aa..72deb80b 100644
--- a/components/signin/public/base/session_binding_utils.h
+++ b/components/signin/public/base/session_binding_utils.h
@@ -29,6 +29,15 @@
const GURL& registration_url,
base::Time timestamp);
+// Creates header and payload parts of an assertion JWT.
+// TODO(b/279026351): Add support for "ephemeral_key".
+absl::optional<std::string> CreateKeyAssertionHeaderAndPayload(
+ crypto::SignatureVerifier::SignatureAlgorithm algorithm,
+ base::span<const uint8_t> pubkey,
+ base::StringPiece client_id,
+ base::StringPiece challenge,
+ const GURL& destination_url);
+
// Appends `signature` to provided `header_and_payload` to form a complete JWT.
std::string AppendSignatureToHeaderAndPayload(
base::StringPiece header_and_payload,
diff --git a/components/signin/public/base/session_binding_utils_unittest.cc b/components/signin/public/base/session_binding_utils_unittest.cc
index 8e046cf..850e91e 100644
--- a/components/signin/public/base/session_binding_utils_unittest.cc
+++ b/components/signin/public/base/session_binding_utils_unittest.cc
@@ -13,6 +13,7 @@
#include "base/time/time.h"
#include "base/value_iterators.h"
#include "base/values.h"
+#include "crypto/signature_verifier.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
@@ -67,6 +68,35 @@
EXPECT_EQ(actual_payload, expected_payload);
}
+TEST(SessionBindingUtilsTest, CreateKeyAssertionHeaderAndPayload) {
+ absl::optional<std::string> result = CreateKeyAssertionHeaderAndPayload(
+ crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256,
+ std::vector<uint8_t>({1, 2, 3}), "test_client_id", "test_challenge",
+ GURL("https://accounts.google.com/VerifyKey"));
+ ASSERT_TRUE(result.has_value());
+
+ std::vector<base::StringPiece> header_and_payload = base::SplitStringPiece(
+ *result, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ ASSERT_EQ(header_and_payload.size(), 2U);
+ base::Value actual_header =
+ Base64UrlEncodedJsonToValue(header_and_payload[0]);
+ base::Value actual_payload =
+ Base64UrlEncodedJsonToValue(header_and_payload[1]);
+
+ base::Value::Dict expected_header =
+ base::Value::Dict().Set("alg", "ES256").Set("typ", "jwt");
+ base::Value::Dict expected_payload =
+ base::Value::Dict()
+ .Set("sub", "test_client_id")
+ .Set("aud", "https://accounts.google.com/VerifyKey")
+ .Set("jti", "test_challenge")
+ // Base64UrlEncode(SHA256(public_key));
+ .Set("iss", "A5BYxvLAy0ksUzsKTRTvd8wPeKvMztUofYShogEc-4E");
+
+ EXPECT_EQ(actual_header, expected_header);
+ EXPECT_EQ(actual_payload, expected_payload);
+}
+
TEST(SessionBindingUtilsTest, AppendSignatureToHeaderAndPayload) {
std::string result = AppendSignatureToHeaderAndPayload(
"abc.efg", std::vector<uint8_t>({1, 2, 3}));
diff --git a/components/signin/public/identity_manager/identity_manager_builder.cc b/components/signin/public/identity_manager/identity_manager_builder.cc
index 5082d02..9943563d5 100644
--- a/components/signin/public/identity_manager/identity_manager_builder.cc
+++ b/components/signin/public/identity_manager/identity_manager_builder.cc
@@ -120,6 +120,9 @@
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
params->token_web_data,
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ params->unexportable_key_service,
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT)
#if BUILDFLAG(IS_IOS)
std::move(params->device_accounts_provider),
diff --git a/components/signin/public/identity_manager/identity_manager_builder.h b/components/signin/public/identity_manager/identity_manager_builder.h
index de6caab..deca6f5 100644
--- a/components/signin/public/identity_manager/identity_manager_builder.h
+++ b/components/signin/public/identity_manager/identity_manager_builder.h
@@ -27,6 +27,11 @@
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
class TokenWebData;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+namespace unexportable_keys {
+class UnexportableKeyService;
+}
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_IOS)
@@ -71,6 +76,9 @@
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
scoped_refptr<TokenWebData> token_web_data;
+#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+ raw_ptr<unexportable_keys::UnexportableKeyService> unexportable_key_service;
+#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#endif
#if BUILDFLAG(IS_CHROMEOS)