Commit 4f4c1cc4 authored by Rob Wu's avatar Rob Wu
Browse files

Bug 1771992 - Use stage cert depending on the configured GMP URL (ESR115)...

Bug 1771992 - Use stage cert depending on the configured GMP URL (ESR115) r=aosmond,bhearsum a=RyanVM

Rebased to resolve conflict with async/await refactor from bug 1862051.

Original Revision: https://phabricator.services.mozilla.com/D205637

Differential Revision: https://phabricator.services.mozilla.com/D205856
parent ee24ef96
Loading
Loading
Loading
Loading
+34 −2
Original line number Diff line number Diff line
@@ -145,6 +145,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.
@@ -318,15 +348,17 @@ 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 addonPromise = ProductAddonChecker.getProductAddonList(
      url,
      allowNonBuiltIn,
      certs,
      checkContentSignature
      checkContentSignature,
      trustedContentSignatureRoot
    )
      .then(res => {
        if (checkContentSignature) {
+54 −0
Original line number Diff line number Diff line
@@ -801,6 +801,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
+22 −13
Original line number Diff line number Diff line
@@ -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);
  },

+10 −5
Original line number Diff line number Diff line
@@ -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) {