Commit 1a63f5a8 authored by David Van Cleve's avatar David Van Cleve Committed by Commit Bot

Parse Trust Tokens key commitments.

Trust Tokens key commitment responses are served during the
issuance and redemption steps of the Trust Tokens (Privacy Pass)
protocol. As part of implementing Trust Tokens, we need to fetch
and parse these responses during the "issuance" and "redemption"
protocol steps, triggered by requests' having been tagged with
the corresponding fetch flags. For the preliminary version, these key
commitment responses' contents are specified in the design doc:
https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#bookmark=id.6wh9crbxdizi

This CL adds parsing code; a concurrent change adds a flow
for requesting these records from issuers' key commitment endpoints.

New code:
- KeyCommitmentResult describes the information we get from
a key commitment response;
- key_commitment_parsing.* implement logic and tests for parsing
the commitments. We try to err on the side of being very strict,
rejecting outright many messages that have any part malformed
(for instance, if a record contains three keys and one is bad,
the entire message gets thrown out).

Bug: 1042962
Change-Id: I3b3d9b0b71bc74f80839b0f14309b990bcbe5a09
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2029292
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748677}
parent 0c01aedd
...@@ -21,6 +21,8 @@ source_set("trust_tokens") { ...@@ -21,6 +21,8 @@ source_set("trust_tokens") {
"trust_token_http_headers.h", "trust_token_http_headers.h",
"trust_token_key_commitment_controller.cc", "trust_token_key_commitment_controller.cc",
"trust_token_key_commitment_controller.h", "trust_token_key_commitment_controller.h",
"trust_token_key_commitment_parser.cc",
"trust_token_key_commitment_parser.h",
"trust_token_key_commitment_result.cc", "trust_token_key_commitment_result.cc",
"trust_token_key_commitment_result.h", "trust_token_key_commitment_result.h",
"trust_token_operation_status.h", "trust_token_operation_status.h",
...@@ -78,6 +80,7 @@ source_set("tests") { ...@@ -78,6 +80,7 @@ source_set("tests") {
"sqlite_trust_token_persister_unittest.cc", "sqlite_trust_token_persister_unittest.cc",
"trust_token_database_owner_unittest.cc", "trust_token_database_owner_unittest.cc",
"trust_token_key_commitment_controller_unittest.cc", "trust_token_key_commitment_controller_unittest.cc",
"trust_token_key_commitment_parser_unittest.cc",
"trust_token_persister_unittest.cc", "trust_token_persister_unittest.cc",
"trust_token_request_canonicalizer_unittest.cc", "trust_token_request_canonicalizer_unittest.cc",
"trust_token_request_signing_helper_unittest.cc", "trust_token_request_signing_helper_unittest.cc",
......
// 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_key_commitment_parser.h"
#include "base/base64.h"
#include "base/json/json_reader.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "services/network/trust_tokens/trust_token_key_commitment_result.h"
namespace network {
namespace {
// Parses a single key label. If |in| is the string representation of an integer
// in in the representable range of uint32_t, sets |*out| to that integer value
// and returns true. Otherwise, returns false.
bool ParseSingleKeyLabel(base::StringPiece in, uint32_t* out) {
uint64_t key_label_in_uint64;
if (!base::StringToUint64(in, &key_label_in_uint64))
return false;
if (!base::IsValueInRangeForNumericType<uint32_t>(key_label_in_uint64))
return false;
*out = base::checked_cast<uint32_t>(key_label_in_uint64);
return true;
}
enum class ParseKeyResult {
// Continue as if the key didn't exist.
kIgnore,
// Fail parsing totally.
kFail,
// Parsing the key succeeded.
kSucceed
};
// Parses a single key, consisting of a body (the key material) and an expiry
// timestamp. Fails the parse if either field is missing or malformed. If the
// key has expired but is otherwise valid, ignores the key rather than failing
// the prase.
ParseKeyResult ParseSingleKeyExceptLabel(
const base::Value& in,
TrustTokenKeyCommitmentResult::Key* out) {
CHECK(in.is_dict());
const std::string* expiry =
in.FindStringKey(kTrustTokenKeyCommitmentExpiryField);
const std::string* key_body =
in.FindStringKey(kTrustTokenKeyCommitmentKeyField);
if (!expiry || !key_body)
return ParseKeyResult::kFail;
uint64_t expiry_microseconds_since_unix_epoch;
if (!base::StringToUint64(*expiry, &expiry_microseconds_since_unix_epoch))
return ParseKeyResult::kFail;
if (!base::Base64Decode(*key_body, &out->body))
return ParseKeyResult::kFail;
out->expiry =
base::Time::UnixEpoch() +
base::TimeDelta::FromMicroseconds(expiry_microseconds_since_unix_epoch);
if (out->expiry <= base::Time::Now())
return ParseKeyResult::kIgnore;
return ParseKeyResult::kSucceed;
}
} // namespace
const char kTrustTokenKeyCommitmentBatchsizeField[] = "batchsize";
const char kTrustTokenKeyCommitmentSrrkeyField[] = "srrkey";
const char kTrustTokenKeyCommitmentExpiryField[] = "expiry";
const char kTrustTokenKeyCommitmentKeyField[] = "Y";
// https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#bookmark=id.6wh9crbxdizi
// {
// "batchsize" : ..., // Optional batch size; value of type int.
// "srrkey" : ..., // Required Signed Redemption Record (SRR)
// // verification key, in base64.
//
// "1" : { // Key label, a number in uint32_t range.
// "Y" : ..., // Required token issuance verification key, in
// // base64.
// "expiry" : ..., // Required token issuance key expiry time, in
// // microseconds since the Unix epoch.
// },
// "17" : { // No guarantee that key labels (1, 17) are dense.
// "Y" : ...,
// "expiry" : ...,
// }
// }
std::unique_ptr<TrustTokenKeyCommitmentResult>
TrustTokenKeyCommitmentParser::Parse(base::StringPiece response_body) {
base::Optional<base::Value> maybe_value =
base::JSONReader::Read(response_body);
if (!maybe_value)
return nullptr;
if (!maybe_value->is_dict())
return nullptr;
auto result = std::make_unique<TrustTokenKeyCommitmentResult>();
// Confirm that the batchsize field is type-safe, if it's present.
if (maybe_value->FindKey(kTrustTokenKeyCommitmentBatchsizeField) &&
!maybe_value->FindIntKey(kTrustTokenKeyCommitmentBatchsizeField)) {
return nullptr;
}
result->batch_size =
maybe_value->FindIntKey(kTrustTokenKeyCommitmentBatchsizeField);
// Confirm that the srrkey field is present and base64-encoded.
const std::string* maybe_srrkey =
maybe_value->FindStringKey(kTrustTokenKeyCommitmentSrrkeyField);
if (!maybe_srrkey)
return nullptr;
if (!base::Base64Decode(*maybe_srrkey,
&result->signed_redemption_record_verification_key)) {
return nullptr;
}
// Parse the key commitments in the result (these are exactly the
// key-value pairs in the dictionary with dictionary-typed values).
for (const auto& kv : maybe_value->DictItems()) {
const base::Value& item = kv.second;
if (!item.is_dict())
continue;
TrustTokenKeyCommitmentResult::Key key;
if (!ParseSingleKeyLabel(kv.first, &key.label))
return nullptr;
switch (ParseSingleKeyExceptLabel(item, &key)) {
case ParseKeyResult::kFail:
return nullptr;
case ParseKeyResult::kIgnore:
continue;
case ParseKeyResult::kSucceed:
result->keys.push_back(std::move(key));
}
}
return result;
}
} // namespace network
// 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.
#ifndef SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_KEY_COMMITMENT_PARSER_H_
#define SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_KEY_COMMITMENT_PARSER_H_
#include <memory>
#include "base/strings/string_piece_forward.h"
#include "services/network/trust_tokens/trust_token_key_commitment_controller.h"
namespace network {
struct TrustTokenKeyCommitmentResult;
// Field names from the key commitment JSON format specified in the Trust Tokens
// design doc
// (https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#bookmark=id.6wh9crbxdizi):
// - "batch size" (number of blinded tokens to provide per issuance request)
extern const char kTrustTokenKeyCommitmentBatchsizeField[];
// - verification key for the signatures the issuer provides over its Signed
// Redemption Records (SRRs)
extern const char kTrustTokenKeyCommitmentSrrkeyField[];
// - each issuance key's expiry timestamp
extern const char kTrustTokenKeyCommitmentExpiryField[];
// - each issuance key's key material
extern const char kTrustTokenKeyCommitmentKeyField[];
class TrustTokenKeyCommitmentParser
: public TrustTokenKeyCommitmentController::Parser {
public:
TrustTokenKeyCommitmentParser() = default;
~TrustTokenKeyCommitmentParser() override = default;
// Parses a JSON key commitment response.
//
// This method returns nullptr unless:
// - the input is valid JSON; and
// - the JSON represents a nonempty dictionary; and
// - within this inner dictionary (which stores metadata like batch size, as
// well as more dictionaries denoting keys' information):
// - every dictionary-type value has an expiry field
// (|kTrustTokenKeyCommitmentExpiryField| above) and a key body field
// (|kTrustTokenKeyCommitmentKeyField|), and
// - the expiry field is a positive integer (microseconds since the Unix
// epoch) storing a time in the future.
std::unique_ptr<TrustTokenKeyCommitmentResult> Parse(
base::StringPiece response_body) override;
};
} // namespace network
#endif // SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_KEY_COMMITMENT_PARSER_H_
// 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_key_commitment_parser.h"
#include "base/base64.h"
#include "base/json/json_reader.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "services/network/trust_tokens/trust_token_key_commitment_result.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::ElementsAre;
using ::testing::Pointee;
using ::testing::UnorderedElementsAre;
namespace network {
TEST(TrustTokenKeyCommitmentParsing, RejectsEmpty) {
// If the input isn't valid JSON, we should
// reject it. In particular, we should reject
// empty input.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(""));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsNonemptyMalformed) {
// If the input isn't valid JSON, we should
// reject it.
const char input[] = "certainly not valid JSON";
// Sanity check that the input is not valid JSON.
ASSERT_FALSE(base::JSONReader::Read(input));
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsNonDictionaryInput) {
// The outermost value should be a dictionary.
// Valid JSON, but not a dictionary.
const char input[] = "5";
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, AcceptsMinimal) {
std::string input = R"( { "srrkey": "aaaa" } )";
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
TrustTokenKeyCommitmentResult expectation;
base::Base64Decode("aaaa",
&expectation.signed_redemption_record_verification_key);
EXPECT_THAT(TrustTokenKeyCommitmentParser().Parse(input),
Pointee(expectation));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsMissingSrrkey) {
std::string input = R"( {} )";
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
std::unique_ptr<TrustTokenKeyCommitmentResult> result =
TrustTokenKeyCommitmentParser().Parse(input);
EXPECT_FALSE(result);
}
TEST(TrustTokenKeyCommitmentParsing, RejectsTypeUnsafeSrrkey) {
std::string input = R"( { "srrkey": 5 } )";
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
std::unique_ptr<TrustTokenKeyCommitmentResult> result =
TrustTokenKeyCommitmentParser().Parse(input);
EXPECT_FALSE(result);
}
TEST(TrustTokenKeyCommitmentParsing, RejectsNonBase64Srrkey) {
std::string input = R"( { "srrkey": "spaces aren't valid base64" } )";
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
std::unique_ptr<TrustTokenKeyCommitmentResult> result =
TrustTokenKeyCommitmentParser().Parse(input);
EXPECT_FALSE(result);
}
TEST(TrustTokenKeyCommitmentParsing, RejectsKeyWithTypeUnsafeKeyLabel) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
// (The expiry will likely exceed the JSON spec's maximum integer value, so
// it's encoded as a string.)
const std::string input = base::StringPrintf(
R"({
"srrkey": "aaaa",
"this label is not an integer": {
"Y": "akey",
"expiry": "%s"
}
})",
base::NumberToString(one_minute_from_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
// Key labels must be integers in the representable range of uint32_t, so this
// result shouldn't parse.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsKeyWithKeyLabelTooSmall) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
const std::string input = base::StringPrintf(
R"({
"srrkey": "aaaa",
"-1": {
"Y": "akey",
"expiry": "%s"
}
})",
base::NumberToString(one_minute_from_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
// Key labels must be integers in the representable range of uint32_t, so this
// result shouldn't parse.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsKeyWithKeyLabelTooLarge) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
const std::string input = base::StringPrintf(
R"({
"srrkey": "aaaa",
"1000000000000": {
"Y": "akey",
"expiry": "%s"
}
})",
base::NumberToString(one_minute_from_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
// Key labels must be integers in the representable range of uint32_t, so this
// result shouldn't parse.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsOtherwiseValidButNonBase64Key) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
const std::string input = base::StringPrintf(
R"({
"srrkey": "aaaa",
"1": {
"Y": "this key isn't valid base64, so it should be rejected",
"expiry": "%s"
}
})",
base::NumberToString(one_minute_from_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON,
// and that the given time is valid.
ASSERT_TRUE(base::JSONReader::Read(input));
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, AcceptsKeyWithExpiryAndBody) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
const std::string input = base::StringPrintf(
R"({
"srrkey": "aaaa",
"1": { "Y": "akey", "expiry": "%s" }
})",
base::NumberToString(one_minute_from_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON,
// and that the given time is valid.
ASSERT_TRUE(base::JSONReader::Read(input));
TrustTokenKeyCommitmentResult::Key my_key;
my_key.label = 1;
ASSERT_TRUE(base::Base64Decode("akey", &my_key.body));
my_key.expiry = one_minute_from_now;
auto result = TrustTokenKeyCommitmentParser().Parse(input);
ASSERT_TRUE(result);
EXPECT_THAT(result->keys, ElementsAre(my_key));
}
TEST(TrustTokenKeyCommitmentParsing, AcceptsMultipleKeys) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
base::Time two_minutes_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(2);
int64_t two_minutes_from_now_in_micros =
(two_minutes_from_now - base::Time::UnixEpoch()).InMicroseconds();
const std::string input = base::StringPrintf(
R"({
"srrkey": "aaaa",
"1": { "Y": "akey", "expiry": "%s" },
"2": { "Y": "aaaa", "expiry": "%s" }
})",
base::NumberToString(one_minute_from_now_in_micros).c_str(),
base::NumberToString(two_minutes_from_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON,
// and that the given time is valid.
ASSERT_TRUE(base::JSONReader::Read(input));
TrustTokenKeyCommitmentResult::Key a_key;
a_key.label = 1;
ASSERT_TRUE(base::Base64Decode("akey", &a_key.body));
a_key.expiry = one_minute_from_now;
TrustTokenKeyCommitmentResult::Key another_key;
another_key.label = 2;
ASSERT_TRUE(base::Base64Decode("aaaa", &another_key.body));
another_key.expiry = two_minutes_from_now;
auto result = TrustTokenKeyCommitmentParser().Parse(input);
ASSERT_TRUE(result);
EXPECT_THAT(result->keys, UnorderedElementsAre(a_key, another_key));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsKeyWithNoExpiry) {
// If a key has a missing "expiry" field, we should reject the entire
// record.
const std::string input = R"( { "srrkey": "aaaa",
"1": { "Y": "akey" } })";
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
// Since the key doesn't have an expiry, reject it.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsKeyWithMalformedExpiry) {
// If a key has a malformed "expiry" field, we should reject the entire
// record.
const std::string input =
R"(
{
"srrkey": "aaaa",
"1": {
"Y": "akey",
"expiry": "absolutely not a valid timestamp"
}
})";
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
// Since the key doesn't have an expiry, reject it.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, IgnoreKeyWithExpiryInThePast) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_before_now =
base::Time::Now() - base::TimeDelta::FromMinutes(1);
int64_t one_minute_before_now_in_micros =
(one_minute_before_now - base::Time::UnixEpoch()).InMicroseconds();
// If the time has passed a key's "expiry" field, we should reject the entire
// record.
const std::string input = base::StringPrintf(
R"( { "srrkey": "aaaa",
"1": { "Y": "akey", "expiry": "%s" } })",
base::NumberToString(one_minute_before_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON.
ASSERT_TRUE(base::JSONReader::Read(input));
TrustTokenKeyCommitmentResult expectation;
base::Base64Decode("aaaa",
&expectation.signed_redemption_record_verification_key);
EXPECT_THAT(TrustTokenKeyCommitmentParser().Parse(input),
Pointee(expectation));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsKeyWithNoBody) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
base::Time one_minute_from_now =
base::Time::Now() + base::TimeDelta::FromMinutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
// If a key has an expiry but is missing its body,
// we should reject the entire result.
const std::string input = base::StringPrintf(
R"( { "srrkey": "aaaa", "1": { "expiry": "%s" } } )",
base::NumberToString(one_minute_from_now_in_micros).c_str());
// Sanity check that the input is actually valid JSON,
// and that the date is valid.
ASSERT_TRUE(base::JSONReader::Read(input));
// Since the key doesn't have a body, reject it.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, RejectsEmptyKey) {
// If a key has neither an expiry or a body,
// we should reject the entire result.
const std::string input = R"( { "srrkey": "aaaa", "1": { } })";
// Sanity check that the input is actually valid JSON,
// and that the date is valid.
ASSERT_TRUE(base::JSONReader::Read(input));
// Since the key doesn't have an expiry or a body, reject it.
EXPECT_FALSE(TrustTokenKeyCommitmentParser().Parse(input));
}
TEST(TrustTokenKeyCommitmentParsing, ParsesBatchSize) {
std::string input =
R"({
"srrkey": "aaaa",
"batchsize": 5
})";
std::unique_ptr<TrustTokenKeyCommitmentResult> result =
TrustTokenKeyCommitmentParser().Parse(input);
ASSERT_TRUE(result);
EXPECT_EQ(result->batch_size, 5);
}
TEST(TrustTokenKeyCommitmentParsing, RejectsTypeUnsafeBatchSize) {
std::string input =
R"({
"srrkey": "aaaa",
"batchsize": "not a number"
})";
std::unique_ptr<TrustTokenKeyCommitmentResult> result =
TrustTokenKeyCommitmentParser().Parse(input);
EXPECT_FALSE(result);
}
} // namespace network
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