Commit d2b92236 authored by David Van Cleve's avatar David Van Cleve Committed by Chromium LUCI CQ

Trust Tokens: Treat empty (but present) issuance responses as successes

This change updates the Trust Tokens issuance helper to treat responses
bearing empty Sec-Trust-Token headers as "successful" issuance responses
that contain no tokens. This was initially implicitly supported by the
"v0" BoringSSL cryptographic logic, but the change to the "v1"
cryptographic logic altered BoringSSL's behavior to no longer treat an
empty string as a well-formed response containing 0 tokens.

This is still useful behavior to have: we'd like to avoid console errors
in the case that the issuer wants to defer judgment about what, or how
many, tokens to issue until a later time, for instance. Consequently,
we're implementing this behavior explicitly at the Trust
Tokens-over-HTTP level: see go/successfully-issuing-no-tokens for some
related background (internal, sorry, due to discussion of server-side
implementation details).

I've updated the "process an issuance response" pseudocode in the
design doc to cover this: https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.2kzxtyuk0nvu

R=csharrison

Test: Adds an issuance helper unit test covering this case
Change-Id: I4f747a1ce3b2d5b937d69895f2ce78382a60d42a
Fixed: 1124443
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2561046
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Cr-Commit-Position: refs/heads/master@{#832647}
parent f01e04c2
...@@ -55,6 +55,13 @@ BeginIssuanceOnPostedSequence(std::unique_ptr<Cryptographer> cryptographer, ...@@ -55,6 +55,13 @@ BeginIssuanceOnPostedSequence(std::unique_ptr<Cryptographer> cryptographer,
TrustTokenRequestIssuanceHelper::CryptographerAndUnblindedTokens TrustTokenRequestIssuanceHelper::CryptographerAndUnblindedTokens
ConfirmIssuanceOnPostedSequence(std::unique_ptr<Cryptographer> cryptographer, ConfirmIssuanceOnPostedSequence(std::unique_ptr<Cryptographer> cryptographer,
std::string response_header) { std::string response_header) {
// From the "spec" (design doc): "If the response has an empty Sec-Trust-Token
// header, return; this is a 'success' response bearing 0 tokens"
if (response_header.empty()) {
return {std::move(cryptographer),
std::make_unique<Cryptographer::UnblindedTokens>()};
}
std::unique_ptr<Cryptographer::UnblindedTokens> unblinded_tokens = std::unique_ptr<Cryptographer::UnblindedTokens> unblinded_tokens =
cryptographer->ConfirmIssuance(response_header); cryptographer->ConfirmIssuance(response_header);
return {std::move(cryptographer), std::move(unblinded_tokens)}; return {std::move(cryptographer), std::move(unblinded_tokens)};
...@@ -286,22 +293,29 @@ void TrustTokenRequestIssuanceHelper::Finalize( ...@@ -286,22 +293,29 @@ void TrustTokenRequestIssuanceHelper::Finalize(
response->headers->RemoveHeader(kTrustTokensSecTrustTokenHeader); response->headers->RemoveHeader(kTrustTokensSecTrustTokenHeader);
ConfirmIssuanceResponse(std::move(header_value), std::move(done)); ProcessIssuanceResponse(std::move(header_value), std::move(done));
} }
void TrustTokenRequestIssuanceHelper::ConfirmIssuanceResponse( void TrustTokenRequestIssuanceHelper::ProcessIssuanceResponse(
std::string issuance_response, std::string issuance_response,
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done) { base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done) {
if (issuance_response.empty()) {
OnDoneProcessingIssuanceResponse(
std::move(done), {std::move(cryptographer_),
std::make_unique<Cryptographer::UnblindedTokens>()});
return;
}
base::ThreadPool::PostTaskAndReplyWithResult( base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, FROM_HERE,
base::BindOnce(&ConfirmIssuanceOnPostedSequence, base::BindOnce(&ConfirmIssuanceOnPostedSequence,
std::move(cryptographer_), std::move(issuance_response)), std::move(cryptographer_), std::move(issuance_response)),
base::BindOnce(&TrustTokenRequestIssuanceHelper:: base::BindOnce(
OnDelegateConfirmIssuanceCallComplete, &TrustTokenRequestIssuanceHelper::OnDoneProcessingIssuanceResponse,
weak_ptr_factory_.GetWeakPtr(), std::move(done))); weak_ptr_factory_.GetWeakPtr(), std::move(done)));
} }
void TrustTokenRequestIssuanceHelper::OnDelegateConfirmIssuanceCallComplete( void TrustTokenRequestIssuanceHelper::OnDoneProcessingIssuanceResponse(
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done, base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done,
CryptographerAndUnblindedTokens cryptographer_and_unblinded_tokens) { CryptographerAndUnblindedTokens cryptographer_and_unblinded_tokens) {
cryptographer_ = std::move(cryptographer_and_unblinded_tokens.cryptographer); cryptographer_ = std::move(cryptographer_and_unblinded_tokens.cryptographer);
...@@ -354,7 +368,7 @@ void TrustTokenRequestIssuanceHelper::DoneRequestingLocallyFulfilledIssuance( ...@@ -354,7 +368,7 @@ void TrustTokenRequestIssuanceHelper::DoneRequestingLocallyFulfilledIssuance(
// the main response processing logic when executing issuance locally: // the main response processing logic when executing issuance locally:
net_log_.BeginEvent( net_log_.BeginEvent(
net::NetLogEventType::TRUST_TOKEN_OPERATION_FINALIZE_ISSUANCE); net::NetLogEventType::TRUST_TOKEN_OPERATION_FINALIZE_ISSUANCE);
ConfirmIssuanceResponse( ProcessIssuanceResponse(
std::move(answer->response), std::move(answer->response),
base::BindOnce(&TrustTokenRequestIssuanceHelper:: base::BindOnce(&TrustTokenRequestIssuanceHelper::
DoneFinalizingLocallyFulfilledIssuance, DoneFinalizingLocallyFulfilledIssuance,
......
...@@ -215,15 +215,16 @@ class TrustTokenRequestIssuanceHelper : public TrustTokenRequestHelper { ...@@ -215,15 +215,16 @@ class TrustTokenRequestIssuanceHelper : public TrustTokenRequestHelper {
// Continuation of |Finalize| after extracting the base64-encoded issuance // Continuation of |Finalize| after extracting the base64-encoded issuance
// response from a response header (or receiving it from a locally executed // response from a response header (or receiving it from a locally executed
// operation). // operation).
void ConfirmIssuanceResponse( void ProcessIssuanceResponse(
std::string issuance_response, std::string issuance_response,
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done); base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done);
// Continuation of |Finalize| after a call to the cryptography delegate to // Continuation of |Finalize| after processing the received issuance response,
// which typically involves an off-thread call to the cryptography delegate to
// execute the bulk of the inbound half of the issuance operation. // execute the bulk of the inbound half of the issuance operation.
// Receives ownership of the cryptographer back from the asynchronous // Receives ownership of the cryptographer back from the asynchronous
// callback. // callback.
void OnDelegateConfirmIssuanceCallComplete( void OnDoneProcessingIssuanceResponse(
base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done, base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done,
CryptographerAndUnblindedTokens cryptographer_and_unblinded_tokens); CryptographerAndUnblindedTokens cryptographer_and_unblinded_tokens);
......
...@@ -41,6 +41,7 @@ using ::testing::_; ...@@ -41,6 +41,7 @@ using ::testing::_;
using ::testing::ByMove; using ::testing::ByMove;
using ::testing::ElementsAre; using ::testing::ElementsAre;
using ::testing::Invoke; using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::Property; using ::testing::Property;
using ::testing::Return; using ::testing::Return;
using ::testing::ReturnNull; using ::testing::ReturnNull;
...@@ -390,6 +391,48 @@ TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfResponseOmitsHeader) { ...@@ -390,6 +391,48 @@ TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfResponseOmitsHeader) {
mojom::TrustTokenOperationStatus::kBadResponse); mojom::TrustTokenOperationStatus::kBadResponse);
} }
// Check that the issuance helper correctly handles responses bearing empty
// Sec-Trust-Token headers, which represent "success but no tokens issued".
TEST_F(TrustTokenRequestIssuanceHelperTest, TreatsEmptyHeaderAsSuccess) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem));
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(kTrustTokensSecTrustTokenHeader, "");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// After the operation has successfully finished, the store should still
// contain no tokens for the issuer.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(store->RetrieveMatchingTokens(issuer, std::move(match_all_keys)),
IsEmpty());
}
// Check that the issuance helper handles an issuance response rejected by the // Check that the issuance helper handles an issuance response rejected by the
// underlying cryptographic library. // underlying cryptographic library.
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfResponseIsUnusable) { TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfResponseIsUnusable) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment