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