Bug 1178518 - Support for verifying signed packages; r?dkeeler, rbarnes draft
authorJonathan Hao <jhao@mozilla.com>
Wed, 26 Aug 2015 19:12:41 +0800
changeset 287970 e77f6c5c96b8bf47a9baa29ae56b1cc385973072
parent 287588 04b8c412d9f58fb6194c58dcaa66bf278bbd53cf
child 508705 b86a05fd34058667a687821113032a8ca4e376d3
push id4772
push userjhao@mozilla.com
push dateWed, 26 Aug 2015 11:15:45 +0000
reviewersdkeeler, rbarnes
bugs1178518
milestone43.0a1
Bug 1178518 - Support for verifying signed packages; r?dkeeler, rbarnes
browser/installer/package-manifest.in
dom/apps/StoreTrustAnchor.jsm
netwerk/protocol/http/SignedPackageVerifier.js
netwerk/protocol/http/SignedPackageVerifier.manifest
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsISignedPackageVerifier.idl
netwerk/test/unit/test_signed_package_verifier.js
netwerk/test/unit/xpcshell.ini
security/apps/AppSignatureVerification.cpp
security/apps/AppTrustDomain.cpp
security/apps/gen_cert_header.py
security/apps/moz.build
security/manager/ssl/nsIX509CertDB.idl
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -366,16 +366,18 @@
 @RESPATH@/components/ChromeNotifications.js
 @RESPATH@/components/ChromeNotifications.manifest
 @RESPATH@/components/ConsoleAPI.manifest
 @RESPATH@/components/ConsoleAPIStorage.js
 @RESPATH@/components/BrowserElementParent.manifest
 @RESPATH@/components/BrowserElementParent.js
 @RESPATH@/components/FeedProcessor.manifest
 @RESPATH@/components/FeedProcessor.js
+@RESPATH@/components/SignedPackageVerifier.js
+@RESPATH@/components/SignedPackageVerifier.manifest
 @RESPATH@/browser/components/BrowserFeeds.manifest
 @RESPATH@/browser/components/FeedConverter.js
 @RESPATH@/browser/components/FeedWriter.js
 @RESPATH@/browser/components/fuelApplication.manifest
 @RESPATH@/browser/components/fuelApplication.js
 @RESPATH@/browser/components/WebContentConverter.js
 @RESPATH@/browser/components/BrowserComponents.manifest
 @RESPATH@/browser/components/nsBrowserContentHandler.js
--- a/dom/apps/StoreTrustAnchor.jsm
+++ b/dom/apps/StoreTrustAnchor.jsm
@@ -11,16 +11,17 @@ this.EXPORTED_SYMBOLS = [
   "TrustedRootCertificate"
 ];
 
 const APP_TRUSTED_ROOTS= ["AppMarketplaceProdPublicRoot",
                           "AppMarketplaceProdReviewersRoot",
                           "AppMarketplaceDevPublicRoot",
                           "AppMarketplaceDevReviewersRoot",
                           "AppMarketplaceStageRoot",
+                          "PrivilegedPackageRoot",
                           "AppXPCShellRoot"];
 
 this.TrustedRootCertificate = {
   _index: Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot,
   get index() {
     return this._index;
   },
   set index(aIndex) {
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SignedPackageVerifier.js
@@ -0,0 +1,80 @@
+/* -*- 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const SIGNEDPACKAGEVERIFIER_CONTRACTID = "@mozilla.org/network/signed-package-verifier;1";
+const SIGNEDPACKAGEVERIFIER_CID = Components.ID("{fe8f1c2e-3c13-11e5-9a3f-bbf47d1e6697}");
+
+function SignedPackageVerifier() {
+
+}
+
+SignedPackageVerifier.prototype = {
+  classID: SIGNEDPACKAGEVERIFIER_CID,
+  contractID: SIGNEDPACKAGEVERIFIER_CONTRACTID,
+  classDescription: "Signed Package Verifier",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISignedPackageVerifier]),
+
+  verifyManifest: function(aHeader, aManifest) {
+    let signature;
+    const signatureField = "manifest-signature: ";
+    for (let item of aHeader.split('\n')) {
+      if (item.substr(0, signatureField.length) == signatureField) {
+        signature = item.substr(signatureField.length);
+        break;
+      }
+    }
+    if (!signature) {
+      return false;
+    }
+    try {
+      signature = atob(signature);
+      this.resources = JSON.parse(aManifest)["moz-resources"];
+    } catch (e) {
+      return false;
+    }
+
+    let manifestStream = Cc["@mozilla.org/io/string-input-stream;1"]
+                           .createInstance(Ci.nsIStringInputStream);
+    let signatureStream = Cc["@mozilla.org/io/string-input-stream;1"]
+                            .createInstance(Ci.nsIStringInputStream);
+    manifestStream.setData(aManifest, aManifest.length);
+    signatureStream.setData(signature, signature.length);
+
+    let certDb;
+    try {
+      certDb = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+    } catch (e) {
+      debug("nsIX509CertDB error: " + e);
+      // unrecoverable error, don't bug the user
+      throw "CERTDB_ERROR";
+    }
+
+    let aSignerCert = certDb.verifySignedManifestSync(
+      Ci.nsIX509CertDB.PrivilegedPackageRoot, manifestStream, signatureStream);
+
+    return aSignerCert !== null;
+  },
+
+  checkIntegrity: function(aFileName, aHashValue) {
+    if (!this.resources) {
+      return false;
+    }
+    for (let r of this.resources) {
+      if (r.src === aFileName) {
+        return r.integrity === aHashValue;
+      }
+    }
+    return false;
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SignedPackageVerifier]);
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SignedPackageVerifier.manifest
@@ -0,0 +1,3 @@
+# SignedPackageVerifier.js
+component {fe8f1c2e-3c13-11e5-9a3f-bbf47d1e6697} SignedPackageVerifier.js
+contract @mozilla.org/network/signed-package-verifier;1 {fe8f1c2e-3c13-11e5-9a3f-bbf47d1e6697}
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -11,16 +11,17 @@ XPIDL_SOURCES += [
     'nsIHttpAuthManager.idl',
     'nsIHttpChannel.idl',
     'nsIHttpChannelAuthProvider.idl',
     'nsIHttpChannelChild.idl',
     'nsIHttpChannelInternal.idl',
     'nsIHttpEventSink.idl',
     'nsIHttpHeaderVisitor.idl',
     'nsIHttpProtocolHandler.idl',
+    'nsISignedPackageVerifier.idl',
 ]
 
 XPIDL_MODULE = 'necko_http'
 
 EXPORTS += [
     'nsHttp.h',
     'nsHttpAtomList.h',
     'nsHttpHeaderArray.h',
@@ -106,8 +107,13 @@ FAIL_ON_WARNINGS = True
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/netwerk/base',
 ]
+
+EXTRA_COMPONENTS += [
+    'SignedPackageVerifier.js',
+    'SignedPackageVerifier.manifest',
+]
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/nsISignedPackageVerifier.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * A package using privileged APIs should be signed by marketplace or trust-
+ * worthy developers. When Necko receives such a package, it has to
+ * extract the manifest and the signature and calls verifyManifest(...) to verify
+ * the manifest. An nsISignedPackagedVerifier will parse the manifest and
+ * store the hash values of each resource. When a resource is ready, Necko
+ * will calculate its hash value (including the header like Content-Location: xxx),
+ * and calls checkIntegrity(...) to verify the integrity.
+ *
+ * For more detail:
+ *   https://wiki.mozilla.org/User:Ptheriault/Packagedprivilegedcontent
+ */
+
+[scriptable, uuid(eafd2113-0dc8-4e5e-a3d8-f639635f0384)]
+interface nsISignedPackageVerifier : nsISupports
+{
+  /**
+   * @aHeader is the package's header including
+   *   - "manifest-signature: xxxxxx (base64 encoding)
+   * @aManifest is the manifest of the package
+   *   - manifest must be the first resource of the package
+   */
+  boolean verifyManifest(in AString aHeader, in AString aManifest);
+
+  /**
+   * @aFileName is the name of a resource in the package
+   * @aHashValue is the hash value of this resource named aFileName
+   *   - aHashValue should be computed by the caller of this method
+   */
+  boolean checkIntegrity(in AString aFileName, in AString aHashValue);
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_signed_package_verifier.js
@@ -0,0 +1,138 @@
+const header_missing_signature = "header1: content1";
+const header_invalid_signature = `header1: content1
+manifest-signature: invalid-signature`;
+const header = `header1: content1
+manifest-signature: MIIF0gYJKoZIhvcNAQcCoIIFwzCCBb8CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA5wwggOYMIICgKADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDgyNDA5MTEzOFoXDTM1MDgyNDA5MTEzOFowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA2qMp97njHVXbnafne6qIx5D+j2AUC6j2159DK6PnL78L5UxD2KgjQZvkOaIZJe11KPYTf7upftat4Shs1c0SsMbHzDY7K0E/lSslD4zmb4TckOGPZzxtEIl7v3+yCjKqMRRMcBnaB20LrxTPQ3PS9iBCzTVbWlosbqmK/+1Pkv4Cmp3sXWJm9QA1QAgJu0dm8sTCyW0F8M3t9zIRNkZoQCERiLYQ/zIDC62B1iS6pOswz2MX3lh05O1FYKJ/y+lM+U7Wv/Ml87cpDNztmNbS1LET1wFjxiXNrc9kuqveT1Ccb4XG3x7KykXTfAnGGJb1mTM1YK84drfVmbgbNixgvwIBA6M4MDYwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBAGEqlAKtBMFSalloBdQBR0KODIhzAJfkzp0FIokZwF4YsXsAZdPpdZ4rwLTfdI1IhF2vLkW3KAuV0fEtShuqTIZVFqszEy/N2mLIZ5bjqJzqDT3/az4/vn/UBBgEiVvkSYYn0WlypRsGtpax9XSZvmXJ7PW9VKokJ5xIuFT87oyhdz3e5NgymmK2KGuBLUWKNNxap9GU2CEHVvsETcjqeELhH2LXd7H4xWVBu54tmuAIGa9Q16OenhMejEkAdbHzth0X/M/KNIEoucXRLtK8xVxdN4wcYZCXwQR1dgej7G4ZzcKtzJqN12msyKaeaJnhHBWYCCRaziH8q4Cswyp3BHwxggH+MIIB+gIBATB4MHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBAgECMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNTA4MjQwOTExMzhaMCMGCSqGSIb3DQEJBDEWBBTtO4QkpGTDLlHwE3ltN+RQpJn30DANBgkqhkiG9w0BAQEFAASCAQAKh/cWKYNqR6gBxY2HM7k5fZTePu4Lo73A0yjEDalKmywrWQ9x88d+dIZNlxfeQ1Uk+RNNVkNNjKkFBLHEP6HHAVYaevRFAqBG4Z2n+jY9Pjko7vqcF3cseg5p4vx7Emb7GMU0V/9Mfvfpw1tST/rn9iUnruIQGkFEnG7VkSBrOJHuQYwXuzd2LHEoj9OhrsNRKccjy9vzX0+1zKBVqRJ4x+TQU19/KED5LW59btStEJhGdmcPgs2QC4rwymvTZiOyd5L2vWtZm2FdAUGpINSnptIA+3m23RtNkeWUoE2H45EWPgCWwQeYSZibxw3Zn1tG9FznTtSCWrsgXXWNBomc
+`;
+
+const manifest = `{
+  "name": "My App",
+  "description": "A great app!",
+  "moz-uuid": "some-uuid",
+  "moz-permissions": [
+    {
+      "systemXHR": {
+        "description": "Needed to download stuff"
+      },
+      "devicestorage:pictures": {
+        "description": "Need to load pictures"
+      }
+    }
+  ],
+  "moz-resources": [
+    {
+      "src": "/index.html",
+      "integrity": "sha256-kass...eoirW-e"
+    },
+    {
+      "src": "/page2.html",
+      "integrity": "sha256-kasguie...ngeW-e"
+    },
+    {
+      "src": "/script.js",
+      "integrity": "sha256-agjdia2...wgda"
+    },
+    {
+      "src": "/library.js",
+       "integrity": "sha256-geijfi...ae3W"
+    }
+  ],
+  "moz-package-location": "https://example.com/myapp/app.pak"
+}
+`;
+
+const manifest_missing_moz_resources = `{
+  "name": "My App",
+  "description": "A great app!",
+  "moz-uuid": "some-uuid",
+  "moz-permissions": [
+    {
+      "systemXHR": {
+        "description": "Needed to download stuff"
+      },
+      "devicestorage:pictures": {
+        "description": "Need to load pictures"
+      }
+    }
+  ],
+  "moz-package-location": "https://example.com/myapp/app.pak"
+}
+`;
+
+const manifest_malformed_json = "}";
+
+let callback, verifier;
+
+function run_test() {
+  add_test(test_verify_manifest_missing_signature);
+  add_test(test_verify_manifest_invalid_signature);
+  add_test(test_verify_manifest_malformed_json);
+  add_test(test_verify_manifest_missing_moz_resources);
+  add_test(test_verify_manifest_success);
+  // The last verification must succeed, because check_integrity use that object;
+  add_test(test_check_integrity_success);
+  add_test(test_check_integrity_filename_not_matched);
+  add_test(test_check_integrity_hashvalue_not_matched);
+
+  run_next_test();
+}
+
+function test_verify_manifest_missing_signature() {
+  verifier = Cc["@mozilla.org/network/signed-package-verifier;1"]
+               .createInstance(Ci.nsISignedPackageVerifier);
+  ok(!verifier.verifyManifest(header_missing_signature, manifest),
+     "header without signature should fail to verify");
+  run_next_test();
+}
+
+function test_verify_manifest_invalid_signature() {
+  verifier = Cc["@mozilla.org/network/signed-package-verifier;1"]
+               .createInstance(Ci.nsISignedPackageVerifier);
+  ok(!verifier.verifyManifest(header_invalid_signature, manifest),
+     "header with invalid signature should fail to verify");
+  run_next_test();
+}
+
+function test_verify_manifest_malformed_json() {
+  verifier = Cc["@mozilla.org/network/signed-package-verifier;1"]
+               .createInstance(Ci.nsISignedPackageVerifier);
+  ok(!verifier.verifyManifest(header, manifest_malformed_json),
+     "manifest with malformed json should fail to verify");
+  run_next_test();
+}
+
+function test_verify_manifest_missing_moz_resources() {
+  verifier = Cc["@mozilla.org/network/signed-package-verifier;1"]
+               .createInstance(Ci.nsISignedPackageVerifier);
+  ok(!verifier.verifyManifest(header, manifest_missing_moz_resources),
+     "manifest without moz-resources attribute should fail to verify");
+  run_next_test();
+}
+
+function test_verify_manifest_success() {
+  verifier = Cc["@mozilla.org/network/signed-package-verifier;1"]
+               .createInstance(Ci.nsISignedPackageVerifier);
+  ok(verifier.verifyManifest(header, manifest),
+     "valid manifest and header should verify successfully");
+  run_next_test();
+}
+
+function test_check_integrity_success() {
+  for (let resource of JSON.parse(manifest)["moz-resources"]) {
+    ok(verifier.checkIntegrity(resource.src, resource.integrity),
+       "resource " + resource.src + " should pass integrity check");
+  }
+  run_next_test();
+}
+
+function test_check_integrity_filename_not_matched() {
+  ok(!verifier.checkIntegrity("/nosuchfile.html", "sha256-kass...eoirW-e"),
+     "mismatched filename should fail integrity check");
+  run_next_test();
+}
+
+function test_check_integrity_hashvalue_not_matched() {
+  ok(!verifier.checkIntegrity("/index.html", "kass...eoirW-e"),
+     "mismatched hashvalue should fail integrity check");
+  run_next_test();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -18,16 +18,17 @@ support-files =
   data/test_readline7.txt
   data/test_readline8.txt
   data/signed_win.exe
   socks_client_subprocess.js
   test_link.desktop
   test_link.url
   ../../dns/effective_tld_names.dat
 
+[test_signed_package_verifier.js]
 [test_packaged_app_channel.js]
 [test_nsIBufferedOutputStream_writeFrom_block.js]
 [test_cache2-00-service-get.js]
 [test_cache2-01-basic.js]
 [test_cache2-01a-basic-readonly.js]
 [test_cache2-01b-basic-datasize.js]
 [test_cache2-01c-basic-hasmeta-only.js]
 [test_cache2-01d-basic-not-wanted.js]
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -1015,16 +1015,25 @@ nsNSSCertificateDB::VerifySignedManifest
   NS_ENSURE_ARG_POINTER(aCallback);
 
   RefPtr<VerifySignedmanifestTask> task(
     new VerifySignedmanifestTask(aTrustedRoot, aManifestStream,
                                  aSignatureStream, aCallback));
   return task->Dispatch("SignedManifest");
 }
 
+NS_IMETHODIMP
+nsNSSCertificateDB::VerifySignedManifestSync(
+  AppTrustedRoot aTrustedRoot, nsIInputStream* aManifestStream,
+  nsIInputStream* aSignatureStream, nsIX509Cert** aSignerCert)
+{
+  nsresult aRv = VerifySignedManifest(aTrustedRoot, aManifestStream,
+                              aSignatureStream, aSignerCert);
+  return NS_OK;
+}
 
 //
 // Signature verification for archives unpacked into a file structure
 //
 
 // Finds the "*.rsa" signature file in the META-INF directory and returns
 // the name. It is an error if there are none or more than one .rsa file
 nsresult
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -21,16 +21,18 @@
 #include "marketplace-stage.inc"
 #include "xpcshell.inc"
 // Trusted Hosted Apps Certificates
 #include "manifest-signing-root.inc"
 #include "manifest-signing-test-root.inc"
 // Add-on signing Certificates
 #include "addons-public.inc"
 #include "addons-stage.inc"
+// Privileged Package Certificates
+#include "privileged-package-root.inc"
 
 using namespace mozilla::pkix;
 
 extern PRLogModuleInfo* gPIPNSSLog;
 
 static const unsigned int DEFAULT_MIN_RSA_BITS = 2048;
 
 namespace mozilla { namespace psm {
@@ -89,16 +91,21 @@ AppTrustDomain::SetTrustedRoot(AppTruste
       trustedDER.len = mozilla::ArrayLength(addonsPublicRoot);
       break;
 
     case nsIX509CertDB::AddonsStageRoot:
       trustedDER.data = const_cast<uint8_t*>(addonsStageRoot);
       trustedDER.len = mozilla::ArrayLength(addonsStageRoot);
       break;
 
+    case nsIX509CertDB::PrivilegedPackageRoot:
+      trustedDER.data = const_cast<uint8_t*>(privilegedPackageRoot);
+      trustedDER.len = mozilla::ArrayLength(privilegedPackageRoot);
+      break;
+
     default:
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
       return SECFailure;
   }
 
   mTrustedRoot = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
                                          &trustedDER, nullptr, false, true);
   if (!mTrustedRoot) {
--- a/security/apps/gen_cert_header.py
+++ b/security/apps/gen_cert_header.py
@@ -32,13 +32,14 @@ array_names = [
   'marketplaceDevPublicRoot',
   'marketplaceDevReviewersRoot',
   'marketplaceStageRoot',
   'trustedAppPublicRoot',
   'trustedAppTestRoot',
   'xpcshellRoot',
   'addonsPublicRoot',
   'addonsStageRoot',
+  'privilegedPackageRoot',
 ]
 
 for n in array_names:
   # Make sure the lambda captures the right string.
   globals()[n] = lambda header, cert_filename, name=n: header.write(_create_header(name, _file_byte_generator(cert_filename)))
--- a/security/apps/moz.build
+++ b/security/apps/moz.build
@@ -31,15 +31,16 @@ headers_arrays_certs = [
     ('marketplace-dev-public.inc', 'marketplaceDevPublicRoot', 'marketplace-dev-public.crt'),
     ('marketplace-dev-reviewers.inc', 'marketplaceDevReviewersRoot', 'marketplace-dev-reviewers.crt'),
     ('marketplace-stage.inc', 'marketplaceStageRoot', 'marketplace-stage.crt'),
     ('manifest-signing-root.inc', 'trustedAppPublicRoot', 'trusted-app-public.der'),
     ('manifest-signing-test-root.inc', 'trustedAppTestRoot', test_ssl_path + '/test_signed_manifest/trusted_ca1.der'),
     ('xpcshell.inc', 'xpcshellRoot', test_ssl_path + '/test_signed_apps/trusted_ca1.der'),
     ('addons-public.inc', 'addonsPublicRoot', 'addons-public.crt'),
     ('addons-stage.inc', 'addonsStageRoot', 'addons-stage.crt'),
+    ('privileged-package-root.inc', 'privilegedPackageRoot', 'privileged-package-root.der'),
 ]
 
 for header, array_name, cert in headers_arrays_certs:
     GENERATED_FILES += [header]
     h = GENERATED_FILES[header]
     h.script = 'gen_cert_header.py:' + array_name
     h.inputs = [cert]
--- a/security/manager/ssl/nsIX509CertDB.idl
+++ b/security/manager/ssl/nsIX509CertDB.idl
@@ -313,16 +313,17 @@ interface nsIX509CertDB : nsISupports {
   const AppTrustedRoot AppMarketplaceProdPublicRoot = 1;
   const AppTrustedRoot AppMarketplaceProdReviewersRoot = 2;
   const AppTrustedRoot AppMarketplaceDevPublicRoot = 3;
   const AppTrustedRoot AppMarketplaceDevReviewersRoot = 4;
   const AppTrustedRoot AppMarketplaceStageRoot = 5;
   const AppTrustedRoot AppXPCShellRoot = 6;
   const AppTrustedRoot AddonsPublicRoot = 7;
   const AppTrustedRoot AddonsStageRoot = 8;
+  const AppTrustedRoot PrivilegedPackageRoot = 9;
   void openSignedAppFileAsync(in AppTrustedRoot trustedRoot,
                               in nsIFile aJarFile,
                               in nsIOpenSignedAppFileCallback callback);
 
   /**
    *  Verifies the signature on a directory representing an unpacked signed
    *  JAR file. To be considered valid, there must be exactly one signature
    *  on the directory structure and that signature must have signed every
@@ -339,25 +340,33 @@ interface nsIX509CertDB : nsISupports {
                                   in nsIVerifySignedDirectoryCallback callback);
 
   /**
    * Given streams containing a signature and a manifest file, verifies
    * that the signature is valid for the manifest. The signature must
    * come from a certificate that is trusted for code signing and that
    * was issued by the given trusted root.
    *
+   * Async version:
    *  On success, NS_OK and the trusted certificate that signed the
    *  Manifest are returned.
    *
    *  On failure, an error code is returned.
+   *
+   * Sync version:
+   *  If verification fails, a null object will be returned.
+   *  Otherwise, the trusted certificate that signed the manifest will be returned
    */
   void verifySignedManifestAsync(in AppTrustedRoot trustedRoot,
                                  in nsIInputStream aManifestStream,
                                  in nsIInputStream aSignatureStream,
                                  in nsIVerifySignedManifestCallback callback);
+  nsIX509Cert verifySignedManifestSync(in AppTrustedRoot trustedRoot,
+                                       in nsIInputStream aManifestStream,
+                                       in nsIInputStream aSignatureStream);
 
   /*
    * Add a cert to a cert DB from a binary string.
    *
    * @param certDER The raw DER encoding of a certificate.
    * @param aTrust decoded by CERT_DecodeTrustString. 3 comma separated characters,
    *                indicating SSL, Email, and Obj signing trust
    * @param aName name of the cert for display purposes.