blob: 49ac06d0aea2adb503fb9bf8761747f7ff772427 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/trust_tokens/trust_token_request_issuance_helper.h"
#include <utility>
#include "base/callback.h"
#include "base/task/thread_pool.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/log/net_log_event_type.h"
#include "net/url_request/url_request.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/mojom/trust_tokens.mojom-forward.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/trust_tokens/proto/public.pb.h"
#include "services/network/trust_tokens/suitable_trust_token_origin.h"
#include "services/network/trust_tokens/trust_token_http_headers.h"
#include "services/network/trust_tokens/trust_token_key_filtering.h"
#include "services/network/trust_tokens/trust_token_parameterization.h"
#include "services/network/trust_tokens/trust_token_store.h"
#include "services/network/trust_tokens/types.h"
#include "url/url_constants.h"
namespace network {
using Cryptographer = TrustTokenRequestIssuanceHelper::Cryptographer;
struct TrustTokenRequestIssuanceHelper::CryptographerAndBlindedTokens {
std::unique_ptr<Cryptographer> cryptographer;
base::Optional<std::string> blinded_tokens;
};
struct TrustTokenRequestIssuanceHelper::CryptographerAndUnblindedTokens {
std::unique_ptr<Cryptographer> cryptographer;
std::unique_ptr<Cryptographer::UnblindedTokens> unblinded_tokens;
};
namespace {
TrustTokenRequestIssuanceHelper::CryptographerAndBlindedTokens
BeginIssuanceOnPostedSequence(std::unique_ptr<Cryptographer> cryptographer,
int batch_size) {
base::Optional<std::string> blinded_tokens =
cryptographer->BeginIssuance(batch_size);
return {std::move(cryptographer), std::move(blinded_tokens)};
}
TrustTokenRequestIssuanceHelper::CryptographerAndUnblindedTokens
ConfirmIssuanceOnPostedSequence(std::unique_ptr<Cryptographer> cryptographer,
std::string response_header) {
std::unique_ptr<Cryptographer::UnblindedTokens> unblinded_tokens =
cryptographer->ConfirmIssuance(response_header);
return {std::move(cryptographer), std::move(unblinded_tokens)};
}
base::Value CreateLogValue(base::StringPiece outcome) {
base::Value ret(base::Value::Type::DICTIONARY);
ret.SetStringKey("outcome", outcome);
return ret;
}
// Define convenience aliases for the NetLogEventTypes for brevity.
enum NetLogOp { kBegin, kFinalize };
void LogOutcome(const net::NetLogWithSource& log,
NetLogOp begin_or_finalize,
base::StringPiece outcome) {
log.EndEvent(
begin_or_finalize == kBegin
? net::NetLogEventType::TRUST_TOKEN_OPERATION_BEGIN_ISSUANCE
: net::NetLogEventType::TRUST_TOKEN_OPERATION_FINALIZE_ISSUANCE,
[outcome]() { return CreateLogValue(outcome); });
}
} // namespace
TrustTokenRequestIssuanceHelper::TrustTokenRequestIssuanceHelper(
SuitableTrustTokenOrigin top_level_origin,
TrustTokenStore* token_store,
const TrustTokenKeyCommitmentGetter* key_commitment_getter,
std::unique_ptr<Cryptographer> cryptographer,
net::NetLogWithSource net_log)
: top_level_origin_(std::move(top_level_origin)),
token_store_(token_store),
key_commitment_getter_(std::move(key_commitment_getter)),
cryptographer_(std::move(cryptographer)),
net_log_(std::move(net_log)) {
DCHECK(token_store_);
DCHECK(key_commitment_getter_);
DCHECK(cryptographer_);
}
TrustTokenRequestIssuanceHelper::~TrustTokenRequestIssuanceHelper() = default;
TrustTokenRequestIssuanceHelper::Cryptographer::UnblindedTokens::
UnblindedTokens() = default;
TrustTokenRequestIssuanceHelper::Cryptographer::UnblindedTokens::
~UnblindedTokens() = default;
void TrustTokenRequestIssuanceHelper::Begin(
net::URLRequest* request,
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done) {
DCHECK(request);
DCHECK(!request->initiator() ||
IsOriginPotentiallyTrustworthy(*request->initiator()))
<< *request->initiator();
net_log_.BeginEvent(
net::NetLogEventType::TRUST_TOKEN_OPERATION_BEGIN_ISSUANCE);
issuer_ = SuitableTrustTokenOrigin::Create(request->url());
if (!issuer_) {
LogOutcome(net_log_, kBegin, "Unsuitable issuer URL");
std::move(done).Run(mojom::TrustTokenOperationStatus::kInvalidArgument);
return;
}
if (!token_store_->SetAssociation(*issuer_, top_level_origin_)) {
LogOutcome(net_log_, kBegin, "Couldn't set issuer-toplevel association");
std::move(done).Run(mojom::TrustTokenOperationStatus::kResourceExhausted);
return;
}
if (token_store_->CountTokens(*issuer_) ==
kTrustTokenPerIssuerTokenCapacity) {
LogOutcome(net_log_, kBegin, "Tokens at capacity");
std::move(done).Run(mojom::TrustTokenOperationStatus::kResourceExhausted);
return;
}
key_commitment_getter_->Get(
*issuer_,
base::BindOnce(&TrustTokenRequestIssuanceHelper::OnGotKeyCommitment,
weak_ptr_factory_.GetWeakPtr(), request, std::move(done)));
}
void TrustTokenRequestIssuanceHelper::OnGotKeyCommitment(
net::URLRequest* request,
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done,
mojom::TrustTokenKeyCommitmentResultPtr commitment_result) {
if (!commitment_result) {
LogOutcome(net_log_, kBegin, "No keys for issuer");
std::move(done).Run(mojom::TrustTokenOperationStatus::kFailedPrecondition);
return;
}
if (!commitment_result->batch_size ||
!cryptographer_->Initialize(commitment_result->batch_size)) {
LogOutcome(net_log_, kBegin,
"Internal error initializing cryptography delegate");
std::move(done).Run(mojom::TrustTokenOperationStatus::kInternalError);
return;
}
for (const mojom::TrustTokenVerificationKeyPtr& key :
commitment_result->keys) {
if (!cryptographer_->AddKey(key->body)) {
LogOutcome(net_log_, kBegin, "Bad key");
std::move(done).Run(
mojom::TrustTokenOperationStatus::kFailedPrecondition);
return;
}
}
// Evict tokens signed with keys other than those from the issuer's most
// recent commitments.
token_store_->PruneStaleIssuerState(*issuer_, commitment_result->keys);
int batch_size = std::min(commitment_result->batch_size,
kMaximumTrustTokenIssuanceBatchSize);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&BeginIssuanceOnPostedSequence, std::move(cryptographer_),
batch_size),
base::BindOnce(
&TrustTokenRequestIssuanceHelper::OnDelegateBeginIssuanceCallComplete,
weak_ptr_factory_.GetWeakPtr(), request, std::move(done)));
// Logic continues... in the continuation
// OnDelegateBeginIssuanceCallComplete; don't add more code here. In
// particular, |cryptographer_| is empty at this point.
}
void TrustTokenRequestIssuanceHelper::OnDelegateBeginIssuanceCallComplete(
net::URLRequest* request,
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done,
CryptographerAndBlindedTokens cryptographer_and_blinded_tokens) {
cryptographer_ = std::move(cryptographer_and_blinded_tokens.cryptographer);
base::Optional<std::string>& maybe_blinded_tokens =
cryptographer_and_blinded_tokens.blinded_tokens; // Convenience alias
if (!maybe_blinded_tokens) {
LogOutcome(net_log_, kBegin, "Internal error generating blinded tokens");
std::move(done).Run(mojom::TrustTokenOperationStatus::kInternalError);
return;
}
request->SetExtraRequestHeaderByName(kTrustTokensSecTrustTokenHeader,
std::move(*maybe_blinded_tokens),
/*overwrite=*/true);
// We don't want cache reads, because the highest priority is to execute the
// protocol operation by sending the server the Trust Tokens request header
// and getting the corresponding response header, but we want cache writes
// in case subsequent requests are made to the same URL in non-trust-token
// settings.
request->SetLoadFlags(request->load_flags() | net::LOAD_BYPASS_CACHE);
LogOutcome(net_log_, kBegin, "Success");
std::move(done).Run(mojom::TrustTokenOperationStatus::kOk);
}
void TrustTokenRequestIssuanceHelper::Finalize(
mojom::URLResponseHead* response,
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done) {
DCHECK(response);
// A response headers object should be present on all responses for
// https-scheme requests (which Trust Tokens requests are).
DCHECK(response->headers);
net_log_.BeginEvent(
net::NetLogEventType::TRUST_TOKEN_OPERATION_FINALIZE_ISSUANCE);
std::string header_value;
// EnumerateHeader(|iter|=nullptr) asks for the first instance of the header,
// if any.
if (!response->headers->EnumerateHeader(
/*iter=*/nullptr, kTrustTokensSecTrustTokenHeader, &header_value)) {
LogOutcome(net_log_, kFinalize, "Response missing Trust Tokens header");
std::move(done).Run(mojom::TrustTokenOperationStatus::kBadResponse);
return;
}
response->headers->RemoveHeader(kTrustTokensSecTrustTokenHeader);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&ConfirmIssuanceOnPostedSequence,
std::move(cryptographer_), std::move(header_value)),
base::BindOnce(&TrustTokenRequestIssuanceHelper::
OnDelegateConfirmIssuanceCallComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(done)));
}
void TrustTokenRequestIssuanceHelper::OnDelegateConfirmIssuanceCallComplete(
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done,
CryptographerAndUnblindedTokens cryptographer_and_unblinded_tokens) {
cryptographer_ = std::move(cryptographer_and_unblinded_tokens.cryptographer);
std::unique_ptr<Cryptographer::UnblindedTokens>& maybe_tokens =
cryptographer_and_unblinded_tokens.unblinded_tokens; // Convenience alias
if (!maybe_tokens) {
LogOutcome(net_log_, kFinalize,
"Response rejected during processing (perhaps malformed?)");
// The response was rejected by the underlying cryptographic library as
// malformed or otherwise invalid.
std::move(done).Run(mojom::TrustTokenOperationStatus::kBadResponse);
return;
}
token_store_->AddTokens(*issuer_, base::make_span(maybe_tokens->tokens),
maybe_tokens->body_of_verifying_key);
net_log_.EndEvent(
net::NetLogEventType::TRUST_TOKEN_OPERATION_FINALIZE_ISSUANCE,
[num_obtained_tokens = maybe_tokens->tokens.size()]() {
base::Value ret = CreateLogValue("Success");
ret.SetIntKey("# tokens obtained", num_obtained_tokens);
return ret;
});
std::move(done).Run(mojom::TrustTokenOperationStatus::kOk);
return;
}
} // namespace network