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/registration_fetcher_param.h"
6
7 #include <vector>
8
9 #include "base/base64url.h"
10 #include "base/logging.h"
11 #include "base/strings/escape.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "net/base/schemeful_site.h"
15 #include "net/http/structured_headers.h"
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 kChallengeParamKey[] = "challenge";
22 constexpr char kPathParamKey[] = "path";
23 constexpr char kAuthCodeParamKey[] = "authorization";
24
25 constexpr char kES256[] = "ES256";
26 constexpr char kRS256[] = "RS256";
27
AlgoFromString(const std::string_view & algo)28 std::optional<crypto::SignatureVerifier::SignatureAlgorithm> AlgoFromString(
29 const std::string_view& algo) {
30 if (algo == kES256) {
31 return crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256;
32 }
33
34 if (algo == kRS256) {
35 return crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256;
36 }
37
38 return std::nullopt;
39 }
40 } // namespace
41
42 namespace net::device_bound_sessions {
43
44 RegistrationFetcherParam::RegistrationFetcherParam(
45 RegistrationFetcherParam&& other) = default;
46
47 RegistrationFetcherParam& RegistrationFetcherParam::operator=(
48 RegistrationFetcherParam&& other) noexcept = default;
49
50 RegistrationFetcherParam::~RegistrationFetcherParam() = default;
51
RegistrationFetcherParam(GURL registration_endpoint,std::vector<crypto::SignatureVerifier::SignatureAlgorithm> supported_algos,std::string challenge,std::optional<std::string> authorization)52 RegistrationFetcherParam::RegistrationFetcherParam(
53 GURL registration_endpoint,
54 std::vector<crypto::SignatureVerifier::SignatureAlgorithm> supported_algos,
55 std::string challenge,
56 std::optional<std::string> authorization)
57 : registration_endpoint_(std::move(registration_endpoint)),
58 supported_algos_(std::move(supported_algos)),
59 challenge_(std::move(challenge)),
60 authorization_(std::move(authorization)) {}
61
ParseItem(const GURL & request_url,const structured_headers::ParameterizedMember & session_registration)62 std::optional<RegistrationFetcherParam> RegistrationFetcherParam::ParseItem(
63 const GURL& request_url,
64 const structured_headers::ParameterizedMember& session_registration) {
65 std::vector<crypto::SignatureVerifier::SignatureAlgorithm> supported_algos;
66 for (const auto& algo_token : session_registration.member) {
67 if (algo_token.item.is_token()) {
68 std::optional<crypto::SignatureVerifier::SignatureAlgorithm> algo =
69 AlgoFromString(algo_token.item.GetString());
70 if (algo) {
71 supported_algos.push_back(*algo);
72 };
73 }
74 }
75 if (supported_algos.empty()) {
76 return std::nullopt;
77 }
78
79 GURL registration_endpoint;
80 std::string challenge;
81 std::optional<std::string> authorization;
82 for (const auto& [key, value] : session_registration.params) {
83 // The keys for the parameters are unique and must be lower case.
84 // Quiche (https://quiche.googlesource.com/quiche), used here,
85 // will currently pick the last if there is more than one.
86 if (key == kPathParamKey) {
87 if (!value.is_string()) {
88 continue;
89 }
90 std::string path = value.GetString();
91 // TODO(kristianm): Update this as same site requirements are solidified
92 std::string unescaped = base::UnescapeURLComponent(
93 path,
94 base::UnescapeRule::PATH_SEPARATORS |
95 base::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
96 GURL candidate_endpoint = request_url.Resolve(unescaped);
97 if (candidate_endpoint.is_valid() &&
98 net::SchemefulSite(candidate_endpoint) ==
99 net::SchemefulSite(request_url)) {
100 registration_endpoint = std::move(candidate_endpoint);
101 }
102 } else if (key == kChallengeParamKey && value.is_string()) {
103 challenge = value.GetString();
104 } else if (key == kAuthCodeParamKey && value.is_string()) {
105 authorization = value.GetString();
106 }
107
108 // Other params are ignored
109 }
110
111 if (!registration_endpoint.is_valid() || challenge.empty()) {
112 return std::nullopt;
113 }
114
115 return RegistrationFetcherParam(
116 std::move(registration_endpoint), std::move(supported_algos),
117 std::move(challenge), std::move(authorization));
118 }
119
CreateIfValid(const GURL & request_url,const net::HttpResponseHeaders * headers)120 std::vector<RegistrationFetcherParam> RegistrationFetcherParam::CreateIfValid(
121 const GURL& request_url,
122 const net::HttpResponseHeaders* headers) {
123 std::vector<RegistrationFetcherParam> params;
124 if (!request_url.is_valid()) {
125 return params;
126 }
127
128 if (!headers) {
129 return params;
130 }
131 std::optional<std::string> header_value =
132 headers->GetNormalizedHeader(kRegistrationHeaderName);
133 if (!header_value) {
134 return params;
135 }
136
137 std::optional<structured_headers::List> list =
138 structured_headers::ParseList(*header_value);
139 if (!list || list->empty()) {
140 return params;
141 }
142
143 for (const auto& item : *list) {
144 if (item.member_is_inner_list) {
145 std::optional<RegistrationFetcherParam> fetcher_param =
146 ParseItem(request_url, item);
147 if (fetcher_param) {
148 params.push_back(std::move(*fetcher_param));
149 }
150 }
151 }
152
153 return params;
154 }
155
156 // static
CreateInstanceForTesting(GURL registration_endpoint,std::vector<crypto::SignatureVerifier::SignatureAlgorithm> supported_algos,std::string challenge,std::optional<std::string> authorization)157 RegistrationFetcherParam RegistrationFetcherParam::CreateInstanceForTesting(
158 GURL registration_endpoint,
159 std::vector<crypto::SignatureVerifier::SignatureAlgorithm> supported_algos,
160 std::string challenge,
161 std::optional<std::string> authorization) {
162 return RegistrationFetcherParam(
163 std::move(registration_endpoint), std::move(supported_algos),
164 std::move(challenge), std::move(authorization));
165 }
166
167 } // namespace net::device_bound_sessions
168