зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1059216 - Verification of Trusted Hosted Apps manifest signature, part 1. r=dkeeler,rlb
This commit is contained in:
Родитель
2915e7de92
Коммит
8818f4947f
|
@ -22,7 +22,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
|
||||
// Shared code for AppsServiceChild.jsm, TrustedHostedAppsUtils.jsm,
|
||||
// Webapps.jsm and Webapps.js
|
||||
|
||||
this.EXPORTED_SYMBOLS =
|
||||
["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
|
||||
|
@ -116,6 +117,84 @@ this.AppsUtils = {
|
|||
return obj;
|
||||
},
|
||||
|
||||
// Creates a nsILoadContext object with a given appId and isBrowser flag.
|
||||
createLoadContext: function createLoadContext(aAppId, aIsBrowser) {
|
||||
return {
|
||||
associatedWindow: null,
|
||||
topWindow : null,
|
||||
appId: aAppId,
|
||||
isInBrowserElement: aIsBrowser,
|
||||
usePrivateBrowsing: false,
|
||||
isContent: false,
|
||||
|
||||
isAppOfType: function(appType) {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext,
|
||||
Ci.nsIInterfaceRequestor,
|
||||
Ci.nsISupports]),
|
||||
getInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsILoadContext))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Sends data downloaded from aRequestChannel to a file
|
||||
// identified by aId and aFileName.
|
||||
getFile: function(aRequestChannel, aId, aFileName) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// Staging the file in TmpD until all the checks are done.
|
||||
let file = FileUtils.getFile("TmpD", ["webapps", aId, aFileName], true);
|
||||
|
||||
// We need an output stream to write the channel content to the out file.
|
||||
let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
// write, create, truncate
|
||||
outputStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
|
||||
let bufferedOutputStream =
|
||||
Cc['@mozilla.org/network/buffered-output-stream;1']
|
||||
.createInstance(Ci.nsIBufferedOutputStream);
|
||||
bufferedOutputStream.init(outputStream, 1024);
|
||||
|
||||
// Create a listener that will give data to the file output stream.
|
||||
let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
|
||||
.createInstance(Ci.nsISimpleStreamListener);
|
||||
|
||||
listener.init(bufferedOutputStream, {
|
||||
onStartRequest: function(aRequest, aContext) {
|
||||
// Nothing to do there anymore.
|
||||
},
|
||||
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
bufferedOutputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: true});
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get a 4XX or a 5XX http status, bail out like if we had a
|
||||
// network error.
|
||||
let responseStatus = aRequestChannel.responseStatus;
|
||||
if (responseStatus >= 400 && responseStatus <= 599) {
|
||||
// unrecoverable error, don't bug the user
|
||||
deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: false});
|
||||
return;
|
||||
}
|
||||
|
||||
deferred.resolve(file);
|
||||
}
|
||||
});
|
||||
aRequestChannel.asyncOpen(listener, null);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
|
||||
debug("getAppByManifestURL " + aManifestURL);
|
||||
// This could be O(1) if |webapps| was a dictionary indexed on manifestURL
|
||||
|
|
|
@ -16,6 +16,8 @@ const APP_TRUSTED_ROOTS= ["AppMarketplaceProdPublicRoot",
|
|||
"AppMarketplaceDevPublicRoot",
|
||||
"AppMarketplaceDevReviewersRoot",
|
||||
"AppMarketplaceStageRoot",
|
||||
"TrustedHostedAppPublicRoot",
|
||||
"TrustedHostedAppTestRoot",
|
||||
"AppXPCShellRoot"];
|
||||
|
||||
this.TrustedRootCertificate = {
|
||||
|
|
|
@ -2,17 +2,25 @@
|
|||
* 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/. */
|
||||
|
||||
/* global Components, Services, dump */
|
||||
/* global Components, Services, dump, AppsUtils, NetUtil, XPCOMUtils */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const signatureFileExtension = ".sig";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["TrustedHostedAppsUtils"];
|
||||
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
// On Android, define the "debug" function as a binding of the "d" function
|
||||
|
@ -65,7 +73,8 @@ this.TrustedHostedAppsUtils = {
|
|||
throw "CERTDB_ERROR";
|
||||
}
|
||||
|
||||
if (siteSecurityService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP, uri.host, 0)) {
|
||||
if (siteSecurityService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP,
|
||||
uri.host, 0)) {
|
||||
debug("\tvalid certificate pinning for host: " + uri.host + "\n");
|
||||
return true;
|
||||
}
|
||||
|
@ -100,7 +109,7 @@ this.TrustedHostedAppsUtils = {
|
|||
.forEach(aList => {
|
||||
// aList[0] contains the directive name.
|
||||
// aList[1..n] contains sources.
|
||||
let directiveName = aList.shift()
|
||||
let directiveName = aList.shift();
|
||||
let sources = aList;
|
||||
|
||||
if ((-1 == validDirectives.indexOf(directiveName))) {
|
||||
|
@ -144,5 +153,105 @@ this.TrustedHostedAppsUtils = {
|
|||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_verifySignedFile: function(aManifestStream, aSignatureStream, aCertDb) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let root = Ci.nsIX509CertDB.TrustedHostedAppPublicRoot;
|
||||
try {
|
||||
// Check if we should use the test certificates.
|
||||
// Please note that this should be changed if we ever allow chages to the
|
||||
// prefs since that would create a way for an attacker to use the test
|
||||
// root for real apps.
|
||||
let useTrustedAppTestCerts = Services.prefs
|
||||
.getBoolPref("dom.mozApps.use_trustedapp_test_certs");
|
||||
if (useTrustedAppTestCerts) {
|
||||
root = Ci.nsIX509CertDB.TrustedHostedAppTestRoot;
|
||||
}
|
||||
} catch (ex) { }
|
||||
|
||||
aCertDb.verifySignedManifestAsync(
|
||||
root, aManifestStream, aSignatureStream,
|
||||
function(aRv, aCert) {
|
||||
if (Components.isSuccessCode(aRv)) {
|
||||
deferred.resolve(aCert);
|
||||
} else if (aRv == Cr.NS_ERROR_FILE_CORRUPTED ||
|
||||
aRv == Cr.NS_ERROR_SIGNED_MANIFEST_FILE_INVALID) {
|
||||
deferred.reject("MANIFEST_SIGNATURE_FILE_INVALID");
|
||||
} else {
|
||||
deferred.reject("MANIFEST_SIGNATURE_VERIFICATION_ERROR");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
verifySignedManifest: function(aApp, aAppId) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
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 mRequestChannel = NetUtil.newChannel(aApp.manifestURL)
|
||||
.QueryInterface(Ci.nsIHttpChannel);
|
||||
mRequestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
mRequestChannel.notificationCallbacks =
|
||||
AppsUtils.createLoadContext(aAppId, false);
|
||||
|
||||
// The manifest signature must be located at the same path as the
|
||||
// manifest and have the same file name, only the file extension
|
||||
// should differ. Any fragment or query parameter will be ignored.
|
||||
let signatureURL;
|
||||
try {
|
||||
let mURL = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService)
|
||||
.newURI(aApp.manifestURL, null, null)
|
||||
.QueryInterface(Ci.nsIURL);
|
||||
signatureURL = mURL.prePath +
|
||||
mURL.directory + mURL.fileBaseName + signatureFileExtension;
|
||||
} catch(e) {
|
||||
deferred.reject("SIGNATURE_PATH_INVALID");
|
||||
return;
|
||||
}
|
||||
|
||||
let sRequestChannel = NetUtil.newChannel(signatureURL)
|
||||
.QueryInterface(Ci.nsIHttpChannel);
|
||||
sRequestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
sRequestChannel.notificationCallbacks =
|
||||
AppsUtils.createLoadContext(aAppId, false);
|
||||
let getAsyncFetchCallback = (resolve, reject) =>
|
||||
(aInputStream, aResult) => {
|
||||
if (!Components.isSuccessCode(aResult)) {
|
||||
debug("Failed to download file");
|
||||
reject("MANIFEST_FILE_UNAVAILABLE");
|
||||
return;
|
||||
}
|
||||
resolve(aInputStream);
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
new Promise((resolve, reject) => {
|
||||
NetUtil.asyncFetch(mRequestChannel,
|
||||
getAsyncFetchCallback(resolve, reject));
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
NetUtil.asyncFetch(sRequestChannel,
|
||||
getAsyncFetchCallback(resolve, reject));
|
||||
})
|
||||
]).then(([aManifestStream, aSignatureStream]) => {
|
||||
this._verifySignedFile(aManifestStream, aSignatureStream, certDb)
|
||||
.then(deferred.resolve, deferred.reject);
|
||||
}, deferred.reject);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2021,7 +2021,7 @@ this.DOMApplicationRegistry = {
|
|||
xhr.setRequestHeader("If-None-Match", app.etag);
|
||||
}
|
||||
xhr.channel.notificationCallbacks =
|
||||
this.createLoadContext(app.installerAppId, app.installerIsBrowser);
|
||||
AppsUtils.createLoadContext(app.installerAppId, app.installerIsBrowser);
|
||||
|
||||
xhr.addEventListener("load", onload.bind(this, xhr, oldManifest), false);
|
||||
xhr.addEventListener("error", (function() {
|
||||
|
@ -2052,30 +2052,6 @@ this.DOMApplicationRegistry = {
|
|||
});
|
||||
},
|
||||
|
||||
// Creates a nsILoadContext object with a given appId and isBrowser flag.
|
||||
createLoadContext: function createLoadContext(aAppId, aIsBrowser) {
|
||||
return {
|
||||
associatedWindow: null,
|
||||
topWindow : null,
|
||||
appId: aAppId,
|
||||
isInBrowserElement: aIsBrowser,
|
||||
usePrivateBrowsing: false,
|
||||
isContent: false,
|
||||
|
||||
isAppOfType: function(appType) {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext,
|
||||
Ci.nsIInterfaceRequestor,
|
||||
Ci.nsISupports]),
|
||||
getInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsILoadContext))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updatePackagedApp: Task.async(function*(aData, aId, aApp, aNewManifest) {
|
||||
debug("updatePackagedApp");
|
||||
|
@ -2342,8 +2318,8 @@ this.DOMApplicationRegistry = {
|
|||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
xhr.open("GET", app.manifestURL, true);
|
||||
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId,
|
||||
aData.isBrowser);
|
||||
xhr.channel.notificationCallbacks = AppsUtils.createLoadContext(aData.appId,
|
||||
aData.isBrowser);
|
||||
xhr.responseType = "json";
|
||||
|
||||
xhr.addEventListener("load", (function() {
|
||||
|
@ -2455,8 +2431,8 @@ this.DOMApplicationRegistry = {
|
|||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
xhr.open("GET", app.manifestURL, true);
|
||||
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId,
|
||||
aData.isBrowser);
|
||||
xhr.channel.notificationCallbacks = AppsUtils.createLoadContext(aData.appId,
|
||||
aData.isBrowser);
|
||||
xhr.responseType = "json";
|
||||
|
||||
xhr.addEventListener("load", (function() {
|
||||
|
@ -3219,52 +3195,15 @@ this.DOMApplicationRegistry = {
|
|||
_getPackage: function(aRequestChannel, aId, aOldApp, aNewApp) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// Staging the zip in TmpD until all the checks are done.
|
||||
let zipFile =
|
||||
FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
|
||||
|
||||
// We need an output stream to write the channel content to the zip file.
|
||||
let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
// write, create, truncate
|
||||
outputStream.init(zipFile, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
|
||||
let bufferedOutputStream =
|
||||
Cc['@mozilla.org/network/buffered-output-stream;1']
|
||||
.createInstance(Ci.nsIBufferedOutputStream);
|
||||
bufferedOutputStream.init(outputStream, 1024);
|
||||
|
||||
// Create a listener that will give data to the file output stream.
|
||||
let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
|
||||
.createInstance(Ci.nsISimpleStreamListener);
|
||||
|
||||
listener.init(bufferedOutputStream, {
|
||||
onStartRequest: function(aRequest, aContext) {
|
||||
// Nothing to do there anymore.
|
||||
},
|
||||
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
bufferedOutputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject("NETWORK_ERROR");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get a 4XX or a 5XX http status, bail out like if we had a
|
||||
// network error.
|
||||
let responseStatus = aRequestChannel.responseStatus;
|
||||
if (responseStatus >= 400 && responseStatus <= 599) {
|
||||
// unrecoverable error, don't bug the user
|
||||
aOldApp.downloadAvailable = false;
|
||||
deferred.reject("NETWORK_ERROR");
|
||||
return;
|
||||
}
|
||||
|
||||
deferred.resolve(zipFile);
|
||||
AppsUtils.getFile(aRequestChannel, aId, "application.zip").then((aFile) => {
|
||||
deferred.resolve(aFile);
|
||||
}, function(rejectStatus) {
|
||||
debug("Failed to download package file: " + rejectStatus.msg);
|
||||
if (!rejectStatus.downloadAvailable) {
|
||||
aOldApp.downloadAvailable = false;
|
||||
}
|
||||
deferred.reject(rejectStatus.msg);
|
||||
});
|
||||
aRequestChannel.asyncOpen(listener, null);
|
||||
|
||||
// send a first progress event to correctly set the DOM object's properties
|
||||
this._sendDownloadProgressEvent(aNewApp, 0);
|
||||
|
|
|
@ -203,6 +203,9 @@ XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE , "An entry in the JAR is to
|
|||
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_INVALID , "An entry in the JAR is invalid.")
|
||||
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID , "The JAR's manifest or signature file is invalid.")
|
||||
|
||||
/* Codes related to signed manifests */
|
||||
XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID , "The signed app manifest or signature file is invalid.")
|
||||
|
||||
/* Codes for printing-related errors. */
|
||||
XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE , "No printers available.")
|
||||
XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND , "The selected printer could not be found.")
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkix/pkixnss.h"
|
||||
#include "pkix/ScopedPtr.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "CryptoTask.h"
|
||||
#include "AppTrustDomain.h"
|
||||
|
@ -20,16 +21,20 @@
|
|||
#include "nsDataSignatureVerifier.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIFileStreams.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIStringEnumerator.h"
|
||||
#include "nsIZipReader.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsNSSCertificate.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "NSSCertDBTrustDomain.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTHashtable.h"
|
||||
|
||||
#include "base64.h"
|
||||
#include "certdb.h"
|
||||
#include "nssb64.h"
|
||||
#include "secmime.h"
|
||||
#include "plstr.h"
|
||||
#include "prlog.h"
|
||||
|
@ -768,6 +773,82 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VerifySignedManifest(AppTrustedRoot aTrustedRoot,
|
||||
nsIInputStream* aManifestStream,
|
||||
nsIInputStream* aSignatureStream,
|
||||
/*out, optional */ nsIX509Cert** aSignerCert)
|
||||
{
|
||||
NS_ENSURE_ARG(aManifestStream);
|
||||
NS_ENSURE_ARG(aSignatureStream);
|
||||
|
||||
if (aSignerCert) {
|
||||
*aSignerCert = nullptr;
|
||||
}
|
||||
|
||||
// Load signature file in buffer
|
||||
ScopedAutoSECItem signatureBuffer;
|
||||
nsresult rv = ReadStream(aSignatureStream, signatureBuffer);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
signatureBuffer.type = siBuffer;
|
||||
|
||||
// Load manifest file in buffer
|
||||
ScopedAutoSECItem manifestBuffer;
|
||||
rv = ReadStream(aManifestStream, manifestBuffer);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Calculate SHA1 digest of the manifest buffer
|
||||
Digest manifestCalculatedDigest;
|
||||
rv = manifestCalculatedDigest.DigestBuf(SEC_OID_SHA1,
|
||||
manifestBuffer.data,
|
||||
manifestBuffer.len - 1); // buffer is null terminated
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Get base64 encoded string from manifest buffer digest
|
||||
ScopedPtr<char, PORT_Free_string> base64EncDigest(NSSBase64_EncodeItem(nullptr,
|
||||
nullptr, 0, const_cast<SECItem*>(&manifestCalculatedDigest.get())));
|
||||
if (NS_WARN_IF(!base64EncDigest)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Calculate SHA1 digest of the base64 encoded string
|
||||
Digest doubleDigest;
|
||||
rv = doubleDigest.DigestBuf(SEC_OID_SHA1,
|
||||
reinterpret_cast<uint8_t*>(base64EncDigest.get()),
|
||||
strlen(base64EncDigest.get()));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Verify the manifest signature (signed digest of the base64 encoded string)
|
||||
ScopedCERTCertList builtChain;
|
||||
rv = VerifySignature(aTrustedRoot, signatureBuffer,
|
||||
doubleDigest.get(), builtChain);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Return the signer's certificate to the reader if they want it.
|
||||
if (aSignerCert) {
|
||||
MOZ_ASSERT(CERT_LIST_HEAD(builtChain));
|
||||
nsCOMPtr<nsIX509Cert> signerCert =
|
||||
nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert);
|
||||
if (NS_WARN_IF(!signerCert)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
signerCert.forget(aSignerCert);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class OpenSignedAppFileTask MOZ_FINAL : public CryptoTask
|
||||
{
|
||||
public:
|
||||
|
@ -803,6 +884,44 @@ private:
|
|||
nsCOMPtr<nsIX509Cert> mSignerCert; // out
|
||||
};
|
||||
|
||||
class VerifySignedmanifestTask MOZ_FINAL : public CryptoTask
|
||||
{
|
||||
public:
|
||||
VerifySignedmanifestTask(AppTrustedRoot aTrustedRoot,
|
||||
nsIInputStream* aManifestStream,
|
||||
nsIInputStream* aSignatureStream,
|
||||
nsIVerifySignedManifestCallback* aCallback)
|
||||
: mTrustedRoot(aTrustedRoot)
|
||||
, mManifestStream(aManifestStream)
|
||||
, mSignatureStream(aSignatureStream)
|
||||
, mCallback(
|
||||
new nsMainThreadPtrHolder<nsIVerifySignedManifestCallback>(aCallback))
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
virtual nsresult CalculateResult() MOZ_OVERRIDE
|
||||
{
|
||||
return VerifySignedManifest(mTrustedRoot, mManifestStream,
|
||||
mSignatureStream, getter_AddRefs(mSignerCert));
|
||||
}
|
||||
|
||||
// nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
|
||||
// needs to be released
|
||||
virtual void ReleaseNSSResources() { }
|
||||
|
||||
virtual void CallCallback(nsresult rv)
|
||||
{
|
||||
(void) mCallback->VerifySignedManifestFinished(rv, mSignerCert);
|
||||
}
|
||||
|
||||
const AppTrustedRoot mTrustedRoot;
|
||||
const nsCOMPtr<nsIInputStream> mManifestStream;
|
||||
const nsCOMPtr<nsIInputStream> mSignatureStream;
|
||||
nsMainThreadPtrHandle<nsIVerifySignedManifestCallback> mCallback;
|
||||
nsCOMPtr<nsIX509Cert> mSignerCert; // out
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -817,3 +936,18 @@ nsNSSCertificateDB::OpenSignedAppFileAsync(
|
|||
aCallback));
|
||||
return task->Dispatch("SignedJAR");
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSCertificateDB::VerifySignedManifestAsync(
|
||||
AppTrustedRoot aTrustedRoot, nsIInputStream* aManifestStream,
|
||||
nsIInputStream* aSignatureStream, nsIVerifySignedManifestCallback* aCallback)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aManifestStream);
|
||||
NS_ENSURE_ARG_POINTER(aSignatureStream);
|
||||
NS_ENSURE_ARG_POINTER(aCallback);
|
||||
|
||||
RefPtr<VerifySignedmanifestTask> task(
|
||||
new VerifySignedmanifestTask(aTrustedRoot, aManifestStream,
|
||||
aSignatureStream, aCallback));
|
||||
return task->Dispatch("SignedManifest");
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
#include "marketplace-dev-reviewers.inc"
|
||||
#include "marketplace-stage.inc"
|
||||
#include "xpcshell.inc"
|
||||
// Trusted Hosted Apps Certificates
|
||||
#include "manifest-signing-root.inc"
|
||||
#include "manifest-signing-test-root.inc"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
|
@ -79,6 +82,16 @@ AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot)
|
|||
trustedDER.len = mozilla::ArrayLength(xpcshellRoot);
|
||||
break;
|
||||
|
||||
case nsIX509CertDB::TrustedHostedAppPublicRoot:
|
||||
trustedDER.data = const_cast<uint8_t*>(trustedAppPublicRoot);
|
||||
trustedDER.len = mozilla::ArrayLength(trustedAppPublicRoot);
|
||||
break;
|
||||
|
||||
case nsIX509CertDB::TrustedHostedAppTestRoot:
|
||||
trustedDER.data = const_cast<uint8_t*>(trustedAppTestRoot);
|
||||
trustedDER.len = mozilla::ArrayLength(trustedAppTestRoot);
|
||||
break;
|
||||
|
||||
default:
|
||||
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
|
||||
return SECFailure;
|
||||
|
|
|
@ -12,6 +12,7 @@ interface nsIFile;
|
|||
interface nsIInterfaceRequestor;
|
||||
interface nsIZipReader;
|
||||
interface nsIX509CertList;
|
||||
interface nsIInputStream;
|
||||
|
||||
%{C++
|
||||
#define NS_X509CERTDB_CONTRACTID "@mozilla.org/security/x509certdb;1"
|
||||
|
@ -27,11 +28,18 @@ interface nsIOpenSignedAppFileCallback : nsISupports
|
|||
in nsIX509Cert aSignerCert);
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(3d6a9c87-5c5f-46fc-9410-96da6092f0f2)]
|
||||
interface nsIVerifySignedManifestCallback : nsISupports
|
||||
{
|
||||
void verifySignedManifestFinished(in nsresult rv,
|
||||
in nsIX509Cert aSignerCert);
|
||||
};
|
||||
|
||||
/**
|
||||
* This represents a service to access and manipulate
|
||||
* X.509 certificates stored in a database.
|
||||
*/
|
||||
[scriptable, uuid(dd6e4af8-23bb-41d9-a1e3-9ce925429f2f)]
|
||||
[scriptable, uuid(8b01c2af-3a44-44d3-8ea5-51c2455e6c4b)]
|
||||
interface nsIX509CertDB : nsISupports {
|
||||
|
||||
/**
|
||||
|
@ -301,10 +309,28 @@ interface nsIX509CertDB : nsISupports {
|
|||
const AppTrustedRoot AppMarketplaceDevReviewersRoot = 4;
|
||||
const AppTrustedRoot AppMarketplaceStageRoot = 5;
|
||||
const AppTrustedRoot AppXPCShellRoot = 6;
|
||||
const AppTrustedRoot TrustedHostedAppPublicRoot = 7;
|
||||
const AppTrustedRoot TrustedHostedAppTestRoot = 8;
|
||||
void openSignedAppFileAsync(in AppTrustedRoot trustedRoot,
|
||||
in nsIFile aJarFile,
|
||||
in nsIOpenSignedAppFileCallback 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.
|
||||
*
|
||||
* On success, NS_OK and the trusted certificate that signed the
|
||||
* Manifest are returned.
|
||||
*
|
||||
* On failure, an error code is returned.
|
||||
*/
|
||||
void verifySignedManifestAsync(in AppTrustedRoot trustedRoot,
|
||||
in nsIInputStream aManifestStream,
|
||||
in nsIInputStream aSignatureStream,
|
||||
in nsIVerifySignedManifestCallback callback);
|
||||
|
||||
/*
|
||||
* Add a cert to a cert DB from a binary string.
|
||||
*
|
||||
|
|
|
@ -873,6 +873,13 @@
|
|||
ERROR(NS_ERROR_DOM_BLUETOOTH_AUTH_REJECTED, FAILURE(11)),
|
||||
#undef MODULE
|
||||
|
||||
/* ======================================================================= */
|
||||
/* 38: NS_ERROR_MODULE_SIGNED_APP */
|
||||
/* ======================================================================= */
|
||||
#define MODULE NS_ERROR_MODULE_SIGNED_APP
|
||||
ERROR(NS_ERROR_SIGNED_APP_MANIFEST_INVALID, FAILURE(1)),
|
||||
#undef MODULE
|
||||
|
||||
/* ======================================================================= */
|
||||
/* 51: NS_ERROR_MODULE_GENERAL */
|
||||
/* ======================================================================= */
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
#define NS_ERROR_MODULE_SIGNED_JAR 35
|
||||
#define NS_ERROR_MODULE_DOM_FILESYSTEM 36
|
||||
#define NS_ERROR_MODULE_DOM_BLUETOOTH 37
|
||||
#define NS_ERROR_MODULE_SIGNED_APP 38
|
||||
|
||||
/* NS_ERROR_MODULE_GENERAL should be used by modules that do not
|
||||
* care if return code values overlap. Callers of methods that
|
||||
|
|
Загрузка…
Ссылка в новой задаче