summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/ContentSignatureVerifier.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/ContentSignatureVerifier.cpp')
-rw-r--r--security/manager/ssl/ContentSignatureVerifier.cpp562
1 files changed, 562 insertions, 0 deletions
diff --git a/security/manager/ssl/ContentSignatureVerifier.cpp b/security/manager/ssl/ContentSignatureVerifier.cpp
new file mode 100644
index 000000000..bdeb3f264
--- /dev/null
+++ b/security/manager/ssl/ContentSignatureVerifier.cpp
@@ -0,0 +1,562 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ContentSignatureVerifier.h"
+
+#include "BRNameMatchingPolicy.h"
+#include "SharedCertVerifier.h"
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Unused.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsISupportsPriority.h"
+#include "nsIURI.h"
+#include "nsNSSComponent.h"
+#include "nsSecurityHeaderParser.h"
+#include "nsStreamUtils.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsXPCOMStrings.h"
+#include "nssb64.h"
+#include "pkix/pkix.h"
+#include "pkix/pkixtypes.h"
+#include "secerr.h"
+
+NS_IMPL_ISUPPORTS(ContentSignatureVerifier,
+ nsIContentSignatureVerifier,
+ nsIInterfaceRequestor,
+ nsIStreamListener)
+
+using namespace mozilla;
+using namespace mozilla::pkix;
+using namespace mozilla::psm;
+
+static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
+#define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
+
+// Content-Signature prefix
+const nsLiteralCString kPREFIX = NS_LITERAL_CSTRING("Content-Signature:\x00");
+
+ContentSignatureVerifier::~ContentSignatureVerifier()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::VerifyContentSignature(
+ const nsACString& aData, const nsACString& aCSHeader,
+ const nsACString& aCertChain, const nsACString& aName, bool* _retval)
+{
+ NS_ENSURE_ARG(_retval);
+ nsresult rv = CreateContext(aData, aCSHeader, aCertChain, aName);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ CSVerifier_LOG(("CSVerifier: Signature verification failed\n"));
+ if (rv == NS_ERROR_INVALID_SIGNATURE) {
+ return NS_OK;
+ }
+ return rv;
+ }
+
+ return End(_retval);
+}
+
+bool
+IsNewLine(char16_t c)
+{
+ return c == '\n' || c == '\r';
+}
+
+nsresult
+ReadChainIntoCertList(const nsACString& aCertChain, CERTCertList* aCertList,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ bool inBlock = false;
+ bool certFound = false;
+
+ const nsCString header = NS_LITERAL_CSTRING("-----BEGIN CERTIFICATE-----");
+ const nsCString footer = NS_LITERAL_CSTRING("-----END CERTIFICATE-----");
+
+ nsCWhitespaceTokenizerTemplate<IsNewLine> tokenizer(aCertChain);
+
+ nsAutoCString blockData;
+ while (tokenizer.hasMoreTokens()) {
+ nsDependentCSubstring token = tokenizer.nextToken();
+ if (token.IsEmpty()) {
+ continue;
+ }
+ if (inBlock) {
+ if (token.Equals(footer)) {
+ inBlock = false;
+ certFound = true;
+ // base64 decode data, make certs, append to chain
+ ScopedAutoSECItem der;
+ if (!NSSBase64_DecodeBuffer(nullptr, &der, blockData.BeginReading(),
+ blockData.Length())) {
+ CSVerifier_LOG(("CSVerifier: decoding the signature failed\n"));
+ return NS_ERROR_FAILURE;
+ }
+ UniqueCERTCertificate tmpCert(
+ CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der, nullptr, false,
+ true));
+ if (!tmpCert) {
+ return NS_ERROR_FAILURE;
+ }
+ // if adding tmpCert succeeds, tmpCert will now be owned by aCertList
+ SECStatus res = CERT_AddCertToListTail(aCertList, tmpCert.get());
+ if (res != SECSuccess) {
+ return MapSECStatus(res);
+ }
+ Unused << tmpCert.release();
+ } else {
+ blockData.Append(token);
+ }
+ } else if (token.Equals(header)) {
+ inBlock = true;
+ blockData = "";
+ }
+ }
+ if (inBlock || !certFound) {
+ // the PEM data did not end; bad data.
+ CSVerifier_LOG(("CSVerifier: supplied chain contains bad data\n"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
+ const nsACString& aCertChain,
+ const nsACString& aName)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ UniqueCERTCertList certCertList(CERT_NewCertList());
+ if (!certCertList) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = ReadChainIntoCertList(aCertChain, certCertList.get(), locker);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CERTCertListNode* node = CERT_LIST_HEAD(certCertList.get());
+ if (!node || CERT_LIST_END(node, certCertList.get()) || !node->cert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SECItem* certSecItem = &node->cert->derCert;
+
+ Input certDER;
+ mozilla::pkix::Result result =
+ certDER.Init(BitwiseCast<uint8_t*, unsigned char*>(certSecItem->data),
+ certSecItem->len);
+ if (result != Success) {
+ return NS_ERROR_FAILURE;
+ }
+
+
+ // Check the signerCert chain is good
+ CSTrustDomain trustDomain(certCertList);
+ result = BuildCertChain(trustDomain, certDER, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_codeSigning,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/);
+ if (result != Success) {
+ // if there was a library error, return an appropriate error
+ if (IsFatalError(result)) {
+ return NS_ERROR_FAILURE;
+ }
+ // otherwise, assume the signature was invalid
+ CSVerifier_LOG(("CSVerifier: The supplied chain is bad\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // Check the SAN
+ Input hostnameInput;
+
+ result = hostnameInput.Init(
+ BitwiseCast<const uint8_t*, const char*>(aName.BeginReading()),
+ aName.Length());
+ if (result != Success) {
+ return NS_ERROR_FAILURE;
+ }
+
+ BRNameMatchingPolicy nameMatchingPolicy(BRNameMatchingPolicy::Mode::Enforce);
+ result = CheckCertHostname(certDER, hostnameInput, nameMatchingPolicy);
+ if (result != Success) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ mKey.reset(CERT_ExtractPublicKey(node->cert));
+
+ // in case we were not able to extract a key
+ if (!mKey) {
+ CSVerifier_LOG(("CSVerifier: unable to extract a key\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // Base 64 decode the signature
+ ScopedAutoSECItem rawSignatureItem;
+ if (!NSSBase64_DecodeBuffer(nullptr, &rawSignatureItem, mSignature.get(),
+ mSignature.Length())) {
+ CSVerifier_LOG(("CSVerifier: decoding the signature failed\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // get signature object
+ ScopedAutoSECItem signatureItem;
+ // 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) {
+ CSVerifier_LOG(("CSVerifier: signature length is bad\n"));
+ return NS_ERROR_FAILURE;
+ }
+ if (DSAU_EncodeDerSigWithLen(&signatureItem, &rawSignatureItem,
+ rawSignatureItem.len) != SECSuccess) {
+ CSVerifier_LOG(("CSVerifier: encoding the signature failed\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // this is the only OID we support for now
+ SECOidTag oid = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
+
+ mCx = UniqueVFYContext(
+ VFY_CreateContext(mKey.get(), &signatureItem, oid, nullptr));
+ if (!mCx) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ if (VFY_Begin(mCx.get()) != SECSuccess) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ rv = UpdateInternal(kPREFIX, locker);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // add data if we got any
+ return UpdateInternal(aData, locker);
+}
+
+nsresult
+ContentSignatureVerifier::DownloadCertChain()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCertChainURL.IsEmpty()) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ nsCOMPtr<nsIURI> certChainURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(certChainURI), mCertChainURL);
+ if (NS_FAILED(rv) || !certChainURI) {
+ return rv;
+ }
+
+ // If the address is not https, fail.
+ bool isHttps = false;
+ rv = certChainURI->SchemeIs("https", &isHttps);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!isHttps) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ rv = NS_NewChannel(getter_AddRefs(mChannel), certChainURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // we need this chain soon
+ nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel);
+ if (priorityChannel) {
+ priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+
+ rv = mChannel->AsyncOpen2(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// Create a context for content signature verification using CreateContext below.
+// This function doesn't require a cert chain to be passed, but instead aCSHeader
+// must contain an x5u value that is then used to download the cert chain.
+NS_IMETHODIMP
+ContentSignatureVerifier::CreateContextWithoutCertChain(
+ nsIContentSignatureReceiverCallback *aCallback, const nsACString& aCSHeader,
+ const nsACString& aName)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCallback);
+ if (mInitialised) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ mInitialised = true;
+
+ // we get the raw content-signature header here, so first parse aCSHeader
+ nsresult rv = ParseContentSignatureHeader(aCSHeader);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mCallback = aCallback;
+ mName.Assign(aName);
+
+ // We must download the cert chain now.
+ // This is async and blocks createContextInternal calls.
+ return DownloadCertChain();
+}
+
+// Create a context for a content signature verification.
+// It sets signature, certificate chain and name that should be used to verify
+// the data. The data parameter is the first part of the data to verify (this
+// can be the empty string).
+NS_IMETHODIMP
+ContentSignatureVerifier::CreateContext(const nsACString& aData,
+ const nsACString& aCSHeader,
+ const nsACString& aCertChain,
+ const nsACString& aName)
+{
+ if (mInitialised) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ mInitialised = true;
+ // The cert chain is given in aCertChain so we don't have to download anything.
+ mHasCertChain = true;
+
+ // we get the raw content-signature header here, so first parse aCSHeader
+ nsresult rv = ParseContentSignatureHeader(aCSHeader);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return CreateContextInternal(aData, aCertChain, aName);
+}
+
+nsresult
+ContentSignatureVerifier::UpdateInternal(
+ const nsACString& aData, const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ if (!aData.IsEmpty()) {
+ if (VFY_Update(mCx.get(), (const unsigned char*)nsPromiseFlatCString(aData).get(),
+ aData.Length()) != SECSuccess){
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Add data to the context that shold be verified.
+ */
+NS_IMETHODIMP
+ContentSignatureVerifier::Update(const nsACString& aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we didn't create the context yet, bail!
+ if (!mHasCertChain) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Someone called ContentSignatureVerifier::Update before "
+ "downloading the cert chain.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return UpdateInternal(aData, locker);
+}
+
+/**
+ * Finish signature verification and return the result in _retval.
+ */
+NS_IMETHODIMP
+ContentSignatureVerifier::End(bool* _retval)
+{
+ NS_ENSURE_ARG(_retval);
+ MOZ_ASSERT(NS_IsMainThread());
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we didn't create the context yet, bail!
+ if (!mHasCertChain) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Someone called ContentSignatureVerifier::End before "
+ "downloading the cert chain.");
+ return NS_ERROR_FAILURE;
+ }
+
+ *_retval = (VFY_End(mCx.get()) == SECSuccess);
+
+ return NS_OK;
+}
+
+nsresult
+ContentSignatureVerifier::ParseContentSignatureHeader(
+ const nsACString& aContentSignatureHeader)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We only support p384 ecdsa according to spec
+ NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
+ NS_NAMED_LITERAL_CSTRING(certChainURL_var, "x5u");
+
+ nsSecurityHeaderParser parser(aContentSignatureHeader.BeginReading());
+ nsresult rv = parser.Parse();
+ if (NS_FAILED(rv)) {
+ CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header\n"));
+ return NS_ERROR_FAILURE;
+ }
+ LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
+
+ for (nsSecurityHeaderDirective* directive = directives->getFirst();
+ directive != nullptr; directive = directive->getNext()) {
+ CSVerifier_LOG(("CSVerifier: found directive %s\n", directive->mName.get()));
+ if (directive->mName.Length() == signature_var.Length() &&
+ directive->mName.EqualsIgnoreCase(signature_var.get(),
+ signature_var.Length())) {
+ if (!mSignature.IsEmpty()) {
+ CSVerifier_LOG(("CSVerifier: found two ContentSignatures\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ CSVerifier_LOG(("CSVerifier: found a ContentSignature directive\n"));
+ mSignature = directive->mValue;
+ }
+ if (directive->mName.Length() == certChainURL_var.Length() &&
+ directive->mName.EqualsIgnoreCase(certChainURL_var.get(),
+ certChainURL_var.Length())) {
+ if (!mCertChainURL.IsEmpty()) {
+ CSVerifier_LOG(("CSVerifier: found two x5u values\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ CSVerifier_LOG(("CSVerifier: found an x5u directive\n"));
+ mCertChainURL = directive->mValue;
+ }
+ }
+
+ // we have to ensure that we found a signature at this point
+ if (mSignature.IsEmpty()) {
+ CSVerifier_LOG(("CSVerifier: got a Content-Signature header but didn't find a signature.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Bug 769521: We have to change b64 url to regular encoding as long as we
+ // don't have a b64 url decoder. This should change soon, but in the meantime
+ // we have to live with this.
+ mSignature.ReplaceChar('-', '+');
+ mSignature.ReplaceChar('_', '/');
+
+ return NS_OK;
+}
+
+/* nsIStreamListener implementation */
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext, nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIContentSignatureReceiverCallback> callback;
+ callback.swap(mCallback);
+ nsresult rv;
+
+ // Check HTTP status code and return if it's not 200.
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
+ uint32_t httpResponseCode;
+ if (NS_FAILED(rv) || NS_FAILED(http->GetResponseStatus(&httpResponseCode)) ||
+ httpResponseCode != 200) {
+ callback->ContextCreated(false);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aStatus)) {
+ callback->ContextCreated(false);
+ return NS_OK;
+ }
+
+ nsAutoCString certChain;
+ for (uint32_t i = 0; i < mCertChain.Length(); ++i) {
+ certChain.Append(mCertChain[i]);
+ }
+
+ // We got the cert chain now. Let's create the context.
+ rv = CreateContextInternal(NS_LITERAL_CSTRING(""), certChain, mName);
+ if (NS_FAILED(rv)) {
+ callback->ContextCreated(false);
+ return NS_OK;
+ }
+
+ mHasCertChain = true;
+ callback->ContextCreated(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsAutoCString buffer;
+
+ nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!mCertChain.AppendElement(buffer, fallible)) {
+ mCertChain.TruncateLength(0);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::GetInterface(const nsIID& uuid, void** result)
+{
+ return QueryInterface(uuid, result);
+}