blob: 67e15feee4118ee23bcd700a3a7d2b684be8fe2a [file] [log] [blame]
// 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 "chrome/browser/signin/bound_session_credentials/session_binding_helper.h"
#include <string>
#include <string_view>
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/types/expected.h"
#include "components/signin/public/base/session_binding_utils.h"
#include "components/unexportable_keys/service_error.h"
#include "components/unexportable_keys/unexportable_key_id.h"
#include "components/unexportable_keys/unexportable_key_loader.h"
#include "components/unexportable_keys/unexportable_key_service.h"
#include "crypto/signature_verifier.h"
#include "url/gurl.h"
namespace {
constexpr std::string_view kSessionBindingNamespace = "CookieBinding";
unexportable_keys::BackgroundTaskPriority kSessionBindingPriority =
unexportable_keys::BackgroundTaskPriority::kUserBlocking;
bool ShouldTryToReloadKey(
const unexportable_keys::ServiceErrorOr<
unexportable_keys::UnexportableKeyId>& key_id_or_error) {
if (key_id_or_error.has_value()) {
// The key was successfully loaded, no need to reload.
return false;
}
unexportable_keys::ServiceError error = key_id_or_error.error();
return error == unexportable_keys::ServiceError::kCryptoApiFailed ||
error == unexportable_keys::ServiceError::kKeyCollision;
}
base::expected<std::string, SessionBindingHelper::Error> CreateAssertionToken(
const std::string& header_and_payload,
crypto::SignatureVerifier::SignatureAlgorithm algorithm,
std::vector<uint8_t> public_key,
unexportable_keys::ServiceErrorOr<std::vector<uint8_t>> signature) {
using enum SessionBindingHelper::Error;
if (!signature.has_value()) {
return base::unexpected(kSignAssertionFailure);
}
crypto::SignatureVerifier verifier;
if (!verifier.VerifyInit(algorithm, *signature, public_key)) {
return base::unexpected(kVerifySignatureFailure);
}
verifier.VerifyUpdate(base::as_byte_span(header_and_payload));
if (!verifier.VerifyFinal()) {
return base::unexpected(kVerifySignatureFailure);
}
std::optional<std::string> assertion_token =
signin::AppendSignatureToHeaderAndPayload(header_and_payload, algorithm,
*signature);
if (!assertion_token) {
return base::unexpected(kAppendSignatureFailure);
}
return *assertion_token;
}
} // namespace
SessionBindingHelper::SessionBindingHelper(
unexportable_keys::UnexportableKeyService& unexportable_key_service,
base::span<const uint8_t> wrapped_binding_key,
std::string session_id)
: unexportable_key_service_(unexportable_key_service),
wrapped_binding_key_(wrapped_binding_key.begin(),
wrapped_binding_key.end()),
session_id_(std::move(session_id)) {}
SessionBindingHelper::~SessionBindingHelper() = default;
void SessionBindingHelper::MaybeLoadBindingKey() {
if (!key_loader_ || ShouldTryToReloadKey(key_loader_->GetKeyIdOrError())) {
key_loader_ =
unexportable_keys::UnexportableKeyLoader::CreateFromWrappedKey(
unexportable_key_service_.get(), wrapped_binding_key_,
kSessionBindingPriority);
}
}
void SessionBindingHelper::GenerateBindingKeyAssertion(
std::string_view challenge,
const GURL& destination_url,
base::OnceCallback<void(base::expected<std::string, Error>)> callback) {
MaybeLoadBindingKey();
// `base::Unretained(this)` is safe because `this` owns the
// `UnexportableKeyLoader`.
key_loader_->InvokeCallbackAfterKeyLoaded(base::BindOnce(
&SessionBindingHelper::SignAssertionToken, base::Unretained(this),
std::string(challenge), destination_url, std::move(callback)));
}
void SessionBindingHelper::SignAssertionToken(
std::string_view challenge,
const GURL& destination_url,
base::OnceCallback<void(base::expected<std::string, Error>)> callback,
unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>
binding_key) {
if (!binding_key.has_value()) {
std::move(callback).Run(base::unexpected(Error::kLoadKeyFailure));
return;
}
crypto::SignatureVerifier::SignatureAlgorithm algorithm =
*unexportable_key_service_->GetAlgorithm(*binding_key);
std::vector<uint8_t> public_key =
*unexportable_key_service_->GetSubjectPublicKeyInfo(*binding_key);
std::optional<std::string> header_and_payload =
signin::CreateKeyAssertionHeaderAndPayload(
algorithm, public_key, session_id_, challenge, destination_url,
kSessionBindingNamespace,
/*ephemeral_public_key=*/std::string_view());
if (!header_and_payload.has_value()) {
std::move(callback).Run(base::unexpected(Error::kCreateAssertionFailure));
return;
}
unexportable_key_service_->SignSlowlyAsync(
*binding_key, base::as_byte_span(*header_and_payload),
kSessionBindingPriority,
base::BindOnce(&CreateAssertionToken, *header_and_payload, algorithm,
std::move(public_key))
.Then(std::move(callback)));
}