--- a/dom/security/ContentVerifier.cpp
+++ b/dom/security/ContentVerifier.cpp
@@ -2,374 +2,228 @@
/* 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 "ContentVerifier.h"
#include "mozilla/fallible.h"
#include "mozilla/Logging.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/StaticPtr.h"
-#include "nsCharSeparatedTokenizer.h"
+#include "MainThreadUtils.h"
#include "nsIInputStream.h"
#include "nsIRequest.h"
-#include "nssb64.h"
-#include "nsSecurityHeaderParser.h"
#include "nsServiceManagerUtils.h"
#include "nsStringStream.h"
-#include "nsThreadUtils.h"
using namespace mozilla;
static LazyLogModule gContentVerifierPRLog("ContentVerifier");
#define CSV_LOG(args) MOZ_LOG(gContentVerifierPRLog, LogLevel::Debug, args)
-// Content-Signature prefix
-const nsLiteralCString kPREFIX = NS_LITERAL_CSTRING("Content-Signature:\x00");
-
-NS_IMPL_ISUPPORTS(ContentVerifier, nsIStreamListener, nsISupports);
+NS_IMPL_ISUPPORTS(ContentVerifier,
+ nsIContentSignatureReceiverCallback,
+ nsIStreamListener);
nsresult
-ContentVerifier::Init(const nsAString& aContentSignatureHeader)
+ContentVerifier::Init(const nsACString& aContentSignatureHeader,
+ nsIRequest* aRequest, nsISupports* aContext)
{
- mVks = Preferences::GetString("browser.newtabpage.remote.keys");
-
- if (aContentSignatureHeader.IsEmpty() || mVks.IsEmpty()) {
- CSV_LOG(
- ("Content-Signature header and verification keys must not be empty!\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aContentSignatureHeader.IsEmpty()) {
+ CSV_LOG(("Content-Signature header must not be empty!\n"));
return NS_ERROR_INVALID_SIGNATURE;
}
- nsresult rv = ParseContentSignatureHeader(aContentSignatureHeader);
- NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
- return CreateContext();
+ // initialise the content signature "service"
+ nsresult rv;
+ mVerifier =
+ do_CreateInstance("@mozilla.org/security/contentsignatureverifier;1", &rv);
+ if (NS_FAILED(rv) || !mVerifier) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // Keep references to the request and context. We need them in FinishSignature
+ // and the ContextCreated callback.
+ mContentRequest = aRequest;
+ mContentContext = aContext;
+
+ return mVerifier->CreateContextWithoutCertChain(
+ this, aContentSignatureHeader,
+ NS_LITERAL_CSTRING("remote-newtab-signer.mozilla.org"));
}
/**
* Implement nsIStreamListener
* We buffer the entire content here and kick off verification
*/
NS_METHOD
AppendNextSegment(nsIInputStream* aInputStream, void* aClosure,
const char* aRawSegment, uint32_t aToOffset, uint32_t aCount,
uint32_t* outWrittenCount)
{
FallibleTArray<nsCString>* decodedData =
static_cast<FallibleTArray<nsCString>*>(aClosure);
- nsAutoCString segment(aRawSegment, aCount);
+ nsDependentCSubstring segment(aRawSegment, aCount);
if (!decodedData->AppendElement(segment, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
*outWrittenCount = aCount;
return NS_OK;
}
+void
+ContentVerifier::FinishSignature()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIStreamListener> nextListener;
+ nextListener.swap(mNextListener);
+
+ // Verify the content:
+ // If this fails, we return an invalid signature error to load a fallback page.
+ // If everthing is good, we return a new stream to the next listener and kick
+ // that one off.
+ bool verified = false;
+ nsresult rv = NS_OK;
+
+ // If the content signature check fails, stop the load
+ // and return a signature error. NSS resources are freed by the
+ // ContentSignatureVerifier on destruction.
+ if (NS_FAILED(mVerifier->End(&verified)) || !verified) {
+ CSV_LOG(("failed to verify content\n"));
+ (void)nextListener->OnStopRequest(mContentRequest, mContentContext,
+ NS_ERROR_INVALID_SIGNATURE);
+ return;
+ }
+ CSV_LOG(("Successfully verified content signature.\n"));
+
+ // We emptied the input stream so we have to create a new one from mContent
+ // to hand it to the consuming listener.
+ for (uint32_t i = 0; i < mContent.Length(); ++i) {
+ nsCOMPtr<nsIInputStream> oInStr;
+ rv = NS_NewCStringInputStream(getter_AddRefs(oInStr), mContent[i]);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ // let the next listener know that there is data in oInStr
+ rv = nextListener->OnDataAvailable(mContentRequest, mContentContext, oInStr,
+ mOffset, mContent[i].Length());
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ // propagate OnStopRequest and return
+ nextListener->OnStopRequest(mContentRequest, mContentContext, rv);
+}
+
NS_IMETHODIMP
ContentVerifier::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
+ MOZ_CRASH("This OnStartRequest should've never been called!");
return NS_OK;
}
NS_IMETHODIMP
ContentVerifier::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
nsresult aStatus)
{
- // Verify the content:
- // If this fails, we return an invalid signature error to load a fallback page.
- // If everthing is good, we return a new stream to the next listener and kick
- // that one of.
- CSV_LOG(("VerifySignedContent, b64signature: %s\n", mSignature.get()));
- CSV_LOG(("VerifySignedContent, key: \n[\n%s\n]\n", mKey.get()));
- bool verified = false;
- nsresult rv = End(&verified);
- if (NS_FAILED(rv) || !verified || NS_FAILED(aStatus)) {
- // cancel the request and return error
- if (NS_FAILED(aStatus)) {
- rv = aStatus;
- } else {
- rv = NS_ERROR_INVALID_SIGNATURE;
- }
- CSV_LOG(("failed to verify content\n"));
- mNextListener->OnStartRequest(aRequest, aContext);
- mNextListener->OnStopRequest(aRequest, aContext, rv);
- return NS_ERROR_INVALID_SIGNATURE;
- }
- CSV_LOG(("Successfully verified content signature.\n"));
+ // Keep references to the request and context. We need them in FinishSignature
+ // and the ContextCreated callback.
+ mContentRequest = aRequest;
+ mContentContext = aContext;
- // start the next listener
- rv = mNextListener->OnStartRequest(aRequest, aContext);
- if (NS_SUCCEEDED(rv)) {
- // We emptied aInStr so we have to create a new one from buf to hand it
- // to the consuming listener.
- for (uint32_t i = 0; i < mContent.Length(); ++i) {
- nsCOMPtr<nsIInputStream> oInStr;
- rv = NS_NewCStringInputStream(getter_AddRefs(oInStr), mContent[i]);
- if (NS_FAILED(rv)) {
- break;
- }
- // let the next listener know that there is data in oInStr
- rv = mNextListener->OnDataAvailable(aRequest, aContext, oInStr, 0,
- mContent[i].Length());
- if (NS_FAILED(rv)) {
- break;
- }
- }
+ // If we don't have a next listener, we handed off this request already.
+ // Return, there's nothing to do here.
+ if (!mNextListener) {
+ return NS_OK;
}
- // propagate OnStopRequest and return
- return mNextListener->OnStopRequest(aRequest, aContext, rv);
+ if (NS_FAILED(aStatus)) {
+ CSV_LOG(("Stream failed\n"));
+ nsCOMPtr<nsIStreamListener> nextListener;
+ nextListener.swap(mNextListener);
+ return nextListener->OnStopRequest(aRequest, aContext, aStatus);
+ }
+
+ mContentRead = true;
+
+ // If the ContentSignatureVerifier is initialised, finish the verification.
+ if (mContextCreated) {
+ FinishSignature();
+ return aStatus;
+ }
+
+ return NS_OK;
}
NS_IMETHODIMP
ContentVerifier::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
nsIInputStream* aInputStream, uint64_t aOffset,
uint32_t aCount)
{
// buffer the entire stream
uint32_t read;
nsresult rv = aInputStream->ReadSegments(AppendNextSegment, &mContent, aCount,
&read);
if (NS_FAILED(rv)) {
return rv;
}
- // update the signature verifier
- return Update(mContent[mContent.Length()-1]);
-}
-
-/**
- * ContentVerifier logic and utils
- */
-
-nsresult
-ContentVerifier::GetVerificationKey(const nsAString& aKeyId)
-{
- // get verification keys from the pref and see if we have |aKeyId|
- nsCharSeparatedTokenizer tokenizerVK(mVks, ';');
- while (tokenizerVK.hasMoreTokens()) {
- nsDependentSubstring token = tokenizerVK.nextToken();
- nsCharSeparatedTokenizer tokenizerKey(token, '=');
- nsString prefKeyId;
- if (tokenizerKey.hasMoreTokens()) {
- prefKeyId = tokenizerKey.nextToken();
- }
- nsString key;
- if (tokenizerKey.hasMoreTokens()) {
- key = tokenizerKey.nextToken();
- }
- if (prefKeyId.Equals(aKeyId)) {
- mKey.Assign(NS_ConvertUTF16toUTF8(key));
- return NS_OK;
- }
+ // Update the signature verifier if the context has been created.
+ if (mContextCreated) {
+ return mVerifier->Update(mContent.LastElement());
}
- // we didn't find the appropriate key
- return NS_ERROR_INVALID_SIGNATURE;
-}
-
-nsresult
-ContentVerifier::ParseContentSignatureHeader(
- const nsAString& aContentSignatureHeader)
-{
- // We only support p384 ecdsa according to spec
- NS_NAMED_LITERAL_CSTRING(keyid_var, "keyid");
- NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
-
- nsAutoString contentSignature;
- nsAutoString keyId;
- nsAutoCString header = NS_ConvertUTF16toUTF8(aContentSignatureHeader);
- nsSecurityHeaderParser parser(header.get());
- nsresult rv = parser.Parse();
- if (NS_FAILED(rv)) {
- CSV_LOG(("ContentVerifier: could not parse ContentSignature header\n"));
- return NS_ERROR_INVALID_SIGNATURE;
- }
- LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
-
- for (nsSecurityHeaderDirective* directive = directives->getFirst();
- directive != nullptr; directive = directive->getNext()) {
- CSV_LOG(("ContentVerifier: found directive %s\n", directive->mName.get()));
- if (directive->mName.Length() == keyid_var.Length() &&
- directive->mName.EqualsIgnoreCase(keyid_var.get(),
- keyid_var.Length())) {
- if (!keyId.IsEmpty()) {
- CSV_LOG(("ContentVerifier: found two keyIds\n"));
- return NS_ERROR_INVALID_SIGNATURE;
- }
-
- CSV_LOG(("ContentVerifier: found a keyid directive\n"));
- keyId = NS_ConvertUTF8toUTF16(directive->mValue);
- rv = GetVerificationKey(keyId);
- NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
- }
- if (directive->mName.Length() == signature_var.Length() &&
- directive->mName.EqualsIgnoreCase(signature_var.get(),
- signature_var.Length())) {
- if (!contentSignature.IsEmpty()) {
- CSV_LOG(("ContentVerifier: found two ContentSignatures\n"));
- return NS_ERROR_INVALID_SIGNATURE;
- }
-
- CSV_LOG(("ContentVerifier: found a ContentSignature directive\n"));
- contentSignature = NS_ConvertUTF8toUTF16(directive->mValue);
- mSignature = directive->mValue;
- }
- }
-
- // we have to ensure that we found a key and a signature at this point
- if (mKey.IsEmpty()) {
- CSV_LOG(("ContentVerifier: got a Content-Signature header but didn't find "
- "an appropriate key.\n"));
- return NS_ERROR_INVALID_SIGNATURE;
- }
- if (mSignature.IsEmpty()) {
- CSV_LOG(("ContentVerifier: got a Content-Signature header but didn't find "
- "a signature.\n"));
- return NS_ERROR_INVALID_SIGNATURE;
- }
+ // update offset
+ mOffset += aCount;
return NS_OK;
}
-/**
- * Parse signature, public key, and algorithm data for input to verification
- * functions in VerifyData and CreateContext.
- *
- * https://datatracker.ietf.org/doc/draft-thomson-http-content-signature/
- * If aSignature is a content signature, the function returns
- * NS_ERROR_INVALID_SIGNATURE if anything goes wrong. Only p384 with sha384
- * is supported and aSignature is a raw signature (r||s).
- */
-nsresult
-ContentVerifier::ParseInput(ScopedSECKEYPublicKey& aPublicKeyOut,
- ScopedSECItem& aSignatureItemOut,
- SECOidTag& aOidOut,
- const nsNSSShutDownPreventionLock&)
+NS_IMETHODIMP
+ContentVerifier::ContextCreated(bool successful)
{
- // Base 64 decode the key
- ScopedSECItem keyItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
- if (!keyItem ||
- !NSSBase64_DecodeBuffer(nullptr, keyItem,
- mKey.get(),
- mKey.Length())) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!successful) {
+ // If we don't have a next listener, the request has been handed off already.
+ if (!mNextListener) {
+ return NS_OK;
+ }
+ // Get local reference to mNextListener and null it to ensure that we don't
+ // call it twice.
+ nsCOMPtr<nsIStreamListener> nextListener;
+ nextListener.swap(mNextListener);
- // Extract the public key from the keyItem
- ScopedCERTSubjectPublicKeyInfo pki(
- SECKEY_DecodeDERSubjectPublicKeyInfo(keyItem));
- if (!pki) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
- aPublicKeyOut = SECKEY_ExtractPublicKey(pki.get());
+ // Make sure that OnStartRequest was called and we have a request.
+ MOZ_ASSERT(mContentRequest);
- // in case we were not able to extract a key
- if (!aPublicKeyOut) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
+ // In this case something went wrong with the cert. Let's stop this load.
+ CSV_LOG(("failed to get a valid cert chain\n"));
+ if (mContentRequest && nextListener) {
+ mContentRequest->Cancel(NS_ERROR_INVALID_SIGNATURE);
+ return nextListener->OnStopRequest(mContentRequest, mContentContext,
+ NS_ERROR_INVALID_SIGNATURE);
+ }
- // Base 64 decode the signature
- ScopedSECItem rawSignatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
- if (!rawSignatureItem ||
- !NSSBase64_DecodeBuffer(nullptr, rawSignatureItem,
- mSignature.get(),
- mSignature.Length())) {
- return NS_ERROR_INVALID_SIGNATURE;
+ // We should never get here!
+ MOZ_ASSERT_UNREACHABLE(
+ "ContentVerifier was used without getting OnStartRequest!");
+ return NS_OK;
}
- // get signature object and oid
- if (!aSignatureItemOut) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
- // We have a raw ecdsa signature r||s so we have to DER-encode it first
- // Note that we have to check rawSignatureItem->len % 2 here as
- // DSAU_EncodeDerSigWithLen asserts this
- if (rawSignatureItem->len == 0 || rawSignatureItem->len % 2 != 0) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
- if (DSAU_EncodeDerSigWithLen(aSignatureItemOut, rawSignatureItem,
- rawSignatureItem->len) != SECSuccess) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
- aOidOut = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
-
- return NS_OK;
-}
-
-/**
- * Create a context for a signature verification.
- * It sets signature, public key, and algorithms that should be used to verify
- * the data. It also updates the verification buffer with the content-signature
- * prefix.
- */
-nsresult
-ContentVerifier::CreateContext()
-{
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- return NS_ERROR_INVALID_SIGNATURE;
+ // In this case the content verifier is initialised and we have to feed it
+ // the buffered content.
+ mContextCreated = true;
+ for (size_t i = 0; i < mContent.Length(); ++i) {
+ if (NS_FAILED(mVerifier->Update(mContent[i]))) {
+ // Bail out if this fails. We can't return an error here, but if this
+ // failed, NS_ERROR_INVALID_SIGNATURE is returned in FinishSignature.
+ break;
+ }
}
- // Bug 769521: We have to change b64 url to regular encoding as long as we
- // don't have a b64 url decoder. This should change soon, but in the meantime
- // we have to live with this.
- mSignature.ReplaceChar('-', '+');
- mSignature.ReplaceChar('_', '/');
-
- ScopedSECKEYPublicKey publicKey;
- ScopedSECItem signatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
- SECOidTag oid;
- nsresult rv = ParseInput(publicKey, signatureItem, oid, locker);
- if (NS_FAILED(rv)) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
-
- mCx = UniqueVFYContext(VFY_CreateContext(publicKey, signatureItem, oid, NULL));
- if (!mCx) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
-
- if (VFY_Begin(mCx.get()) != SECSuccess) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
-
- // add the prefix to the verification buffer
- return Update(kPREFIX);
-}
-
-/**
- * Add data to the context that should be verified.
- */
-nsresult
-ContentVerifier::Update(const nsACString& aData)
-{
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
-
- if (!aData.IsEmpty()) {
- if (VFY_Update(mCx.get(),
- (const unsigned char*)nsPromiseFlatCString(aData).get(),
- aData.Length()) != SECSuccess) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
+ // We read all content, let's verify the signature.
+ if (mContentRead) {
+ FinishSignature();
}
return NS_OK;
}
-
-/**
- * Finish signature verification and return the result in _retval.
- */
-nsresult
-ContentVerifier::End(bool* _retval)
-{
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- return NS_ERROR_INVALID_SIGNATURE;
- }
-
- *_retval = (VFY_End(mCx.get()) == SECSuccess);
-
- return NS_OK;
-}
\ No newline at end of file
--- a/dom/security/ContentVerifier.h
+++ b/dom/security/ContentVerifier.h
@@ -2,97 +2,65 @@
/* 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/. */
#ifndef mozilla_dom_ContentVerifier_h
#define mozilla_dom_ContentVerifier_h
#include "nsCOMPtr.h"
+#include "nsIContentSignatureVerifier.h"
#include "nsIObserver.h"
#include "nsIStreamListener.h"
-#include "nsNSSShutDown.h"
#include "nsString.h"
#include "nsTArray.h"
-#include "ScopedNSSTypes.h"
/**
* Mediator intercepting OnStartRequest in nsHttpChannel, blocks until all
* data is read from the input stream, verifies the content signature and
* releases the request to the next listener if the verification is successful.
* If the verification fails or anything else goes wrong, a
* NS_ERROR_INVALID_SIGNATURE is thrown.
*/
class ContentVerifier : public nsIStreamListener
- , public nsNSSShutDownObject
+ , public nsIContentSignatureReceiverCallback
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSICONTENTSIGNATURERECEIVERCALLBACK
explicit ContentVerifier(nsIStreamListener* aMediatedListener,
nsISupports* aMediatedContext)
: mNextListener(aMediatedListener)
- , mContext(aMediatedContext)
- , mCx(nullptr) {}
-
- nsresult Init(const nsAString& aContentSignatureHeader);
+ , mContextCreated(false)
+ , mContentRead(false) {}
- // nsNSSShutDownObject
- virtual void virtualDestroyNSSReference() override
- {
- destructorSafeDestroyNSSReference();
- }
+ nsresult Init(const nsACString& aContentSignatureHeader, nsIRequest* aRequest,
+ nsISupports* aContext);
protected:
- virtual ~ContentVerifier()
- {
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- return;
- }
- destructorSafeDestroyNSSReference();
- shutdown(calledFromObject);
- }
-
- void destructorSafeDestroyNSSReference()
- {
- mCx = nullptr;
- }
+ virtual ~ContentVerifier() {}
private:
- nsresult ParseContentSignatureHeader(const nsAString& aContentSignatureHeader);
- nsresult GetVerificationKey(const nsAString& aKeyId);
-
- // utility function to parse input before put into verification functions
- nsresult ParseInput(mozilla::ScopedSECKEYPublicKey& aPublicKeyOut,
- mozilla::ScopedSECItem& aSignatureItemOut,
- SECOidTag& aOidOut,
- const nsNSSShutDownPreventionLock&);
+ void FinishSignature();
- // create a verifier context and store it in mCx
- nsresult CreateContext();
-
- // Adds data to the context that was used to generate the signature.
- nsresult Update(const nsACString& aData);
-
- // Finalises the signature and returns the result of the signature
- // verification.
- nsresult End(bool* _retval);
-
+ // buffered content to verify
+ FallibleTArray<nsCString> mContent;
// content and next listener for nsIStreamListener
nsCOMPtr<nsIStreamListener> mNextListener;
- nsCOMPtr<nsISupports> mContext;
-
- // verifier context for incrementel verifications
- mozilla::UniqueVFYContext mCx;
- // buffered content to verify
- FallibleTArray<nsCString> mContent;
- // signature to verify
- nsCString mSignature;
- // verification key
- nsCString mKey;
- // verification key preference
- nsString mVks;
+ // the verifier
+ nsCOMPtr<nsIContentSignatureVerifier> mVerifier;
+ // holding a pointer to the content request and context to resume/cancel it
+ nsCOMPtr<nsIRequest> mContentRequest;
+ nsCOMPtr<nsISupports> mContentContext;
+ // Semaphors to indicate that the verifying context was created, the entire
+ // content was read resp. The context gets created by ContentSignatureVerifier
+ // and mContextCreated is set in the ContextCreated callback. The content is
+ // read, i.e. mContentRead is set, when the content OnStopRequest is called.
+ bool mContextCreated;
+ bool mContentRead;
+ // bytes read in OnDataAvailable
+ uint64_t mOffset = 0;
};
#endif /* mozilla_dom_ContentVerifier_h */
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -1,15 +1,16 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsAttrValue.h"
+#include "nsCharSeparatedTokenizer.h"
#include "nsContentUtils.h"
#include "nsCSPUtils.h"
#include "nsDebug.h"
#include "nsIConsoleService.h"
#include "nsICryptoHash.h"
#include "nsIScriptError.h"
#include "nsIServiceManager.h"
#include "nsIStringBundle.h"
--- a/dom/security/test/contentverifier/browser.ini
+++ b/dom/security/test/contentverifier/browser.ini
@@ -5,12 +5,13 @@ support-files =
file_about_newtab_bad.html
file_about_newtab_bad_csp.html
file_about_newtab_bad_csp_signature
file_about_newtab_good_signature
file_about_newtab_bad_signature
file_about_newtab_broken_signature
file_about_newtab_sri.html
file_about_newtab_sri_signature
+ goodChain.pem
script.js
style.css
[browser_verify_content_about_newtab.js]
--- a/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
@@ -15,44 +15,45 @@
* - loading a file that doesn't fit the key or signature
* - cache poisoning (load a malicious remote page not in newtab, subsequent
* newtab load has to load the fallback)
*/
const ABOUT_NEWTAB_URI = "about:newtab";
const BASE = "https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?";
-const URI_GOOD = BASE + "sig=good&key=good&file=good&header=good";
+const URI_GOOD = BASE + "sig=good&x5u=good&file=good&header=good";
const INVALIDATE_FILE = BASE + "invalidateFile=yep";
const VALIDATE_FILE = BASE + "validateFile=yep";
-const URI_HEADER_BASE = BASE + "sig=good&key=good&file=good&header=";
+const URI_HEADER_BASE = BASE + "sig=good&x5u=good&file=good&header=";
const URI_ERROR_HEADER = URI_HEADER_BASE + "error";
-const URI_KEYERROR_HEADER = URI_HEADER_BASE + "errorInKeyid";
+const URI_KEYERROR_HEADER = URI_HEADER_BASE + "errorInX5U";
const URI_SIGERROR_HEADER = URI_HEADER_BASE + "errorInSignature";
const URI_NO_HEADER = URI_HEADER_BASE + "noHeader";
-const URI_BAD_SIG = BASE + "sig=bad&key=good&file=good&header=good";
-const URI_BROKEN_SIG = BASE + "sig=broken&key=good&file=good&header=good";
-const URI_BAD_KEY = BASE + "sig=good&key=bad&file=good&header=good";
-const URI_BAD_FILE = BASE + "sig=good&key=good&file=bad&header=good";
-const URI_BAD_ALL = BASE + "sig=bad&key=bad&file=bad&header=bad";
-const URI_BAD_CSP = BASE + "sig=bad-csp&key=good&file=bad-csp&header=good";
+const URI_BAD_SIG = BASE + "sig=bad&x5u=good&file=good&header=good";
+const URI_BROKEN_SIG = BASE + "sig=broken&x5u=good&file=good&header=good";
+const URI_BAD_X5U = BASE + "sig=good&x5u=bad&file=good&header=good";
+const URI_HTTP_X5U = BASE + "sig=good&x5u=http&file=good&header=good";
+const URI_BAD_FILE = BASE + "sig=good&x5u=good&file=bad&header=good";
+const URI_BAD_ALL = BASE + "sig=bad&x5u=bad&file=bad&header=bad";
+const URI_BAD_CSP = BASE + "sig=bad-csp&x5u=good&file=bad-csp&header=good";
-const URI_BAD_FILE_CACHED = BASE + "sig=good&key=good&file=bad&header=good&cached=true";
+const URI_BAD_FILE_CACHED = BASE + "sig=good&x5u=good&file=bad&header=good&cached=true";
const GOOD_ABOUT_STRING = "Just a fully good testpage for Bug 1226928";
const BAD_ABOUT_STRING = "Just a bad testpage for Bug 1226928";
const ABOUT_BLANK = "<head></head><body></body>";
const URI_CLEANUP = BASE + "cleanup=true";
const CLEANUP_DONE = "Done";
-const URI_SRI = BASE + "sig=sri&key=good&file=sri&header=good";
+const URI_SRI = BASE + "sig=sri&x5u=good&file=sri&header=good";
const STYLESHEET_WITHOUT_SRI_BLOCKED = "Stylesheet without SRI blocked";
const STYLESHEET_WITH_SRI_BLOCKED = "Stylesheet with SRI blocked";
const STYLESHEET_WITH_SRI_LOADED = "Stylesheet with SRI loaded";
const SCRIPT_WITHOUT_SRI_BLOCKED = "Script without SRI blocked";
const SCRIPT_WITH_SRI_BLOCKED = "Script with SRI blocked";
const SCRIPT_WITH_SRI_LOADED = "Script with SRI loaded";
const CSP_TEST_SUCCESS_STRING = "CSP violation test succeeded.";
@@ -65,17 +66,18 @@ const TESTS = [
// testStrings : expected strings in the loaded page }
{ "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
{ "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_KEYERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_SIGERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_NO_HEADER, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_BAD_SIG, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_BROKEN_SIG, "testStrings" : [ABOUT_BLANK] },
- { "aboutURI" : URI_BAD_KEY, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_BAD_X5U, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_HTTP_X5U, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_BAD_FILE, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_BAD_ALL, "testStrings" : [ABOUT_BLANK] },
{ "url" : URI_BAD_FILE_CACHED, "testStrings" : [BAD_ABOUT_STRING] },
{ "aboutURI" : URI_BAD_FILE_CACHED, "testStrings" : [ABOUT_BLANK] },
{ "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
{ "aboutURI" : URI_SRI, "testStrings" : [
STYLESHEET_WITHOUT_SRI_BLOCKED,
STYLESHEET_WITH_SRI_LOADED,
@@ -103,27 +105,19 @@ function doTest(aExpectedStrings, reload
// set about:newtab location for this test if it's a newtab test
if (aNewTabPref) {
aboutNewTabService.newTabURL = aNewTabPref;
}
// set prefs
yield pushPrefs(
["browser.newtabpage.remote.content-signing-test", true],
- ["browser.newtabpage.remote", true], [
- "browser.newtabpage.remote.keys",
- "RemoteNewTabNightlyv0=BO9QHuP6E2eLKybql8iuD4o4Np9YFDfW3D+k" +
- "a70EcXXTqZcikc7Am1CwyP1xBDTpEoe6gb9SWzJmaDW3dNh1av2u90VkUM" +
- "B7aHIrImjTjLNg/1oC8GRcTKM4+WzbKF00iA==;OtherKey=eKQJ2fNSId" +
- "CFzL6N326EzZ/5LCeFU5eyq3enwZ5MLmvOw+3gycr4ZVRc36/EiSPsQYHE" +
- "3JvJs1EKs0QCaguHFOZsHwqXMPicwp/gLdeYbuOmN2s1SEf/cxw8GtcxSA" +
- "kG;RemoteNewTab=MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4k3FmG7dFo" +
- "Ot3Tuzl76abTRtK8sb/r/ibCSeVKa96RbrOX2ciscz/TT8wfqBYS/8cN4z" +
- "Me1+f7wRmkNrCUojZR1ZKmYM2BeiUOMlMoqk2O7+uwsn1DwNQSYP58TkvZt6"
- ]);
+ ["browser.newtabpage.remote", true],
+ ["security.content.signature.root_hash",
+ "65:AE:D8:1E:B5:12:AE:B0:6B:38:58:BC:7C:47:35:3D:D4:EA:25:F1:63:DA:08:BB:86:3A:2E:97:39:66:8F:55"]);
if (aNewTabPref === URI_BAD_CSP) {
// Use stricter CSP to test CSP violation.
yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self'; style-src 'self'"]);
} else {
// Use weaker CSP to test normal content.
yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self' 'unsafe-inline'; style-src 'self'"]);
}
--- a/dom/security/test/contentverifier/file_about_newtab_bad_csp_signature
+++ b/dom/security/test/contentverifier/file_about_newtab_bad_csp_signature
@@ -1,1 +1,1 @@
-8qXVAqzuF3TsF6C750u_v_JiRu90WJXf_0xT9x0S4Fgmvolgtfu-KSWq3lYpmk2dxO8u64zaHM3iguZdWAqcSL82RFtV7OPiprt16omCbHCKfVi-Bt_rXILRlexgmRl_
\ No newline at end of file
+oiypz3lb-IyJsmKNsnlp2zDrqncste8yONn9WUE6ksgJWMhSEQ9lp8vRqN0W3JPwJb6uSk16RI-tDv7uy0jxon5jL1BZpqlqIpvimg7FCQEedMKoHZwtE9an-e95sOTd
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_about_newtab_good_signature
+++ b/dom/security/test/contentverifier/file_about_newtab_good_signature
@@ -1,1 +1,1 @@
-XBKzej3i6TAFZc3VZsuCekn-4dYWJBE4-b3OOtKrOV-JIzIvAnAhnOV1aj-kEm07kh-FciIxV-Xk2QUQlRQzHO7oW7E4mXkMKkbbAcvL0CFrItTObhfhKnBnpAE9ql1O
\ No newline at end of file
+-mqpvTYdZX4HYQDW1nScojL7ICw5yj8UF2gzxyLbSCx9UIfHH-gWZ40F_PFtqjHxoC1J3dHDb3VedVhOYczdaLrNKbRvPrlnkdGx7Rl8qEBrtZpF1py1Z9uAGoCrgUHa
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_about_newtab_sri_signature
+++ b/dom/security/test/contentverifier/file_about_newtab_sri_signature
@@ -1,1 +1,1 @@
-i5jOnrZWwyNwrTcIjfJ6fUR-8MhhvhtMvQbdrUD7j8aHTybNolv25v9NwJAT6rVU6kgkxmD_st9Kla086CQmzYQdLhKfzgLbTDXz0-1j23fQnyjsP1_4MNIu2xTea11p
\ No newline at end of file
+yoIyAYiiEzdP1zpkRy3KaqdsjUy62Notku89cytwVwcH0x6fKsMCdM-df1wbk9N28CSTaIOW5kcSenFy5K3nU-zPIoqZDjQo6aSjF8hF6lrw1a1xbhfl9K3g4YJsuWsO
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_contentserver.sjs
+++ b/dom/security/test/contentverifier/file_contentserver.sjs
@@ -1,37 +1,51 @@
+/* -*- 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/. */
// sjs for remote about:newtab (bug 1226928)
+"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.importGlobalProperties(["URLSearchParams"]);
const path = "browser/dom/security/test/contentverifier/";
const goodFileName = "file_about_newtab.html";
const goodFileBase = path + goodFileName;
const goodFile = FileUtils.getDir("TmpD", [], true);
goodFile.append(goodFileName);
const goodSignature = path + "file_about_newtab_good_signature";
-const goodKeyId = "RemoteNewTab";
+const goodX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
const scriptFileName = "script.js";
const cssFileName = "style.css";
const badFile = path + "file_about_newtab_bad.html";
const brokenSignature = path + "file_about_newtab_broken_signature";
const badSignature = path + "file_about_newtab_bad_signature";
-const badKeyId = "OldRemoteNewTabKey";
+const badX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=bad\"";
+const httpX5UString = "\"http://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
const sriFile = path + "file_about_newtab_sri.html";
const sriSignature = path + "file_about_newtab_sri_signature";
const badCspFile = path + "file_about_newtab_bad_csp.html";
const badCspSignature = path + "file_about_newtab_bad_csp_signature";
+// This cert chain is copied from
+// security/manager/ssl/tests/unit/test_content_signing/
+// using the certificates
+// * content_signing_remote_newtab_ee.pem
+// * content_signing_int.pem
+// * content_signing_root.pem
+const goodCertChainPath = path + "goodChain.pem";
+
const tempFileNames = [goodFileName, scriptFileName, cssFileName];
// we copy the file to serve as newtab to a temp directory because
// we modify it during tests.
setupTestFiles();
function setupTestFiles() {
for (let fileName of tempFileNames) {
@@ -110,24 +124,25 @@ function cleanupTestFiles() {
* sig=good&key=good&file=good&header=good&cached=no to serve pages with
* content signatures
*
* it further handles invalidateFile=yep and validateFile=yep to change the
* served file
*/
function handleRequest(request, response) {
let params = new URLSearchParams(request.queryString);
- let keyType = params.get("key");
+ let x5uType = params.get("x5u");
let signatureType = params.get("sig");
let fileType = params.get("file");
let headerType = params.get("header");
let cached = params.get("cached");
let invalidateFile = params.get("invalidateFile");
let validateFile = params.get("validateFile");
let resource = params.get("resource");
+ let x5uParam = params.get("x5u");
if (params.get("cleanup")) {
cleanupTestFiles();
response.setHeader("Content-Type", "text/html", false);
response.write("Done");
return;
}
@@ -166,36 +181,46 @@ function handleRequest(request, response
r = "Error";
}
}
response.setHeader("Content-Type", "text/html", false);
response.write(r);
return;
}
+ // we have to return the certificate chain on request for the x5u parameter
+ if (x5uParam && x5uParam == "default") {
+ response.setHeader("Cache-Control", "max-age=216000", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(loadFile(getFileName(goodCertChainPath, "CurWorkD")));
+ return;
+ }
+
// avoid confusing cache behaviours
if (!cached) {
response.setHeader("Cache-Control", "no-cache", false);
} else {
response.setHeader("Cache-Control", "max-age=3600", false);
}
// send HTML to test allowed/blocked behaviours
response.setHeader("Content-Type", "text/html", false);
// set signature header and key for Content-Signature header
/* By default a good content-signature header is returned. Any broken return
* value has to be indicated in the url.
*/
let csHeader = "";
- let keyId = goodKeyId;
+ let x5uString = goodX5UString;
let signature = goodSignature;
let file = goodFile;
- if (keyType == "bad") {
- keyId = badKeyId;
+ if (x5uType == "bad") {
+ x5uString = badX5UString;
+ } else if (x5uType == "http") {
+ x5uString = httpX5UString;
}
if (signatureType == "bad") {
signature = badSignature;
} else if (signatureType == "broken") {
signature = brokenSignature;
} else if (signatureType == "sri") {
signature = sriSignature;
} else if (signatureType == "bad-csp") {
@@ -206,29 +231,29 @@ function handleRequest(request, response
} else if (fileType == "sri") {
file = getFileName(sriFile, "CurWorkD");
} else if (fileType == "bad-csp") {
file = getFileName(badCspFile, "CurWorkD");
}
if (headerType == "good") {
// a valid content-signature header
- csHeader = "keyid=" + keyId + ";p384ecdsa=" +
+ csHeader = "x5u=" + x5uString + ";p384ecdsa=" +
loadFile(getFileName(signature, "CurWorkD"));
} else if (headerType == "error") {
// this content-signature header is missing ; before p384ecdsa
- csHeader = "keyid=" + keyId + "p384ecdsa=" +
+ csHeader = "x5u=" + x5uString + "p384ecdsa=" +
loadFile(getFileName(signature, "CurWorkD"));
- } else if (headerType == "errorInKeyid") {
+ } else if (headerType == "errorInX5U") {
// this content-signature header is missing the keyid directive
- csHeader = "keid=" + keyId + ";p384ecdsa=" +
+ csHeader = "x6u=" + x5uString + ";p384ecdsa=" +
loadFile(getFileName(signature, "CurWorkD"));
} else if (headerType == "errorInSignature") {
// this content-signature header is missing the p384ecdsa directive
- csHeader = "keyid=" + keyId + ";p385ecdsa=" +
+ csHeader = "x5u=" + x5uString + ";p385ecdsa=" +
loadFile(getFileName(signature, "CurWorkD"));
}
if (csHeader) {
response.setHeader("Content-Signature", csHeader, false);
}
let result = loadFile(file);
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/goodChain.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIICSTCCATOgAwIBAgIUWQzTTfKLNZgX5ngi/ENiI2DO2kowCwYJKoZIhvcNAQEL
+MBExDzANBgNVBAMMBmludC1DQTAiGA8yMDE0MTEyNzAwMDAwMFoYDzIwMTcwMjA0
+MDAwMDAwWjAUMRIwEAYDVQQDDAllZS1pbnQtQ0EwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAShaHJDNitcexiJ83kVRhWhxz+0je6GPgIpFdtgjiUt5LcTLajOmOgxU05q
+nAwLCcjWOa3oMgbluoE0c6EfozDgXajJbkOD/ieHPalxA74oiM/wAvBa9xof3cyD
+dKpuqc6jRDBCMBMGA1UdJQQMMAoGCCsGAQUFBwMDMCsGA1UdEQQkMCKCIHJlbW90
+ZS1uZXd0YWItc2lnbmVyLm1vemlsbGEub3JnMAsGCSqGSIb3DQEBCwOCAQEAc2nE
+feYpA8WFyiPfZi56NgVgc8kXSKRNgplDtBHXK7gT7ICNQTSKkt+zHxnS9tAoXoix
+OGKsyp/8LNIYGMr4vHVNyOGnxuiLzAYjmDxXhp3t36xOFlU5Y7UaKf9G4feMXrNH
++q1SPYlP84keo1MaC5yhTZTTmJMKkRBsCbIVhfDnL3BUczxVZmk9F+7qK/trL222
+RoAaTZW5hdXUZrX630CYs1sQHWgL0B5rg2y9bwFk7toQ34JbjS0Z25e/MZUtFz19
+5tSjAZQHlLE6fAYZ3knrxF9xVMJCZf7gQqVphJzBtgy9yvTAtlMsrf6XS6sRRngz
+27HBxIpd4tYniYrtfg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbugAwIBAgIULYyr3v/0zZ+XiR22NH7hOcnj2FcwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMBExDzANBgNVBAMMBmludC1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72x
+nAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lM
+wmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF
+4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20
+yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xx
+j5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMlMCMwDAYDVR0TBAUwAwEB/zAT
+BgNVHSUEDDAKBggrBgEFBQcDAzALBgkqhkiG9w0BAQsDggEBADfRBKSM08JF6vqz
+0EA+KNc0XIEAWApuHuwX6XXWeLgo6QN4E/9qfrsaO+C366WT+JDsjDOi40wW46SA
+XbguxtZQeZasNDUWp/leZix4RSJoHB7OllG1rgZJfN76zKVaXRGUmyQObkMMOJZe
+wIA0OBURT8ik9Z89pD0IWrqscds71Edfjt0hHgg63wVvIaklReZXvFOD3VmSCPNn
+2wB6ZzECcbhJpnzxZdsoMSGH0C6apYnNNTjqZjO90JVm/Ph/7nbi/KncYXA6ccl6
+Jz2mfiAquWIua2+CzBGbqjZVSATTpWCp+cXQJE1xka+hWUaL5HPTq1bTULRFlauZ
+HGl5lJk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICzTCCAbegAwIBAgIUIVkGGA8HiO3RIKGjdOjVi+d6EVkwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMA0xCzAJBgNVBAMMAmNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptu
+Gobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO
+7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgf
+qDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/yt
+HSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcx
+uLP+SSP6clHEMdUDrNoYCjXtjQIDAQABoyUwIzAMBgNVHRMEBTADAQH/MBMGA1Ud
+JQQMMAoGCCsGAQUFBwMDMAsGCSqGSIb3DQEBCwOCAQEAlpbRzRIPnf43AwGfMvKP
+zOtntRy2nE9GlmY9I00uioHUnUrPLs8aw3UDtyiDWMGqcYysXGx9EX2Vk0POS4gf
+G6PA95F6GxTtbzIEZmTPVuzA/cfc9HU3HXDPqh+dySJ8/Ta4c4vX1lgeGGAvstNe
+q+9DaCGXs8MqMF8KtXNmOm3eS9q622hKEvTVEoxqj1t365kwKHaNpbObddQ6Xcny
+akvfh2L+8QbJSflcm8fL/JTup/2/cRG1ytOsaiXEr9JBEITOtQO0Ot/4Qzq+MJjv
+weaJ3hZ0c+cTy3tEvt+I7+lnW4Q5dB7aLR2/BZfLubhxz1SUVMuHfLH64fc0Uf1Q
+Nw==
+-----END CERTIFICATE-----
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1007,30 +1007,16 @@ nsHttpChannel::CallOnStartRequest()
if (NS_ERROR_FILE_TOO_BIG == rv) {
// Don't throw the entry away, we will need it later.
LOG((" entry too big"));
} else {
NS_ENSURE_SUCCESS(rv, rv);
}
}
- // Check for a Content-Signature header and inject mediator if the header is
- // requested and available.
- // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
- // present but not valid, fail this channel and return
- // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
- // fallback load in nsDocShell.
- if (!mCanceled) {
- rv = ProcessContentSignatureHeader(mResponseHead);
- if (NS_FAILED(rv)) {
- LOG(("Content-signature verification failed.\n"));
- return rv;
- }
- }
-
LOG((" calling mListener->OnStartRequest\n"));
if (mListener) {
MOZ_ASSERT(!mOnStartRequestCalled,
"We should not call OsStartRequest twice");
rv = mListener->OnStartRequest(this, mListenerContext);
mOnStartRequestCalled = true;
if (NS_FAILED(rv))
return rv;
@@ -1082,16 +1068,34 @@ nsHttpChannel::CallOnStartRequest()
if (NS_FAILED(rv)) return rv;
}
} else if (mApplicationCacheForWrite) {
LOG(("offline cache is up to date, not updating"));
CloseOfflineCacheEntry();
}
}
+ // Check for a Content-Signature header and inject mediator if the header is
+ // requested and available.
+ // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
+ // present but not valid, fail this channel and return
+ // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
+ // fallback load in nsDocShell.
+ // Note that OnStartRequest has already been called on the target stream
+ // listener at this point. We have to add the listener here that late to
+ // ensure that it's the last listener and can thus block the load in
+ // OnStopRequest.
+ if (!mCanceled) {
+ rv = ProcessContentSignatureHeader(mResponseHead);
+ if (NS_FAILED(rv)) {
+ LOG(("Content-signature verification failed.\n"));
+ return rv;
+ }
+ }
+
return NS_OK;
}
nsresult
nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus)
{
// Failure to set up a proxy tunnel via CONNECT means one of the following:
// 1) Proxy wants authorization, or forbids.
@@ -1403,18 +1407,18 @@ nsHttpChannel::ProcessContentSignatureHe
if (!aResponseHead->HasContentType()) {
NS_WARNING("Empty content type can get us in trouble when verifying "
"content signatures");
return NS_ERROR_INVALID_SIGNATURE;
}
// create a new listener that meadiates the content
RefPtr<ContentVerifier> contentVerifyingMediator =
new ContentVerifier(mListener, mListenerContext);
- rv = contentVerifyingMediator->Init(
- NS_ConvertUTF8toUTF16(contentSignatureHeader));
+ rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
+ mListenerContext);
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
mListener = contentVerifyingMediator;
return NS_OK;
}
/**
* Decide whether or not to send a security report and, if so, give the
--- a/security/manager/ssl/ContentSignatureVerifier.cpp
+++ b/security/manager/ssl/ContentSignatureVerifier.cpp
@@ -5,28 +5,36 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ContentSignatureVerifier.h"
#include "BRNameMatchingPolicy.h"
#include "SharedCertVerifier.h"
#include "cryptohi.h"
#include "keyhi.h"
+#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsISupportsPriority.h"
+#include "nsIURI.h"
#include "nsNSSComponent.h"
#include "nsSecurityHeaderParser.h"
+#include "nsStreamUtils.h"
#include "nsWhitespaceTokenizer.h"
#include "nsXPCOMStrings.h"
#include "nssb64.h"
#include "pkix/pkix.h"
#include "pkix/pkixtypes.h"
#include "secerr.h"
-NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
+NS_IMPL_ISUPPORTS(ContentSignatureVerifier,
+ nsIContentSignatureVerifier,
+ nsIInterfaceRequestor,
+ nsIStreamListener)
using namespace mozilla;
using namespace mozilla::pkix;
using namespace mozilla::psm;
static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
#define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
@@ -121,37 +129,28 @@ ReadChainIntoCertList(const nsACString&
if (inBlock || !certFound) {
// the PEM data did not end; bad data.
CSVerifier_LOG(("CSVerifier: supplied chain contains bad data\n"));
return NS_ERROR_FAILURE;
}
return NS_OK;
}
-// Create a context for a content signature verification.
-// It sets signature, certificate chain and name that should be used to verify
-// the data. The data parameter is the first part of the data to verify (this
-// can be the empty string).
-NS_IMETHODIMP
-ContentSignatureVerifier::CreateContext(const nsACString& aData,
- const nsACString& aCSHeader,
- const nsACString& aCertChain,
- const nsACString& aName)
+nsresult
+ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
+ const nsACString& aCertChain,
+ const nsACString& aName)
{
- MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(NS_IsMainThread());
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
return NS_ERROR_FAILURE;
}
- if (mCx) {
- return NS_ERROR_ALREADY_INITIALIZED;
- }
-
UniqueCERTCertList certCertList(CERT_NewCertList());
if (!certCertList) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsresult rv = ReadChainIntoCertList(aCertChain, certCertList.get(), locker);
if (NS_FAILED(rv)) {
return rv;
@@ -209,22 +208,16 @@ ContentSignatureVerifier::CreateContext(
mKey.reset(CERT_ExtractPublicKey(node->cert));
// in case we were not able to extract a key
if (!mKey) {
CSVerifier_LOG(("CSVerifier: unable to extract a key\n"));
return NS_ERROR_INVALID_SIGNATURE;
}
- // we get the raw content-signature header here, so first parse aCSHeader
- rv = ParseContentSignatureHeader(aCSHeader);
- if (NS_FAILED(rv)) {
- return rv;
- }
-
// Base 64 decode the signature
UniqueSECItem rawSignatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
if (!rawSignatureItem ||
!NSSBase64_DecodeBuffer(nullptr, rawSignatureItem.get(),
mSignature.get(), mSignature.Length())) {
CSVerifier_LOG(("CSVerifier: decoding the signature failed\n"));
return NS_ERROR_FAILURE;
}
@@ -256,78 +249,197 @@ ContentSignatureVerifier::CreateContext(
if (!mCx) {
return NS_ERROR_INVALID_SIGNATURE;
}
if (VFY_Begin(mCx.get()) != SECSuccess) {
return NS_ERROR_INVALID_SIGNATURE;
}
- rv = UpdateInternal(kPREFIX, lock, locker);
+ rv = UpdateInternal(kPREFIX, locker);
if (NS_FAILED(rv)) {
return rv;
}
// add data if we got any
- return UpdateInternal(aData, lock, locker);
+ return UpdateInternal(aData, locker);
}
nsresult
-ContentSignatureVerifier::UpdateInternal(const nsACString& aData,
- MutexAutoLock& /*proofOfLock*/,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+ContentSignatureVerifier::DownloadCertChain()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCertChainURL.IsEmpty()) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ nsCOMPtr<nsIURI> certChainURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(certChainURI), mCertChainURL);
+ if (NS_FAILED(rv) || !certChainURI) {
+ return rv;
+ }
+
+ // If the address is not https, fail.
+ bool isHttps = false;
+ rv = certChainURI->SchemeIs("https", &isHttps);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!isHttps) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ rv = NS_NewChannel(getter_AddRefs(mChannel), certChainURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // we need this chain soon
+ nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel);
+ if (priorityChannel) {
+ priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+
+ rv = mChannel->AsyncOpen2(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// Create a context for content signature verification using CreateContext below.
+// This function doesn't require a cert chain to be passed, but instead aCSHeader
+// must contain an x5u value that is then used to download the cert chain.
+NS_IMETHODIMP
+ContentSignatureVerifier::CreateContextWithoutCertChain(
+ nsIContentSignatureReceiverCallback *aCallback, const nsACString& aCSHeader,
+ const nsACString& aName)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mInitialised) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ mInitialised = true;
+
+ // we get the raw content-signature header here, so first parse aCSHeader
+ nsresult rv = ParseContentSignatureHeader(aCSHeader);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mCallback = aCallback;
+ mName.Assign(aName);
+
+ // We must download the cert chain now.
+ // This is async and blocks createContextInternal calls.
+ return DownloadCertChain();
+}
+
+// Create a context for a content signature verification.
+// It sets signature, certificate chain and name that should be used to verify
+// the data. The data parameter is the first part of the data to verify (this
+// can be the empty string).
+NS_IMETHODIMP
+ContentSignatureVerifier::CreateContext(const nsACString& aData,
+ const nsACString& aCSHeader,
+ const nsACString& aCertChain,
+ const nsACString& aName)
+{
+ if (mInitialised) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ mInitialised = true;
+ // The cert chain is given in aCertChain so we don't have to download anything.
+ mHasCertChain = true;
+
+ // we get the raw content-signature header here, so first parse aCSHeader
+ nsresult rv = ParseContentSignatureHeader(aCSHeader);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return CreateContextInternal(aData, aCertChain, aName);
+}
+
+nsresult
+ContentSignatureVerifier::UpdateInternal(
+ const nsACString& aData, const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
if (!aData.IsEmpty()) {
if (VFY_Update(mCx.get(), (const unsigned char*)nsPromiseFlatCString(aData).get(),
aData.Length()) != SECSuccess){
return NS_ERROR_INVALID_SIGNATURE;
}
}
return NS_OK;
}
/**
* Add data to the context that shold be verified.
*/
NS_IMETHODIMP
ContentSignatureVerifier::Update(const nsACString& aData)
{
+ MOZ_ASSERT(NS_IsMainThread());
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
return NS_ERROR_FAILURE;
}
- MutexAutoLock lock(mMutex);
- return UpdateInternal(aData, lock, locker);
+
+ // If we didn't create the context yet, bail!
+ if (!mHasCertChain) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Someone called ContentSignatureVerifier::Update before "
+ "downloading the cert chain.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return UpdateInternal(aData, locker);
}
/**
* Finish signature verification and return the result in _retval.
*/
NS_IMETHODIMP
ContentSignatureVerifier::End(bool* _retval)
{
NS_ENSURE_ARG(_retval);
- MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(NS_IsMainThread());
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
return NS_ERROR_FAILURE;
}
+ // If we didn't create the context yet, bail!
+ if (!mHasCertChain) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Someone called ContentSignatureVerifier::End before "
+ "downloading the cert chain.");
+ return NS_ERROR_FAILURE;
+ }
+
*_retval = (VFY_End(mCx.get()) == SECSuccess);
return NS_OK;
}
nsresult
ContentSignatureVerifier::ParseContentSignatureHeader(
const nsACString& aContentSignatureHeader)
{
+ MOZ_ASSERT(NS_IsMainThread());
// We only support p384 ecdsa according to spec
NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
+ NS_NAMED_LITERAL_CSTRING(certChainURL_var, "x5u");
nsSecurityHeaderParser parser(aContentSignatureHeader.BeginReading());
nsresult rv = parser.Parse();
if (NS_FAILED(rv)) {
CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header\n"));
return NS_ERROR_FAILURE;
}
LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
@@ -341,24 +453,113 @@ ContentSignatureVerifier::ParseContentSi
if (!mSignature.IsEmpty()) {
CSVerifier_LOG(("CSVerifier: found two ContentSignatures\n"));
return NS_ERROR_INVALID_SIGNATURE;
}
CSVerifier_LOG(("CSVerifier: found a ContentSignature directive\n"));
mSignature = directive->mValue;
}
+ if (directive->mName.Length() == certChainURL_var.Length() &&
+ directive->mName.EqualsIgnoreCase(certChainURL_var.get(),
+ certChainURL_var.Length())) {
+ if (!mCertChainURL.IsEmpty()) {
+ CSVerifier_LOG(("CSVerifier: found two x5u values\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ CSVerifier_LOG(("CSVerifier: found an x5u directive\n"));
+ mCertChainURL = directive->mValue;
+ }
}
// we have to ensure that we found a signature at this point
if (mSignature.IsEmpty()) {
CSVerifier_LOG(("CSVerifier: got a Content-Signature header but didn't find a signature.\n"));
return NS_ERROR_FAILURE;
}
// Bug 769521: We have to change b64 url to regular encoding as long as we
// don't have a b64 url decoder. This should change soon, but in the meantime
// we have to live with this.
mSignature.ReplaceChar('-', '+');
mSignature.ReplaceChar('_', '/');
return NS_OK;
}
+
+/* nsIStreamListener implementation */
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext, nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIContentSignatureReceiverCallback> callback;
+ callback.swap(mCallback);
+ nsresult rv;
+
+ // Check HTTP status code and return if it's not 200.
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
+ uint32_t httpResponseCode;
+ if (NS_FAILED(rv) || NS_FAILED(http->GetResponseStatus(&httpResponseCode)) ||
+ httpResponseCode != 200) {
+ callback->ContextCreated(false);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aStatus)) {
+ callback->ContextCreated(false);
+ return NS_OK;
+ }
+
+ nsAutoCString certChain;
+ for (uint32_t i = 0; i < mCertChain.Length(); ++i) {
+ certChain.Append(mCertChain[i]);
+ }
+
+ // We got the cert chain now. Let's create the context.
+ rv = CreateContextInternal(NS_LITERAL_CSTRING(""), certChain, mName);
+ if (NS_FAILED(rv)) {
+ callback->ContextCreated(false);
+ return NS_OK;
+ }
+
+ mHasCertChain = true;
+ callback->ContextCreated(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsAutoCString buffer;
+
+ nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!mCertChain.AppendElement(buffer, fallible)) {
+ mCertChain.TruncateLength(0);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::GetInterface(const nsIID& uuid, void** result)
+{
+ return QueryInterface(uuid, result);
+}
--- a/security/manager/ssl/ContentSignatureVerifier.h
+++ b/security/manager/ssl/ContentSignatureVerifier.h
@@ -6,62 +6,87 @@
#ifndef ContentSignatureVerifier_h
#define ContentSignatureVerifier_h
#include "cert.h"
#include "CSTrustDomain.h"
#include "nsIContentSignatureVerifier.h"
+#include "nsIStreamListener.h"
#include "nsNSSShutDown.h"
#include "ScopedNSSTypes.h"
// 45a5fe2f-c350-4b86-962d-02d5aaaa955a
#define NS_CONTENTSIGNATUREVERIFIER_CID \
{ 0x45a5fe2f, 0xc350, 0x4b86, \
{ 0x96, 0x2d, 0x02, 0xd5, 0xaa, 0xaa, 0x95, 0x5a } }
#define NS_CONTENTSIGNATUREVERIFIER_CONTRACTID \
"@mozilla.org/security/contentsignatureverifier;1"
class ContentSignatureVerifier final : public nsIContentSignatureVerifier
+ , public nsIStreamListener
, public nsNSSShutDownObject
+ , public nsIInterfaceRequestor
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICONTENTSIGNATUREVERIFIER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
ContentSignatureVerifier()
: mCx(nullptr)
- , mMutex("CSVerifier::mMutex")
+ , mInitialised(false)
+ , mHasCertChain(false)
{
}
// nsNSSShutDownObject
virtual void virtualDestroyNSSReference() override
{
destructorSafeDestroyNSSReference();
}
private:
~ContentSignatureVerifier();
nsresult UpdateInternal(const nsACString& aData,
- MutexAutoLock& /*proofOfLock*/,
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ nsresult DownloadCertChain();
+ nsresult CreateContextInternal(const nsACString& aData,
+ const nsACString& aCertChain,
+ const nsACString& aName);
void destructorSafeDestroyNSSReference()
{
mCx = nullptr;
mKey = nullptr;
}
nsresult ParseContentSignatureHeader(const nsACString& aContentSignatureHeader);
// verifier context for incremental verifications
mozilla::UniqueVFYContext mCx;
+ bool mInitialised;
+ // Indicates whether we hold a cert chain to verify the signature or not.
+ // It's set by default in CreateContext or when the channel created in
+ // DownloadCertChain finished. Update and End must only be called after
+ // mHashCertChain is set.
+ bool mHasCertChain;
// signature to verify
nsCString mSignature;
+ // x5u (X.509 URL) value pointing to pem cert chain
+ nsCString mCertChainURL;
+ // the downloaded cert chain to verify against
+ FallibleTArray<nsCString> mCertChain;
// verification key
mozilla::UniqueSECKEYPublicKey mKey;
- mozilla::Mutex mMutex;
+ // name of the verifying context
+ nsCString mName;
+ // callback to notify when finished
+ nsCOMPtr<nsIContentSignatureReceiverCallback> mCallback;
+ // channel to download the cert chain
+ nsCOMPtr<nsIChannel> mChannel;
};
#endif // ContentSignatureVerifier_h
--- a/security/manager/ssl/nsIContentSignatureVerifier.idl
+++ b/security/manager/ssl/nsIContentSignatureVerifier.idl
@@ -1,15 +1,17 @@
/* 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"
+interface nsIContentSignatureReceiverCallback;
+
/**
* An interface for verifying content-signatures, inspired by
* https://tools.ietf.org/html/draft-thomson-http-content-signature-00
* described here https://github.com/franziskuskiefer/content-signature/tree/pki
*
* A new signature verifier instance should be created for each signature
* verification - you can create these instances with do_CreateInstance.
*
@@ -33,44 +35,82 @@ interface nsIContentSignatureVerifier :
* url-safe base64 encoded.
* @param aCertificateChain The certificate chain to use for verification.
* PEM encoded string.
* @param aName The (host)name for which the end entity must
be valid.
* @returns true if the signature matches the data and aCertificateChain is
* valid within aContext, false if not.
*/
- boolean verifyContentSignature(in ACString aData, in ACString aSignature,
+ boolean verifyContentSignature(in ACString aData,
+ in ACString aContentSignatureHeader,
in ACString aCertificateChain,
in ACString aName);
/**
* Creates a context to verify a content signature against data that is added
* later with update calls.
*
* @param aData The first chunk of data to be tested.
* @param aContentSignatureHeader The signature of the data, url-safe base64
* encoded.
* @param aCertificateChain The certificate chain to use for
* verification. PEM encoded string.
* @param aName The (host)name for which the end entity must
be valid.
*/
- void createContext(in ACString aData, in ACString aSignature,
+ void createContext(in ACString aData, in ACString aContentSignatureHeader,
in ACString aCertificateChain, in ACString aName);
/**
+ * Creates a context to verify a content signature against data that is added
+ * later with update calls.
+ * This does not require the caller to download the certificate chain. It's
+ * done internally.
+ * It requires the x5u parameter to be present in aContentSignatureHeader
+ *
+ * NOTE: Callers have to wait for aCallback to return before invoking anything
+ * else. Otherwise the ContentSignatureVerifier will fail.
+ *
+ * @param aCallback Callback that's invoked when the cert chain
+ * got fetched.
+ * @param aContentSignatureHeader The signature of the data, url-safe base64
+ * encoded, and the x5u value.
+ * @param aName The (host)name for which the end entity must
+ be valid.
+ */
+ void createContextWithoutCertChain(in nsIContentSignatureReceiverCallback aCallback,
+ in ACString aContentSignatureHeader,
+ in ACString aName);
+
+ /**
* Adds data to the context that was used to generate the signature.
*
* @param aData More data to be tested.
*/
void update(in ACString aData);
/**
* Finalises the signature and returns the result of the signature
* verification.
*
* @returns true if the signature matches the data added with createContext
* and update, false if not.
*/
boolean end();
+};
+/**
+ * Callback for nsIContentSignatureVerifier.
+ * { 0x1eb90707, 0xdf59, 0x48b7, \
+ * { 0x9d, 0x42, 0xd8, 0xbf, 0x63, 0x0a, 0xe7, 0x44 } }
+ */
+[scriptable, uuid(1eb90707-df59-48b7-9d42-d8bf630ae744)]
+interface nsIContentSignatureReceiverCallback : nsISupports
+{
+ /**
+ * Notification callback that's called by nsIContentSignatureVerifier when
+ * the cert chain is downloaded.
+ * If download and initialisation were successful, successful is true,
+ * otherwise false. If successful is false, the verification must be aborted.
+ */
+ void contextCreated(in boolean successful);
};