tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Dsecurity/manager/ssl/QWACs.sys.mjs | 403-------------------------------------------------------------------------------
Msecurity/manager/ssl/moz.build | 1-
Msecurity/manager/ssl/nsIX509Cert.idl | 6------
Msecurity/manager/ssl/nsNSSCertificate.cpp | 21---------------------
Msecurity/manager/ssl/tests/unit/head_psm.js | 4++--
Msecurity/manager/ssl/tests/unit/test_qwacs.js | 404-------------------------------------------------------------------------------
Dsecurity/manager/ssl/tests/unit/test_qwacs/2-qwac-ec.pem | 15---------------
Dsecurity/manager/ssl/tests/unit/test_qwacs/2-qwac-ec.pem.certspec | 7-------
Dsecurity/manager/ssl/tests/unit/test_qwacs/secp256r1.key | 5-----
Dsecurity/manager/ssl/tests/unit/test_qwacs/secp256r1.key.keyspec | 1-
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