blob: 598c1851ab802aa0595a8d301d8906f2454c3158 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2021 The Chromium Authors
Liquan (Max) Gu292fc3d2021-07-19 19:03:032// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/webauth/client_data_json.h"
6
Md Hasibul Hasana963a9342024-04-03 10:15:147#include <string_view>
8
Liquan (Max) Gu292fc3d2021-07-19 19:03:039#include "base/base64url.h"
10#include "base/check.h"
Adem Derinel620520b42024-11-04 15:45:4411#include "base/containers/span.h"
Liquan (Max) Gu292fc3d2021-07-19 19:03:0312#include "base/rand_util.h"
Peter Kastingabc2bc32023-10-27 22:30:0913#include "base/strings/string_number_conversions.h"
Liquan (Max) Gu292fc3d2021-07-19 19:03:0314#include "base/strings/utf_string_conversion_utils.h"
Adem Derinel620520b42024-11-04 15:45:4415#include "content/browser/webauth/common_utils.h"
Stephen McGruera80677c2023-01-12 22:32:2416#include "content/public/common/content_features.h"
Liquan (Max) Gu292fc3d2021-07-19 19:03:0317
18namespace content {
19namespace {
20
Liquan (Max) Gu292fc3d2021-07-19 19:03:0321// ToJSONString encodes |in| as a JSON string, using the specific escaping rules
22// required by https://github.com/w3c/webauthn/pull/1375.
Md Hasibul Hasana963a9342024-04-03 10:15:1423std::string ToJSONString(std::string_view in) {
Liquan (Max) Gu292fc3d2021-07-19 19:03:0324 std::string ret;
25 ret.reserve(in.size() + 2);
26 ret.push_back('"');
27
Adem Derinel620520b42024-11-04 15:45:4428 base::span<const char> in_bytes = base::span(in);
Peter Kasting8bb45c22022-06-16 19:39:2729 const size_t length = in.size();
30 size_t offset = 0;
Liquan (Max) Gu292fc3d2021-07-19 19:03:0331
32 while (offset < length) {
Peter Kasting8bb45c22022-06-16 19:39:2733 const size_t prior_offset = offset;
Liquan (Max) Gu292fc3d2021-07-19 19:03:0334 // Input strings must be valid UTF-8.
Peter Kasting5565d8672022-05-31 18:19:1035 base_icu::UChar32 codepoint;
Adem Derinel620520b42024-11-04 15:45:4436 CHECK(base::ReadUnicodeCharacter(in_bytes.data(), length, &offset,
37 &codepoint));
Liquan (Max) Gu292fc3d2021-07-19 19:03:0338 // offset is updated by |ReadUnicodeCharacter| to index the last byte of the
39 // codepoint. Increment it to index the first byte of the next codepoint for
40 // the subsequent iteration.
41 offset++;
42
43 if (codepoint == 0x20 || codepoint == 0x21 ||
44 (codepoint >= 0x23 && codepoint <= 0x5b) || codepoint >= 0x5d) {
Adem Derinel620520b42024-11-04 15:45:4445 ret.append(&in_bytes[prior_offset], offset - prior_offset);
Liquan (Max) Gu292fc3d2021-07-19 19:03:0346 } else if (codepoint == 0x22) {
47 ret.append("\\\"");
48 } else if (codepoint == 0x5c) {
49 ret.append("\\\\");
50 } else {
Liquan (Max) Gu292fc3d2021-07-19 19:03:0351 ret.append("\\u00");
Peter Kastingabc2bc32023-10-27 22:30:0952 base::AppendHexEncodedByte(static_cast<uint8_t>(codepoint), ret, false);
Liquan (Max) Gu292fc3d2021-07-19 19:03:0353 }
54 }
55
56 ret.push_back('"');
57 return ret;
58}
59
60} // namespace
61
Ken Buchananbe8629f2025-01-11 03:37:1662ClientDataJsonParams::ClientDataJsonParams(
63 ClientDataRequestType type,
64 url::Origin origin,
65 url::Origin top_origin,
66 std::optional<std::vector<uint8_t>> challenge,
67 bool is_cross_origin_iframe)
Martin Kreichgauere255af062022-04-18 19:40:5668 : type(type),
69 origin(std::move(origin)),
Ken Buchanan4fb3ef12024-08-19 20:19:3270 top_origin(std::move(top_origin)),
Martin Kreichgauere255af062022-04-18 19:40:5671 challenge(std::move(challenge)),
72 is_cross_origin_iframe(is_cross_origin_iframe) {}
73ClientDataJsonParams::ClientDataJsonParams(ClientDataJsonParams&&) = default;
74ClientDataJsonParams& ClientDataJsonParams::operator=(ClientDataJsonParams&&) =
75 default;
76ClientDataJsonParams::~ClientDataJsonParams() = default;
77
78std::string BuildClientDataJson(ClientDataJsonParams params) {
Ken Buchananbe8629f2025-01-11 03:37:1679 CHECK(params.challenge.has_value());
80
Liquan (Max) Gu292fc3d2021-07-19 19:03:0381 std::string ret;
82 ret.reserve(128);
83
Martin Kreichgauere255af062022-04-18 19:40:5684 switch (params.type) {
Liquan (Max) Gu292fc3d2021-07-19 19:03:0385 case ClientDataRequestType::kWebAuthnCreate:
86 ret.append(R"({"type":"webauthn.create")");
87 break;
88 case ClientDataRequestType::kWebAuthnGet:
89 ret.append(R"({"type":"webauthn.get")");
90 break;
Liquan (Max) Gu292fc3d2021-07-19 19:03:0391 case ClientDataRequestType::kPaymentGet:
92 ret.append(R"({"type":"payment.get")");
93 break;
94 }
95
96 ret.append(R"(,"challenge":)");
Ken Buchananbe8629f2025-01-11 03:37:1697 ret.append(ToJSONString(Base64UrlEncodeOmitPadding(*params.challenge)));
Liquan (Max) Gu292fc3d2021-07-19 19:03:0398
99 ret.append(R"(,"origin":)");
Martin Kreichgauere255af062022-04-18 19:40:56100 ret.append(ToJSONString(params.origin.Serialize()));
Liquan (Max) Gu292fc3d2021-07-19 19:03:03101
Ken Buchanan4fb3ef12024-08-19 20:19:32102 std::string serialized_top_origin =
103 ToJSONString(params.top_origin.Serialize());
Martin Kreichgauere255af062022-04-18 19:40:56104 if (params.is_cross_origin_iframe) {
Liquan (Max) Gu292fc3d2021-07-19 19:03:03105 ret.append(R"(,"crossOrigin":true)");
Ken Buchanan4fb3ef12024-08-19 20:19:32106 ret.append(R"(,"topOrigin":)");
107 ret.append(serialized_top_origin);
Liquan (Max) Gu292fc3d2021-07-19 19:03:03108 } else {
109 ret.append(R"(,"crossOrigin":false)");
110 }
111
Slobodan Pejic7996c952025-04-29 16:30:49112 if (params.payment_options &&
113 params.type == ClientDataRequestType::kPaymentGet) {
Liquan (Max) Gu292fc3d2021-07-19 19:03:03114 ret.append(R"(,"payment":{)");
115
Stephen McGruer3472dda2022-08-25 17:48:47116 ret.append(R"("rpId":)");
117 ret.append(ToJSONString(params.payment_rp));
118
Liquan (Max) Gu292fc3d2021-07-19 19:03:03119 ret.append(R"(,"topOrigin":)");
Ken Buchanan4fb3ef12024-08-19 20:19:32120 ret.append(serialized_top_origin);
Liquan (Max) Gu292fc3d2021-07-19 19:03:03121
Martin Kreichgauere255af062022-04-18 19:40:56122 if (params.payment_options->payee_name.has_value()) {
Nick Burrisf0b1a99a2022-03-21 20:10:57123 ret.append(R"(,"payeeName":)");
Martin Kreichgauere255af062022-04-18 19:40:56124 ret.append(ToJSONString(params.payment_options->payee_name.value()));
Nick Burrisf0b1a99a2022-03-21 20:10:57125 }
Martin Kreichgauere255af062022-04-18 19:40:56126 if (params.payment_options->payee_origin.has_value()) {
Nick Burrisf0b1a99a2022-03-21 20:10:57127 ret.append(R"(,"payeeOrigin":)");
Martin Kreichgauere255af062022-04-18 19:40:56128 ret.append(
129 ToJSONString(params.payment_options->payee_origin->Serialize()));
Nick Burrisf0b1a99a2022-03-21 20:10:57130 }
Rouslan Solomakhin586792562021-08-31 15:50:16131
Slobodan Pejic44df0c12025-06-04 16:20:11132 if (params.payment_options->payment_entities_logos.has_value()) {
133 const std::vector<blink::mojom::ShownPaymentEntityLogoPtr>& logos =
134 *params.payment_options->payment_entities_logos;
135 ret.append(R"(,"paymentEntitiesLogos":[)");
136 for (auto logo_iterator = logos.begin(); logo_iterator != logos.end();
137 ++logo_iterator) {
138 ret.append(R"({"url":)");
Slobodan Pejicadc9ad22025-07-22 15:30:50139 if ((*logo_iterator)->url.is_empty()) {
140 ret.append(R"("")");
141 } else {
142 ret.append(ToJSONString((*logo_iterator)->url.spec()));
143 }
Slobodan Pejic44df0c12025-06-04 16:20:11144 ret.append(R"(,"label":)");
145 ret.append(ToJSONString((*logo_iterator)->label));
146 ret.append("}");
147 if ((logo_iterator + 1) != logos.end()) {
148 ret.append(",");
149 }
150 }
151 ret.append("]");
152 }
153
Liquan (Max) Gu292fc3d2021-07-19 19:03:03154 ret.append(R"(,"total":{)");
155
156 ret.append(R"("value":)");
Martin Kreichgauere255af062022-04-18 19:40:56157 ret.append(ToJSONString(params.payment_options->total->value));
Liquan (Max) Gu292fc3d2021-07-19 19:03:03158
159 ret.append(R"(,"currency":)");
Martin Kreichgauere255af062022-04-18 19:40:56160 ret.append(ToJSONString(params.payment_options->total->currency));
Liquan (Max) Gu292fc3d2021-07-19 19:03:03161
162 ret.append(R"(},"instrument":{)");
163
164 ret.append(R"("icon":)");
Martin Kreichgauere255af062022-04-18 19:40:56165 ret.append(ToJSONString(params.payment_options->instrument->icon.spec()));
Liquan (Max) Gu292fc3d2021-07-19 19:03:03166
Rouslan Solomakhinda5027242021-07-23 20:40:21167 ret.append(R"(,"displayName":)");
Martin Kreichgauere255af062022-04-18 19:40:56168 ret.append(ToJSONString(params.payment_options->instrument->display_name));
Liquan (Max) Gu292fc3d2021-07-19 19:03:03169
Stephen McGruer58532312025-06-27 14:13:38170 if (params.payment_options->instrument->details.has_value()) {
171 // SPC calls should have been rejected if the details field was present
172 // but empty.
173 CHECK(!params.payment_options->instrument->details->empty());
174
Slobodan Pejic78ffb052025-06-16 23:16:41175 ret.append(R"(,"details":)");
Stephen McGruer58532312025-06-27 14:13:38176 ret.append(ToJSONString(*params.payment_options->instrument->details));
Slobodan Pejic78ffb052025-06-16 23:16:41177 }
178
Slobodan Pejic435c11ef2024-12-09 18:28:24179 ret.append("}");
180 if (params.payment_options->browser_bound_public_key.has_value()) {
181 ret.append(R"(,"browserBoundPublicKey":)");
182 ret.append(ToJSONString(Base64UrlEncodeOmitPadding(
183 *params.payment_options->browser_bound_public_key)));
184 }
185 ret.append("}");
Slobodan Pejic7996c952025-04-29 16:30:49186 } else if (params.payment_options &&
187 params.payment_options->browser_bound_public_key.has_value() &&
188 params.type == ClientDataRequestType::kWebAuthnCreate) {
Slobodan Pejic2b3eb132025-06-09 19:54:56189 ret.append(R"(,"payment":{"browserBoundPublicKey":)");
Slobodan Pejic7996c952025-04-29 16:30:49190 ret.append(ToJSONString(Base64UrlEncodeOmitPadding(
191 *params.payment_options->browser_bound_public_key)));
192 ret.append("}");
Liquan (Max) Gu292fc3d2021-07-19 19:03:03193 }
194
195 if (base::RandDouble() < 0.2) {
196 // An extra key is sometimes added to ensure that RPs do not make
197 // unreasonably specific assumptions about the clientData JSON. This is
198 // done in the fashion of
199 // https://tools.ietf.org/html/draft-ietf-tls-grease
200 ret.append(R"(,"other_keys_can_be_added_here":")");
201 ret.append(
202 "do not compare clientDataJSON against a template. See "
203 "https://goo.gl/yabPex\"");
204 }
205
206 ret.append("}");
207 return ret;
208}
209
210} // namespace content