• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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