blob: 8bc264bcdb9079b86f66f9707b5bb24121e6c18c [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/device_bound_sessions/session_binding_utils.h"
#include <optional>
#include <string_view>
#include "base/base64url.h"
#include "base/containers/span.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_view_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "crypto/ecdsa_utils.h"
#include "crypto/keypair.h"
#include "crypto/sha2.h"
#include "crypto/signature_verifier.h"
#include "net/base/url_util.h"
#include "net/device_bound_sessions/jwk_utils.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/ecdsa.h"
#include "url/gurl.h"
namespace net::device_bound_sessions {
namespace {
// Source: JSON Web Signature and Encryption Algorithms
// https://www.iana.org/assignments/jose/jose.xhtml
std::string SignatureAlgorithmToString(
crypto::SignatureVerifier::SignatureAlgorithm algorithm) {
switch (algorithm) {
case crypto::SignatureVerifier::ECDSA_SHA256:
return "ES256";
case crypto::SignatureVerifier::RSA_PKCS1_SHA256:
return "RS256";
case crypto::SignatureVerifier::RSA_PSS_SHA256:
return "PS256";
case crypto::SignatureVerifier::RSA_PKCS1_SHA1:
return "RS1";
}
}
std::string Base64UrlEncode(std::string_view data) {
std::string output;
base::Base64UrlEncode(data, base::Base64UrlEncodePolicy::OMIT_PADDING,
&output);
return output;
}
std::optional<std::string> CombineHeaderAndPayload(
const base::Value::Dict& header,
const base::Value::Dict& payload) {
std::optional<std::string> header_serialized = base::WriteJson(header);
if (!header_serialized) {
DVLOG(1) << "Unexpected JSONWriter error while serializing a registration "
"token header";
return std::nullopt;
}
std::optional<std::string> payload_serialized = base::WriteJsonWithOptions(
payload, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION);
if (!payload_serialized) {
DVLOG(1) << "Unexpected JSONWriter error while serializing a registration "
"token payload";
return std::nullopt;
}
return base::StrCat({Base64UrlEncode(*header_serialized), ".",
Base64UrlEncode(*payload_serialized)});
}
// Helper function for the shared functionality of refresh and
// registration JWTs.
std::optional<std::string> CreateHeaderAndPayload(
std::string_view challenge,
crypto::SignatureVerifier::SignatureAlgorithm algorithm,
std::optional<base::Value::Dict> jwk,
const std::optional<std::string>& authorization) {
auto header = base::Value::Dict()
.Set("alg", SignatureAlgorithmToString(algorithm))
.Set("typ", "dbsc+jwt");
if (jwk.has_value()) {
header.Set("jwk", std::move(*jwk));
}
auto payload = base::Value::Dict().Set("jti", challenge);
if (authorization.has_value()) {
payload.Set("authorization", authorization.value());
}
return CombineHeaderAndPayload(header, payload);
}
} // namespace
std::optional<std::string> CreateLegacyKeyRegistrationHeaderAndPayload(
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,
std::optional<std::string> session_id) {
base::Value::Dict jwk = ConvertPkeySpkiToJwk(algorithm, pubkey_spki);
if (jwk.empty()) {
DVLOG(1) << "Unexpected error when converting the SPKI to a JWK";
return std::nullopt;
}
auto header = base::Value::Dict()
.Set("alg", SignatureAlgorithmToString(algorithm))
.Set("typ", "dbsc+jwt");
auto payload =
base::Value::Dict()
.Set("aud", registration_url.spec())
.Set("jti", challenge)
// Write out int64_t variable as a double.
// Note: this may discard some precision, but for `base::Value`
// there's no other option.
.Set("iat", static_cast<double>(
(timestamp - base::Time::UnixEpoch()).InSeconds()))
.Set("key", std::move(jwk));
if (authorization.has_value()) {
payload.Set("authorization", authorization.value());
}
if (session_id.has_value()) {
payload.Set("sub", session_id.value());
}
return CombineHeaderAndPayload(header, payload);
}
std::optional<std::string> CreateKeyRegistrationHeaderAndPayload(
std::string_view challenge,
crypto::SignatureVerifier::SignatureAlgorithm algorithm,
base::span<const uint8_t> pubkey_spki,
std::optional<std::string> authorization) {
base::Value::Dict jwk = ConvertPkeySpkiToJwk(algorithm, pubkey_spki);
if (jwk.empty()) {
DVLOG(1) << "Unexpected error when converting the SPKI to a JWK";
return std::nullopt;
}
return CreateHeaderAndPayload(challenge, algorithm, std::move(jwk),
std::move(authorization));
}
std::optional<std::string> CreateKeyRefreshHeaderAndPayload(
std::string_view challenge,
crypto::SignatureVerifier::SignatureAlgorithm algorithm) {
return CreateHeaderAndPayload(challenge, algorithm, /*jwk=*/std::nullopt,
/*authorization=*/std::nullopt);
}
std::optional<std::string> AppendSignatureToHeaderAndPayload(
std::string_view header_and_payload,
crypto::SignatureVerifier::SignatureAlgorithm algorithm,
base::span<const uint8_t> pubkey_spki,
base::span<const uint8_t> signature) {
std::optional<std::vector<uint8_t>> signature_holder;
if (algorithm == crypto::SignatureVerifier::ECDSA_SHA256) {
std::optional<crypto::keypair::PublicKey> public_key =
crypto::keypair::PublicKey::FromSubjectPublicKeyInfo(pubkey_spki);
if (!public_key.has_value()) {
return std::nullopt;
}
signature_holder =
crypto::ConvertEcdsaDerSignatureToRaw(*public_key, signature);
if (!signature_holder.has_value()) {
return std::nullopt;
}
signature = base::span(*signature_holder);
}
return base::StrCat(
{header_and_payload, ".", Base64UrlEncode(as_string_view(signature))});
}
bool IsSecure(const GURL& url) {
return url.SchemeIsCryptographic() || IsLocalhost(url);
}
} // namespace net::device_bound_sessions