diff --git a/security/manager/ssl/AppTrustDomain.cpp b/security/manager/ssl/AppTrustDomain.cpp index 6ce1a9741e9d..3e99f0fd7731 100644 --- a/security/manager/ssl/AppTrustDomain.cpp +++ b/security/manager/ssl/AppTrustDomain.cpp @@ -32,6 +32,12 @@ // Add-on signing Certificates #include "addons-public.inc" #include "addons-public-intermediate.inc" +#include "addons-public-2018-intermediate.inc" +const mozilla::Span addonsPublicIntermediates[] = { + mozilla::Span(addonsPublicIntermediate, sizeof(addonsPublicIntermediate)), + mozilla::Span(addonsPublic2018Intermediate, + sizeof(addonsPublic2018Intermediate)), +}; #include "addons-stage.inc" #include "addons-stage-intermediate.inc" // Content signature root certificates @@ -90,12 +96,15 @@ nsresult AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot) { // The intermediate bundled with signed XPI files may have expired and be // considered invalid, which can result in bug 1548973. if (trustedRoot == nsIX509CertDB::AddonsPublicRoot) { - mAddonsIntermediate = {addonsPublicIntermediate}; + mAddonsIntermediates.AppendElements( + addonsPublicIntermediates, MOZ_ARRAY_LENGTH(addonsPublicIntermediates)); } // Similarly to the above logic for production, we hardcode the intermediate // stage certificate here, so that stage is equivalent to production. if (trustedRoot == nsIX509CertDB::AddonsStageRoot) { - mAddonsIntermediate = {addonsStageIntermediate}; + Span addonsStageIntermediateSpan = { + addonsStageIntermediate, sizeof(addonsStageIntermediate)}; + mAddonsIntermediates.AppendElement(std::move(addonsStageIntermediateSpan)); } return NS_OK; @@ -118,10 +127,10 @@ pkix::Result AppTrustDomain::FindIssuer(Input encodedIssuerName, return rv; } candidates.AppendElement(std::move(rootInput)); - if (!mAddonsIntermediate.IsEmpty()) { + for (const auto& intermediate : mAddonsIntermediates) { Input intermediateInput; - rv = intermediateInput.Init(mAddonsIntermediate.Elements(), - mAddonsIntermediate.Length()); + pkix::Result rv = + intermediateInput.Init(intermediate.Elements(), intermediate.Length()); // Again, this should never fail for the same reason as above. if (rv != Success) { return rv; diff --git a/security/manager/ssl/AppTrustDomain.h b/security/manager/ssl/AppTrustDomain.h index 4d09cdabdfe1..db3d804bddfb 100644 --- a/security/manager/ssl/AppTrustDomain.h +++ b/security/manager/ssl/AppTrustDomain.h @@ -83,7 +83,7 @@ class AppTrustDomain final : public mozilla::pkix::TrustDomain { private: Span mTrustedRoot; - Span mAddonsIntermediate; + nsTArray> mAddonsIntermediates; nsTArray> mIntermediates; nsCOMPtr mCertBlocklist; }; diff --git a/security/manager/ssl/addons-public-2018-intermediate.crt b/security/manager/ssl/addons-public-2018-intermediate.crt new file mode 100644 index 000000000000..5ab1af50dab7 Binary files /dev/null and b/security/manager/ssl/addons-public-2018-intermediate.crt differ diff --git a/security/manager/ssl/gen_cert_header.py b/security/manager/ssl/gen_cert_header.py index d0ed40e7a2bf..8227a09dec61 100644 --- a/security/manager/ssl/gen_cert_header.py +++ b/security/manager/ssl/gen_cert_header.py @@ -32,6 +32,7 @@ def _create_header(array_name, cert_bytes): # def arrayName(header, cert_filename): # header.write(_create_header("arrayName", cert_filename)) array_names = [ + "addonsPublic2018Intermediate", "addonsPublicIntermediate", "addonsPublicRoot", "addonsStageRoot", diff --git a/security/manager/ssl/moz.build b/security/manager/ssl/moz.build index 1065bd97a5dd..65253cd75cdd 100644 --- a/security/manager/ssl/moz.build +++ b/security/manager/ssl/moz.build @@ -238,6 +238,11 @@ headers_arrays_certs = [ "tests/unit/test_signed_apps/xpcshellTestRoot.der", ), ("addons-public.inc", "addonsPublicRoot", "addons-public.crt"), + ( + "addons-public-2018-intermediate.inc", + "addonsPublic2018Intermediate", + "addons-public-2018-intermediate.crt", + ), ( "addons-public-intermediate.inc", "addonsPublicIntermediate", diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/disable_ctrl_q_and_cmd_q-1.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/disable_ctrl_q_and_cmd_q-1.xpi new file mode 100644 index 000000000000..da27ea6441f3 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/disable_ctrl_q_and_cmd_q-1.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/disable_ctrl_q_and_cmd_q-2resigned1.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/disable_ctrl_q_and_cmd_q-2resigned1.xpi new file mode 100644 index 000000000000..d0aecc858c7b Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/disable_ctrl_q_and_cmd_q-2resigned1.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js index e801485c73f4..a7a9313d0974 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js @@ -23,7 +23,7 @@ function verifySignatures() { }); } -createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4"); +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "48"); add_setup(async () => { await promiseStartupManager(); @@ -537,3 +537,49 @@ add_task(useAMOStageCert(), async function test_disable() { await addon.uninstall(); AddonManager.removeAddonListener(listener); }); + +// Regression test for https://bugzilla.mozilla.org/show_bug.cgi?id=1954818 +// +// Do NOT remove this test or the XPI files. If this test becomes obsolete due +// to dropped support for these XPI files (e.g. if support for add-ons with +// SHA-1 signatures were to be dropped entirely), don't forget to delete +// addons-public-2018-intermediate.pem (undo the patch to bug 1954818). +add_task(async function test_xpi_signed_in_or_before_feb_2018() { + // Disable schema warnings for two reasons: + // - The "commands" property in the manifest is not supported on Android. + // - The resigned version "2resigned1" results in the following warning: + // "version must be a version string consisting of at most 4 integers of at + // most 9 digits without leading zeros, and separated with dots" + ExtensionTestUtils.failOnSchemaWarnings(false); + + async function checkAddonIsValid(xpiPath) { + let { addon } = await promiseInstallFile(do_get_file(xpiPath)); + Assert.notEqual(addon, null); + Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED); + Assert.ok(addon.isActive); + Assert.equal(addon.appDisabled, false); + await addon.uninstall(); + } + + // The test extension is chosen such that it was signed before 2018, because + // that was signed with CN=production-signing-ca.addons.mozilla.org + // instead of CN=signingca1.addons.mozilla.org (used after 8 feb 2018). + // + // "disable-ctrl-q-and-cmd-q@robwu.nl" is a simple extension consisting of + // one manifest.json. It was signed in 2016, and later resigned in 2024 + // because of enforcing stronger signatures (starting with bug 1885004). + + info("Checking add-on signed before 2018, 2016-12-22"); + // Pre-2018 signed extensions only used SHA-1, so we need to relax the weak + // signature policy so we can verify that the signature validation passes. + // Otherwise installation may fail due to the restrictions from bug 1885004. + const resetWeakSignaturePref = + AddonTestUtils.setWeakSignatureInstallAllowed(true); + await checkAddonIsValid(`${DATA}/disable_ctrl_q_and_cmd_q-1.xpi`); + resetWeakSignaturePref(); + + info("Checking add-on signed after 2018, 2024-04-25"); + await checkAddonIsValid(`${DATA}/disable_ctrl_q_and_cmd_q-2resigned1.xpi`); + + ExtensionTestUtils.failOnSchemaWarnings(true); +});