1 // Copyright 2024 The Chromium Authors
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 "net/device_bound_sessions/device_bound_session_registration_fetcher_param.h"
6 
7 #include "base/base64url.h"
8 #include "base/logging.h"
9 #include "base/strings/escape.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "net/base/schemeful_site.h"
13 #include "net/http/structured_headers.h"
14 
15 #include <vector>
16 
17 namespace {
18 // TODO(kristianm): See if these can be used with
19 // services/network/sec_header_helpers.cc
20 constexpr char kRegistrationHeaderName[] = "Sec-Session-Registration";
21 constexpr char kChallengeItemKey[] = "challenge";
22 
23 constexpr char kES256[] = "es256";
24 constexpr char kRS256[] = "rs256";
25 
AlgoFromString(const std::string_view & algo)26 std::optional<crypto::SignatureVerifier::SignatureAlgorithm> AlgoFromString(
27     const std::string_view& algo) {
28   if (base::EqualsCaseInsensitiveASCII(algo, kES256)) {
29     return crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256;
30   }
31 
32   if (base::EqualsCaseInsensitiveASCII(algo, kRS256)) {
33     return crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256;
34   }
35 
36   return std::nullopt;
37 }
38 }  // namespace
39 
40 namespace net {
41 
42 DeviceBoundSessionRegistrationFetcherParam::
43     DeviceBoundSessionRegistrationFetcherParam(
44         DeviceBoundSessionRegistrationFetcherParam&& other) = default;
45 
46 DeviceBoundSessionRegistrationFetcherParam&
47 DeviceBoundSessionRegistrationFetcherParam::
48     operator=(
49         DeviceBoundSessionRegistrationFetcherParam&& other) noexcept = default;
50 
51 DeviceBoundSessionRegistrationFetcherParam::
52     ~DeviceBoundSessionRegistrationFetcherParam() =
53         default;
54 
55 DeviceBoundSessionRegistrationFetcherParam::
DeviceBoundSessionRegistrationFetcherParam(GURL registration_endpoint,std::vector<crypto::SignatureVerifier::SignatureAlgorithm> supported_algos,std::string challenge)56     DeviceBoundSessionRegistrationFetcherParam(
57         GURL registration_endpoint,
58         std::vector<crypto::SignatureVerifier::SignatureAlgorithm>
59             supported_algos,
60         std::string challenge)
61         : registration_endpoint_(std::move(registration_endpoint)),
62           supported_algos_(std::move(supported_algos)),
63           challenge_(std::move(challenge)) {}
64 
65 std::optional<DeviceBoundSessionRegistrationFetcherParam>
ParseItem(const GURL & request_url,structured_headers::Item item,structured_headers::Parameters params)66 DeviceBoundSessionRegistrationFetcherParam::ParseItem(
67     const GURL& request_url,
68     structured_headers::Item item,
69     structured_headers::Parameters params) {
70   if (!item.is_string()) {
71     return std::nullopt;
72   }
73 
74   // TODO(kristianm): Update this as same site requirements are solidified
75   std::string unescaped = base::UnescapeURLComponent(
76       item.GetString(),
77       base::UnescapeRule::PATH_SEPARATORS |
78           base::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
79   GURL registration_endpoint = request_url.Resolve(unescaped);
80   if (!registration_endpoint.is_valid() ||
81       net::SchemefulSite(registration_endpoint) !=
82           net::SchemefulSite(request_url)) {
83     return std::nullopt;
84   }
85 
86   std::vector<crypto::SignatureVerifier::SignatureAlgorithm> supported_algos;
87   std::string challenge;
88   for (const auto& [name, value] : params) {
89     // The only boolean parameters are the supported algorithms
90     if (value.is_boolean()) {
91       if (value.GetBoolean()) {
92         std::optional<crypto::SignatureVerifier::SignatureAlgorithm> algo =
93             AlgoFromString(name);
94         if (algo) {
95           supported_algos.push_back(*algo);
96         }
97       }
98     }
99 
100     // The only byte sequence parameter is the challenge
101     // TODO(kristianm): Add authorization parameter as well
102     if (value.is_byte_sequence()) {
103       if (name == kChallengeItemKey) {
104         challenge = value.GetString();
105       }
106     }
107   }
108 
109   if (challenge.empty() || supported_algos.empty()) {
110     return {};
111   }
112 
113   return DeviceBoundSessionRegistrationFetcherParam(
114       std::move(registration_endpoint),
115       std::move(supported_algos),
116       std::move(challenge));
117 }
118 
119 std::vector<DeviceBoundSessionRegistrationFetcherParam>
CreateIfValid(const GURL & request_url,const net::HttpResponseHeaders * headers)120 DeviceBoundSessionRegistrationFetcherParam::CreateIfValid(
121     const GURL& request_url,
122     const net::HttpResponseHeaders* headers) {
123   if (!request_url.is_valid()) {
124     return {};
125   }
126 
127   std::string header_value;
128   if (!headers ||
129       !headers->GetNormalizedHeader(kRegistrationHeaderName, &header_value)) {
130     return {};
131   }
132 
133   std::optional<structured_headers::List> list =
134       structured_headers::ParseList(header_value);
135   if (!list || list->empty()) {
136     return {};
137   }
138 
139   std::vector<DeviceBoundSessionRegistrationFetcherParam> params;
140   for (const auto& item : *list) {
141     // Header spec does not support inner lists
142     if (item.member_is_inner_list) {
143       continue;
144     }
145 
146     // This is not obvious, the way the Google structured header parser
147     // works these params will be considered to belong to the list item
148     // and not to the item itself. This is to enable the more intuitive syntax:
149     // Sec-Session-Registration: "path1"; challenge=:Y2hhbGxlbmdl:; es256;
150     // instead of:
151     // Sec-Session-Registration: ("path1"; challenge=:Y2hhbGxlbmdl:; es256;)
152     std::optional<DeviceBoundSessionRegistrationFetcherParam> param =
153         ParseItem(request_url, item.member[0].item, item.params);
154     if (param) {
155       params.push_back(std::move(*param));
156     }
157   }
158 
159   return params;
160 }
161 
162 }  // namespace net
163