зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 21d8bb5af7b4 (bug 1263793
) for leaks in various jobs CLOSED TREE
This commit is contained in:
Родитель
2f160622c6
Коммит
e2d9911273
|
@ -7,47 +7,41 @@
|
|||
|
||||
#include "mozilla/fallible.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsCharSeparatedTokenizer.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)
|
||||
|
||||
NS_IMPL_ISUPPORTS(ContentVerifier,
|
||||
nsIContentSignatureReceiverCallback,
|
||||
nsIStreamListener);
|
||||
// Content-Signature prefix
|
||||
const nsLiteralCString kPREFIX = NS_LITERAL_CSTRING("Content-Signature:\x00");
|
||||
|
||||
NS_IMPL_ISUPPORTS(ContentVerifier, nsIStreamListener, nsISupports);
|
||||
|
||||
nsresult
|
||||
ContentVerifier::Init(const nsACString& aContentSignatureHeader,
|
||||
nsIRequest* aRequest, nsISupports* aContext)
|
||||
ContentVerifier::Init(const nsAString& aContentSignatureHeader)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (aContentSignatureHeader.IsEmpty()) {
|
||||
CSV_LOG(("Content-Signature header must not be empty!\n"));
|
||||
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"));
|
||||
return NS_ERROR_INVALID_SIGNATURE;
|
||||
}
|
||||
|
||||
// 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"));
|
||||
nsresult rv = ParseContentSignatureHeader(aContentSignatureHeader);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
|
||||
return CreateContext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +55,7 @@ AppendNextSegment(nsIInputStream* aInputStream, void* aClosure,
|
|||
{
|
||||
FallibleTArray<nsCString>* decodedData =
|
||||
static_cast<FallibleTArray<nsCString>*>(aClosure);
|
||||
nsDependentCSubstring segment(aRawSegment, aCount);
|
||||
nsAutoCString segment(aRawSegment, aCount);
|
||||
if (!decodedData->AppendElement(segment, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
@ -69,57 +63,9 @@ AppendNextSegment(nsIInputStream* aInputStream, void* aClosure,
|
|||
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.
|
||||
uint64_t offset = 0;
|
||||
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,
|
||||
offset, mContent[i].Length());
|
||||
offset += 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;
|
||||
}
|
||||
|
||||
|
@ -127,28 +73,50 @@ NS_IMETHODIMP
|
|||
ContentVerifier::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
||||
nsresult aStatus)
|
||||
{
|
||||
// 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;
|
||||
// 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"));
|
||||
|
||||
// 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 (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;
|
||||
// propagate OnStopRequest and return
|
||||
return mNextListener->OnStopRequest(aRequest, aContext, rv);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -164,63 +132,244 @@ ContentVerifier::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
|
|||
return rv;
|
||||
}
|
||||
|
||||
// Update the signature verifier if the context has been created.
|
||||
if (mContextCreated) {
|
||||
return mVerifier->Update(mContent.LastElement());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
// update the signature verifier
|
||||
return Update(mContent[mContent.Length()-1]);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContentVerifier::ContextCreated(bool successful)
|
||||
/**
|
||||
* ContentVerifier logic and utils
|
||||
*/
|
||||
|
||||
nsresult
|
||||
ContentVerifier::GetVerificationKey(const nsAString& aKeyId)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!successful) {
|
||||
// If we don't have a next listener, the request has been handed off already.
|
||||
if (!mNextListener) {
|
||||
// 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;
|
||||
}
|
||||
// Get local reference to mNextListener and null it to ensure that we don't
|
||||
// call it twice.
|
||||
nsCOMPtr<nsIStreamListener> nextListener;
|
||||
nextListener.swap(mNextListener);
|
||||
|
||||
// Make sure that OnStartRequest was called and we have a request.
|
||||
MOZ_ASSERT(mContentRequest);
|
||||
|
||||
// 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);
|
||||
nsresult rv = nextListener->OnStopRequest(mContentRequest, mContentContext,
|
||||
NS_ERROR_INVALID_SIGNATURE);
|
||||
mContentRequest = nullptr;
|
||||
mContentContext = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We should never get here!
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"ContentVerifier was used without getting OnStartRequest!");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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 read all content, let's verify the signature.
|
||||
if (mContentRead) {
|
||||
FinishSignature();
|
||||
// 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;
|
||||
}
|
||||
|
||||
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&)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Extract the public key from the keyItem
|
||||
ScopedCERTSubjectPublicKeyInfo pki(
|
||||
SECKEY_DecodeDERSubjectPublicKeyInfo(keyItem));
|
||||
if (!pki) {
|
||||
return NS_ERROR_INVALID_SIGNATURE;
|
||||
}
|
||||
aPublicKeyOut = SECKEY_ExtractPublicKey(pki.get());
|
||||
|
||||
// in case we were not able to extract a key
|
||||
if (!aPublicKeyOut) {
|
||||
return 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -7,11 +7,12 @@
|
|||
#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
|
||||
|
@ -21,44 +22,77 @@
|
|||
* NS_ERROR_INVALID_SIGNATURE is thrown.
|
||||
*/
|
||||
class ContentVerifier : public nsIStreamListener
|
||||
, public nsIContentSignatureReceiverCallback
|
||||
, public nsNSSShutDownObject
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSICONTENTSIGNATURERECEIVERCALLBACK
|
||||
|
||||
explicit ContentVerifier(nsIStreamListener* aMediatedListener,
|
||||
nsISupports* aMediatedContext)
|
||||
: mNextListener(aMediatedListener)
|
||||
, mContextCreated(false)
|
||||
, mContentRead(false) {}
|
||||
, mContext(aMediatedContext)
|
||||
, mCx(nullptr) {}
|
||||
|
||||
nsresult Init(const nsACString& aContentSignatureHeader, nsIRequest* aRequest,
|
||||
nsISupports* aContext);
|
||||
nsresult Init(const nsAString& aContentSignatureHeader);
|
||||
|
||||
// nsNSSShutDownObject
|
||||
virtual void virtualDestroyNSSReference() override
|
||||
{
|
||||
destructorSafeDestroyNSSReference();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~ContentVerifier() {}
|
||||
virtual ~ContentVerifier()
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return;
|
||||
}
|
||||
destructorSafeDestroyNSSReference();
|
||||
shutdown(calledFromObject);
|
||||
}
|
||||
|
||||
void destructorSafeDestroyNSSReference()
|
||||
{
|
||||
mCx = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void FinishSignature();
|
||||
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&);
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
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;
|
||||
};
|
||||
|
||||
#endif /* mozilla_dom_ContentVerifier_h */
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 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"
|
||||
|
|
|
@ -10,10 +10,7 @@ support-files =
|
|||
file_about_newtab_broken_signature
|
||||
file_about_newtab_sri.html
|
||||
file_about_newtab_sri_signature
|
||||
goodChain.pem
|
||||
head.js
|
||||
script.js
|
||||
style.css
|
||||
|
||||
[browser_verify_content_about_newtab.js]
|
||||
[browser_verify_content_about_newtab2.js]
|
||||
|
|
|
@ -1,9 +1,78 @@
|
|||
/*
|
||||
* Test Content-Signature for remote about:newtab
|
||||
* - Bug 1226928 - allow about:newtab to load remote content
|
||||
*
|
||||
* This tests content-signature verification on remote about:newtab in the
|
||||
* following cases (see TESTS, all failed loads display about:blank fallback):
|
||||
* - good case (signature should verify and correct page is displayed)
|
||||
* - reload of newtab when the siganture was invalidated after the last correct
|
||||
* load
|
||||
* - malformed content-signature header
|
||||
* - malformed keyid directive
|
||||
* - malformed p384ecdsa directive
|
||||
* - wrong signature (this is not a siganture for the delivered document)
|
||||
* - invalid signature (this is not even a signature)
|
||||
* - 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 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_ERROR_HEADER = URI_HEADER_BASE + "error";
|
||||
const URI_KEYERROR_HEADER = URI_HEADER_BASE + "errorInKeyid";
|
||||
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_FILE_CACHED = BASE + "sig=good&key=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 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.";
|
||||
|
||||
// Needs to sync with pref "security.signed_content.CSP.default".
|
||||
const SIGNED_CONTENT_CSP = `{"csp-policies":[{"report-only":false,"script-src":["https://example.com","'unsafe-inline'"],"style-src":["https://example.com"]}]}`;
|
||||
|
||||
const TESTS = [
|
||||
// { newtab (aboutURI) or regular load (url) : url,
|
||||
// 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_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] },
|
||||
|
@ -17,4 +86,158 @@ const TESTS = [
|
|||
{ "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
|
||||
];
|
||||
|
||||
add_task(runTests);
|
||||
var browser = null;
|
||||
var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
|
||||
.getService(Ci.nsIAboutNewTabService);
|
||||
|
||||
function pushPrefs(...aPrefs) {
|
||||
return new Promise((resolve) => {
|
||||
SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* run tests with input from TESTS
|
||||
*/
|
||||
function doTest(aExpectedStrings, reload, aUrl, aNewTabPref) {
|
||||
// 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"
|
||||
]);
|
||||
|
||||
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'"]);
|
||||
}
|
||||
|
||||
// start the test
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: aUrl,
|
||||
},
|
||||
function * (browser) {
|
||||
// check if everything's set correct for testing
|
||||
ok(Services.prefs.getBoolPref(
|
||||
"browser.newtabpage.remote.content-signing-test"),
|
||||
"sanity check: remote newtab signing test should be used");
|
||||
ok(Services.prefs.getBoolPref("browser.newtabpage.remote"),
|
||||
"sanity check: remote newtab should be used");
|
||||
// we only check this if we really do a newtab test
|
||||
if (aNewTabPref) {
|
||||
ok(aboutNewTabService.overridden,
|
||||
"sanity check: default URL for about:newtab should be overriden");
|
||||
is(aboutNewTabService.newTabURL, aNewTabPref,
|
||||
"sanity check: default URL for about:newtab should return the new URL");
|
||||
}
|
||||
|
||||
// Every valid remote newtab page must have built-in CSP.
|
||||
let shouldHaveCSP = ((aUrl === ABOUT_NEWTAB_URI) &&
|
||||
(aNewTabPref === URI_GOOD || aNewTabPref === URI_SRI));
|
||||
|
||||
if (shouldHaveCSP) {
|
||||
is(browser.contentDocument.nodePrincipal.cspJSON, SIGNED_CONTENT_CSP,
|
||||
"Valid remote newtab page must have built-in CSP.");
|
||||
}
|
||||
|
||||
yield ContentTask.spawn(
|
||||
browser, aExpectedStrings, function * (aExpectedStrings) {
|
||||
for (let expectedString of aExpectedStrings) {
|
||||
ok(content.document.documentElement.innerHTML.includes(expectedString),
|
||||
"Expect the following value in the result\n" + expectedString +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
}
|
||||
});
|
||||
|
||||
// for good test cases we check if a reload fails if the remote page
|
||||
// changed from valid to invalid in the meantime
|
||||
if (reload) {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: INVALIDATE_FILE,
|
||||
},
|
||||
function * (browser2) {
|
||||
yield ContentTask.spawn(browser2, null, function * () {
|
||||
ok(content.document.documentElement.innerHTML.includes("Done"),
|
||||
"Expect the following value in the result\n" + "Done" +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
browser.reload();
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
let expectedStrings = [ABOUT_BLANK];
|
||||
if (aNewTabPref == URI_SRI) {
|
||||
expectedStrings = [
|
||||
STYLESHEET_WITHOUT_SRI_BLOCKED,
|
||||
STYLESHEET_WITH_SRI_BLOCKED,
|
||||
SCRIPT_WITHOUT_SRI_BLOCKED,
|
||||
SCRIPT_WITH_SRI_BLOCKED
|
||||
];
|
||||
}
|
||||
yield ContentTask.spawn(browser, expectedStrings,
|
||||
function * (expectedStrings) {
|
||||
for (let expectedString of expectedStrings) {
|
||||
ok(content.document.documentElement.innerHTML.includes(expectedString),
|
||||
"Expect the following value in the result\n" + expectedString +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: VALIDATE_FILE,
|
||||
},
|
||||
function * (browser2) {
|
||||
yield ContentTask.spawn(browser2, null, function * () {
|
||||
ok(content.document.documentElement.innerHTML.includes("Done"),
|
||||
"Expect the following value in the result\n" + "Done" +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
add_task(function * test() {
|
||||
// run tests from TESTS
|
||||
for (let i = 0; i < TESTS.length; i++) {
|
||||
let testCase = TESTS[i];
|
||||
let url = "", aNewTabPref = "";
|
||||
let reload = false;
|
||||
var aExpectedStrings = testCase.testStrings;
|
||||
if (testCase.aboutURI) {
|
||||
url = ABOUT_NEWTAB_URI;
|
||||
aNewTabPref = testCase.aboutURI;
|
||||
if (aNewTabPref == URI_GOOD || aNewTabPref == URI_SRI) {
|
||||
reload = true;
|
||||
}
|
||||
} else {
|
||||
url = testCase.url;
|
||||
}
|
||||
|
||||
yield doTest(aExpectedStrings, reload, url, aNewTabPref);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
const TESTS = [
|
||||
// { newtab (aboutURI) or regular load (url) : url,
|
||||
// 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_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_CLEANUP, "testStrings" : [CLEANUP_DONE] },
|
||||
];
|
||||
|
||||
add_task(runTests);
|
|
@ -1 +1 @@
|
|||
oiypz3lb-IyJsmKNsnlp2zDrqncste8yONn9WUE6ksgJWMhSEQ9lp8vRqN0W3JPwJb6uSk16RI-tDv7uy0jxon5jL1BZpqlqIpvimg7FCQEedMKoHZwtE9an-e95sOTd
|
||||
8qXVAqzuF3TsF6C750u_v_JiRu90WJXf_0xT9x0S4Fgmvolgtfu-KSWq3lYpmk2dxO8u64zaHM3iguZdWAqcSL82RFtV7OPiprt16omCbHCKfVi-Bt_rXILRlexgmRl_
|
|
@ -1 +1 @@
|
|||
-mqpvTYdZX4HYQDW1nScojL7ICw5yj8UF2gzxyLbSCx9UIfHH-gWZ40F_PFtqjHxoC1J3dHDb3VedVhOYczdaLrNKbRvPrlnkdGx7Rl8qEBrtZpF1py1Z9uAGoCrgUHa
|
||||
XBKzej3i6TAFZc3VZsuCekn-4dYWJBE4-b3OOtKrOV-JIzIvAnAhnOV1aj-kEm07kh-FciIxV-Xk2QUQlRQzHO7oW7E4mXkMKkbbAcvL0CFrItTObhfhKnBnpAE9ql1O
|
|
@ -1 +1 @@
|
|||
yoIyAYiiEzdP1zpkRy3KaqdsjUy62Notku89cytwVwcH0x6fKsMCdM-df1wbk9N28CSTaIOW5kcSenFy5K3nU-zPIoqZDjQo6aSjF8hF6lrw1a1xbhfl9K3g4YJsuWsO
|
||||
i5jOnrZWwyNwrTcIjfJ6fUR-8MhhvhtMvQbdrUD7j8aHTybNolv25v9NwJAT6rVU6kgkxmD_st9Kla086CQmzYQdLhKfzgLbTDXz0-1j23fQnyjsP1_4MNIu2xTea11p
|
|
@ -1,9 +1,4 @@
|
|||
/* -*- 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");
|
||||
|
@ -17,15 +12,14 @@ const goodFileBase = path + goodFileName;
|
|||
const goodFile = FileUtils.getDir("TmpD", [], true);
|
||||
goodFile.append(goodFileName);
|
||||
const goodSignature = path + "file_about_newtab_good_signature";
|
||||
const goodX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
|
||||
const goodKeyId = "RemoteNewTab";
|
||||
|
||||
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 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 badKeyId = "OldRemoteNewTabKey";
|
||||
|
||||
const sriFile = path + "file_about_newtab_sri.html";
|
||||
const sriSignature = path + "file_about_newtab_sri_signature";
|
||||
|
@ -33,14 +27,6 @@ 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
|
||||
|
@ -129,7 +115,7 @@ function cleanupTestFiles() {
|
|||
*/
|
||||
function handleRequest(request, response) {
|
||||
let params = new URLSearchParams(request.queryString);
|
||||
let x5uType = params.get("x5u");
|
||||
let keyType = params.get("key");
|
||||
let signatureType = params.get("sig");
|
||||
let fileType = params.get("file");
|
||||
let headerType = params.get("header");
|
||||
|
@ -137,7 +123,6 @@ function handleRequest(request, response) {
|
|||
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();
|
||||
|
@ -186,14 +171,6 @@ function handleRequest(request, response) {
|
|||
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);
|
||||
|
@ -209,13 +186,11 @@ function handleRequest(request, response) {
|
|||
* value has to be indicated in the url.
|
||||
*/
|
||||
let csHeader = "";
|
||||
let x5uString = goodX5UString;
|
||||
let keyId = goodKeyId;
|
||||
let signature = goodSignature;
|
||||
let file = goodFile;
|
||||
if (x5uType == "bad") {
|
||||
x5uString = badX5UString;
|
||||
} else if (x5uType == "http") {
|
||||
x5uString = httpX5UString;
|
||||
if (keyType == "bad") {
|
||||
keyId = badKeyId;
|
||||
}
|
||||
if (signatureType == "bad") {
|
||||
signature = badSignature;
|
||||
|
@ -236,19 +211,19 @@ function handleRequest(request, response) {
|
|||
|
||||
if (headerType == "good") {
|
||||
// a valid content-signature header
|
||||
csHeader = "x5u=" + x5uString + ";p384ecdsa=" +
|
||||
csHeader = "keyid=" + keyId + ";p384ecdsa=" +
|
||||
loadFile(getFileName(signature, "CurWorkD"));
|
||||
} else if (headerType == "error") {
|
||||
// this content-signature header is missing ; before p384ecdsa
|
||||
csHeader = "x5u=" + x5uString + "p384ecdsa=" +
|
||||
csHeader = "keyid=" + keyId + "p384ecdsa=" +
|
||||
loadFile(getFileName(signature, "CurWorkD"));
|
||||
} else if (headerType == "errorInX5U") {
|
||||
} else if (headerType == "errorInKeyid") {
|
||||
// this content-signature header is missing the keyid directive
|
||||
csHeader = "x6u=" + x5uString + ";p384ecdsa=" +
|
||||
csHeader = "keid=" + keyId + ";p384ecdsa=" +
|
||||
loadFile(getFileName(signature, "CurWorkD"));
|
||||
} else if (headerType == "errorInSignature") {
|
||||
// this content-signature header is missing the p384ecdsa directive
|
||||
csHeader = "x5u=" + x5uString + ";p385ecdsa=" +
|
||||
csHeader = "keyid=" + keyId + ";p385ecdsa=" +
|
||||
loadFile(getFileName(signature, "CurWorkD"));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
-----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-----
|
|
@ -1,210 +0,0 @@
|
|||
/*
|
||||
* Test Content-Signature for remote about:newtab
|
||||
* - Bug 1226928 - allow about:newtab to load remote content
|
||||
*
|
||||
* This tests content-signature verification on remote about:newtab in the
|
||||
* following cases (see TESTS, all failed loads display about:blank fallback):
|
||||
* - good case (signature should verify and correct page is displayed)
|
||||
* - reload of newtab when the siganture was invalidated after the last correct
|
||||
* load
|
||||
* - malformed content-signature header
|
||||
* - malformed keyid directive
|
||||
* - malformed p384ecdsa directive
|
||||
* - wrong signature (this is not a siganture for the delivered document)
|
||||
* - invalid signature (this is not even a signature)
|
||||
* - 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&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&x5u=good&file=good&header=";
|
||||
const URI_ERROR_HEADER = URI_HEADER_BASE + "error";
|
||||
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&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&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&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.";
|
||||
|
||||
// Needs to sync with pref "security.signed_content.CSP.default".
|
||||
const SIGNED_CONTENT_CSP = `{"csp-policies":[{"report-only":false,"script-src":["https://example.com","'unsafe-inline'"],"style-src":["https://example.com"]}]}`;
|
||||
|
||||
var browser = null;
|
||||
var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
|
||||
.getService(Ci.nsIAboutNewTabService);
|
||||
|
||||
function pushPrefs(...aPrefs) {
|
||||
return new Promise((resolve) => {
|
||||
SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* run tests with input from TESTS
|
||||
*/
|
||||
function doTest(aExpectedStrings, reload, aUrl, aNewTabPref) {
|
||||
// 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],
|
||||
["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'"]);
|
||||
}
|
||||
|
||||
// start the test
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: aUrl,
|
||||
},
|
||||
function * (browser) {
|
||||
// check if everything's set correct for testing
|
||||
ok(Services.prefs.getBoolPref(
|
||||
"browser.newtabpage.remote.content-signing-test"),
|
||||
"sanity check: remote newtab signing test should be used");
|
||||
ok(Services.prefs.getBoolPref("browser.newtabpage.remote"),
|
||||
"sanity check: remote newtab should be used");
|
||||
// we only check this if we really do a newtab test
|
||||
if (aNewTabPref) {
|
||||
ok(aboutNewTabService.overridden,
|
||||
"sanity check: default URL for about:newtab should be overriden");
|
||||
is(aboutNewTabService.newTabURL, aNewTabPref,
|
||||
"sanity check: default URL for about:newtab should return the new URL");
|
||||
}
|
||||
|
||||
// Every valid remote newtab page must have built-in CSP.
|
||||
let shouldHaveCSP = ((aUrl === ABOUT_NEWTAB_URI) &&
|
||||
(aNewTabPref === URI_GOOD || aNewTabPref === URI_SRI));
|
||||
|
||||
if (shouldHaveCSP) {
|
||||
is(browser.contentDocument.nodePrincipal.cspJSON, SIGNED_CONTENT_CSP,
|
||||
"Valid remote newtab page must have built-in CSP.");
|
||||
}
|
||||
|
||||
yield ContentTask.spawn(
|
||||
browser, aExpectedStrings, function * (aExpectedStrings) {
|
||||
for (let expectedString of aExpectedStrings) {
|
||||
ok(content.document.documentElement.innerHTML.includes(expectedString),
|
||||
"Expect the following value in the result\n" + expectedString +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
}
|
||||
});
|
||||
|
||||
// for good test cases we check if a reload fails if the remote page
|
||||
// changed from valid to invalid in the meantime
|
||||
if (reload) {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: INVALIDATE_FILE,
|
||||
},
|
||||
function * (browser2) {
|
||||
yield ContentTask.spawn(browser2, null, function * () {
|
||||
ok(content.document.documentElement.innerHTML.includes("Done"),
|
||||
"Expect the following value in the result\n" + "Done" +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
browser.reload();
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
let expectedStrings = [ABOUT_BLANK];
|
||||
if (aNewTabPref == URI_SRI) {
|
||||
expectedStrings = [
|
||||
STYLESHEET_WITHOUT_SRI_BLOCKED,
|
||||
STYLESHEET_WITH_SRI_BLOCKED,
|
||||
SCRIPT_WITHOUT_SRI_BLOCKED,
|
||||
SCRIPT_WITH_SRI_BLOCKED
|
||||
];
|
||||
}
|
||||
yield ContentTask.spawn(browser, expectedStrings,
|
||||
function * (expectedStrings) {
|
||||
for (let expectedString of expectedStrings) {
|
||||
ok(content.document.documentElement.innerHTML.includes(expectedString),
|
||||
"Expect the following value in the result\n" + expectedString +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: VALIDATE_FILE,
|
||||
},
|
||||
function * (browser2) {
|
||||
yield ContentTask.spawn(browser2, null, function * () {
|
||||
ok(content.document.documentElement.innerHTML.includes("Done"),
|
||||
"Expect the following value in the result\n" + "Done" +
|
||||
"\nand got " + content.document.documentElement.innerHTML);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
// run tests from TESTS
|
||||
for (let i = 0; i < TESTS.length; i++) {
|
||||
let testCase = TESTS[i];
|
||||
let url = "", aNewTabPref = "";
|
||||
let reload = false;
|
||||
var aExpectedStrings = testCase.testStrings;
|
||||
if (testCase.aboutURI) {
|
||||
url = ABOUT_NEWTAB_URI;
|
||||
aNewTabPref = testCase.aboutURI;
|
||||
if (aNewTabPref == URI_GOOD || aNewTabPref == URI_SRI) {
|
||||
reload = true;
|
||||
}
|
||||
} else {
|
||||
url = testCase.url;
|
||||
}
|
||||
|
||||
yield doTest(aExpectedStrings, reload, url, aNewTabPref);
|
||||
}
|
||||
}
|
|
@ -1131,6 +1131,20 @@ nsHttpChannel::CallOnStartRequest()
|
|||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -1193,24 +1207,6 @@ nsHttpChannel::CallOnStartRequest()
|
|||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -1532,8 +1528,8 @@ nsHttpChannel::ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead)
|
|||
// create a new listener that meadiates the content
|
||||
RefPtr<ContentVerifier> contentVerifyingMediator =
|
||||
new ContentVerifier(mListener, mListenerContext);
|
||||
rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
|
||||
mListenerContext);
|
||||
rv = contentVerifyingMediator->Init(
|
||||
NS_ConvertUTF8toUTF16(contentSignatureHeader));
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
|
||||
mListener = contentVerifyingMediator;
|
||||
|
||||
|
|
|
@ -10,15 +10,10 @@
|
|||
#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"
|
||||
|
@ -26,10 +21,7 @@
|
|||
#include "pkix/pkixtypes.h"
|
||||
#include "secerr.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(ContentSignatureVerifier,
|
||||
nsIContentSignatureVerifier,
|
||||
nsIInterfaceRequestor,
|
||||
nsIStreamListener)
|
||||
NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::pkix;
|
||||
|
@ -134,18 +126,27 @@ ReadChainIntoCertList(const nsACString& aCertChain, CERTCertList* aCertList,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
|
||||
const nsACString& aCertChain,
|
||||
const nsACString& aName)
|
||||
// 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)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MutexAutoLock lock(mMutex);
|
||||
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;
|
||||
|
@ -213,6 +214,12 @@ ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
|
|||
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 ||
|
||||
|
@ -254,118 +261,18 @@ ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
|
|||
return NS_ERROR_INVALID_SIGNATURE;
|
||||
}
|
||||
|
||||
rv = UpdateInternal(kPREFIX, locker);
|
||||
rv = UpdateInternal(kPREFIX, lock, locker);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// add data if we got any
|
||||
return UpdateInternal(aData, locker);
|
||||
return UpdateInternal(aData, lock, locker);
|
||||
}
|
||||
|
||||
nsresult
|
||||
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*/)
|
||||
ContentSignatureVerifier::UpdateInternal(const nsACString& aData,
|
||||
MutexAutoLock& /*proofOfLock*/,
|
||||
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
||||
{
|
||||
if (!aData.IsEmpty()) {
|
||||
if (VFY_Update(mCx.get(), (const unsigned char*)nsPromiseFlatCString(aData).get(),
|
||||
|
@ -382,22 +289,13 @@ ContentSignatureVerifier::UpdateInternal(
|
|||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
MutexAutoLock lock(mMutex);
|
||||
return UpdateInternal(aData, lock, locker);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -407,21 +305,13 @@ NS_IMETHODIMP
|
|||
ContentSignatureVerifier::End(bool* _retval)
|
||||
{
|
||||
NS_ENSURE_ARG(_retval);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MutexAutoLock lock(mMutex);
|
||||
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;
|
||||
|
@ -431,10 +321,8 @@ 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();
|
||||
|
@ -458,17 +346,6 @@ ContentSignatureVerifier::ParseContentSignatureHeader(
|
|||
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
|
||||
|
@ -485,81 +362,3 @@ ContentSignatureVerifier::ParseContentSignatureHeader(
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "cert.h"
|
||||
#include "CSTrustDomain.h"
|
||||
#include "nsIContentSignatureVerifier.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsNSSShutDown.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
|
||||
|
@ -23,21 +22,15 @@
|
|||
"@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)
|
||||
, mInitialised(false)
|
||||
, mHasCertChain(false)
|
||||
, mMutex("CSVerifier::mMutex")
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -51,11 +44,8 @@ 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()
|
||||
{
|
||||
|
@ -67,26 +57,11 @@ private:
|
|||
|
||||
// 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;
|
||||
// name of the verifying context
|
||||
nsCString mName;
|
||||
// callback to notify when finished
|
||||
nsCOMPtr<nsIContentSignatureReceiverCallback> mCallback;
|
||||
// channel to download the cert chain
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
mozilla::Mutex mMutex;
|
||||
};
|
||||
|
||||
#endif // ContentSignatureVerifier_h
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIContentSignatureReceiverCallback;
|
||||
|
||||
/**
|
||||
* An interface for verifying content-signatures, inspired by
|
||||
* https://tools.ietf.org/html/draft-thomson-http-content-signature-00
|
||||
|
@ -40,8 +38,7 @@ interface nsIContentSignatureVerifier : nsISupports
|
|||
* @returns true if the signature matches the data and aCertificateChain is
|
||||
* valid within aContext, false if not.
|
||||
*/
|
||||
boolean verifyContentSignature(in ACString aData,
|
||||
in ACString aContentSignatureHeader,
|
||||
boolean verifyContentSignature(in ACString aData, in ACString aSignature,
|
||||
in ACString aCertificateChain,
|
||||
in ACString aName);
|
||||
|
||||
|
@ -57,30 +54,9 @@ interface nsIContentSignatureVerifier : nsISupports
|
|||
* @param aName The (host)name for which the end entity must
|
||||
be valid.
|
||||
*/
|
||||
void createContext(in ACString aData, in ACString aContentSignatureHeader,
|
||||
void createContext(in ACString aData, in ACString aSignature,
|
||||
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.
|
||||
*
|
||||
|
@ -96,21 +72,5 @@ interface nsIContentSignatureVerifier : nsISupports
|
|||
* 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);
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче