1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
// Copyright 2025 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/test/two_qwac_cert_binding_builder.h"
#include "base/base64.h"
#include "base/base64url.h"
#include "base/json/json_string_value_serializer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
namespace net {
TwoQwacCertBindingBuilder::TwoQwacCertBindingBuilder()
: cert_chain_(CertBuilder::CreateSimpleChain(2)) {
GetLeafBuilder()->SetCertificatePolicies({"0.4.0.194112.1.6"}); // QNCP-w-gen
GetLeafBuilder()->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});
GetLeafBuilder()->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});
GenerateKeyForSigAlg();
// set bound_certs_ to two bogus values
bound_certs_ = {"one", "two"};
}
TwoQwacCertBindingBuilder::~TwoQwacCertBindingBuilder() = default;
void TwoQwacCertBindingBuilder::GenerateKeyForSigAlg() {
switch (sig_alg_) {
case JwsSigAlg::kRsaPkcs1Sha256:
case JwsSigAlg::kRsaPssSha256:
cert_chain_[0]->GenerateRSAKey();
break;
case JwsSigAlg::kEcdsaP256Sha256:
cert_chain_[0]->GenerateECKey();
}
Invalidate();
}
std::string TwoQwacCertBindingBuilder::SigAlg() const {
switch (sig_alg_) {
case JwsSigAlg::kRsaPkcs1Sha256:
return "RS256";
case JwsSigAlg::kRsaPssSha256:
return "PS256";
case JwsSigAlg::kEcdsaP256Sha256:
return "ES256";
}
return "";
}
std::string TwoQwacCertBindingBuilder::HashAlg() const {
switch (hash_alg_) {
case crypto::hash::kSha256:
return "S256";
case crypto::hash::kSha384:
return "S384";
case crypto::hash::kSha512:
return "S512";
default:
return "";
}
}
base::ListValue TwoQwacCertBindingBuilder::GenerateX5cHeaderValue() {
base::ListValue x5c_list;
for (const auto& cert : cert_chain_) {
x5c_list.Append(base::Base64Encode(cert->GetDER()));
}
return x5c_list;
}
base::DictValue TwoQwacCertBindingBuilder::GenerateSigDHeaderValue() {
base::DictValue sig_d =
base::Value::Dict()
.Set("mId", "http://uri.etsi.org/19182/ObjectIdByURIHash")
.Set("hashM", HashAlg());
base::ListValue pars;
base::ListValue hash_v;
for (const auto& bound_cert : bound_certs_) {
// ETSI TS 119 182-1 clause 5.2.8.1: Each element of the "hashV" array
// shall contain the base64url-encoded digest value of the
// base64url-encoded data object.
std::string cert_b64;
base::Base64UrlEncode(bound_cert, base::Base64UrlEncodePolicy::OMIT_PADDING,
&cert_b64);
std::vector<uint8_t> cert_hash(
crypto::hash::DigestSizeForHashKind(hash_alg_));
crypto::hash::Hash(hash_alg_, cert_b64, cert_hash);
std::string hash_b64;
base::Base64UrlEncode(cert_hash, base::Base64UrlEncodePolicy::OMIT_PADDING,
&hash_b64);
hash_v.Append(hash_b64);
pars.Append("");
}
sig_d.Set("pars", std::move(pars));
sig_d.Set("hashV", std::move(hash_v));
return sig_d;
}
void TwoQwacCertBindingBuilder::GenerateHeader() {
// Build the minimal JWS header needed for a 2-QWAC TLS certificate binding.
base::DictValue header = base::DictValue()
.Set("alg", SigAlg())
.Set("cty", "TLS-Certificate-Binding-v1")
.Set("x5c", GenerateX5cHeaderValue())
.Set("sigD", GenerateSigDHeaderValue());
// Add/override values in the header
header.Merge(header_overrides_.Clone());
std::string header_string;
ASSERT_TRUE(JSONStringValueSerializer(&header_string).Serialize(header));
header_b64_ = std::string();
base::Base64UrlEncode(
header_string, base::Base64UrlEncodePolicy::OMIT_PADDING, &*header_b64_);
}
void TwoQwacCertBindingBuilder::GenerateSignature() {
// All JWS signature algorithms that we support use SHA-256 as their digest.
const EVP_MD* digest = EVP_sha256();
bssl::ScopedEVP_MD_CTX ctx;
EVP_PKEY_CTX* pkey_ctx;
EVP_PKEY* key = cert_chain_[0]->GetKey();
ASSERT_TRUE(
EVP_DigestSignInit(ctx.get(), &pkey_ctx, EVP_sha256(), nullptr, key));
if (sig_alg_ == JwsSigAlg::kRsaPssSha256) {
ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING));
ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, digest));
ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(
pkey_ctx, -1 /* match digest and salt length */));
}
// The JWS signing input is the the (base64url-encoded) header and payload
// concatenated and separated by a '.'. For a 2-QWAC cert binding, the
// payload is always empty.
const std::string& header = GetHeader();
const std::array<uint8_t, 1> separator = {'.'};
ASSERT_TRUE(EVP_DigestSignUpdate(ctx.get(), header.data(), header.size()));
ASSERT_TRUE(
EVP_DigestSignUpdate(ctx.get(), separator.data(), separator.size()));
size_t len = 0;
std::vector<uint8_t> sig;
ASSERT_TRUE(EVP_DigestSignFinal(ctx.get(), nullptr, &len));
sig.resize(len);
ASSERT_TRUE(EVP_DigestSignFinal(ctx.get(), sig.data(), &len));
sig.resize(len);
signature_b64_ = std::string();
base::Base64UrlEncode(sig, base::Base64UrlEncodePolicy::OMIT_PADDING,
&*signature_b64_);
}
} // namespace net
|