commit add6f3d272feffa145cb0d274825374009e195b7
parent 02a679cd24517d2935791df41f1b176c2693e5f6
Author: Cosmin Sabou <csabou@mozilla.com>
Date: Thu, 6 Nov 2025 21:51:14 +0200
Revert "Bug 1996724 - implement 2-QWACs r=jschanck" for having modules/psm/QWACs.sys.mjs as unreferenced file.
This reverts commit 46cfa20e0b412b5a9fbee20b725d0f6a6ce061aa.
Diffstat:
10 files changed, 2 insertions(+), 865 deletions(-)
diff --git a/security/manager/ssl/QWACs.sys.mjs b/security/manager/ssl/QWACs.sys.mjs
@@ -1,403 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-
-const lazy = {};
-
-XPCOMUtils.defineLazyServiceGetters(lazy, {
- CertDB: ["@mozilla.org/security/x509certdb;1", Ci.nsIX509CertDB],
-});
-
-function arrayToString(a) {
- let s = "";
- for (let b of a) {
- s += String.fromCharCode(b);
- }
- return s;
-}
-
-function stringToArrayBuffer(str) {
- let bytes = new Uint8Array(str.length);
- for (let i = 0; i < str.length; i++) {
- bytes[i] = str.charCodeAt(i);
- }
- return bytes;
-}
-
-export var QWACs = {
- fromBase64URLEncoding(base64URLEncoded) {
- return atob(base64URLEncoded.replaceAll("-", "+").replaceAll("_", "/"));
- },
-
- toBase64URLEncoding(str) {
- return btoa(str)
- .replaceAll("+", "-")
- .replaceAll("/", "_")
- .replaceAll("=", "");
- },
-
- // Validates and returns the decoded parameters of a TLS certificate binding
- // header as specified by ETSI TS 119 411-5 V2.1.1 Annex B, ETSI TS 119 182-1
- // V1.2.1, and RFC 7515.
- // If the header contains invalid values or otherwise fails to validate,
- // returns false.
- // This should probably not be called directly outside of tests -
- // verifyTLSCertificateBinding is the main entrypoint of this implementation.
- validateTLSCertificateBindingHeader(header) {
- // ETSI TS 119 411-5 V2.1.1 Annex B specifies the TLS Certificate Binding
- // Profile and states that "Only header parameters specified in this
- // profile may be present in the header of the generated JAdES signature."
- const allowedHeaderKeys = new Set([
- "alg",
- "kid",
- "cty",
- "x5t#S256",
- "x5c",
- "iat",
- "exp",
- "sigD",
- ]);
- let headerKeys = new Set(Object.keys(header));
- if (!headerKeys.isSubsetOf(allowedHeaderKeys)) {
- console.error("header contains invalid parameter");
- return false;
- }
-
- // ETSI TS 119 182-1 V1.2.1 Section 5.1.2 specifies that "alg" shall be as
- // described in RFC 7515 Section 4.1.1, which references RFC 7518. None of
- // these specifications require support for a particular signature
- // algorithm, but RFC 7518 recommends supporting "RS256" (RSASSA-PKCS1-v1_5
- // with SHA-256) and "ES256" (ECDSA with P-256 and SHA-256), so those are
- // supported. Additionally, "PS256" (RSASSA-PSS with SHA-256) is supported
- // for compatibility and as better alternative to RSASSA-PKCS1-v1_5.
- // The specification says "alg" can't conflict with signing certificate
- // key. This is enforced when the signature is verified.
- if (!("alg" in header)) {
- console.error("header missing 'alg' field");
- return false;
- }
- let algorithm;
- switch (header.alg) {
- case "RS256":
- algorithm = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
- break;
- case "PS256":
- algorithm = { name: "RSA-PSS", saltLength: 32, hash: "SHA-256" };
- break;
- case "ES256":
- algorithm = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
- break;
- default:
- console.error("unsupported alg:", header.alg);
- return false;
- }
-
- // RFC 7515 defines "kid" as an optional hint. It is unnecessary.
-
- // ETSI TS 119 411-5 V2.1.1 Annex B says that "cty" will have the value
- // "TLS-Certificate-Binding-v1". However, ETSI TS 119 182-1 V1.2.1 Section
- // 5.1.3 states that "The cty header parameter should not be present if the
- // sigD header parameter, specified in clause 5.2.8 of the present
- // document, is present within the JAdES signature."
- // ETSI TS 119 411-5 V2.1.1 Annex B also requires sigD to be present, so
- // either this is a mistake, or ETSI TS 119 411-5 Annex B deliberately
- // disregards ETSI TS 119 182-1.
- // Chrome's implementation requires "cty", so for compatibility, this
- // matches that behavior.
- if (!("cty" in header)) {
- console.error("header missing field 'cty'");
- return false;
- }
- if (header.cty != "TLS-Certificate-Binding-v1") {
- console.error("invalid value for cty:", header.cty);
- return false;
- }
-
- // RFC 7515 defines "x5t#S256" as "base64url-encoded SHA-256 thumbprint
- // (a.k.a. digest) of the DER encoding of the X.509 certificate [RFC5280]
- // corresponding to the key used to digitally sign the JWS".
- // It is optional. If present, it must match the digest of the 0th element
- // of "x5c" (this is checked after processing x5c, below).
- let x5tS256;
- if ("x5t#S256" in header) {
- x5tS256 = header["x5t#S256"];
- }
-
- // RFC 7515:
- // The "x5c" (X.509 certificate chain) Header Parameter contains the
- // X.509 public key certificate or certificate chain [RFC5280]
- // corresponding to the key used to digitally sign the JWS. The
- // certificate or certificate chain is represented as a JSON array of
- // certificate value strings. Each string in the array is a
- // base64-encoded (Section 4 of [RFC4648] -- not base64url-encoded) DER
- // [ITU.X690.2008] PKIX certificate value.
- if (!("x5c" in header)) {
- console.error("header missing field 'x5c'");
- return false;
- }
- let certificates = [];
- for (let base64 of header.x5c) {
- try {
- certificates.push(lazy.CertDB.constructX509FromBase64(base64));
- } catch (e) {
- console.error("couldn't decode certificate");
- return false;
- }
- }
- // ETSI TS 119 411-5 V2.1.1 Annex B states that "x5c" consists of the full
- // certificate chain, including the trust anchor. However, only the signing
- // certificate and any intermediates relevant to path-building are strictly
- // necessary.
- if (certificates.length < 1) {
- console.error("header must specify certificate chain");
- return false;
- }
- if (x5tS256) {
- // signingCertificateHashHex will be of the form "AA:BB:..."
- let signingCertificateHashHex = certificates[0].sha256Fingerprint;
- let signingCertificateHashBytes = signingCertificateHashHex
- .split(":")
- .map(hexStr => parseInt(hexStr, 16));
- if (
- x5tS256 !=
- QWACs.toBase64URLEncoding(arrayToString(signingCertificateHashBytes))
- ) {
- console.error("x5t#S256 does not match signing certificate");
- return false;
- }
- }
-
- // ETSI TS 119 411-5 V2.1.1 Annex B's definition of "iat" reads "This field
- // contains the claimed signing time. The value shall be encoded as
- // specified in IETF RFC 7519 [9]." However, in RFC 7519, "iat" is a claim
- // that can be made by a JWT, not used as a header field in a JWS. In any
- // case, ETSI TS 119 411-5 offers no guidance on how this header affects
- // validation. Consequently, as it is optional, it is ignored.
-
- // Similarly, the definition of "exp" reads "This field contains the expiry
- // date of the binding. The maximum effective expiry time is whichever is
- // soonest of this field, the longest-lived TLS certificate identified in
- // the sigD member payload (below), or the notAfter time of the signing
- // certificate. The value shall be encoded as specified in IETF RFC 7519
- // [9]," again referencing a JWT claim and not a JWS header.
- // We interpret this to be an optional mechanism to expire bindings earlier
- // than the earliest "notAfter" value amongst the certificates specified in
- // "x5c".
- // RFC 7519 says this will be a NumericDate, which is a "JSON numeric value
- // representing the number of seconds from 1970-01-01T00:00:00Z UTC".
- if ("exp" in header) {
- let expirationSeconds = parseInt(header.exp);
- if (isNaN(expirationSeconds)) {
- console.error("invalid expiration time");
- return false;
- }
- let expiration = new Date(expirationSeconds * 1000);
- if (expiration < new Date()) {
- console.error("header has expired");
- return false;
- }
- }
-
- // "sigD" lists the TLS server certificates being bound, and must be
- // present.
- if (!("sigD" in header)) {
- console.error("header missing field 'sigD'");
- return false;
- }
- let sigD = header.sigD;
- const allowedSigDKeys = new Set(["mId", "pars", "hashM", "hashV"]);
- let sigDKeys = new Set(Object.keys(sigD));
- if (!sigDKeys.isSubsetOf(allowedSigDKeys)) {
- console.error("sigD contains invalid parameter");
- return false;
- }
- // ETSI TS 119 411-5 V2.1.1 Annex B requires that "sigD.mId" be
- // "http://uri.etsi.org/19182/ObjectIdByURIHash".
- if (!("mId" in sigD)) {
- console.error("header missing field 'sigD.mId'");
- return false;
- }
- if (sigD.mId != "http://uri.etsi.org/19182/ObjectIdByURIHash") {
- console.error("invalid value for sigD.mId:", sigD.mId);
- return false;
- }
-
- // ETSI TS 119 411-5 V2.1.1 Annex B defines "sigD.pars" as "A comma-separated
- // list of TLS certificate file names." The only thing to validate here is
- // that pars has as many elements as "hashV", later.
- if (!("pars" in sigD)) {
- console.error("header missing field 'sigD.pars'");
- return false;
- }
- let pars = sigD.pars;
-
- // ETSI TS 119 411-5 V2.1.1 Annex B defines "sigD.hashM" as 'The string
- // identifying one of the approved hashing algorithms identified by ETSI TS
- // 119 312 [8] for JAdES. This hashing algorithm is used to calculate the
- // hashes described in the "hashV" member below.' It further requires that
- // "SHA-256, SHA-384, and SHA-512 are supported, and it is assumed that
- // strings identifying them are S256, S384, and S512 respectively".
- if (!("hashM" in sigD)) {
- console.error("header missing field 'sigD.hashM'");
- return false;
- }
- let hashAlg;
- switch (sigD.hashM) {
- case "S256":
- hashAlg = "SHA-256";
- break;
- case "S384":
- hashAlg = "SHA-384";
- break;
- case "S512":
- hashAlg = "SHA-512";
- break;
- default:
- console.error("unsupported hashM:", sigD.hashM);
- return false;
- }
-
- // ETSI TS 119 411-5 V2.1.1 Annex B defines "sigD.hashV" as 'A
- // comma-separated list of TLS certificate file hashes. Each hash is
- // produced by taking the corresponding X.509 certificate, computing its
- // base64url encoding, and calculating its hash using the algorithm
- // identified in the "hashM" member above.'
- // This array must be the same length as the "sigD.pars" array.
- if (!("hashV" in sigD)) {
- console.error("header missing field 'sigD.hashV'");
- return false;
- }
- let hashes = sigD.hashV;
- if (hashes.length != pars.length) {
- console.error("header sigD.pars/hashV mismatch");
- return false;
- }
- for (let hash of hashes) {
- if (typeof hash != "string") {
- console.error("invalid hash:", hash);
- return false;
- }
- }
-
- return { algorithm, certificates, hashAlg, hashes };
- },
-
- // Given a TLS certificate binding, a TLS server certificate, and a hostname,
- // this function validates the binding, extracts its parameters, verifies
- // that the binding signing certificate is a 2-QWAC certificate valid for the
- // given hostname that chains to a QWAC trust anchor, verifies the signature
- // on the binding, and finally verifies that the binding covers the server
- // certificate.
- async verifyTLSCertificateBinding(
- tlsCertificateBinding,
- serverCertificate,
- hostname
- ) {
- // tlsCertificateBinding is a JAdES signature, which is a JWS. Because ETSI
- // TS 119 411-5 V2.1.1 Annex B requires sigD be present, and because ETSI
- // TS 119 182-1 V1.2.1 states "The sigD header parameter shall not appear
- // in JAdES signatures whose JWS Payload is attached",
- // tlsCertificateBinding must have a detached payload.
- // In other words, tlsCertificateBinding is a consists of:
- // "<base64url-encoded header>..<base64url-encoded signature>"
- let parts = tlsCertificateBinding.split(".");
- if (parts.length != 3) {
- console.error("invalid TLS certificate binding");
- return false;
- }
- if (parts[1] != "") {
- console.error("TLS certificate binding must have empty payload");
- return false;
- }
- let header;
- try {
- header = JSON.parse(QWACs.fromBase64URLEncoding(parts[0]));
- } catch (e) {
- console.error("header is not base64(JSON)");
- return false;
- }
- let params = QWACs.validateTLSCertificateBindingHeader(header);
- if (!params) {
- return false;
- }
-
- // The 0th certificate signed the binding. It must be a 2-QWAC that is
- // valid for the given hostname (ETSI TS 119 411-5 V2.1.1 Section 6.2.2
- // Step 4).
- let signingCertificate = params.certificates[0];
- let chain = params.certificates.slice(1);
- if (
- !(await lazy.CertDB.asyncVerifyQWAC(
- Ci.nsIX509CertDB.TwoQWAC,
- signingCertificate,
- hostname,
- chain
- ))
- ) {
- console.error("signing certificate not 2-QWAC");
- return false;
- }
-
- let spki = signingCertificate.subjectPublicKeyInfo;
- let signingKey;
- try {
- signingKey = await crypto.subtle.importKey(
- "spki",
- new Uint8Array(spki),
- params.algorithm,
- true,
- ["verify"]
- );
- } catch (e) {
- console.error("invalid signing key (algorithm mismatch?)");
- return false;
- }
-
- let signature;
- try {
- signature = QWACs.fromBase64URLEncoding(parts[2]);
- } catch (e) {
- console.error("signature is not base64");
- return false;
- }
-
- // Validate the signature (Step 5).
- let signatureValid;
- try {
- signatureValid = await crypto.subtle.verify(
- params.algorithm,
- signingKey,
- stringToArrayBuffer(signature),
- stringToArrayBuffer(parts[0] + ".")
- );
- } catch (e) {
- console.error("failed to verify signature");
- return false;
- }
- if (!signatureValid) {
- console.error("invalid signature");
- return false;
- }
-
- // The binding must list the server certificate's hash (Step 6).
- let serverCertificateHash = await crypto.subtle.digest(
- params.hashAlg,
- stringToArrayBuffer(
- QWACs.toBase64URLEncoding(arrayToString(serverCertificate.getRawDER()))
- )
- );
- if (
- !params.hashes.includes(
- QWACs.toBase64URLEncoding(
- arrayToString(new Uint8Array(serverCertificateHash))
- )
- )
- ) {
- console.error("TLS binding does not cover server certificate");
- return false;
- }
- return true;
- },
-};
diff --git a/security/manager/ssl/moz.build b/security/manager/ssl/moz.build
@@ -49,7 +49,6 @@ XPCOM_MANIFESTS += [
EXTRA_JS_MODULES.psm += [
"ClientAuthDialogService.sys.mjs",
"DER.sys.mjs",
- "QWACs.sys.mjs",
"RemoteSecuritySettings.sys.mjs",
"X509.sys.mjs",
]
diff --git a/security/manager/ssl/nsIX509Cert.idl b/security/manager/ssl/nsIX509Cert.idl
@@ -180,12 +180,6 @@ interface nsIX509Cert : nsISupports {
ACString getBase64DERString();
/**
- * The bytes of the certificate's DER encoded subject public key info.
- */
- [must_use]
- readonly attribute Array<octet> subjectPublicKeyInfo;
-
- /**
* The base64 encoding of the DER encoded public key info using the specified
* digest.
*/
diff --git a/security/manager/ssl/nsNSSCertificate.cpp b/security/manager/ssl/nsNSSCertificate.cpp
@@ -487,27 +487,6 @@ nsNSSCertificate::GetTokenName(nsAString& aTokenName) {
}
NS_IMETHODIMP
-nsNSSCertificate::GetSubjectPublicKeyInfo(nsTArray<uint8_t>& aSPKI) {
- aSPKI.Clear();
-
- pkix::Input certInput;
- pkix::Result result = certInput.Init(mDER.Elements(), mDER.Length());
- if (result != pkix::Result::Success) {
- return NS_ERROR_INVALID_ARG;
- }
- // NB: since we're not building a trust path, the endEntityOrCA parameter is
- // irrelevant.
- pkix::BackCert cert(certInput, pkix::EndEntityOrCA::MustBeEndEntity, nullptr);
- result = cert.Init();
- if (result != pkix::Result::Success) {
- return NS_ERROR_INVALID_ARG;
- }
- pkix::Input spki = cert.GetSubjectPublicKeyInfo();
- aSPKI.AppendElements(spki.UnsafeGetData(), spki.GetLength());
- return NS_OK;
-}
-
-NS_IMETHODIMP
nsNSSCertificate::GetSha256SubjectPublicKeyInfoDigest(
nsACString& aSha256SPKIDigest) {
aSha256SPKIDigest.Truncate();
diff --git a/security/manager/ssl/tests/unit/head_psm.js b/security/manager/ssl/tests/unit/head_psm.js
@@ -165,8 +165,8 @@ function arrayToString(a) {
// PEM to the format that nsIX509CertDB requires.
function pemToBase64(pem) {
return pem
- .replace(/-----BEGIN (CERTIFICATE|(EC )?PRIVATE KEY)-----/, "")
- .replace(/-----END (CERTIFICATE|(EC )?PRIVATE KEY)-----/, "")
+ .replace(/-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----/, "")
.replace(/[\r\n]/g, "");
}
diff --git a/security/manager/ssl/tests/unit/test_qwacs.js b/security/manager/ssl/tests/unit/test_qwacs.js
@@ -10,10 +10,6 @@ const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
-const { QWACs } = ChromeUtils.importESModule(
- "resource://gre/modules/psm/QWACs.sys.mjs"
-);
-
async function verify_1_qwacs(filename, expectSuccess, extraCertNames = []) {
let cert = constructCertFromFile(filename);
let result = await certdb.asyncVerifyQWAC(
@@ -91,403 +87,3 @@ add_task(async function test_verify_2_qwacs() {
await verify_2_qwacs("test_qwacs/2-qwac-multiple-key-purpose-eku.pem", false);
await verify_2_qwacs("test_qwacs/2-qwac.pem", false, "example.org");
});
-
-// Produces base64url(hash(base64url(certificate DER)))
-async function certificateHash(certificate, hashAlg) {
- let hash = await crypto.subtle.digest(
- hashAlg.replace("S", "SHA-"),
- new Uint8Array(
- stringToArray(
- QWACs.toBase64URLEncoding(arrayToString(certificate.getRawDER()))
- )
- )
- );
- return QWACs.toBase64URLEncoding(arrayToString(new Uint8Array(hash)));
-}
-
-const kTLSCertificateBindingEE = constructCertFromFile("test_qwacs/2-qwac.pem");
-
-const kTLSCertificateBindingHeader = {
- alg: "",
- cty: "TLS-Certificate-Binding-v1",
- // RFC 7515 Section 4.1.6: "Each string in the array is a base64-encoded
- // (Section 4 of [RFC4648] -- not base64url-encoded) DER [ITU.X690.2008] PKIX
- // certificate value."
- x5c: [],
- sigD: {
- mId: "http://uri.etsi.org/19182/ObjectIdByURIHash",
- pars: [],
- hashM: "",
- hashV: [],
- },
-};
-
-async function makeBindingHeader(
- certificateChain,
- certificatesToBind,
- signingAlg,
- hashAlg
-) {
- let header = structuredClone(kTLSCertificateBindingHeader);
- header.alg = signingAlg;
- header.x5c = certificateChain.map(c => btoa(arrayToString(c.getRawDER())));
- header.sigD.hashM = hashAlg;
- for (let toBind of certificatesToBind) {
- header.sigD.pars.push("");
- header.sigD.hashV.push(await certificateHash(toBind, hashAlg));
- }
- return header;
-}
-
-add_task(async function test_validate_tls_certificate_binding_header() {
- let serverCertificate = constructCertFromFile("bad_certs/default-ee.pem");
- let testHeader = await makeBindingHeader(
- [kTLSCertificateBindingEE],
- [serverCertificate],
- "RS256",
- "S256"
- );
- let validatedHeader = QWACs.validateTLSCertificateBindingHeader(testHeader);
- ok(validatedHeader, "header should validate successfully");
- deepEqual(validatedHeader.algorithm, {
- name: "RSASSA-PKCS1-v1_5",
- hash: "SHA-256",
- });
- equal(validatedHeader.certificates.length, 1);
- equal(validatedHeader.hashAlg, "SHA-256");
- equal(validatedHeader.hashes.length, 1);
-
- let headerWithExtraKey = structuredClone(testHeader);
- headerWithExtraKey.extra = "foo";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithExtraKey),
- "header with extra key should not validate"
- );
-
- let headerWithExtraSigDKey = structuredClone(testHeader);
- headerWithExtraSigDKey.sigD.additional = "bar";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithExtraSigDKey),
- "header with extra key in sigD should not parse"
- );
-
- let headerWithWrongCty = structuredClone(testHeader);
- headerWithWrongCty.cty = "TLS-Certificate-Binding-v2";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithWrongCty),
- "header with wrong cty should not parse"
- );
-
- let headerWithWrongMId = structuredClone(testHeader);
- headerWithWrongMId.sigD.mId = "http://example.org";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithWrongMId),
- "header with wrong sigD.mId should not parse"
- );
-
- let headerWithTooManyPars = structuredClone(testHeader);
- headerWithTooManyPars.sigD.pars.push("");
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithTooManyPars),
- "header with too many sigD.pars elements should not parse"
- );
-
- let headerWithInvalidHashV = structuredClone(testHeader);
- headerWithInvalidHashV.sigD.hashV[0] = 1234;
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithInvalidHashV),
- "header with invalid sigD.hashV should not parse"
- );
-
- let headerWithTooManyHashV = structuredClone(testHeader);
- headerWithTooManyHashV.sigD.hashV.push(headerWithTooManyHashV.sigD.hashV[0]);
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithTooManyHashV),
- "header with too many sigD.hashV elements should not parse"
- );
-
- let headerWithEmptyX5c = structuredClone(testHeader);
- headerWithEmptyX5c.x5c = [];
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithEmptyX5c),
- "header with empty x5c should not parse"
- );
-
- let headerWithUnsupportedAlg = structuredClone(testHeader);
- headerWithUnsupportedAlg.alg = "RS384";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithUnsupportedAlg),
- "header with unsupported alg should not parse"
- );
-
- let headerWithUnsupportedHashM = structuredClone(testHeader);
- headerWithUnsupportedHashM.sigD.hashM = "S224";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithUnsupportedHashM),
- "header with unsupported sigD.hashM should not parse"
- );
-
- let headerWithOptionalHeaders = structuredClone(testHeader);
- headerWithOptionalHeaders.kid = "optional kid";
- headerWithOptionalHeaders.iat = "optional iat";
- ok(
- QWACs.validateTLSCertificateBindingHeader(headerWithOptionalHeaders),
- "header with optional headers should parse"
- );
-
- let headerWithX5tS256Match = structuredClone(testHeader);
- let signingCertificateHash = await crypto.subtle.digest(
- "SHA-256",
- new Uint8Array(kTLSCertificateBindingEE.getRawDER())
- );
- headerWithX5tS256Match["x5t#S256"] = QWACs.toBase64URLEncoding(
- arrayToString(new Uint8Array(signingCertificateHash))
- );
- ok(
- QWACs.validateTLSCertificateBindingHeader(headerWithX5tS256Match),
- "header with matching x5t#S256 should parse"
- );
-
- let headerWithX5tS256Mismatch = structuredClone(testHeader);
- headerWithX5tS256Mismatch["x5t#S256"] =
- "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithX5tS256Mismatch),
- "header with x5t#S256 mismatch should not parse"
- );
-
- let headerWithBadExp = structuredClone(testHeader);
- headerWithBadExp.exp = "not a number";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(headerWithBadExp),
- "header with bad expiration time should not parse"
- );
-
- let expiredHeader = structuredClone(testHeader);
- expiredHeader.exp = "946684800";
- ok(
- !QWACs.validateTLSCertificateBindingHeader(expiredHeader),
- "expired header should not parse"
- );
-
- let unexpiredHeader = structuredClone(testHeader);
- unexpiredHeader.exp = "4102444800";
- ok(
- QWACs.validateTLSCertificateBindingHeader(unexpiredHeader),
- "unexpired header should parse"
- );
-});
-
-async function validate_tls_certificate_binding_header_with_algorithms(
- signatureAlg,
- hashAlg,
- expectedSignatureAlg,
- expectedHashAlg
-) {
- let serverCertificate = constructCertFromFile("bad_certs/default-ee.pem");
- let testHeader = await makeBindingHeader(
- [kTLSCertificateBindingEE],
- [serverCertificate],
- signatureAlg,
- hashAlg
- );
- let validatedHeader = QWACs.validateTLSCertificateBindingHeader(testHeader);
- ok(validatedHeader, "header should validate successfully");
- deepEqual(validatedHeader.algorithm, expectedSignatureAlg);
- equal(validatedHeader.certificates.length, 1);
- equal(validatedHeader.hashAlg, expectedHashAlg);
- equal(validatedHeader.hashes.length, 1);
-}
-
-add_task(
- async function test_validate_tls_certificate_binding_header_with_algorithms() {
- let options = [
- {
- signatureAlg: "RS256",
- hashAlg: "S256",
- expectedSignatureAlg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
- expectedHashAlg: "SHA-256",
- },
- {
- signatureAlg: "PS256",
- hashAlg: "S384",
- expectedSignatureAlg: {
- name: "RSA-PSS",
- saltLength: 32,
- hash: "SHA-256",
- },
- expectedHashAlg: "SHA-384",
- },
- {
- signatureAlg: "ES256",
- hashAlg: "S512",
- expectedSignatureAlg: {
- name: "ECDSA",
- namedCurve: "P-256",
- hash: "SHA-256",
- },
- expectedHashAlg: "SHA-512",
- },
- ];
- for (let option of options) {
- await validate_tls_certificate_binding_header_with_algorithms(
- option.signatureAlg,
- option.hashAlg,
- option.expectedSignatureAlg,
- option.expectedHashAlg
- );
- }
- }
-);
-
-async function sign(privateKeyInfo, algorithm, header) {
- let toSign = new Uint8Array(stringToArray(header + "."));
- let key = await crypto.subtle.importKey(
- "pkcs8",
- privateKeyInfo,
- algorithm,
- true,
- ["sign"]
- );
- let signature = await crypto.subtle.sign(algorithm, key, toSign);
- return (
- header +
- ".." +
- QWACs.toBase64URLEncoding(arrayToString(new Uint8Array(signature)))
- );
-}
-
-async function signTLSCertificateBinding(header, key, algorithm) {
- return sign(
- key,
- algorithm,
- QWACs.toBase64URLEncoding(JSON.stringify(header))
- );
-}
-
-async function verify_tls_certificate_binding_signature(
- headerSignatureAlgorithm,
- headerHashAlgorithm,
- signatureAlgorithm,
- signingKeyFilename,
- bindingCertificateFilename,
- serverCertificateFilename,
- presentedServerCertificateFilename,
- expectSuccess
-) {
- let signingKey = new Uint8Array(
- stringToArray(
- QWACs.fromBase64URLEncoding(
- pemToBase64(readFile(do_get_file(signingKeyFilename, false)))
- )
- )
- );
- let bindingCertificate = constructCertFromFile(bindingCertificateFilename);
- let serverCertificate = constructCertFromFile(serverCertificateFilename);
- let testHeader = await makeBindingHeader(
- [bindingCertificate],
- [serverCertificate],
- headerSignatureAlgorithm,
- headerHashAlgorithm
- );
- let binding = await signTLSCertificateBinding(
- testHeader,
- signingKey,
- signatureAlgorithm
- );
- let presentedServerCertificate = constructCertFromFile(
- presentedServerCertificateFilename
- );
- equal(
- await QWACs.verifyTLSCertificateBinding(
- binding,
- presentedServerCertificate,
- "example.com"
- ),
- expectSuccess,
- `TLS certificate binding ${expectSuccess ? "should" : "should not"} verify correctly`
- );
-}
-
-add_task(async function test_verify_tls_certificate_binding_signatures() {
- let options = [
- {
- headerSignatureAlgorithm: "RS256",
- headerHashAlgorithm: "S512",
- signatureAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
- signingKeyFilename: "bad_certs/default-ee.key",
- bindingCertificateFilename: "test_qwacs/2-qwac.pem",
- serverCertificateFilename: "bad_certs/default-ee.pem",
- presentedServerCertificateFilename: "bad_certs/default-ee.pem",
- expectSuccess: true,
- },
- // presented server certificate / bound server certificate mismatch
- {
- headerSignatureAlgorithm: "RS256",
- headerHashAlgorithm: "S512",
- signatureAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
- signingKeyFilename: "bad_certs/default-ee.key",
- bindingCertificateFilename: "test_qwacs/2-qwac.pem",
- serverCertificateFilename: "bad_certs/default-ee.pem",
- presentedServerCertificateFilename:
- "bad_certs/ee-from-missing-intermediate.pem",
- expectSuccess: false,
- },
- {
- headerSignatureAlgorithm: "PS256",
- headerHashAlgorithm: "S256",
- signatureAlgorithm: {
- name: "RSA-PSS",
- hash: "SHA-256",
- saltLength: 32,
- },
- signingKeyFilename: "bad_certs/default-ee.key",
- bindingCertificateFilename: "test_qwacs/2-qwac.pem",
- serverCertificateFilename: "bad_certs/default-ee.pem",
- presentedServerCertificateFilename: "bad_certs/default-ee.pem",
- expectSuccess: true,
- },
- {
- headerSignatureAlgorithm: "ES256",
- headerHashAlgorithm: "S384",
- signatureAlgorithm: {
- name: "ECDSA",
- namedCurve: "P-256",
- hash: "SHA-256",
- },
- signingKeyFilename: "test_qwacs/secp256r1.key",
- bindingCertificateFilename: "test_qwacs/2-qwac-ec.pem",
- serverCertificateFilename: "bad_certs/default-ee.pem",
- presentedServerCertificateFilename: "bad_certs/default-ee.pem",
- expectSuccess: true,
- },
- // header signature algorithm / actual signature algorithm mismatch
- {
- headerSignatureAlgorithm: "RS256",
- headerHashAlgorithm: "S384",
- signatureAlgorithm: {
- name: "ECDSA",
- namedCurve: "P-256",
- hash: "SHA-256",
- },
- signingKeyFilename: "test_qwacs/secp256r1.key",
- bindingCertificateFilename: "test_qwacs/2-qwac-ec.pem",
- serverCertificateFilename: "bad_certs/default-ee.pem",
- presentedServerCertificateFilename: "bad_certs/default-ee.pem",
- expectSuccess: false,
- },
- ];
-
- for (let option of options) {
- await verify_tls_certificate_binding_signature(
- option.headerSignatureAlgorithm,
- option.headerHashAlgorithm,
- option.signatureAlgorithm,
- option.signingKeyFilename,
- option.bindingCertificateFilename,
- option.serverCertificateFilename,
- option.presentedServerCertificateFilename,
- option.expectSuccess
- );
- }
-});
diff --git a/security/manager/ssl/tests/unit/test_qwacs/2-qwac-ec.pem b/security/manager/ssl/tests/unit/test_qwacs/2-qwac-ec.pem
@@ -1,15 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICaTCCAVGgAwIBAgIUI7puI/7AoO5YJyA1Ur7jnUMlPWswDQYJKoZIhvcNAQEL
-BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAiGA8yMDIzMTEyODAwMDAwMFoYDzIwMjYw
-MjA1MDAwMDAwWjAdMRswGQYDVQQDDBIyLVFXQUMgd2l0aCBFQyBrZXkwWTATBgcq
-hkjOPQIBBggqhkjOPQMBBwNCAARPv7u7YeD4+bGmClmshwTi7AULQj489y6SPyxP
-eUtFXCpp0jNFbDbEEZ0HBuAO7cjRk5DXmRt7LQejBOqgSqbAo3MwcTAtBggrBgEF
-BQcBAwQhMB8wCAYGBACORgEBMBMGBgQAjkYBBjAJBgcEAI5GAQYDMBQGA1UdIAQN
-MAswCQYHBACL7EABBjASBgNVHSUECzAJBgcEAIvsQwEAMBYGA1UdEQQPMA2CC2V4
-YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCLH2MoMgd+32MGtyxv12FaQk9l
-+vctL03M6i1lUmv9kKUVVpvU8R+489gKLaJ1/Qbu1874yrpS9TyqN8IGq9bHZNpY
-TY9As2sv4z9Uyh4tkysrYB+ecI9o6Al3fIbsxYgfBwF+rre9e6yTSx8vrikfQc5p
-Ni8xYd7nULEW+Hbo5XjbT5Yt0UcFBTYthH65Rjck8qAJwyKDleXDXgaJ2TpA4nQ2
-2C3PK0SZWWJ8Ua9hYkGWZEvNIDIiyZ99REdXEghEarT1R+qSBW0+d1MX7UO3QdIN
-xNhERu68/2rnu/RN17LaG3jbR20IASQJQfDv1uXVc1PulYnLzK2J9t08Me6U
------END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/unit/test_qwacs/2-qwac-ec.pem.certspec b/security/manager/ssl/tests/unit/test_qwacs/2-qwac-ec.pem.certspec
@@ -1,7 +0,0 @@
-issuer:Test CA
-subject:2-QWAC with EC key
-subjectKey:secp256r1
-extension:qcStatements:0.4.0.1862.1.1,0.4.0.1862.1.6:0.4.0.1862.1.6.3
-extension:certificatePolicies:0.4.0.194112.1.6
-extension:extKeyUsage:tlsBinding
-extension:subjectAlternativeName:example.com
diff --git a/security/manager/ssl/tests/unit/test_qwacs/secp256r1.key b/security/manager/ssl/tests/unit/test_qwacs/secp256r1.key
@@ -1,5 +0,0 @@
------BEGIN EC PRIVATE KEY-----
-MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgIZFAPVcQvxWiZYGM
-1C7W/t8JrdkteLGOeh6f65VSRwKhRANCAARPv7u7YeD4+bGmClmshwTi7AULQj48
-9y6SPyxPeUtFXCpp0jNFbDbEEZ0HBuAO7cjRk5DXmRt7LQejBOqgSqbA
------END EC PRIVATE KEY-----
diff --git a/security/manager/ssl/tests/unit/test_qwacs/secp256r1.key.keyspec b/security/manager/ssl/tests/unit/test_qwacs/secp256r1.key.keyspec
@@ -1 +0,0 @@
-secp256r1