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/session_binding_utils.h"
6
7 #include <optional>
8 #include <string_view>
9
10 #include "base/base64url.h"
11 #include "base/containers/span.h"
12 #include "base/json/json_writer.h"
13 #include "base/logging.h"
14 #include "base/strings/strcat.h"
15 #include "base/time/time.h"
16 #include "base/values.h"
17 #include "crypto/sha2.h"
18 #include "crypto/signature_verifier.h"
19 #include "net/device_bound_sessions/jwk_utils.h"
20 #include "third_party/boringssl/src/include/openssl/bn.h"
21 #include "third_party/boringssl/src/include/openssl/ecdsa.h"
22 #include "url/gurl.h"
23
24 namespace net::device_bound_sessions {
25
26 namespace {
27
28 // Source: JSON Web Signature and Encryption Algorithms
29 // https://www.iana.org/assignments/jose/jose.xhtml
SignatureAlgorithmToString(crypto::SignatureVerifier::SignatureAlgorithm algorithm)30 std::string SignatureAlgorithmToString(
31 crypto::SignatureVerifier::SignatureAlgorithm algorithm) {
32 switch (algorithm) {
33 case crypto::SignatureVerifier::ECDSA_SHA256:
34 return "ES256";
35 case crypto::SignatureVerifier::RSA_PKCS1_SHA256:
36 return "RS256";
37 case crypto::SignatureVerifier::RSA_PSS_SHA256:
38 return "PS256";
39 case crypto::SignatureVerifier::RSA_PKCS1_SHA1:
40 return "RS1";
41 }
42 }
43
Base64UrlEncode(std::string_view data)44 std::string Base64UrlEncode(std::string_view data) {
45 std::string output;
46 base::Base64UrlEncode(data, base::Base64UrlEncodePolicy::OMIT_PADDING,
47 &output);
48 return output;
49 }
50
CreateHeaderAndPayloadWithCustomPayload(crypto::SignatureVerifier::SignatureAlgorithm algorithm,std::string_view schema,const base::Value::Dict & payload)51 std::optional<std::string> CreateHeaderAndPayloadWithCustomPayload(
52 crypto::SignatureVerifier::SignatureAlgorithm algorithm,
53 std::string_view schema,
54 const base::Value::Dict& payload) {
55 auto header = base::Value::Dict()
56 .Set("alg", SignatureAlgorithmToString(algorithm))
57 .Set("typ", "jwt");
58 if (!schema.empty()) {
59 header.Set("schema", schema);
60 }
61 std::optional<std::string> header_serialized = base::WriteJson(header);
62 if (!header_serialized) {
63 DVLOG(1) << "Unexpected JSONWriter error while serializing a registration "
64 "token header";
65 return std::nullopt;
66 }
67
68 std::optional<std::string> payload_serialized = base::WriteJsonWithOptions(
69 payload, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION);
70 if (!payload_serialized) {
71 DVLOG(1) << "Unexpected JSONWriter error while serializing a registration "
72 "token payload";
73 return std::nullopt;
74 }
75
76 return base::StrCat({Base64UrlEncode(*header_serialized), ".",
77 Base64UrlEncode(*payload_serialized)});
78 }
79
ConvertDERSignatureToRaw(base::span<const uint8_t> der_signature)80 std::optional<std::vector<uint8_t>> ConvertDERSignatureToRaw(
81 base::span<const uint8_t> der_signature) {
82 bssl::UniquePtr<ECDSA_SIG> ecdsa_sig(
83 ECDSA_SIG_from_bytes(der_signature.data(), der_signature.size()));
84 if (!ecdsa_sig) {
85 DVLOG(1) << "Failed to create ECDSA_SIG";
86 return std::nullopt;
87 }
88
89 // TODO(b/301888680): this implicitly depends on a curve used by
90 // `crypto::UnexportableKey`. Make this dependency more explicit.
91 const size_t kMaxBytesPerBN = 32;
92 std::vector<uint8_t> jwt_signature(2 * kMaxBytesPerBN);
93
94 if (!BN_bn2bin_padded(&jwt_signature[0], kMaxBytesPerBN, ecdsa_sig->r) ||
95 !BN_bn2bin_padded(&jwt_signature[kMaxBytesPerBN], kMaxBytesPerBN,
96 ecdsa_sig->s)) {
97 DVLOG(1) << "Failed to serialize R and S to " << kMaxBytesPerBN << " bytes";
98 return std::nullopt;
99 }
100
101 return jwt_signature;
102 }
103
104 } // namespace
105
CreateKeyRegistrationHeaderAndPayload(std::string_view challenge,const GURL & registration_url,crypto::SignatureVerifier::SignatureAlgorithm algorithm,base::span<const uint8_t> pubkey_spki,base::Time timestamp,std::optional<std::string> authorization)106 std::optional<std::string> CreateKeyRegistrationHeaderAndPayload(
107 std::string_view challenge,
108 const GURL& registration_url,
109 crypto::SignatureVerifier::SignatureAlgorithm algorithm,
110 base::span<const uint8_t> pubkey_spki,
111 base::Time timestamp,
112 std::optional<std::string> authorization) {
113 base::Value::Dict jwk = ConvertPkeySpkiToJwk(algorithm, pubkey_spki);
114 if (jwk.empty()) {
115 DVLOG(1) << "Unexpected error when converting the SPKI to a JWK";
116 return std::nullopt;
117 }
118
119 auto payload =
120 base::Value::Dict()
121 .Set("aud", registration_url.spec())
122 .Set("jti", challenge)
123 // Write out int64_t variable as a double.
124 // Note: this may discard some precision, but for `base::Value`
125 // there's no other option.
126 .Set("iat", static_cast<double>(
127 (timestamp - base::Time::UnixEpoch()).InSeconds()))
128 .Set("key", std::move(jwk));
129
130 if (authorization.has_value()) {
131 payload.Set("authorization", authorization.value());
132 }
133 return CreateHeaderAndPayloadWithCustomPayload(algorithm, /*schema=*/"",
134 payload);
135 }
136
CreateKeyAssertionHeaderAndPayload(crypto::SignatureVerifier::SignatureAlgorithm algorithm,base::span<const uint8_t> pubkey,std::string_view client_id,std::string_view challenge,const GURL & destination_url,std::string_view name_space)137 std::optional<std::string> CreateKeyAssertionHeaderAndPayload(
138 crypto::SignatureVerifier::SignatureAlgorithm algorithm,
139 base::span<const uint8_t> pubkey,
140 std::string_view client_id,
141 std::string_view challenge,
142 const GURL& destination_url,
143 std::string_view name_space) {
144 auto payload = base::Value::Dict()
145 .Set("sub", client_id)
146 .Set("aud", destination_url.spec())
147 .Set("jti", challenge)
148 .Set("iss", Base64UrlEncode(base::as_string_view(
149 crypto::SHA256Hash(pubkey))))
150 .Set("namespace", name_space);
151 return CreateHeaderAndPayloadWithCustomPayload(
152 algorithm, "DEVICE_BOUND_SESSION_CREDENTIALS_ASSERTION", payload);
153 }
154
AppendSignatureToHeaderAndPayload(std::string_view header_and_payload,crypto::SignatureVerifier::SignatureAlgorithm algorithm,base::span<const uint8_t> signature)155 std::optional<std::string> AppendSignatureToHeaderAndPayload(
156 std::string_view header_and_payload,
157 crypto::SignatureVerifier::SignatureAlgorithm algorithm,
158 base::span<const uint8_t> signature) {
159 std::optional<std::vector<uint8_t>> signature_holder;
160 if (algorithm == crypto::SignatureVerifier::ECDSA_SHA256) {
161 signature_holder = ConvertDERSignatureToRaw(signature);
162 if (!signature_holder.has_value()) {
163 return std::nullopt;
164 }
165 signature = base::span(*signature_holder);
166 }
167
168 return base::StrCat(
169 {header_and_payload, ".", Base64UrlEncode(as_string_view(signature))});
170 }
171
172 } // namespace net::device_bound_sessions
173