summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js201
1 files changed, 201 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js
new file mode 100644
index 0000000000..5ae61568ef
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js
@@ -0,0 +1,201 @@
+"use strict";
+
+const { ProductAddonChecker } = ChromeUtils.importESModule(
+ "resource://gre/modules/addons/ProductAddonChecker.sys.mjs"
+);
+
+Services.prefs.setBoolPref("media.gmp-manager.updateEnabled", true);
+
+// Setup a test server for content signature tests.
+const signedTestServer = new HttpServer();
+const testDataDir = "data/productaddons/";
+
+// Start the server so we can grab the identity. We need to know this so the
+// server can reference itself in the handlers that will be set up.
+signedTestServer.start();
+const signedBaseUri =
+ signedTestServer.identity.primaryScheme +
+ "://" +
+ signedTestServer.identity.primaryHost +
+ ":" +
+ signedTestServer.identity.primaryPort;
+
+// Setup endpoint to handle x5u lookups correctly.
+const validX5uPath = "/valid_x5u";
+// These certificates are generated using ./mach generate-test-certs <path_to_certspec>
+const validCertChain = loadCertChain(testDataDir + "content_signing", [
+ "aus_ee",
+ "int",
+]);
+signedTestServer.registerPathHandler(validX5uPath, (req, res) => {
+ res.write(validCertChain.join("\n"));
+});
+const validX5uUrl = signedBaseUri + validX5uPath;
+
+// Setup endpoint to handle x5u lookups incorrectly.
+const invalidX5uPath = "/invalid_x5u";
+const invalidCertChain = loadCertChain(testDataDir + "content_signing", [
+ "aus_ee",
+ // This cert chain is missing the intermediate cert!
+]);
+signedTestServer.registerPathHandler(invalidX5uPath, (req, res) => {
+ res.write(invalidCertChain.join("\n"));
+});
+const invalidX5uUrl = signedBaseUri + invalidX5uPath;
+
+// Will hold the XML data from good.xml.
+let goodXml;
+// This sig is generated using the following command at mozilla-central root
+// `cat toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml | ./mach python security/manager/ssl/tests/unit/test_content_signing/pysign.py`
+// If test certificates are regenerated, this signature must also be.
+const goodXmlContentSignature =
+ "7QYnPqFoOlS02BpDdIRIljzmPr6BFwPs1z1y8KJUBlnU7EVG6FbnXmVVt5Op9wDzgvhXX7th8qFJvpPOZs_B_tHRDNJ8SK0HN95BAN15z3ZW2r95SSHmU-fP2JgoNOR3";
+
+const goodXmlPath = "/good.xml";
+// Requests use query strings to test different signature states.
+const validSignatureQuery = "validSignature";
+const invalidSignatureQuery = "invalidSignature";
+const missingSignatureQuery = "missingSignature";
+const incompleteSignatureQuery = "incompleteSignature";
+const badX5uSignatureQuery = "badX5uSignature";
+signedTestServer.registerPathHandler(goodXmlPath, (req, res) => {
+ if (req.queryString == validSignatureQuery) {
+ res.setHeader(
+ "content-signature",
+ `x5u=${validX5uUrl}; p384ecdsa=${goodXmlContentSignature}`
+ );
+ } else if (req.queryString == invalidSignatureQuery) {
+ res.setHeader("content-signature", `x5u=${validX5uUrl}; p384ecdsa=garbage`);
+ } else if (req.queryString == missingSignatureQuery) {
+ // Intentionally don't set the header.
+ } else if (req.queryString == incompleteSignatureQuery) {
+ res.setHeader(
+ "content-signature",
+ `x5u=${validX5uUrl}` // There's no p384ecdsa part!
+ );
+ } else if (req.queryString == badX5uSignatureQuery) {
+ res.setHeader(
+ "content-signature",
+ `x5u=${invalidX5uUrl}; p384ecdsa=${goodXmlContentSignature}`
+ );
+ } else {
+ Assert.ok(
+ false,
+ "Invalid queryString passed to server! Tests shouldn't do that!"
+ );
+ }
+ res.write(goodXml);
+});
+
+// Handle aysnc load of good.xml.
+add_task(async function load_good_xml() {
+ goodXml = await IOUtils.readUTF8(do_get_file(testDataDir + "good.xml").path);
+});
+
+add_task(async function test_valid_content_signature() {
+ try {
+ const res = await ProductAddonChecker.getProductAddonList(
+ signedBaseUri + goodXmlPath + "?" + validSignatureQuery,
+ /*allowNonBuiltIn*/ false,
+ /*allowedCerts*/ false,
+ /*verifyContentSignature*/ true
+ );
+ Assert.ok(true, "Should successfully get addon list");
+
+ // Smoke test the results are as expected.
+ Assert.equal(res.addons[0].id, "test1");
+ Assert.equal(res.addons[1].id, "test2");
+ Assert.equal(res.addons[2].id, "test3");
+ Assert.equal(res.addons[3].id, "test4");
+ Assert.equal(res.addons[4].id, undefined);
+ } catch (e) {
+ Assert.ok(
+ false,
+ `Should successfully get addon list, instead failed with ${e}`
+ );
+ }
+});
+
+add_task(async function test_invalid_content_signature() {
+ try {
+ await ProductAddonChecker.getProductAddonList(
+ signedBaseUri + goodXmlPath + "?" + invalidSignatureQuery,
+ /*allowNonBuiltIn*/ false,
+ /*allowedCerts*/ false,
+ /*verifyContentSignature*/ true
+ );
+ Assert.ok(false, "Should fail to get addon list");
+ } catch (e) {
+ Assert.ok(true, "Should fail to get addon list");
+ // The nsIContentSignatureVerifier will throw an error on this path,
+ // check that we've caught and re-thrown, but don't check the full error
+ // message as it's messy and subject to change.
+ Assert.ok(
+ e.message.startsWith("Content signature validation failed:"),
+ "Should get signature failure message"
+ );
+ }
+});
+
+add_task(async function test_missing_content_signature_header() {
+ try {
+ await ProductAddonChecker.getProductAddonList(
+ signedBaseUri + goodXmlPath + "?" + missingSignatureQuery,
+ /*allowNonBuiltIn*/ false,
+ /*allowedCerts*/ false,
+ /*verifyContentSignature*/ true
+ );
+ Assert.ok(false, "Should fail to get addon list");
+ } catch (e) {
+ Assert.ok(true, "Should fail to get addon list");
+ Assert.equal(
+ e.addonCheckerErr,
+ ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR
+ );
+ Assert.equal(
+ e.message,
+ "Content signature validation failed: missing content signature header"
+ );
+ }
+});
+
+add_task(async function test_incomplete_content_signature_header() {
+ try {
+ await ProductAddonChecker.getProductAddonList(
+ signedBaseUri + goodXmlPath + "?" + incompleteSignatureQuery,
+ /*allowNonBuiltIn*/ false,
+ /*allowedCerts*/ false,
+ /*verifyContentSignature*/ true
+ );
+ Assert.ok(false, "Should fail to get addon list");
+ } catch (e) {
+ Assert.ok(true, "Should fail to get addon list");
+ Assert.equal(
+ e.addonCheckerErr,
+ ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR
+ );
+ Assert.equal(
+ e.message,
+ "Content signature validation failed: missing signature"
+ );
+ }
+});
+
+add_task(async function test_bad_x5u_content_signature_header() {
+ try {
+ await ProductAddonChecker.getProductAddonList(
+ signedBaseUri + goodXmlPath + "?" + badX5uSignatureQuery,
+ /*allowNonBuiltIn*/ false,
+ /*allowedCerts*/ false,
+ /*verifyContentSignature*/ true
+ );
+ Assert.ok(false, "Should fail to get addon list");
+ } catch (e) {
+ Assert.ok(true, "Should fail to get addon list");
+ Assert.equal(
+ e.addonCheckerErr,
+ ProductAddonChecker.VERIFICATION_INVALID_ERR
+ );
+ Assert.equal(e.message, "Content signature is not valid");
+ }
+});