tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ContentSignatureVerifier.cpp (15696B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ContentSignatureVerifier.h"
      8 
      9 #include "AppTrustDomain.h"
     10 #include "CryptoTask.h"
     11 #include "ScopedNSSTypes.h"
     12 #include "SharedCertVerifier.h"
     13 #include "cryptohi.h"
     14 #include "keyhi.h"
     15 #include "mozilla/Base64.h"
     16 #include "mozilla/Logging.h"
     17 #include "mozilla/dom/Promise.h"
     18 #include "mozilla/glean/SecurityManagerSslMetrics.h"
     19 #include "nsCOMPtr.h"
     20 #include "nsPromiseFlatString.h"
     21 #include "nsSecurityHeaderParser.h"
     22 #include "nsWhitespaceTokenizer.h"
     23 #include "mozpkix/pkix.h"
     24 #include "mozpkix/pkixtypes.h"
     25 #include "mozpkix/pkixutil.h"
     26 #include "secerr.h"
     27 #include "ssl.h"
     28 
     29 NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
     30 
     31 using namespace mozilla;
     32 using namespace mozilla::pkix;
     33 using namespace mozilla::psm;
     34 using dom::Promise;
     35 
     36 static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
     37 #define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
     38 
     39 // Content-Signature prefix
     40 const unsigned char kPREFIX[] = {'C', 'o', 'n', 't', 'e', 'n', 't',
     41                                 '-', 'S', 'i', 'g', 'n', 'a', 't',
     42                                 'u', 'r', 'e', ':', 0};
     43 
     44 class VerifyContentSignatureTask : public CryptoTask {
     45 public:
     46  VerifyContentSignatureTask(const nsACString& aData,
     47                             const nsACString& aCSHeader,
     48                             const nsACString& aCertChain,
     49                             const nsACString& aHostname,
     50                             AppTrustedRoot aTrustedRoot,
     51                             RefPtr<Promise>& aPromise)
     52      : mData(aData),
     53        mCSHeader(aCSHeader),
     54        mCertChain(aCertChain),
     55        mHostname(aHostname),
     56        mTrustedRoot(aTrustedRoot),
     57        mSignatureVerified(false),
     58        mPromise(new nsMainThreadPtrHolder<Promise>(
     59            "VerifyContentSignatureTask::mPromise", aPromise)) {}
     60 
     61 private:
     62  virtual nsresult CalculateResult() override;
     63  virtual void CallCallback(nsresult rv) override;
     64 
     65  nsCString mData;
     66  nsCString mCSHeader;
     67  nsCString mCertChain;
     68  nsCString mHostname;
     69  AppTrustedRoot mTrustedRoot;
     70  bool mSignatureVerified;
     71  nsMainThreadPtrHandle<Promise> mPromise;
     72 };
     73 
     74 NS_IMETHODIMP
     75 ContentSignatureVerifier::AsyncVerifyContentSignature(
     76    const nsACString& aData, const nsACString& aCSHeader,
     77    const nsACString& aCertChain, const nsACString& aHostname,
     78    AppTrustedRoot aTrustedRoot, JSContext* aCx, Promise** aPromise) {
     79  NS_ENSURE_ARG_POINTER(aCx);
     80 
     81  nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
     82  if (NS_WARN_IF(!globalObject)) {
     83    return NS_ERROR_UNEXPECTED;
     84  }
     85 
     86  ErrorResult result;
     87  RefPtr<Promise> promise = Promise::Create(globalObject, result);
     88  if (NS_WARN_IF(result.Failed())) {
     89    return result.StealNSResult();
     90  }
     91 
     92  RefPtr<VerifyContentSignatureTask> task(new VerifyContentSignatureTask(
     93      aData, aCSHeader, aCertChain, aHostname, aTrustedRoot, promise));
     94  nsresult rv = task->Dispatch();
     95  if (NS_FAILED(rv)) {
     96    return rv;
     97  }
     98 
     99  promise.forget(aPromise);
    100  return NS_OK;
    101 }
    102 
    103 static nsresult VerifyContentSignatureInternal(
    104    const nsACString& aData, const nsACString& aCSHeader,
    105    const nsACString& aCertChain, const nsACString& aHostname,
    106    AppTrustedRoot aTrustedRoot,
    107    /* out */ nsACString& aErrorLabel,
    108    /* out */ nsACString& aCertFingerprint, /* out */ uint32_t& aErrorValue);
    109 static nsresult ParseContentSignatureHeader(
    110    const nsACString& aContentSignatureHeader,
    111    /* out */ nsCString& aSignature);
    112 
    113 nsresult VerifyContentSignatureTask::CalculateResult() {
    114  // 3 is the default, non-specific, "something failed" error.
    115  nsAutoCString errorLabel("otherError"_ns);
    116  nsAutoCString certFingerprint;
    117  uint32_t errorValue = 3;
    118  nsresult rv = VerifyContentSignatureInternal(
    119      mData, mCSHeader, mCertChain, mHostname, mTrustedRoot, errorLabel,
    120      certFingerprint, errorValue);
    121  if (NS_FAILED(rv)) {
    122    CSVerifier_LOG(("CSVerifier: Signature verification failed"));
    123    if (certFingerprint.Length() > 0) {
    124      glean::security::content_signature_verification_errors
    125          .Get(certFingerprint, errorLabel)
    126          .Add();
    127    }
    128    glean::security::content_signature_verification_status
    129        .AccumulateSingleSample(errorValue);
    130    if (rv == NS_ERROR_INVALID_SIGNATURE) {
    131      return NS_OK;
    132    }
    133    return rv;
    134  }
    135 
    136  mSignatureVerified = true;
    137  glean::security::content_signature_verification_status.AccumulateSingleSample(
    138      0);
    139 
    140  return NS_OK;
    141 }
    142 
    143 void VerifyContentSignatureTask::CallCallback(nsresult rv) {
    144  if (NS_FAILED(rv)) {
    145    mPromise->MaybeReject(rv);
    146  } else {
    147    mPromise->MaybeResolve(mSignatureVerified);
    148  }
    149 }
    150 
    151 bool IsNewLine(char16_t c) { return c == '\n' || c == '\r'; }
    152 
    153 nsresult ReadChainIntoCertList(const nsACString& aCertChain,
    154                               nsTArray<nsTArray<uint8_t>>& aCertList) {
    155  bool inBlock = false;
    156  bool certFound = false;
    157 
    158  const nsCString header = "-----BEGIN CERTIFICATE-----"_ns;
    159  const nsCString footer = "-----END CERTIFICATE-----"_ns;
    160 
    161  nsCWhitespaceTokenizerTemplate<IsNewLine> tokenizer(aCertChain);
    162 
    163  nsAutoCString blockData;
    164  while (tokenizer.hasMoreTokens()) {
    165    nsDependentCSubstring token = tokenizer.nextToken();
    166    if (token.IsEmpty()) {
    167      continue;
    168    }
    169    if (inBlock) {
    170      if (token.Equals(footer)) {
    171        inBlock = false;
    172        certFound = true;
    173        // base64 decode data, make certs, append to chain
    174        nsAutoCString derString;
    175        nsresult rv = Base64Decode(blockData, derString);
    176        if (NS_FAILED(rv)) {
    177          CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
    178          return rv;
    179        }
    180        nsTArray<uint8_t> derBytes(derString.Data(), derString.Length());
    181        aCertList.AppendElement(std::move(derBytes));
    182      } else {
    183        blockData.Append(token);
    184      }
    185    } else if (token.Equals(header)) {
    186      inBlock = true;
    187      blockData = "";
    188    }
    189  }
    190  if (inBlock || !certFound) {
    191    // the PEM data did not end; bad data.
    192    CSVerifier_LOG(("CSVerifier: supplied chain contains bad data"));
    193    return NS_ERROR_FAILURE;
    194  }
    195  return NS_OK;
    196 }
    197 
    198 // Given data to verify, a content signature header value, a string representing
    199 // a list of PEM-encoded certificates, and a hostname to validate the
    200 // certificates against, this function attempts to validate the certificate
    201 // chain, extract the signature from the header, and verify the data using the
    202 // key in the end-entity certificate from the chain. Returns NS_OK if everything
    203 // is satisfactory and a failing nsresult otherwise. The output parameters are
    204 // filled with telemetry data to report in the case of failures.
    205 static nsresult VerifyContentSignatureInternal(
    206    const nsACString& aData, const nsACString& aCSHeader,
    207    const nsACString& aCertChain, const nsACString& aHostname,
    208    AppTrustedRoot aTrustedRoot,
    209    /* out */ nsACString& aErrorLabel,
    210    /* out */ nsACString& aCertFingerprint,
    211    /* out */ uint32_t& aErrorValue) {
    212  nsTArray<nsTArray<uint8_t>> certList;
    213  nsresult rv = ReadChainIntoCertList(aCertChain, certList);
    214  if (NS_FAILED(rv)) {
    215    return rv;
    216  }
    217  if (certList.Length() < 1) {
    218    return NS_ERROR_FAILURE;
    219  }
    220  // The 0th element should be the end-entity that issued the content
    221  // signature.
    222  nsTArray<uint8_t>& certBytes(certList.ElementAt(0));
    223  Input certInput;
    224  mozilla::pkix::Result result =
    225      certInput.Init(certBytes.Elements(), certBytes.Length());
    226  if (result != Success) {
    227    return NS_ERROR_FAILURE;
    228  }
    229 
    230  // Get EE certificate fingerprint for telemetry.
    231  unsigned char fingerprint[SHA256_LENGTH] = {0};
    232  SECStatus srv =
    233      PK11_HashBuf(SEC_OID_SHA256, fingerprint, certInput.UnsafeGetData(),
    234                   certInput.GetLength());
    235  if (srv != SECSuccess) {
    236    return NS_ERROR_FAILURE;
    237  }
    238  SECItem fingerprintItem = {siBuffer, fingerprint, SHA256_LENGTH};
    239  UniquePORTString tmpFingerprintString(
    240      CERT_Hexify(&fingerprintItem, false /* don't use colon delimiters */));
    241  if (!tmpFingerprintString) {
    242    return NS_ERROR_OUT_OF_MEMORY;
    243  }
    244  aCertFingerprint.Assign(tmpFingerprintString.get());
    245 
    246  nsTArray<Span<const uint8_t>> certSpans;
    247  // Collect just the CAs.
    248  for (size_t i = 1; i < certList.Length(); i++) {
    249    Span<const uint8_t> certSpan(certList.ElementAt(i).Elements(),
    250                                 certList.ElementAt(i).Length());
    251    certSpans.AppendElement(std::move(certSpan));
    252  }
    253  AppTrustDomain trustDomain(std::move(certSpans));
    254  rv = trustDomain.SetTrustedRoot(aTrustedRoot);
    255  if (NS_FAILED(rv)) {
    256    return rv;
    257  }
    258  // Check the signerCert chain is good
    259  result = BuildCertChain(
    260      trustDomain, certInput, Now(), EndEntityOrCA::MustBeEndEntity,
    261      KeyUsage::noParticularKeyUsageRequired, KeyPurposeId::id_kp_codeSigning,
    262      CertPolicyId::anyPolicy, nullptr /*stapledOCSPResponse*/);
    263  if (result != Success) {
    264    // if there was a library error, return an appropriate error
    265    if (IsFatalError(result)) {
    266      return NS_ERROR_FAILURE;
    267    }
    268    // otherwise, assume the signature was invalid
    269    if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
    270      aErrorLabel = "expiredCert"_ns;
    271      aErrorValue = 4;
    272    } else if (result ==
    273               mozilla::pkix::Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
    274      aErrorLabel = "certNotValidYet"_ns;
    275      aErrorValue = 5;
    276    } else {
    277      // Building cert chain failed for some other reason.
    278      aErrorLabel = "buildCertChainFailed"_ns;
    279      aErrorValue = 6;
    280    }
    281    CSVerifier_LOG(("CSVerifier: The supplied chain is bad (%s)",
    282                    MapResultToName(result)));
    283    return NS_ERROR_INVALID_SIGNATURE;
    284  }
    285 
    286  // Check the SAN
    287  Input hostnameInput;
    288 
    289  result = hostnameInput.Init(
    290      BitwiseCast<const uint8_t*, const char*>(aHostname.BeginReading()),
    291      aHostname.Length());
    292  if (result != Success) {
    293    return NS_ERROR_FAILURE;
    294  }
    295 
    296  result = CheckCertHostname(certInput, hostnameInput);
    297  if (result != Success) {
    298    // EE cert isnot valid for the given host name.
    299    aErrorLabel = "eeCertForWrongHost"_ns;
    300    aErrorValue = 7;
    301    return NS_ERROR_INVALID_SIGNATURE;
    302  }
    303 
    304  pkix::BackCert backCert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
    305  result = backCert.Init();
    306  // This should never fail, because we've already built a verified certificate
    307  // chain with this certificate.
    308  if (result != Success) {
    309    aErrorLabel = "extractKeyError"_ns;
    310    aErrorValue = 8;
    311    CSVerifier_LOG(("CSVerifier: couldn't decode certificate to get spki"));
    312    return NS_ERROR_INVALID_SIGNATURE;
    313  }
    314  Input spkiInput = backCert.GetSubjectPublicKeyInfo();
    315  SECItem spkiItem = {siBuffer, const_cast<uint8_t*>(spkiInput.UnsafeGetData()),
    316                      spkiInput.GetLength()};
    317  UniqueCERTSubjectPublicKeyInfo spki(
    318      SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
    319  if (!spki) {
    320    aErrorLabel = "extractKeyError"_ns;
    321    aErrorValue = 8;
    322    CSVerifier_LOG(("CSVerifier: couldn't decode spki"));
    323    return NS_ERROR_INVALID_SIGNATURE;
    324  }
    325  mozilla::UniqueSECKEYPublicKey key(SECKEY_ExtractPublicKey(spki.get()));
    326  if (!key) {
    327    aErrorLabel = "extractKeyError"_ns;
    328    aErrorValue = 8;
    329    CSVerifier_LOG(("CSVerifier: unable to extract a key"));
    330    return NS_ERROR_INVALID_SIGNATURE;
    331  }
    332 
    333  nsAutoCString signature;
    334  rv = ParseContentSignatureHeader(aCSHeader, signature);
    335  if (NS_FAILED(rv)) {
    336    return rv;
    337  }
    338 
    339  // Base 64 decode the signature
    340  nsAutoCString rawSignature;
    341  rv = Base64Decode(signature, rawSignature);
    342  if (NS_FAILED(rv)) {
    343    CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
    344    return rv;
    345  }
    346 
    347  // get signature object
    348  ScopedAutoSECItem signatureItem;
    349  SECItem rawSignatureItem = {
    350      siBuffer,
    351      BitwiseCast<unsigned char*, const char*>(rawSignature.get()),
    352      uint32_t(rawSignature.Length()),
    353  };
    354  // We have a raw ecdsa signature r||s so we have to DER-encode it first
    355  // Note that we have to check rawSignatureItem->len % 2 here as
    356  // DSAU_EncodeDerSigWithLen asserts this
    357  if (rawSignatureItem.len == 0 || rawSignatureItem.len % 2 != 0) {
    358    CSVerifier_LOG(("CSVerifier: signature length is bad"));
    359    return NS_ERROR_FAILURE;
    360  }
    361  if (DSAU_EncodeDerSigWithLen(&signatureItem, &rawSignatureItem,
    362                               rawSignatureItem.len) != SECSuccess) {
    363    CSVerifier_LOG(("CSVerifier: encoding the signature failed"));
    364    return NS_ERROR_FAILURE;
    365  }
    366 
    367  // this is the only OID we support for now
    368  SECOidTag oid = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
    369  mozilla::UniqueVFYContext cx(
    370      VFY_CreateContext(key.get(), &signatureItem, oid, nullptr));
    371  if (!cx) {
    372    // Creating context failed.
    373    aErrorLabel = "vfyContextError"_ns;
    374    aErrorValue = 9;
    375    return NS_ERROR_INVALID_SIGNATURE;
    376  }
    377 
    378  if (VFY_Begin(cx.get()) != SECSuccess) {
    379    // Creating context failed.
    380    aErrorLabel = "vfyContextError"_ns;
    381    aErrorValue = 9;
    382    return NS_ERROR_INVALID_SIGNATURE;
    383  }
    384  if (VFY_Update(cx.get(), kPREFIX, sizeof(kPREFIX)) != SECSuccess) {
    385    aErrorLabel = "invalid"_ns;
    386    aErrorValue = 1;
    387    return NS_ERROR_INVALID_SIGNATURE;
    388  }
    389  if (VFY_Update(cx.get(),
    390                 reinterpret_cast<const unsigned char*>(aData.BeginReading()),
    391                 aData.Length()) != SECSuccess) {
    392    aErrorLabel = "invalid"_ns;
    393    aErrorValue = 1;
    394    return NS_ERROR_INVALID_SIGNATURE;
    395  }
    396  if (VFY_End(cx.get()) != SECSuccess) {
    397    aErrorLabel = "invalid"_ns;
    398    aErrorValue = 1;
    399    return NS_ERROR_INVALID_SIGNATURE;
    400  }
    401 
    402  return NS_OK;
    403 }
    404 
    405 static nsresult ParseContentSignatureHeader(
    406    const nsACString& aContentSignatureHeader,
    407    /* out */ nsCString& aSignature) {
    408  // We only support p384 ecdsa.
    409  constexpr auto signature_var = "p384ecdsa"_ns;
    410 
    411  aSignature.Truncate();
    412 
    413  const nsCString& flatHeader = PromiseFlatCString(aContentSignatureHeader);
    414  nsSecurityHeaderParser parser(flatHeader);
    415  nsresult rv = parser.Parse();
    416  if (NS_FAILED(rv)) {
    417    CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header"));
    418    return NS_ERROR_FAILURE;
    419  }
    420  LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
    421 
    422  for (nsSecurityHeaderDirective* directive = directives->getFirst();
    423       directive != nullptr; directive = directive->getNext()) {
    424    CSVerifier_LOG(
    425        ("CSVerifier: found directive '%s'", directive->mName.get()));
    426    if (directive->mName.EqualsIgnoreCase(signature_var)) {
    427      if (!aSignature.IsEmpty()) {
    428        CSVerifier_LOG(("CSVerifier: found two ContentSignatures"));
    429        return NS_ERROR_INVALID_SIGNATURE;
    430      }
    431      if (directive->mValue.isNothing()) {
    432        CSVerifier_LOG(("CSVerifier: found empty ContentSignature directive"));
    433        return NS_ERROR_INVALID_SIGNATURE;
    434      }
    435 
    436      CSVerifier_LOG(("CSVerifier: found a ContentSignature directive"));
    437      aSignature.Assign(*(directive->mValue));
    438    }
    439  }
    440 
    441  // we have to ensure that we found a signature at this point
    442  if (aSignature.IsEmpty()) {
    443    CSVerifier_LOG(
    444        ("CSVerifier: got a Content-Signature header but didn't find a "
    445         "signature."));
    446    return NS_ERROR_FAILURE;
    447  }
    448 
    449  // Bug 769521: We have to change b64 url to regular encoding as long as we
    450  // don't have a b64 url decoder. This should change soon, but in the meantime
    451  // we have to live with this.
    452  aSignature.ReplaceChar('-', '+');
    453  aSignature.ReplaceChar('_', '/');
    454 
    455  return NS_OK;
    456 }