gecko-dev/dom/security/ContentVerifier.cpp

231 lines
7.1 KiB
C++
Raw Normal View History

/* -*- 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;
}