diff options
Diffstat (limited to 'dom/security/ContentVerifier.cpp')
-rw-r--r-- | dom/security/ContentVerifier.cpp | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/dom/security/ContentVerifier.cpp b/dom/security/ContentVerifier.cpp new file mode 100644 index 000000000..2d3fadea8 --- /dev/null +++ b/dom/security/ContentVerifier.cpp @@ -0,0 +1,230 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "ContentVerifier.h" + +#include "mozilla/fallible.h" +#include "mozilla/Logging.h" +#include "MainThreadUtils.h" +#include "nsIInputStream.h" +#include "nsIRequest.h" +#include "nsServiceManagerUtils.h" +#include "nsStringStream.h" + +using namespace mozilla; + +static LazyLogModule gContentVerifierPRLog("ContentVerifier"); +#define CSV_LOG(args) MOZ_LOG(gContentVerifierPRLog, LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(ContentVerifier, + nsIContentSignatureReceiverCallback, + nsIStreamListener); + +nsresult +ContentVerifier::Init(const nsACString& aContentSignatureHeader, + nsIRequest* aRequest, nsISupports* aContext) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (aContentSignatureHeader.IsEmpty()) { + CSV_LOG(("Content-Signature header 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; + + rv = mVerifier->CreateContextWithoutCertChain( + this, aContentSignatureHeader, + NS_LITERAL_CSTRING("remotenewtab.content-signature.mozilla.org")); + if (NS_FAILED(rv)){ + mVerifier = nullptr; + } + return rv; +} + +/** + * Implement nsIStreamListener + * We buffer the entire content here and kick off verification + */ +nsresult +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); + 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. + 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; +} + +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; + } + + 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 if the context has been created. + if (mContextCreated) { + return mVerifier->Update(mContent.LastElement()); + } + + return NS_OK; +} + +NS_IMETHODIMP +ContentVerifier::ContextCreated(bool successful) +{ + 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); + + // 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 read all content, let's verify the signature. + if (mContentRead) { + FinishSignature(); + } + + return NS_OK; +} |