forked from mirrors/gecko-dev
Bug 1771992 - Use stage cert depending on the configured GMP URL r=aosmond,bhearsum
Differential Revision: https://phabricator.services.mozilla.com/D205637
This commit is contained in:
parent
33c7e57530
commit
7bbc54b70e
4 changed files with 120 additions and 20 deletions
|
|
@ -161,6 +161,36 @@ GMPInstallManager.prototype = {
|
|||
return url;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the root to use for verifying content signatures.
|
||||
* @param url
|
||||
* The Balrog URL, i.e. the return value of _getURL().
|
||||
*/
|
||||
_getContentSignatureRootForURL(url) {
|
||||
// The prod and stage URLs of Balrog are documented at:
|
||||
// https://mozilla-balrog.readthedocs.io/en/latest/infrastructure.html
|
||||
// Note: we are matching by prefix without the full domain nor slash, to
|
||||
// enable us to move to a different host name in the future if desired.
|
||||
if (url.startsWith("https://aus")) {
|
||||
return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
|
||||
}
|
||||
if (url.startsWith("https://stage.")) {
|
||||
return Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot;
|
||||
}
|
||||
if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
|
||||
return Ci.nsIX509CertDB.AppXPCShellRoot;
|
||||
}
|
||||
// When content signature verification for GMP was added (bug 1714621), a
|
||||
// pref existed to configure an arbitrary root, which enabled local testing.
|
||||
// This pref was removed later in bug 1769669, and replaced with hard-coded
|
||||
// roots (prod and tests only). Support for testing against the stage server
|
||||
// was restored in bug 1771992.
|
||||
// Note: other verifiers ultimately fall back to ContentSignatureLocalRoot,
|
||||
// to support local development. Here we use ContentSignatureProdRoot to
|
||||
// minimize risk (and the unclear demand for "local" development).
|
||||
return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
|
||||
},
|
||||
|
||||
/**
|
||||
* Records telemetry results on if fetching update.xml from Balrog succeeded
|
||||
* when content signature was used to verify the response from Balrog.
|
||||
|
|
@ -335,9 +365,10 @@ GMPInstallManager.prototype = {
|
|||
}
|
||||
|
||||
let url = await this._getURL();
|
||||
let trustedContentSignatureRoot = this._getContentSignatureRootForURL(url);
|
||||
|
||||
log.info(
|
||||
`Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}`
|
||||
`Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}, trustedContentSignatureRoot=${trustedContentSignatureRoot}`
|
||||
);
|
||||
|
||||
let success = true;
|
||||
|
|
@ -347,7 +378,8 @@ GMPInstallManager.prototype = {
|
|||
url,
|
||||
allowNonBuiltIn,
|
||||
certs,
|
||||
checkContentSignature
|
||||
checkContentSignature,
|
||||
trustedContentSignatureRoot
|
||||
);
|
||||
|
||||
if (checkContentSignature) {
|
||||
|
|
|
|||
|
|
@ -836,6 +836,60 @@ add_task(async function test_checkForAddons_contentSignatureFailure() {
|
|||
revertContentSigTestPrefs(previousUrlOverride);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the signature verification URL is as expected.
|
||||
*/
|
||||
add_task(async function test_checkForAddons_get_verifier_url() {
|
||||
const previousUrlOverride = setupContentSigTestPrefs();
|
||||
|
||||
let installManager = new GMPInstallManager();
|
||||
// checkForAddons() calls _getContentSignatureRootForURL() with the return
|
||||
// value of _getURL(), which is effectively KEY_URL_OVERRIDE or KEY_URL
|
||||
// followed by some normalization.
|
||||
const rootForUrl = async () => {
|
||||
const url = await installManager._getURL();
|
||||
return installManager._getContentSignatureRootForURL(url);
|
||||
};
|
||||
|
||||
Assert.equal(
|
||||
await rootForUrl(),
|
||||
Ci.nsIX509CertDB.AppXPCShellRoot,
|
||||
"XPCShell root used by default in xpcshell test"
|
||||
);
|
||||
|
||||
const defaultPrefs = Services.prefs.getDefaultBranch("");
|
||||
const defaultUrl = defaultPrefs.getStringPref(GMPPrefs.KEY_URL);
|
||||
Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, defaultUrl);
|
||||
Assert.equal(
|
||||
await rootForUrl(),
|
||||
Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot,
|
||||
"Production cert should be used for the default Balrog URL: " + defaultUrl
|
||||
);
|
||||
|
||||
// The current Balrog endpoint is at aus5.mozilla.org. Confirm that the prod
|
||||
// cert is used even if we bump the version (e.g. aus6):
|
||||
const potentialProdUrl = "https://aus1337.mozilla.org/potential/prod/URL";
|
||||
Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, potentialProdUrl);
|
||||
Assert.equal(
|
||||
await rootForUrl(),
|
||||
Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot,
|
||||
"Production cert should be used for: " + potentialProdUrl
|
||||
);
|
||||
|
||||
// Stage URL documented at https://mozilla-balrog.readthedocs.io/en/latest/infrastructure.html
|
||||
const stageUrl = "https://stage.balrog.nonprod.cloudops.mozgcp.net/etc.";
|
||||
Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, stageUrl);
|
||||
Assert.equal(
|
||||
await rootForUrl(),
|
||||
Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot,
|
||||
"Stage cert should be used with the stage URL: " + stageUrl
|
||||
);
|
||||
|
||||
installManager.uninit();
|
||||
|
||||
revertContentSigTestPrefs(previousUrlOverride);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that checkForAddons() works as expected when certificate pinning
|
||||
* checking is enabled. We plan to move away from cert pinning in favor of
|
||||
|
|
|
|||
|
|
@ -118,12 +118,18 @@ async function conservativeFetch(input) {
|
|||
* @param contentSignatureHeader
|
||||
* The contents of the 'content-signature' header received along with
|
||||
* `data`.
|
||||
* @param trustedRoot
|
||||
* The identifier of the trusted root to use for certificate validation.
|
||||
* @return A promise that will resolve to nothing if the signature verification
|
||||
* succeeds, or rejects on failure, with an Error that sets its
|
||||
* addonCheckerErr property disambiguate failure cases and a message
|
||||
* explaining the error.
|
||||
*/
|
||||
async function verifyGmpContentSignature(data, contentSignatureHeader) {
|
||||
async function verifyGmpContentSignature(
|
||||
data,
|
||||
contentSignatureHeader,
|
||||
trustedRoot
|
||||
) {
|
||||
if (!contentSignatureHeader) {
|
||||
logger.warn(
|
||||
"Unexpected missing content signature header during content signature validation"
|
||||
|
|
@ -186,13 +192,6 @@ async function verifyGmpContentSignature(data, contentSignatureHeader) {
|
|||
"@mozilla.org/security/contentsignatureverifier;1"
|
||||
].createInstance(Ci.nsIContentSignatureVerifier);
|
||||
|
||||
// See bug 1771992. In the future, this may need to handle staging and dev
|
||||
// environments in addition to just production and testing.
|
||||
let root = Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
|
||||
if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
|
||||
root = Ci.nsIX509CertDB.AppXPCShellRoot;
|
||||
}
|
||||
|
||||
let valid;
|
||||
try {
|
||||
valid = await verifier.asyncVerifyContentSignature(
|
||||
|
|
@ -200,7 +199,7 @@ async function verifyGmpContentSignature(data, contentSignatureHeader) {
|
|||
signature,
|
||||
certChain,
|
||||
"aus.content-signature.mozilla.org",
|
||||
root
|
||||
trustedRoot
|
||||
);
|
||||
} catch (err) {
|
||||
logger.warn(`Unexpected error while validating content signature: ${err}`);
|
||||
|
|
@ -329,6 +328,9 @@ function downloadXMLWithRequest(
|
|||
* @param verifyContentSignature
|
||||
* When true, will verify the content signature information from the
|
||||
* response header. Failure to verify will result in an error.
|
||||
* @param trustedContentSignatureRoot
|
||||
* The trusted root to use for certificate validation.
|
||||
* Must be set if verifyContentSignature is true.
|
||||
* @return a promise that resolves to the DOM document downloaded or rejects
|
||||
* with a JS exception in case of error.
|
||||
*/
|
||||
|
|
@ -336,7 +338,8 @@ async function downloadXML(
|
|||
url,
|
||||
allowNonBuiltIn = false,
|
||||
allowedCerts = null,
|
||||
verifyContentSignature = false
|
||||
verifyContentSignature = false,
|
||||
trustedContentSignatureRoot = null
|
||||
) {
|
||||
let request = await downloadXMLWithRequest(
|
||||
url,
|
||||
|
|
@ -346,7 +349,8 @@ async function downloadXML(
|
|||
if (verifyContentSignature) {
|
||||
await verifyGmpContentSignature(
|
||||
request.response,
|
||||
request.getResponseHeader("content-signature")
|
||||
request.getResponseHeader("content-signature"),
|
||||
trustedContentSignatureRoot
|
||||
);
|
||||
}
|
||||
return request.responseXML;
|
||||
|
|
@ -535,6 +539,9 @@ export const ProductAddonChecker = {
|
|||
* @param verifyContentSignature
|
||||
* When true, will verify the content signature information from the
|
||||
* response header. Failure to verify will result in an error.
|
||||
* @param trustedContentSignatureRoot
|
||||
* The trusted root to use for certificate validation.
|
||||
* Must be set if verifyContentSignature is true.
|
||||
* @return a promise that resolves to an object containing the list of add-ons
|
||||
* and whether the local fallback was used, or rejects with a JS
|
||||
* exception in case of error. In the case of an error, a best effort
|
||||
|
|
@ -545,13 +552,15 @@ export const ProductAddonChecker = {
|
|||
url,
|
||||
allowNonBuiltIn = false,
|
||||
allowedCerts = null,
|
||||
verifyContentSignature = false
|
||||
verifyContentSignature = false,
|
||||
trustedContentSignatureRoot = null
|
||||
) {
|
||||
return downloadXML(
|
||||
url,
|
||||
allowNonBuiltIn,
|
||||
allowedCerts,
|
||||
verifyContentSignature
|
||||
verifyContentSignature,
|
||||
trustedContentSignatureRoot
|
||||
).then(parseXML);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ add_task(async function test_valid_content_signature() {
|
|||
signedBaseUri + goodXmlPath + "?" + validSignatureQuery,
|
||||
/*allowNonBuiltIn*/ false,
|
||||
/*allowedCerts*/ false,
|
||||
/*verifyContentSignature*/ true
|
||||
/*verifyContentSignature*/ true,
|
||||
/*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
|
||||
);
|
||||
Assert.ok(true, "Should successfully get addon list");
|
||||
|
||||
|
|
@ -122,7 +123,8 @@ add_task(async function test_invalid_content_signature() {
|
|||
signedBaseUri + goodXmlPath + "?" + invalidSignatureQuery,
|
||||
/*allowNonBuiltIn*/ false,
|
||||
/*allowedCerts*/ false,
|
||||
/*verifyContentSignature*/ true
|
||||
/*verifyContentSignature*/ true,
|
||||
/*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
|
||||
);
|
||||
Assert.ok(false, "Should fail to get addon list");
|
||||
} catch (e) {
|
||||
|
|
@ -143,7 +145,8 @@ add_task(async function test_missing_content_signature_header() {
|
|||
signedBaseUri + goodXmlPath + "?" + missingSignatureQuery,
|
||||
/*allowNonBuiltIn*/ false,
|
||||
/*allowedCerts*/ false,
|
||||
/*verifyContentSignature*/ true
|
||||
/*verifyContentSignature*/ true,
|
||||
/*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
|
||||
);
|
||||
Assert.ok(false, "Should fail to get addon list");
|
||||
} catch (e) {
|
||||
|
|
@ -165,7 +168,8 @@ add_task(async function test_incomplete_content_signature_header() {
|
|||
signedBaseUri + goodXmlPath + "?" + incompleteSignatureQuery,
|
||||
/*allowNonBuiltIn*/ false,
|
||||
/*allowedCerts*/ false,
|
||||
/*verifyContentSignature*/ true
|
||||
/*verifyContentSignature*/ true,
|
||||
/*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
|
||||
);
|
||||
Assert.ok(false, "Should fail to get addon list");
|
||||
} catch (e) {
|
||||
|
|
@ -187,7 +191,8 @@ add_task(async function test_bad_x5u_content_signature_header() {
|
|||
signedBaseUri + goodXmlPath + "?" + badX5uSignatureQuery,
|
||||
/*allowNonBuiltIn*/ false,
|
||||
/*allowedCerts*/ false,
|
||||
/*verifyContentSignature*/ true
|
||||
/*verifyContentSignature*/ true,
|
||||
/*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
|
||||
);
|
||||
Assert.ok(false, "Should fail to get addon list");
|
||||
} catch (e) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue