Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: !msix
- Manifest: security/manager/ssl/tests/unit/xpcshell.toml
// 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/.
"use strict";
do_get_profile(); // must be called before getting nsIX509CertDB
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(
Ci.nsIX509CertDB.OneQWAC,
cert,
"example.com",
extraCertNames.map(filename => constructCertFromFile(filename))
);
equal(
result,
expectSuccess,
`${filename} ${expectSuccess ? "should" : "should not"} verify as 1-QWAC`
);
}
add_task(async function test_verify_1_qwacs() {
Services.prefs.clearUserPref("security.qwacs.enable_test_trust_anchors");
// By default, the QWACs test trust anchors are not used.
await verify_1_qwacs("test_qwacs/1-qwac.pem", false);
await verify_1_qwacs("test_qwacs/1-qwac-qevcpw.pem", false);
Services.prefs.setBoolPref("security.qwacs.enable_test_trust_anchors", true);
await verify_1_qwacs("test_qwacs/1-qwac.pem", true);
await verify_1_qwacs("test_qwacs/1-qwac-qevcpw.pem", true);
await verify_1_qwacs("test_qwacs/1-qwac-other-optional-qcs.pem", true);
await verify_1_qwacs("test_qwacs/1-qwac-unrelated-policy.pem", true);
// One or more intermediates may be necessary for path building.
await verify_1_qwacs("test_qwacs/1-qwac-via-intermediate.pem", false);
await verify_1_qwacs("test_qwacs/1-qwac-via-intermediate.pem", true, [
"test_qwacs/test-int.pem",
]);
await verify_1_qwacs("test_qwacs/empty-qc-type-statement.pem", false);
await verify_1_qwacs("test_qwacs/missing-qc-type-statement.pem", false);
await verify_1_qwacs("test_qwacs/missing-qcs-compliance.pem", false);
await verify_1_qwacs("test_qwacs/wrong-qc-type.pem", false);
await verify_1_qwacs("test_qwacs/no-1-qwac-policies.pem", false);
await verify_1_qwacs("test_qwacs/no-policies.pem", false);
await verify_1_qwacs("test_qwacs/2-qwac.pem", false);
});
async function verify_2_qwacs(
filename,
expectSuccess,
hostname = "example.com"
) {
let cert = constructCertFromFile(filename);
let result = await certdb.asyncVerifyQWAC(
Ci.nsIX509CertDB.TwoQWAC,
cert,
hostname,
[]
);
equal(
result,
expectSuccess,
`${filename} ${expectSuccess ? "should" : "should not"} verify as 2-QWAC`
);
}
add_task(async function test_verify_2_qwacs() {
Services.prefs.clearUserPref("security.qwacs.enable_test_trust_anchors");
// By default, the QWACs test trust anchors are not used.
await verify_2_qwacs("test_qwacs/2-qwac.pem", false);
Services.prefs.setBoolPref("security.qwacs.enable_test_trust_anchors", true);
await verify_2_qwacs("test_qwacs/2-qwac.pem", true);
await verify_2_qwacs("test_qwacs/1-qwac.pem", false);
await verify_2_qwacs("test_qwacs/2-qwac-no-eku.pem", false);
await verify_2_qwacs("test_qwacs/2-qwac-tls-server-eku.pem", false);
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: [],
},
};
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,
expectedCertificateSubject
) {
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
);
let qwac = await QWACs.verifyTLSCertificateBinding(
binding,
presentedServerCertificate,
"example.com"
);
equal(
!!qwac,
expectSuccess,
`TLS certificate binding ${expectSuccess ? "should" : "should not"} verify correctly`
);
if (expectSuccess) {
equal(
qwac.commonName,
expectedCertificateSubject,
"Verification should return the expected certificate"
);
}
}
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,
expectedCertificateSubject: "2-QWAC",
},
// 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,
expectedCertificateSubject: null,
},
{
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,
expectedCertificateSubject: "2-QWAC",
},
{
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,
expectedCertificateSubject: "2-QWAC with EC key",
},
// 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,
expectedCertificateSubject: null,
},
];
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,
option.expectedCertificateSubject
);
}
});