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