зеркало из 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/fallible.h"
|
||||||
#include "mozilla/Logging.h"
|
#include "mozilla/Logging.h"
|
||||||
#include "MainThreadUtils.h"
|
#include "mozilla/Preferences.h"
|
||||||
|
#include "mozilla/StaticPtr.h"
|
||||||
|
#include "nsCharSeparatedTokenizer.h"
|
||||||
#include "nsIInputStream.h"
|
#include "nsIInputStream.h"
|
||||||
#include "nsIRequest.h"
|
#include "nsIRequest.h"
|
||||||
|
#include "nssb64.h"
|
||||||
|
#include "nsSecurityHeaderParser.h"
|
||||||
#include "nsServiceManagerUtils.h"
|
#include "nsServiceManagerUtils.h"
|
||||||
#include "nsStringStream.h"
|
#include "nsStringStream.h"
|
||||||
|
#include "nsThreadUtils.h"
|
||||||
|
|
||||||
using namespace mozilla;
|
using namespace mozilla;
|
||||||
|
|
||||||
static LazyLogModule gContentVerifierPRLog("ContentVerifier");
|
static LazyLogModule gContentVerifierPRLog("ContentVerifier");
|
||||||
#define CSV_LOG(args) MOZ_LOG(gContentVerifierPRLog, LogLevel::Debug, args)
|
#define CSV_LOG(args) MOZ_LOG(gContentVerifierPRLog, LogLevel::Debug, args)
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS(ContentVerifier,
|
// Content-Signature prefix
|
||||||
nsIContentSignatureReceiverCallback,
|
const nsLiteralCString kPREFIX = NS_LITERAL_CSTRING("Content-Signature:\x00");
|
||||||
nsIStreamListener);
|
|
||||||
|
NS_IMPL_ISUPPORTS(ContentVerifier, nsIStreamListener, nsISupports);
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
ContentVerifier::Init(const nsACString& aContentSignatureHeader,
|
ContentVerifier::Init(const nsAString& aContentSignatureHeader)
|
||||||
nsIRequest* aRequest, nsISupports* aContext)
|
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
mVks = Preferences::GetString("browser.newtabpage.remote.keys");
|
||||||
if (aContentSignatureHeader.IsEmpty()) {
|
|
||||||
CSV_LOG(("Content-Signature header must not be empty!\n"));
|
if (aContentSignatureHeader.IsEmpty() || mVks.IsEmpty()) {
|
||||||
|
CSV_LOG(
|
||||||
|
("Content-Signature header and verification keys must not be empty!\n"));
|
||||||
return NS_ERROR_INVALID_SIGNATURE;
|
return NS_ERROR_INVALID_SIGNATURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialise the content signature "service"
|
nsresult rv = ParseContentSignatureHeader(aContentSignatureHeader);
|
||||||
nsresult rv;
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
|
||||||
mVerifier =
|
return CreateContext();
|
||||||
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"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,7 +55,7 @@ AppendNextSegment(nsIInputStream* aInputStream, void* aClosure,
|
||||||
{
|
{
|
||||||
FallibleTArray<nsCString>* decodedData =
|
FallibleTArray<nsCString>* decodedData =
|
||||||
static_cast<FallibleTArray<nsCString>*>(aClosure);
|
static_cast<FallibleTArray<nsCString>*>(aClosure);
|
||||||
nsDependentCSubstring segment(aRawSegment, aCount);
|
nsAutoCString segment(aRawSegment, aCount);
|
||||||
if (!decodedData->AppendElement(segment, fallible)) {
|
if (!decodedData->AppendElement(segment, fallible)) {
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
@ -69,57 +63,9 @@ AppendNextSegment(nsIInputStream* aInputStream, void* aClosure,
|
||||||
return NS_OK;
|
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
|
NS_IMETHODIMP
|
||||||
ContentVerifier::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
ContentVerifier::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||||
{
|
{
|
||||||
MOZ_CRASH("This OnStartRequest should've never been called!");
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,28 +73,50 @@ NS_IMETHODIMP
|
||||||
ContentVerifier::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
ContentVerifier::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
||||||
nsresult aStatus)
|
nsresult aStatus)
|
||||||
{
|
{
|
||||||
// If we don't have a next listener, we handed off this request already.
|
// Verify the content:
|
||||||
// Return, there's nothing to do here.
|
// If this fails, we return an invalid signature error to load a fallback page.
|
||||||
if (!mNextListener) {
|
// If everthing is good, we return a new stream to the next listener and kick
|
||||||
return NS_OK;
|
// 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)) {
|
// propagate OnStopRequest and return
|
||||||
CSV_LOG(("Stream failed\n"));
|
return mNextListener->OnStopRequest(aRequest, aContext, rv);
|
||||||
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
|
NS_IMETHODIMP
|
||||||
|
@ -164,63 +132,244 @@ ContentVerifier::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the signature verifier if the context has been created.
|
// update the signature verifier
|
||||||
if (mContextCreated) {
|
return Update(mContent[mContent.Length()-1]);
|
||||||
return mVerifier->Update(mContent.LastElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
/**
|
||||||
ContentVerifier::ContextCreated(bool successful)
|
* ContentVerifier logic and utils
|
||||||
|
*/
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
ContentVerifier::GetVerificationKey(const nsAString& aKeyId)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
// get verification keys from the pref and see if we have |aKeyId|
|
||||||
if (!successful) {
|
nsCharSeparatedTokenizer tokenizerVK(mVks, ';');
|
||||||
// If we don't have a next listener, the request has been handed off already.
|
while (tokenizerVK.hasMoreTokens()) {
|
||||||
if (!mNextListener) {
|
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;
|
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
|
// we didn't find the appropriate key
|
||||||
// the buffered content.
|
return NS_ERROR_INVALID_SIGNATURE;
|
||||||
mContextCreated = true;
|
}
|
||||||
for (size_t i = 0; i < mContent.Length(); ++i) {
|
|
||||||
if (NS_FAILED(mVerifier->Update(mContent[i]))) {
|
nsresult
|
||||||
// Bail out if this fails. We can't return an error here, but if this
|
ContentVerifier::ParseContentSignatureHeader(
|
||||||
// failed, NS_ERROR_INVALID_SIGNATURE is returned in FinishSignature.
|
const nsAString& aContentSignatureHeader)
|
||||||
break;
|
{
|
||||||
|
// 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.
|
// we have to ensure that we found a key and a signature at this point
|
||||||
if (mContentRead) {
|
if (mKey.IsEmpty()) {
|
||||||
FinishSignature();
|
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;
|
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
|
#define mozilla_dom_ContentVerifier_h
|
||||||
|
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsIContentSignatureVerifier.h"
|
|
||||||
#include "nsIObserver.h"
|
#include "nsIObserver.h"
|
||||||
#include "nsIStreamListener.h"
|
#include "nsIStreamListener.h"
|
||||||
|
#include "nsNSSShutDown.h"
|
||||||
#include "nsString.h"
|
#include "nsString.h"
|
||||||
#include "nsTArray.h"
|
#include "nsTArray.h"
|
||||||
|
#include "ScopedNSSTypes.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mediator intercepting OnStartRequest in nsHttpChannel, blocks until all
|
* Mediator intercepting OnStartRequest in nsHttpChannel, blocks until all
|
||||||
|
@ -21,44 +22,77 @@
|
||||||
* NS_ERROR_INVALID_SIGNATURE is thrown.
|
* NS_ERROR_INVALID_SIGNATURE is thrown.
|
||||||
*/
|
*/
|
||||||
class ContentVerifier : public nsIStreamListener
|
class ContentVerifier : public nsIStreamListener
|
||||||
, public nsIContentSignatureReceiverCallback
|
, public nsNSSShutDownObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
NS_DECL_NSISTREAMLISTENER
|
NS_DECL_NSISTREAMLISTENER
|
||||||
NS_DECL_NSIREQUESTOBSERVER
|
NS_DECL_NSIREQUESTOBSERVER
|
||||||
NS_DECL_NSICONTENTSIGNATURERECEIVERCALLBACK
|
|
||||||
|
|
||||||
explicit ContentVerifier(nsIStreamListener* aMediatedListener,
|
explicit ContentVerifier(nsIStreamListener* aMediatedListener,
|
||||||
nsISupports* aMediatedContext)
|
nsISupports* aMediatedContext)
|
||||||
: mNextListener(aMediatedListener)
|
: mNextListener(aMediatedListener)
|
||||||
, mContextCreated(false)
|
, mContext(aMediatedContext)
|
||||||
, mContentRead(false) {}
|
, mCx(nullptr) {}
|
||||||
|
|
||||||
nsresult Init(const nsACString& aContentSignatureHeader, nsIRequest* aRequest,
|
nsresult Init(const nsAString& aContentSignatureHeader);
|
||||||
nsISupports* aContext);
|
|
||||||
|
// nsNSSShutDownObject
|
||||||
|
virtual void virtualDestroyNSSReference() override
|
||||||
|
{
|
||||||
|
destructorSafeDestroyNSSReference();
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~ContentVerifier() {}
|
virtual ~ContentVerifier()
|
||||||
|
{
|
||||||
|
nsNSSShutDownPreventionLock locker;
|
||||||
|
if (isAlreadyShutDown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
destructorSafeDestroyNSSReference();
|
||||||
|
shutdown(calledFromObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destructorSafeDestroyNSSReference()
|
||||||
|
{
|
||||||
|
mCx = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
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
|
// content and next listener for nsIStreamListener
|
||||||
nsCOMPtr<nsIStreamListener> mNextListener;
|
nsCOMPtr<nsIStreamListener> mNextListener;
|
||||||
// the verifier
|
nsCOMPtr<nsISupports> mContext;
|
||||||
nsCOMPtr<nsIContentSignatureVerifier> mVerifier;
|
|
||||||
// holding a pointer to the content request and context to resume/cancel it
|
// verifier context for incrementel verifications
|
||||||
nsCOMPtr<nsIRequest> mContentRequest;
|
mozilla::UniqueVFYContext mCx;
|
||||||
nsCOMPtr<nsISupports> mContentContext;
|
// buffered content to verify
|
||||||
// Semaphors to indicate that the verifying context was created, the entire
|
FallibleTArray<nsCString> mContent;
|
||||||
// content was read resp. The context gets created by ContentSignatureVerifier
|
// signature to verify
|
||||||
// and mContextCreated is set in the ContextCreated callback. The content is
|
nsCString mSignature;
|
||||||
// read, i.e. mContentRead is set, when the content OnStopRequest is called.
|
// verification key
|
||||||
bool mContextCreated;
|
nsCString mKey;
|
||||||
bool mContentRead;
|
// verification key preference
|
||||||
|
nsString mVks;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* mozilla_dom_ContentVerifier_h */
|
#endif /* mozilla_dom_ContentVerifier_h */
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "nsAttrValue.h"
|
#include "nsAttrValue.h"
|
||||||
#include "nsCharSeparatedTokenizer.h"
|
|
||||||
#include "nsContentUtils.h"
|
#include "nsContentUtils.h"
|
||||||
#include "nsCSPUtils.h"
|
#include "nsCSPUtils.h"
|
||||||
#include "nsDebug.h"
|
#include "nsDebug.h"
|
||||||
|
|
|
@ -10,10 +10,7 @@ support-files =
|
||||||
file_about_newtab_broken_signature
|
file_about_newtab_broken_signature
|
||||||
file_about_newtab_sri.html
|
file_about_newtab_sri.html
|
||||||
file_about_newtab_sri_signature
|
file_about_newtab_sri_signature
|
||||||
goodChain.pem
|
|
||||||
head.js
|
|
||||||
script.js
|
script.js
|
||||||
style.css
|
style.css
|
||||||
|
|
||||||
[browser_verify_content_about_newtab.js]
|
[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 = [
|
const TESTS = [
|
||||||
// { newtab (aboutURI) or regular load (url) : url,
|
// { newtab (aboutURI) or regular load (url) : url,
|
||||||
// testStrings : expected strings in the loaded page }
|
// testStrings : expected strings in the loaded page }
|
||||||
{ "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
|
{ "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
|
||||||
{ "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
|
{ "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] },
|
{ "url" : URI_BAD_FILE_CACHED, "testStrings" : [BAD_ABOUT_STRING] },
|
||||||
{ "aboutURI" : URI_BAD_FILE_CACHED, "testStrings" : [ABOUT_BLANK] },
|
{ "aboutURI" : URI_BAD_FILE_CACHED, "testStrings" : [ABOUT_BLANK] },
|
||||||
{ "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
|
{ "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
|
||||||
|
@ -17,4 +86,158 @@ const TESTS = [
|
||||||
{ "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
|
{ "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)
|
// sjs for remote about:newtab (bug 1226928)
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||||
|
@ -17,15 +12,14 @@ const goodFileBase = path + goodFileName;
|
||||||
const goodFile = FileUtils.getDir("TmpD", [], true);
|
const goodFile = FileUtils.getDir("TmpD", [], true);
|
||||||
goodFile.append(goodFileName);
|
goodFile.append(goodFileName);
|
||||||
const goodSignature = path + "file_about_newtab_good_signature";
|
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 scriptFileName = "script.js";
|
||||||
const cssFileName = "style.css";
|
const cssFileName = "style.css";
|
||||||
const badFile = path + "file_about_newtab_bad.html";
|
const badFile = path + "file_about_newtab_bad.html";
|
||||||
const brokenSignature = path + "file_about_newtab_broken_signature";
|
const brokenSignature = path + "file_about_newtab_broken_signature";
|
||||||
const badSignature = path + "file_about_newtab_bad_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 badKeyId = "OldRemoteNewTabKey";
|
||||||
const httpX5UString = "\"http://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
|
|
||||||
|
|
||||||
const sriFile = path + "file_about_newtab_sri.html";
|
const sriFile = path + "file_about_newtab_sri.html";
|
||||||
const sriSignature = path + "file_about_newtab_sri_signature";
|
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 badCspFile = path + "file_about_newtab_bad_csp.html";
|
||||||
const badCspSignature = path + "file_about_newtab_bad_csp_signature";
|
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];
|
const tempFileNames = [goodFileName, scriptFileName, cssFileName];
|
||||||
|
|
||||||
// we copy the file to serve as newtab to a temp directory because
|
// we copy the file to serve as newtab to a temp directory because
|
||||||
|
@ -129,7 +115,7 @@ function cleanupTestFiles() {
|
||||||
*/
|
*/
|
||||||
function handleRequest(request, response) {
|
function handleRequest(request, response) {
|
||||||
let params = new URLSearchParams(request.queryString);
|
let params = new URLSearchParams(request.queryString);
|
||||||
let x5uType = params.get("x5u");
|
let keyType = params.get("key");
|
||||||
let signatureType = params.get("sig");
|
let signatureType = params.get("sig");
|
||||||
let fileType = params.get("file");
|
let fileType = params.get("file");
|
||||||
let headerType = params.get("header");
|
let headerType = params.get("header");
|
||||||
|
@ -137,7 +123,6 @@ function handleRequest(request, response) {
|
||||||
let invalidateFile = params.get("invalidateFile");
|
let invalidateFile = params.get("invalidateFile");
|
||||||
let validateFile = params.get("validateFile");
|
let validateFile = params.get("validateFile");
|
||||||
let resource = params.get("resource");
|
let resource = params.get("resource");
|
||||||
let x5uParam = params.get("x5u");
|
|
||||||
|
|
||||||
if (params.get("cleanup")) {
|
if (params.get("cleanup")) {
|
||||||
cleanupTestFiles();
|
cleanupTestFiles();
|
||||||
|
@ -186,14 +171,6 @@ function handleRequest(request, response) {
|
||||||
return;
|
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
|
// avoid confusing cache behaviours
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
response.setHeader("Cache-Control", "no-cache", false);
|
response.setHeader("Cache-Control", "no-cache", false);
|
||||||
|
@ -209,13 +186,11 @@ function handleRequest(request, response) {
|
||||||
* value has to be indicated in the url.
|
* value has to be indicated in the url.
|
||||||
*/
|
*/
|
||||||
let csHeader = "";
|
let csHeader = "";
|
||||||
let x5uString = goodX5UString;
|
let keyId = goodKeyId;
|
||||||
let signature = goodSignature;
|
let signature = goodSignature;
|
||||||
let file = goodFile;
|
let file = goodFile;
|
||||||
if (x5uType == "bad") {
|
if (keyType == "bad") {
|
||||||
x5uString = badX5UString;
|
keyId = badKeyId;
|
||||||
} else if (x5uType == "http") {
|
|
||||||
x5uString = httpX5UString;
|
|
||||||
}
|
}
|
||||||
if (signatureType == "bad") {
|
if (signatureType == "bad") {
|
||||||
signature = badSignature;
|
signature = badSignature;
|
||||||
|
@ -236,19 +211,19 @@ function handleRequest(request, response) {
|
||||||
|
|
||||||
if (headerType == "good") {
|
if (headerType == "good") {
|
||||||
// a valid content-signature header
|
// a valid content-signature header
|
||||||
csHeader = "x5u=" + x5uString + ";p384ecdsa=" +
|
csHeader = "keyid=" + keyId + ";p384ecdsa=" +
|
||||||
loadFile(getFileName(signature, "CurWorkD"));
|
loadFile(getFileName(signature, "CurWorkD"));
|
||||||
} else if (headerType == "error") {
|
} else if (headerType == "error") {
|
||||||
// this content-signature header is missing ; before p384ecdsa
|
// this content-signature header is missing ; before p384ecdsa
|
||||||
csHeader = "x5u=" + x5uString + "p384ecdsa=" +
|
csHeader = "keyid=" + keyId + "p384ecdsa=" +
|
||||||
loadFile(getFileName(signature, "CurWorkD"));
|
loadFile(getFileName(signature, "CurWorkD"));
|
||||||
} else if (headerType == "errorInX5U") {
|
} else if (headerType == "errorInKeyid") {
|
||||||
// this content-signature header is missing the keyid directive
|
// this content-signature header is missing the keyid directive
|
||||||
csHeader = "x6u=" + x5uString + ";p384ecdsa=" +
|
csHeader = "keid=" + keyId + ";p384ecdsa=" +
|
||||||
loadFile(getFileName(signature, "CurWorkD"));
|
loadFile(getFileName(signature, "CurWorkD"));
|
||||||
} else if (headerType == "errorInSignature") {
|
} else if (headerType == "errorInSignature") {
|
||||||
// this content-signature header is missing the p384ecdsa directive
|
// this content-signature header is missing the p384ecdsa directive
|
||||||
csHeader = "x5u=" + x5uString + ";p385ecdsa=" +
|
csHeader = "keyid=" + keyId + ";p385ecdsa=" +
|
||||||
loadFile(getFileName(signature, "CurWorkD"));
|
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"));
|
LOG((" calling mListener->OnStartRequest\n"));
|
||||||
if (mListener) {
|
if (mListener) {
|
||||||
MOZ_ASSERT(!mOnStartRequestCalled,
|
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;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1532,8 +1528,8 @@ nsHttpChannel::ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead)
|
||||||
// create a new listener that meadiates the content
|
// create a new listener that meadiates the content
|
||||||
RefPtr<ContentVerifier> contentVerifyingMediator =
|
RefPtr<ContentVerifier> contentVerifyingMediator =
|
||||||
new ContentVerifier(mListener, mListenerContext);
|
new ContentVerifier(mListener, mListenerContext);
|
||||||
rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
|
rv = contentVerifyingMediator->Init(
|
||||||
mListenerContext);
|
NS_ConvertUTF8toUTF16(contentSignatureHeader));
|
||||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
|
||||||
mListener = contentVerifyingMediator;
|
mListener = contentVerifyingMediator;
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,10 @@
|
||||||
#include "SharedCertVerifier.h"
|
#include "SharedCertVerifier.h"
|
||||||
#include "cryptohi.h"
|
#include "cryptohi.h"
|
||||||
#include "keyhi.h"
|
#include "keyhi.h"
|
||||||
#include "mozilla/Assertions.h"
|
|
||||||
#include "mozilla/Casting.h"
|
#include "mozilla/Casting.h"
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsContentUtils.h"
|
|
||||||
#include "nsISupportsPriority.h"
|
|
||||||
#include "nsIURI.h"
|
|
||||||
#include "nsNSSComponent.h"
|
#include "nsNSSComponent.h"
|
||||||
#include "nsSecurityHeaderParser.h"
|
#include "nsSecurityHeaderParser.h"
|
||||||
#include "nsStreamUtils.h"
|
|
||||||
#include "nsWhitespaceTokenizer.h"
|
#include "nsWhitespaceTokenizer.h"
|
||||||
#include "nsXPCOMStrings.h"
|
#include "nsXPCOMStrings.h"
|
||||||
#include "nssb64.h"
|
#include "nssb64.h"
|
||||||
|
@ -26,10 +21,7 @@
|
||||||
#include "pkix/pkixtypes.h"
|
#include "pkix/pkixtypes.h"
|
||||||
#include "secerr.h"
|
#include "secerr.h"
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS(ContentSignatureVerifier,
|
NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
|
||||||
nsIContentSignatureVerifier,
|
|
||||||
nsIInterfaceRequestor,
|
|
||||||
nsIStreamListener)
|
|
||||||
|
|
||||||
using namespace mozilla;
|
using namespace mozilla;
|
||||||
using namespace mozilla::pkix;
|
using namespace mozilla::pkix;
|
||||||
|
@ -134,18 +126,27 @@ ReadChainIntoCertList(const nsACString& aCertChain, CERTCertList* aCertList,
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
// Create a context for a content signature verification.
|
||||||
ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
|
// It sets signature, certificate chain and name that should be used to verify
|
||||||
const nsACString& aCertChain,
|
// the data. The data parameter is the first part of the data to verify (this
|
||||||
const nsACString& aName)
|
// 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;
|
nsNSSShutDownPreventionLock locker;
|
||||||
if (isAlreadyShutDown()) {
|
if (isAlreadyShutDown()) {
|
||||||
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
|
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mCx) {
|
||||||
|
return NS_ERROR_ALREADY_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
UniqueCERTCertList certCertList(CERT_NewCertList());
|
UniqueCERTCertList certCertList(CERT_NewCertList());
|
||||||
if (!certCertList) {
|
if (!certCertList) {
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
@ -213,6 +214,12 @@ ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
|
||||||
return NS_ERROR_INVALID_SIGNATURE;
|
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
|
// Base 64 decode the signature
|
||||||
UniqueSECItem rawSignatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
|
UniqueSECItem rawSignatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
|
||||||
if (!rawSignatureItem ||
|
if (!rawSignatureItem ||
|
||||||
|
@ -254,118 +261,18 @@ ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
|
||||||
return NS_ERROR_INVALID_SIGNATURE;
|
return NS_ERROR_INVALID_SIGNATURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = UpdateInternal(kPREFIX, locker);
|
rv = UpdateInternal(kPREFIX, lock, locker);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
// add data if we got any
|
// add data if we got any
|
||||||
return UpdateInternal(aData, locker);
|
return UpdateInternal(aData, lock, locker);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
ContentSignatureVerifier::DownloadCertChain()
|
ContentSignatureVerifier::UpdateInternal(const nsACString& aData,
|
||||||
{
|
MutexAutoLock& /*proofOfLock*/,
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
||||||
|
|
||||||
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 (!aData.IsEmpty()) {
|
||||||
if (VFY_Update(mCx.get(), (const unsigned char*)nsPromiseFlatCString(aData).get(),
|
if (VFY_Update(mCx.get(), (const unsigned char*)nsPromiseFlatCString(aData).get(),
|
||||||
|
@ -382,22 +289,13 @@ ContentSignatureVerifier::UpdateInternal(
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
ContentSignatureVerifier::Update(const nsACString& aData)
|
ContentSignatureVerifier::Update(const nsACString& aData)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
|
||||||
nsNSSShutDownPreventionLock locker;
|
nsNSSShutDownPreventionLock locker;
|
||||||
if (isAlreadyShutDown()) {
|
if (isAlreadyShutDown()) {
|
||||||
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
|
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
MutexAutoLock lock(mMutex);
|
||||||
// If we didn't create the context yet, bail!
|
return UpdateInternal(aData, lock, locker);
|
||||||
if (!mHasCertChain) {
|
|
||||||
MOZ_ASSERT_UNREACHABLE(
|
|
||||||
"Someone called ContentSignatureVerifier::Update before "
|
|
||||||
"downloading the cert chain.");
|
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return UpdateInternal(aData, locker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -407,21 +305,13 @@ NS_IMETHODIMP
|
||||||
ContentSignatureVerifier::End(bool* _retval)
|
ContentSignatureVerifier::End(bool* _retval)
|
||||||
{
|
{
|
||||||
NS_ENSURE_ARG(_retval);
|
NS_ENSURE_ARG(_retval);
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MutexAutoLock lock(mMutex);
|
||||||
nsNSSShutDownPreventionLock locker;
|
nsNSSShutDownPreventionLock locker;
|
||||||
if (isAlreadyShutDown()) {
|
if (isAlreadyShutDown()) {
|
||||||
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
|
CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
|
||||||
return NS_ERROR_FAILURE;
|
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);
|
*_retval = (VFY_End(mCx.get()) == SECSuccess);
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
@ -431,10 +321,8 @@ nsresult
|
||||||
ContentSignatureVerifier::ParseContentSignatureHeader(
|
ContentSignatureVerifier::ParseContentSignatureHeader(
|
||||||
const nsACString& aContentSignatureHeader)
|
const nsACString& aContentSignatureHeader)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
|
||||||
// We only support p384 ecdsa according to spec
|
// We only support p384 ecdsa according to spec
|
||||||
NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
|
NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
|
||||||
NS_NAMED_LITERAL_CSTRING(certChainURL_var, "x5u");
|
|
||||||
|
|
||||||
nsSecurityHeaderParser parser(aContentSignatureHeader.BeginReading());
|
nsSecurityHeaderParser parser(aContentSignatureHeader.BeginReading());
|
||||||
nsresult rv = parser.Parse();
|
nsresult rv = parser.Parse();
|
||||||
|
@ -458,17 +346,6 @@ ContentSignatureVerifier::ParseContentSignatureHeader(
|
||||||
CSVerifier_LOG(("CSVerifier: found a ContentSignature directive\n"));
|
CSVerifier_LOG(("CSVerifier: found a ContentSignature directive\n"));
|
||||||
mSignature = directive->mValue;
|
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
|
// we have to ensure that we found a signature at this point
|
||||||
|
@ -485,81 +362,3 @@ ContentSignatureVerifier::ParseContentSignatureHeader(
|
||||||
|
|
||||||
return NS_OK;
|
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 "cert.h"
|
||||||
#include "CSTrustDomain.h"
|
#include "CSTrustDomain.h"
|
||||||
#include "nsIContentSignatureVerifier.h"
|
#include "nsIContentSignatureVerifier.h"
|
||||||
#include "nsIStreamListener.h"
|
|
||||||
#include "nsNSSShutDown.h"
|
#include "nsNSSShutDown.h"
|
||||||
#include "ScopedNSSTypes.h"
|
#include "ScopedNSSTypes.h"
|
||||||
|
|
||||||
|
@ -23,21 +22,15 @@
|
||||||
"@mozilla.org/security/contentsignatureverifier;1"
|
"@mozilla.org/security/contentsignatureverifier;1"
|
||||||
|
|
||||||
class ContentSignatureVerifier final : public nsIContentSignatureVerifier
|
class ContentSignatureVerifier final : public nsIContentSignatureVerifier
|
||||||
, public nsIStreamListener
|
|
||||||
, public nsNSSShutDownObject
|
, public nsNSSShutDownObject
|
||||||
, public nsIInterfaceRequestor
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
NS_DECL_NSICONTENTSIGNATUREVERIFIER
|
NS_DECL_NSICONTENTSIGNATUREVERIFIER
|
||||||
NS_DECL_NSIINTERFACEREQUESTOR
|
|
||||||
NS_DECL_NSISTREAMLISTENER
|
|
||||||
NS_DECL_NSIREQUESTOBSERVER
|
|
||||||
|
|
||||||
ContentSignatureVerifier()
|
ContentSignatureVerifier()
|
||||||
: mCx(nullptr)
|
: mCx(nullptr)
|
||||||
, mInitialised(false)
|
, mMutex("CSVerifier::mMutex")
|
||||||
, mHasCertChain(false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +44,8 @@ private:
|
||||||
~ContentSignatureVerifier();
|
~ContentSignatureVerifier();
|
||||||
|
|
||||||
nsresult UpdateInternal(const nsACString& aData,
|
nsresult UpdateInternal(const nsACString& aData,
|
||||||
|
MutexAutoLock& /*proofOfLock*/,
|
||||||
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
|
||||||
nsresult DownloadCertChain();
|
|
||||||
nsresult CreateContextInternal(const nsACString& aData,
|
|
||||||
const nsACString& aCertChain,
|
|
||||||
const nsACString& aName);
|
|
||||||
|
|
||||||
void destructorSafeDestroyNSSReference()
|
void destructorSafeDestroyNSSReference()
|
||||||
{
|
{
|
||||||
|
@ -67,26 +57,11 @@ private:
|
||||||
|
|
||||||
// verifier context for incremental verifications
|
// verifier context for incremental verifications
|
||||||
mozilla::UniqueVFYContext mCx;
|
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
|
// signature to verify
|
||||||
nsCString mSignature;
|
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
|
// verification key
|
||||||
mozilla::UniqueSECKEYPublicKey mKey;
|
mozilla::UniqueSECKEYPublicKey mKey;
|
||||||
// name of the verifying context
|
mozilla::Mutex mMutex;
|
||||||
nsCString mName;
|
|
||||||
// callback to notify when finished
|
|
||||||
nsCOMPtr<nsIContentSignatureReceiverCallback> mCallback;
|
|
||||||
// channel to download the cert chain
|
|
||||||
nsCOMPtr<nsIChannel> mChannel;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ContentSignatureVerifier_h
|
#endif // ContentSignatureVerifier_h
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
interface nsIContentSignatureReceiverCallback;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for verifying content-signatures, inspired by
|
* An interface for verifying content-signatures, inspired by
|
||||||
* https://tools.ietf.org/html/draft-thomson-http-content-signature-00
|
* 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
|
* @returns true if the signature matches the data and aCertificateChain is
|
||||||
* valid within aContext, false if not.
|
* valid within aContext, false if not.
|
||||||
*/
|
*/
|
||||||
boolean verifyContentSignature(in ACString aData,
|
boolean verifyContentSignature(in ACString aData, in ACString aSignature,
|
||||||
in ACString aContentSignatureHeader,
|
|
||||||
in ACString aCertificateChain,
|
in ACString aCertificateChain,
|
||||||
in ACString aName);
|
in ACString aName);
|
||||||
|
|
||||||
|
@ -57,30 +54,9 @@ interface nsIContentSignatureVerifier : nsISupports
|
||||||
* @param aName The (host)name for which the end entity must
|
* @param aName The (host)name for which the end entity must
|
||||||
be valid.
|
be valid.
|
||||||
*/
|
*/
|
||||||
void createContext(in ACString aData, in ACString aContentSignatureHeader,
|
void createContext(in ACString aData, in ACString aSignature,
|
||||||
in ACString aCertificateChain, in ACString aName);
|
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.
|
* Adds data to the context that was used to generate the signature.
|
||||||
*
|
*
|
||||||
|
@ -96,21 +72,5 @@ interface nsIContentSignatureVerifier : nsISupports
|
||||||
* and update, false if not.
|
* and update, false if not.
|
||||||
*/
|
*/
|
||||||
boolean end();
|
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);
|
|
||||||
};
|
};
|
||||||
|
|
Загрузка…
Ссылка в новой задаче