Bug 657228 - Preload all known intermediate certificates in our certificate store r?keeler draft
authorMark Goodwin <mgoodwin@mozilla.com>
Mon, 05 Feb 2018 11:45:50 +0000
changeset 755630 3784141b5357cb6ece07529a59f8de1802ebd404
parent 755533 9b69cc60e5848f2f8802c911fd00771b50eed41f
push id99220
push usermgoodwin@mozilla.com
push dateThu, 15 Feb 2018 16:02:58 +0000
reviewerskeeler
bugs657228
milestone60.0a1
Bug 657228 - Preload all known intermediate certificates in our certificate store r?keeler MozReview-Commit-ID: 1IqPjHQyAs0
modules/libpref/init/all.js
security/manager/ssl/tests/unit/test_intermediate_preloads.js
security/manager/ssl/tests/unit/test_intermediate_preloads/ca.pem
security/manager/ssl/tests/unit/test_intermediate_preloads/ca.pem.certspec
security/manager/ssl/tests/unit/test_intermediate_preloads/ca2.pem
security/manager/ssl/tests/unit/test_intermediate_preloads/ca2.pem.certspec
security/manager/ssl/tests/unit/test_intermediate_preloads/ee.pem
security/manager/ssl/tests/unit/test_intermediate_preloads/ee.pem.certspec
security/manager/ssl/tests/unit/test_intermediate_preloads/ee2.pem
security/manager/ssl/tests/unit/test_intermediate_preloads/ee2.pem.certspec
security/manager/ssl/tests/unit/test_intermediate_preloads/int.pem
security/manager/ssl/tests/unit/test_intermediate_preloads/int.pem.certspec
security/manager/ssl/tests/unit/test_intermediate_preloads/int2.pem
security/manager/ssl/tests/unit/test_intermediate_preloads/int2.pem.certspec
security/manager/ssl/tests/unit/xpcshell.ini
services/common/blocklist-clients.js
services/common/blocklist-updater.js
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2682,16 +2682,20 @@ pref("services.blocklist.addons.checked"
 pref("services.blocklist.plugins.collection", "plugins");
 pref("services.blocklist.plugins.checked", 0);
 pref("services.blocklist.pinning.enabled", true);
 pref("services.blocklist.pinning.bucket", "pinning");
 pref("services.blocklist.pinning.collection", "pins");
 pref("services.blocklist.pinning.checked", 0);
 pref("services.blocklist.gfx.collection", "gfx");
 pref("services.blocklist.gfx.checked", 0);
+pref("services.blocklist.intermediates.bucket", "security-state");
+pref("services.blocklist.intermediates.collection", "intermediates");
+pref("services.blocklist.intermediates.checked", 0);
+
 
 // Controls whether signing should be enforced on signature-capable blocklist
 // collections.
 pref("services.blocklist.signing.enforced", true);
 
 // Enable blocklists via the services settings mechanism
 pref("services.blocklist.update_enabled", true);
 
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads.js
@@ -0,0 +1,190 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"use strict";
+
+const { IntermediateCertClient } = ChromeUtils.import("resource://services-common/blocklist-clients.js", {});
+
+let server;
+
+do_get_profile(); // must be called before getting nsIX509CertDB
+const certdb  = Cc["@mozilla.org/security/x509certdb;1"]
+                  .getService(Ci.nsIX509CertDB);
+
+let intermediate1Data;
+let intermediate2Data;
+
+function load_cert(cert_name, trust_string) {
+  let cert_filename = cert_name + ".pem";
+  return addCertFromFile(certdb, "test_intermediate_preloads/" + cert_filename,
+                         trust_string);
+}
+
+add_task(async function test_preload() {
+  const dummyServerURL = `http://localhost:${server.identity.primaryPort}/v1`;
+  dump("The dummy server URL is "+dummyServerURL+"\n");
+  Services.prefs.setCharPref("services.settings.server", dummyServerURL);
+
+  const configPath = "/v1/";
+  const recordsPath = "/v1/buckets/security-state/collections/intermediates/records";
+
+  // register a handler
+  function handleResponse(request, response) {
+    try {
+      const sample = getSampleResponse(request, server.identity.primaryPort);
+      if (!sample) {
+        do_throw(`unexpected ${request.method} request for ${request.path}?${request.queryString}`);
+      }
+
+      response.setStatusLine(null, sample.status.status,
+                             sample.status.statusText);
+      // send the headers
+      for (let headerLine of sample.sampleHeaders) {
+        let headerElements = headerLine.split(":");
+        response.setHeader(headerElements[0], headerElements[1].trimLeft());
+      }
+      response.setHeader("Date", (new Date()).toUTCString());
+
+      response.write(sample.responseBody);
+    } catch (e) {
+      info(e);
+    }
+  }
+  server.registerPathHandler(configPath, handleResponse);
+  server.registerPathHandler(recordsPath, handleResponse);
+
+  // load the first root and end entity, ignore the initial intermediate
+  let ca_cert = load_cert("ca", "C,,");
+  notEqual(ca_cert, null, "EE cert should have successfully loaded");
+  let ee_cert = load_cert("ee", ",,");
+  notEqual(ee_cert, null, "EE cert should have successfully loaded");
+
+  // load the second end entity, ignore both intermediate and root
+  let ee_cert_2 = load_cert("ee2", ",,");
+  notEqual(ee_cert, null, "EE cert 2 should have successfully loaded");
+
+  // check that the missing intermediate causes an unknown issuer error, as
+  // expected, in both cases
+  checkCertErrorGeneric(certdb, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
+                        certificateUsageSSLServer);
+  checkCertErrorGeneric(certdb, ee_cert_2, SEC_ERROR_UNKNOWN_ISSUER,
+                        certificateUsageSSLServer);
+
+  // sync to the kinto server.
+  await IntermediateCertClient.maybeSync(0, Date.now());
+  await IntermediateCertClient.maybeSync(1000, Date.now());
+
+  // check that ee cert 1 verifies now the update has happened and there is
+  // an intermediate
+  checkCertErrorGeneric(certdb, ee_cert, PRErrorCodeSuccess,
+                        certificateUsageSSLServer);
+
+  // check that ee cert 2 does not verify - since we tried to load an
+  // intermediate with an unknown issuer
+  checkCertErrorGeneric(certdb, ee_cert_2, SEC_ERROR_UNKNOWN_ISSUER,
+                        certificateUsageSSLServer);
+
+});
+
+function run_test() {
+  // Ensure that signature verification is disabled to prevent interference
+  // with basic certificate sync tests
+  Services.prefs.setBoolPref("services.blocklist.signing.enforced", false);
+
+  let intermediate1File = do_get_file("test_intermediate_preloads/" + "int.pem", false);
+  intermediate1Data = readFile(intermediate1File);
+
+  let intermediate2File = do_get_file("test_intermediate_preloads/" + "int2.pem", false);
+  intermediate2Data = readFile(intermediate2File);
+
+  // Set up an HTTP Server
+  server = new HttpServer();
+  server.start(-1);
+
+  run_next_test();
+
+  registerCleanupFunction(function() {
+    server.stop(() => { });
+  });
+}
+
+
+// get a response for a given request from sample data
+function getSampleResponse(req, port) {
+  dump("Resource requested "+`${req.method}:${req.path}?${req.queryString}`+"\n\n");
+  const responses = {
+    "OPTIONS": {
+      "sampleHeaders": [
+        "Access-Control-Allow-Headers: Content-Length,Expires,Backoff,Retry-After,Last-Modified,Total-Records,ETag,Pragma,Cache-Control,authorization,content-type,if-none-match,Alert,Next-Page",
+        "Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS",
+        "Access-Control-Allow-Origin: *",
+        "Content-Type: application/json; charset=UTF-8",
+        "Server: waitress"
+      ],
+      "status": {status: 200, statusText: "OK"},
+      "responseBody": "null"
+    },
+    "GET:/v1/?": {
+      "sampleHeaders": [
+        "Access-Control-Allow-Origin: *",
+        "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
+        "Content-Type: application/json; charset=UTF-8",
+        "Server: waitress"
+      ],
+      "status": {status: 200, statusText: "OK"},
+      "responseBody": JSON.stringify({
+        "settings": {
+          "batch_max_requests": 25
+        },
+        "url": `http://localhost:${port}/v1/`,
+        "documentation": "https://kinto.readthedocs.org/",
+        "version": "1.5.1",
+        "commit": "cbc6f58",
+        "hello": "kinto"
+      })
+    },
+    "GET:/v1/buckets/security-state/collections/intermediates/records?_sort=-last_modified": {
+      "sampleHeaders": [
+        "Access-Control-Allow-Origin: *",
+        "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
+        "Content-Type: application/json; charset=UTF-8",
+        "Server: waitress",
+        "Etag: \"1000\""
+      ],
+      "status": {status: 200, statusText: "OK"},
+      "responseBody": JSON.stringify({"data": [{
+        "details": {
+          "who": "",
+          "why": "",
+          "name": "",
+          "created": ""
+        },
+        "subject": "",
+        "certData": `${intermediate1Data}`,
+        "whitelist": false,
+        "pubKeyHash": "",
+        "id": "78cf8900-fdea-4ce5-f8fb-b78710617718",
+        "last_modified": 3000
+      },
+      {
+        "details": {
+          "who": "",
+          "why": "",
+          "name": "",
+          "created": ""
+        },
+        "subject": "",
+        "certData": `${intermediate2Data}`,
+        "whitelist": false,
+        "pubKeyHash": "",
+        "id": "78cf8900-fdea-4ce5-f8fb-b78710617720",
+        "last_modified": 3000
+      }]})
+    }
+  };
+  return responses[`${req.method}:${req.path}?${req.queryString}`] ||
+         responses[req.method];
+
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIICyTCCAbGgAwIBAgIUUWOwyj16EyppwNjhm/JvdM3jghEwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAxNjExMjcwMDAwMDBaGA8yMDE5MDIwNTAw
+MDAwMFowDTELMAkGA1UEAwwCY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwG
+m24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJr
+bA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4
+SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3
+/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+Z
+FzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYD
+VR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQCcPH5N9BPOQrFOFCsw++lFly+p
+cH4A1ejRfyDVJXyms8JKk/37oLmU5vc7QZJx6LIKnAORgI0wAGJEQetU5W8X5qpU
+19QmbvqJW6AfQDZlUZiaueXMbQxFfEuggDU+UxhC4LrhSFjW5EsDnHeOfPEcu6Gt
+tbYFTAyAJjPNVBIMEyplFavU2HAe/h3HJYu8VnqNheCehtLRDMtbtG3+nvIM02Dy
+pHtlA+7o1c4HfzOsCp4uogsSgw8EprduyJlt/aokn5HDb96G+dWFW9dCR6G36uKr
+vtOSaYkyI7HM7CKLSiMHmJa25UY2dVTR1TgDJLYPSnR2Oo0wPD0+uY+h4K0+
+-----END CERTIFICATE-----
+
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ca.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca
+subject:ca
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ca2.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIICyzCCAbOgAwIBAgIUOyE6i/A7kQ62YOkveidINdc+ba0wDQYJKoZIhvcNAQEL
+BQAwDjEMMAoGA1UEAwwDY2EyMCIYDzIwMTYxMTI3MDAwMDAwWhgPMjAxOTAyMDUw
+MDAwMDBaMA4xDDAKBgNVBAMMA2NhMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72x
+nAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lM
+wmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF
+4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20
+yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xx
+j5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMdMBswDAYDVR0TBAUwAwEB/zAL
+BgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAAOr8ilIB1yKoVu6/fLbeJD8
+OfGcJ7es/mEnHEU9rqQZ6yJ4FMEF9AOV2zAiqsgftUDcKCQ3Ia0TkCOFtmP1BwCz
+1RLiGuKW4GouLu67ox8aaHAKwfUvaLyxulOK+81LSOERdVTlm8h+dFjAhdAQuokx
+nz8USSr+PBOTm0I2+0tc/f3jNy7qJpK6IS44HQJcBcV3jRakBy09xmcGKJZDjPlJ
+4wHeiJTPPcVaoa/8slOSPwriO6SjWcGsX/R9KCu5KQmqE+Yc+xoSahRNFLHC87Cu
+wqjbG/xaqojvHw4PXpPT9hVqRx4egp6LqjmxVonAElz48y+5xmiv7GsIolnsu9I=
+-----END CERTIFICATE-----
+
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ca2.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca2
+subject:ca2
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIICxDCCAaygAwIBAgIUIcpti0CUt2+1XNFemuy3VxgPSEowDQYJKoZIhvcNAQEL
+BQAwDjEMMAoGA1UEAwwDaW50MCIYDzIwMTYxMTI3MDAwMDAwWhgPMjAxOTAyMDUw
+MDAwMDBaMA0xCzAJBgNVBAMMAmVlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGc
+BptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzC
+a2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8Xg
+uEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK
+9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGP
+mRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEF
+BQcDATANBgkqhkiG9w0BAQsFAAOCAQEAqvuSHEse07ewo1CEN1TDuUJH80Zncj7t
+ncWeT3TkcfB47Bq5VNk7LnbTqRaf3m5kt1qlk4xJOR7Ihl7vEmHsQoaOBq0+ZRuQ
+P8/TjtMNxnkp8D/rNgPFNAdRDkP4J7239pAzhYL16uHKmLa01emZ2YaHFO1GRmyV
+N5s2QrA9IT4LnzCZKZzhLsiNlEFKeeTDPrjt3ClCEL7dzo6Rf2jR5BXo2w44zimg
+c3/wlmIsItzd+DHDXUIVwD00PvYcopSyL9tkY7zpoLKeBKQzjGjPsoZJtQaZF62D
+OZZxbd3ibDULfMyRMmqAODsU1WW3XtVS7E1ksst1jRv8UT9I6Vrjfw==
+-----END CERTIFICATE-----
+
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee.pem.certspec
@@ -0,0 +1,3 @@
+issuer:int
+subject:ee
+extension:extKeyUsage:serverAuth
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee2.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIICxjCCAa6gAwIBAgIUHXr3TX0R8G3uVzcWj764g2VugEMwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEaW50MjAiGA8yMDE2MTEyNzAwMDAwMFoYDzIwMTkwMjA1
+MDAwMDAwWjAOMQwwCgYDVQQDDANlZTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9
+sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5
+TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7
+xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHd
+tMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l
+8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjFzAVMBMGA1UdJQQMMAoGCCsG
+AQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQB2h1Z0O2BfhyFJ7Pq9FtyWqLlEITMd
+cYMiDIwK84/w67SJEKrMabNM+lNw+e+Nq6AedqNinpjv7DupOo6L3bWjR7Uz4Eui
+Vd9lKZNyazfb2oZbbHuSzKISPkXHB3ZSgYdp4zwc/dw4U/LkQwgyhkUyS5XdX6EE
+ghNs9+vDLv7LkjOiX5qtEKdOybr1TWkQllSQcPJzKZb95a4Cw2iwWJ3P2tG3R/zk
+8cPzVPJBCkZmQxhfTAmUiI6r9ev35WW/W5QDb1/t58M4XyrZbN+i5QWo95O0UWl0
+TrIUO/clXVig/sh3vE399KyPADS1+703I5wi9gxvLiOJrh0XAbqKFzmJ
+-----END CERTIFICATE-----
+
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee2.pem.certspec
@@ -0,0 +1,3 @@
+issuer:int2
+subject:ee2
+extension:extKeyUsage:serverAuth
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIICyjCCAbKgAwIBAgIUTS9BHHwv7mbGYr1g/GCSrK9pLmMwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAxNjExMjcwMDAwMDBaGA8yMDE5MDIwNTAw
+MDAwMFowDjEMMAoGA1UEAwwDaW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGc
+BptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzC
+a2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8Xg
+uEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK
+9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGP
+mRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABox0wGzAMBgNVHRMEBTADAQH/MAsG
+A1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAZpEQtY8SY9eBeFjdDqUTSj07
+MWBBgfI1FHFu/W225I2cJ4aPZAdq2bbO8B6FL5qbyWwDI+7XoclcfakMJbEQC+im
+X1LcgCpR+GmuXsdF5neWzT6XqWCR0P3VLWrPBpOD0ZhkN37Q5y7hl2IQAms0Fuaz
+AjA7FEpgzMKwaINmxCYqldbGYfIAa7+jC1mXO9p8wG7fWpvuAFO73ee/NWzW/VaG
+77Di8JOZI0FvHpr+IyPOt1+muj4Zc1rWczsLPQ2QPX0RoD3VqOA5lh7r9/PrJrnO
+HuUd1mA4sP7tz8PJASlTIZ+bYlpL5hNopLvYqEKsTteGtSSsJwqPqgKgTf3a9Q==
+-----END CERTIFICATE-----
+
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca
+subject:int
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int2.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIICzDCCAbSgAwIBAgIUc0u3YCpD5t2ApbVTtPqX13F1PKwwDQYJKoZIhvcNAQEL
+BQAwDjEMMAoGA1UEAwwDY2EyMCIYDzIwMTYxMTI3MDAwMDAwWhgPMjAxOTAyMDUw
+MDAwMDBaMA8xDTALBgNVBAMMBGludDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9
+sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5
+TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7
+xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHd
+tMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l
+8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8w
+CwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQAhXxaRNXRH0opXBVVR3tUG
+FbFoQVvGlaeIREO0dZNPhneP8uWxw/Hhkz/WNpLOuhVxOavYqwT6DfyQ/t72Kv1t
+WIhqLUebZpwL2Dwk7nrdK9hCY1GGdpeKMksl5IpqYbls9/D1ftob5e6xR88ofT85
+DUL0J01oiB0mRQHS3QaR2UbpXbOq3GvpKCe7GKUtLzC3xGDu2qtarEqrRFJeJ8W3
+vNIay9NqANSAY7n9se44AmFqRSd8nEJ1nCKtKtXu5oV3p0gvYkbhJyd2eKC+TFJv
+9OJilfKacC6VjjWBdmWoYIHjwO9oUruZTOlEXDTv4eEEh6XtiDjNbSF2pxUhZcRv
+-----END CERTIFICATE-----
+
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int2.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca2
+subject:int2
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -17,16 +17,17 @@ support-files =
   test_cert_version/**
   test_certDB_import/**
   test_certviewer_invalid_oids/**
   test_content_signing/**
   test_ct/**
   test_ev_certs/**
   test_getchain/**
   test_intermediate_basic_usage_constraints/**
+  test_intermediate_preloads/**
   test_keysize/**
   test_keysize_ev/**
   test_missing_intermediate/**
   test_name_constraints/**
   test_ocsp_fetch_method/**
   test_ocsp_url/**
   test_onecrl/**
   test_pinning_dynamic/**
@@ -92,16 +93,17 @@ run-sequentially = hardcoded ports
 skip-if = toolkit == 'android'
 [test_getchain.js]
 [test_hash_algorithms.js]
 [test_hash_algorithms_wrap.js]
 # bug 1124289 - run_test_in_child violates the sandbox on android
 skip-if = toolkit == 'android'
 [test_hmac.js]
 [test_intermediate_basic_usage_constraints.js]
+[test_intermediate_preloads.js]
 [test_js_cert_override_service.js]
 run-sequentially = hardcoded ports
 [test_keysize.js]
 [test_keysize_ev.js]
 run-sequentially = hardcoded ports
 [test_local_cert.js]
 [test_logoutAndTeardown.js]
 run-sequentially = hardcoded ports
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["AddonBlocklistClient",
                          "GfxBlocklistClient",
                          "OneCRLBlocklistClient",
                          "PinningBlocklistClient",
-                         "PluginBlocklistClient"];
+                         "PluginBlocklistClient",
+                         "IntermediateCertClient"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm", {});
 Cu.importGlobalProperties(["fetch"]);
 
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
@@ -23,31 +24,37 @@ ChromeUtils.defineModuleGetter(this, "Ki
                                "resource://services-common/kinto-http-client.js");
 ChromeUtils.defineModuleGetter(this, "FirefoxAdapter",
                                "resource://services-common/kinto-storage-adapter.js");
 ChromeUtils.defineModuleGetter(this, "CanonicalJSON",
                                "resource://gre/modules/CanonicalJSON.jsm");
 ChromeUtils.defineModuleGetter(this, "UptakeTelemetry",
                                "resource://services-common/uptake-telemetry.js");
 
+const certificateUsageSSLCA = 0x0008;
+const NO_FLAGS = 0;
+
 const KEY_APPDIR                             = "XCurProcD";
 const PREF_SETTINGS_SERVER                   = "services.settings.server";
 const PREF_BLOCKLIST_BUCKET                  = "services.blocklist.bucket";
 const PREF_BLOCKLIST_ONECRL_COLLECTION       = "services.blocklist.onecrl.collection";
 const PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS  = "services.blocklist.onecrl.checked";
 const PREF_BLOCKLIST_ADDONS_COLLECTION       = "services.blocklist.addons.collection";
 const PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS  = "services.blocklist.addons.checked";
 const PREF_BLOCKLIST_PLUGINS_COLLECTION      = "services.blocklist.plugins.collection";
 const PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS = "services.blocklist.plugins.checked";
 const PREF_BLOCKLIST_PINNING_ENABLED         = "services.blocklist.pinning.enabled";
 const PREF_BLOCKLIST_PINNING_BUCKET          = "services.blocklist.pinning.bucket";
 const PREF_BLOCKLIST_PINNING_COLLECTION      = "services.blocklist.pinning.collection";
 const PREF_BLOCKLIST_PINNING_CHECKED_SECONDS = "services.blocklist.pinning.checked";
 const PREF_BLOCKLIST_GFX_COLLECTION          = "services.blocklist.gfx.collection";
 const PREF_BLOCKLIST_GFX_CHECKED_SECONDS     = "services.blocklist.gfx.checked";
+const PREF_INTERMEDIATES_BUCKET              = "services.blocklist.intermediates.bucket";
+const PREF_INTERMEDIATES_COLLECTION          = "services.blocklist.intermediates.collection";
+const PREF_INTERMEDIATES_CHECKED_SECONDS     = "services.blocklist.intermediates.checked";
 const PREF_BLOCKLIST_ENFORCE_SIGNING         = "services.blocklist.signing.enforced";
 
 const INVALID_SIGNATURE = "Invalid content/signature";
 
 // This was the default path in earlier versions of
 // FirefoxAdapter, so for backwards compatibility we maintain this
 // filename, even though it isn't descriptive of who is using it.
 const KINTO_STORAGE_PATH = "kinto.sqlite";
@@ -407,16 +414,67 @@ async function updatePinningList(records
     } catch (e) {
       // prevent errors relating to individual preload entries from causing
       // sync to fail. We will accumulate telemetry for such failures in bug
       // 1254099.
     }
   }
 }
 
+class CertVerificationResult {
+  constructor(resolve) {
+    this.resolve = resolve;
+  }
+
+  verifyCertFinished(aPRErrorCode, aVerifiedChain, aHasEVPolicy) {
+    // if it's PRErrorCodeSuccess (0) the certificate has verified correctly
+    this.resolve(aPRErrorCode == 0);
+  }
+}
+
+/**
+ * Modify the profile's intermediate cert data based on records from the remote
+ * collection.
+ *
+ * @param {Object} records   current records in the local db.
+ */
+async function updateIntermediates(records) {
+  let nsX509CertDB = "@mozilla.org/security/x509certdb;1";
+  let nsIX509Cert = Ci.nsIX509Cert;
+  let nsIX509CertDB = Ci.nsIX509CertDB;
+
+  let certdb = Cc[nsX509CertDB].getService(nsIX509CertDB);
+
+  for (let item of records) {
+    if (item.certData) {
+      let data = item.certData;
+
+      // split off the header and footer, base64 decode, construct the cert
+      // from the resulting DER data.
+      let b64data = data.split('-----')[2].replace(/\s/g, '');
+      let certDer = atob(b64data);
+      let cert = certdb.constructX509(certDer);
+
+      // verify the certificate - we don't want to import CA certs that do
+      // not chain to roots in our program
+      let promise = new Promise((resolve, reject) => {
+        let now = (new Date()).getTime() / 1000;
+        let result = new CertVerificationResult(resolve);
+        certdb.asyncVerifyCertAtTime(cert, certificateUsageSSLCA, 0, null, now, result);
+      });
+      let certVerifies = await promise;
+
+      // IF the cert verifies, add the certificate to the cert db
+      if (true == certVerifies) {
+        certdb.addCert(certDer,'C,,','NSS ignores nicknames');
+      }
+    }
+  }
+}
+
 /**
  * Write list of records into JSON file, and notify nsBlocklistService.
  *
  * @param {String} filename  path relative to profile dir.
  * @param {Object} records   current records in the local db.
  */
 async function updateJSONBlocklist(filename, records) {
   // Write JSON dump for synchronous load at startup.
@@ -467,8 +525,16 @@ this.PluginBlocklistClient = new Blockli
 
 this.PinningPreloadClient = new BlocklistClient(
   Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_COLLECTION),
   PREF_BLOCKLIST_PINNING_CHECKED_SECONDS,
   updatePinningList,
   Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_BUCKET),
   "pinning-preload.content-signature.mozilla.org"
 );
+
+this.IntermediateCertClient = new BlocklistClient(
+  Services.prefs.getCharPref(PREF_INTERMEDIATES_COLLECTION),
+  PREF_INTERMEDIATES_CHECKED_SECONDS,
+  updateIntermediates,
+  Services.prefs.getCharPref(PREF_INTERMEDIATES_BUCKET),
+  "onecrl.content-signature.mozilla.org"
+);
--- a/services/common/blocklist-updater.js
+++ b/services/common/blocklist-updater.js
@@ -27,16 +27,17 @@ const TELEMETRY_HISTOGRAM_KEY = "setting
 XPCOMUtils.defineLazyGetter(this, "gBlocklistClients", function() {
   const BlocklistClients = ChromeUtils.import("resource://services-common/blocklist-clients.js", {});
   return {
     [BlocklistClients.OneCRLBlocklistClient.collectionName]: BlocklistClients.OneCRLBlocklistClient,
     [BlocklistClients.AddonBlocklistClient.collectionName]: BlocklistClients.AddonBlocklistClient,
     [BlocklistClients.GfxBlocklistClient.collectionName]: BlocklistClients.GfxBlocklistClient,
     [BlocklistClients.PluginBlocklistClient.collectionName]: BlocklistClients.PluginBlocklistClient,
     [BlocklistClients.PinningPreloadClient.collectionName]: BlocklistClients.PinningPreloadClient,
+    [BlocklistClients.IntermediateCertClient.collectionName]: BlocklistClients.IntermediateCertClient,
   };
 });
 
 // Add a blocklist client for testing purposes. Do not use for any other purpose
 this.addTestBlocklistClient = (name, client) => { gBlocklistClients[name] = client; };
 
 
 async function pollChanges(url, lastEtag) {