diff options
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js')
-rw-r--r-- | toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js | 201 |
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"); + } +}); |