1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "services/network/trust_tokens/trust_token_key_commitment_parser.h"
6
7 #include "base/base64.h"
8 #include "base/json/json_reader.h"
9 #include "base/numerics/safe_conversions.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_piece.h"
12 #include "base/values.h"
13 #include "services/network/public/mojom/trust_tokens.mojom.h"
14
15 namespace network {
16
17 namespace {
18
19 // Parses a single key label. If |in| is the string representation of an integer
20 // in in the representable range of uint32_t, sets |*out| to that integer value
21 // and returns true. Otherwise, returns false.
ParseSingleKeyLabel(base::StringPiece in,uint32_t * out)22 bool ParseSingleKeyLabel(base::StringPiece in, uint32_t* out) {
23 uint64_t key_label_in_uint64;
24 if (!base::StringToUint64(in, &key_label_in_uint64))
25 return false;
26 if (!base::IsValueInRangeForNumericType<uint32_t>(key_label_in_uint64))
27 return false;
28 *out = base::checked_cast<uint32_t>(key_label_in_uint64);
29 return true;
30 }
31
32 enum class ParseKeyResult {
33 // Continue as if the key didn't exist.
34 kIgnore,
35 // Fail parsing totally.
36 kFail,
37 // Parsing the key succeeded.
38 kSucceed
39 };
40
41 // Parses a single key, consisting of a body (the key material) and an expiry
42 // timestamp. Fails the parse if either field is missing or malformed. If the
43 // key has expired but is otherwise valid, ignores the key rather than failing
44 // the prase.
ParseSingleKeyExceptLabel(const base::Value & in,mojom::TrustTokenVerificationKey * out)45 ParseKeyResult ParseSingleKeyExceptLabel(
46 const base::Value& in,
47 mojom::TrustTokenVerificationKey* out) {
48 CHECK(in.is_dict());
49
50 const std::string* expiry =
51 in.FindStringKey(kTrustTokenKeyCommitmentExpiryField);
52 const std::string* key_body =
53 in.FindStringKey(kTrustTokenKeyCommitmentKeyField);
54 if (!expiry || !key_body)
55 return ParseKeyResult::kFail;
56
57 uint64_t expiry_microseconds_since_unix_epoch;
58 if (!base::StringToUint64(*expiry, &expiry_microseconds_since_unix_epoch))
59 return ParseKeyResult::kFail;
60
61 if (!base::Base64Decode(*key_body, &out->body))
62 return ParseKeyResult::kFail;
63
64 out->expiry =
65 base::Time::UnixEpoch() +
66 base::TimeDelta::FromMicroseconds(expiry_microseconds_since_unix_epoch);
67 if (out->expiry <= base::Time::Now())
68 return ParseKeyResult::kIgnore;
69
70 return ParseKeyResult::kSucceed;
71 }
72
73 } // namespace
74
75 const char kTrustTokenKeyCommitmentBatchsizeField[] = "batchsize";
76 const char kTrustTokenKeyCommitmentSrrkeyField[] = "srrkey";
77 const char kTrustTokenKeyCommitmentExpiryField[] = "expiry";
78 const char kTrustTokenKeyCommitmentKeyField[] = "Y";
79
80 // https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#bookmark=id.6wh9crbxdizi
81 // {
82 // "batchsize" : ..., // Optional batch size; value of type int.
83 // "srrkey" : ..., // Required Signed Redemption Record (SRR)
84 // // verification key, in base64.
85 //
86 // "1" : { // Key label, a number in uint32_t range.
87 // "Y" : ..., // Required token issuance verification key, in
88 // // base64.
89 // "expiry" : ..., // Required token issuance key expiry time, in
90 // // microseconds since the Unix epoch.
91 // },
92 // "17" : { // No guarantee that key labels (1, 17) are dense.
93 // "Y" : ...,
94 // "expiry" : ...,
95 // }
96 // }
Parse(base::StringPiece response_body)97 mojom::TrustTokenKeyCommitmentResultPtr TrustTokenKeyCommitmentParser::Parse(
98 base::StringPiece response_body) {
99 base::Optional<base::Value> maybe_value =
100 base::JSONReader::Read(response_body);
101 if (!maybe_value)
102 return nullptr;
103
104 if (!maybe_value->is_dict())
105 return nullptr;
106
107 auto result = mojom::TrustTokenKeyCommitmentResult::New();
108
109 // Confirm that the batchsize field is type-safe, if it's present.
110 if (maybe_value->FindKey(kTrustTokenKeyCommitmentBatchsizeField) &&
111 !maybe_value->FindIntKey(kTrustTokenKeyCommitmentBatchsizeField)) {
112 return nullptr;
113 }
114 if (base::Optional<int> maybe_batch_size =
115 maybe_value->FindIntKey(kTrustTokenKeyCommitmentBatchsizeField)) {
116 result->batch_size =
117 mojom::TrustTokenKeyCommitmentBatchSize::New(*maybe_batch_size);
118 }
119
120 // Confirm that the srrkey field is present and base64-encoded.
121 const std::string* maybe_srrkey =
122 maybe_value->FindStringKey(kTrustTokenKeyCommitmentSrrkeyField);
123 if (!maybe_srrkey)
124 return nullptr;
125 if (!base::Base64Decode(*maybe_srrkey,
126 &result->signed_redemption_record_verification_key)) {
127 return nullptr;
128 }
129
130 // Parse the key commitments in the result (these are exactly the
131 // key-value pairs in the dictionary with dictionary-typed values).
132 for (const auto& kv : maybe_value->DictItems()) {
133 const base::Value& item = kv.second;
134 if (!item.is_dict())
135 continue;
136
137 auto key = mojom::TrustTokenVerificationKey::New();
138
139 if (!ParseSingleKeyLabel(kv.first, &key->label))
140 return nullptr;
141
142 switch (ParseSingleKeyExceptLabel(item, key.get())) {
143 case ParseKeyResult::kFail:
144 return nullptr;
145 case ParseKeyResult::kIgnore:
146 continue;
147 case ParseKeyResult::kSucceed:
148 result->keys.push_back(std::move(key));
149 }
150 }
151
152 return result;
153 }
154
155 } // namespace network
156