tor-browser

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

QWACs.cpp (13692B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "nsIX509CertDB.h"
      6 
      7 #include "CryptoTask.h"
      8 #include "QWACTrustDomain.h"
      9 #include "mozilla/dom/Promise.h"
     10 #include "mozpkix/pkix.h"
     11 #include "mozpkix/pkixder.h"
     12 #include "mozpkix/pkixnss.h"
     13 #include "mozpkix/pkixtypes.h"
     14 #include "mozpkix/pkixutil.h"
     15 #include "nsIX509Cert.h"
     16 #include "nsNSSCertificateDB.h"
     17 
     18 using namespace mozilla::pkix;
     19 using namespace mozilla::psm;
     20 
     21 using mozilla::dom::Promise;
     22 
     23 class VerifyQWACTask : public mozilla::CryptoTask {
     24 public:
     25  VerifyQWACTask(nsIX509CertDB::QWACType aType, nsIX509Cert* aCert,
     26                 const nsACString& aHostname,
     27                 const nsTArray<RefPtr<nsIX509Cert>>& aCollectedCerts,
     28                 RefPtr<Promise>& aPromise)
     29      : mType(aType),
     30        mCert(aCert),
     31        mHostname(aHostname),
     32        mCollectedCerts(aCollectedCerts.Clone()),
     33        mPromise(new nsMainThreadPtrHolder<Promise>("VerifyQWACTask::mPromise",
     34                                                    aPromise)),
     35        mVerified(false) {}
     36 
     37 private:
     38  virtual nsresult CalculateResult() override;
     39  virtual void CallCallback(nsresult rv) override;
     40 
     41  nsIX509CertDB::QWACType mType;
     42  RefPtr<nsIX509Cert> mCert;
     43  nsCString mHostname;
     44  nsTArray<RefPtr<nsIX509Cert>> mCollectedCerts;
     45  nsMainThreadPtrHandle<Promise> mPromise;
     46 
     47  bool mVerified;
     48 };
     49 
     50 // Does this certificate have the correct qcStatements ("qualified certificate
     51 // statements") to be a QWAC ("qualified website authentication certificate")?
     52 // ETSI EN 319 412-5 Clauses 4.2.1 and 4.2.3 state that a certificate issued in
     53 // compliance with Annex IV of Regulation (EU) No 910/2014 (i.e. a QWAC) has
     54 //   1) a QCStatement with statementId equal to id-etsi-qsc-QcCompliance and
     55 //      an omitted statementInfo, and
     56 //   2) a QCStatement with statementId equal to id-etsi-qcs-QcType and a
     57 //      statementInfo of length one that contains the id-etsi-qct-web
     58 //      identifier.
     59 bool CertHasQWACSQCStatements(Input cert) {
     60  using namespace mozilla::pkix::der;
     61 
     62  // python DottedOIDToCode.py id-etsi-qcs-QcCompliance 0.4.0.1862.1.1
     63  static const uint8_t id_etsi_qcs_QcCompliance[] = {0x04, 0x00, 0x8e,
     64                                                     0x46, 0x01, 0x01};
     65 
     66  // python DottedOIDToCode.py id-etsi-qcs-QcType 0.4.0.1862.1.6
     67  static const uint8_t id_etsi_qcs_QcType[] = {0x04, 0x00, 0x8e,
     68                                               0x46, 0x01, 0x06};
     69 
     70  // python DottedOIDToCode.py id-etsi-qct-web 0.4.0.1862.1.6.3
     71  static const uint8_t id_etsi_qct_web[] = {0x04, 0x00, 0x8e, 0x46,
     72                                            0x01, 0x06, 0x03};
     73 
     74  BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
     75  if (backCert.Init() != Success) {
     76    return false;
     77  }
     78  const Input* qcStatementsInput(backCert.GetQCStatements());
     79  if (!qcStatementsInput) {
     80    return false;
     81  }
     82  Reader qcStatements(*qcStatementsInput);
     83  // QCStatements ::= SEQUENCE OF QCStatement
     84  // QCStatement ::= SEQUENCE {
     85  //     statementId   QC-STATEMENT.&Id({SupportedStatements}),
     86  //     statementInfo QC-STATEMENT.&Type
     87  //     ({SupportedStatements}{@statementId}) OPTIONAL }
     88  //
     89  // SupportedStatements QC-STATEMENT ::= { qcStatement-1,...}
     90  bool foundQCComplianceStatement = false;
     91  bool foundQCTypeStatementWithWebType = false;
     92  mozilla::pkix::Result rv =
     93      NestedOf(qcStatements, SEQUENCE, SEQUENCE, EmptyAllowed::No,
     94               [&](Reader& qcStatementContents) {
     95                 Reader statementId;
     96                 mozilla::pkix::Result rv = ExpectTagAndGetValue(
     97                     qcStatementContents, OIDTag, statementId);
     98                 if (rv != Success) {
     99                   return rv;
    100                 }
    101                 if (statementId.MatchRest(id_etsi_qcs_QcCompliance)) {
    102                   foundQCComplianceStatement = true;
    103                   return End(qcStatementContents);
    104                 }
    105                 if (statementId.MatchRest(id_etsi_qcs_QcType)) {
    106                   Reader supportedStatementsContents;
    107                   rv = ExpectTagAndGetValue(qcStatementContents, SEQUENCE,
    108                                             supportedStatementsContents);
    109                   if (rv != Success) {
    110                     return rv;
    111                   }
    112                   Reader supportedStatementId;
    113                   rv = ExpectTagAndGetValue(supportedStatementsContents,
    114                                             OIDTag, supportedStatementId);
    115                   if (supportedStatementId.MatchRest(id_etsi_qct_web)) {
    116                     foundQCTypeStatementWithWebType = true;
    117                   }
    118                   rv = End(supportedStatementsContents);
    119                   if (rv != Success) {
    120                     return rv;
    121                   }
    122                   return End(qcStatementContents);
    123                 }
    124                 // Ignore the contents of unknown qcStatements.
    125                 qcStatementContents.SkipToEnd();
    126                 return Success;
    127               });
    128  if (rv != Success) {
    129    return false;
    130  }
    131  if (!qcStatements.AtEnd()) {
    132    return false;
    133  }
    134  return foundQCComplianceStatement && foundQCTypeStatementWithWebType;
    135 }
    136 
    137 // Helper function to determine if a certificate has a policy from the given
    138 // list of acceptable policies.
    139 bool CertHasPolicyFrom(Input cert, const nsTArray<Input>& policies) {
    140  using namespace mozilla::pkix::der;
    141 
    142  BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
    143  if (backCert.Init() != Success) {
    144    return false;
    145  }
    146  const Input* certificatePoliciesInput(backCert.GetCertificatePolicies());
    147  if (!certificatePoliciesInput) {
    148    return false;
    149  }
    150  Reader certificatePolicies(*certificatePoliciesInput);
    151  // certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
    152  // PolicyInformation ::= SEQUENCE {
    153  //   policyIdentifier   CertPolicyId,
    154  //   ...
    155  // }
    156  // CertPolicyId ::= OBJECT IDENTIFIER
    157  bool foundPolicy = false;
    158  mozilla::pkix::Result rv =
    159      NestedOf(certificatePolicies, SEQUENCE, SEQUENCE, EmptyAllowed::No,
    160               [&](Reader& policyInformationContents) {
    161                 Reader policyIdentifier;
    162                 mozilla::pkix::Result rv = ExpectTagAndGetValue(
    163                     policyInformationContents, OIDTag, policyIdentifier);
    164                 if (rv != Success) {
    165                   return rv;
    166                 }
    167                 for (const auto& policy : policies) {
    168                   if (policyIdentifier.MatchRest(policy)) {
    169                     foundPolicy = true;
    170                   }
    171                 }
    172                 return Success;
    173               });
    174  if (rv != Success) {
    175    return false;
    176  }
    177  if (!certificatePolicies.AtEnd()) {
    178    return false;
    179  }
    180  return foundPolicy;
    181 }
    182 
    183 // For 1-QWACs, ETSI TS 119 411-5 V2.1.1 clause 6.1.2 ("Validation of QWACs")
    184 // item 5 references clause 4.1.2, which references clause 4.1.1, which states
    185 // that such certificates must have either the QEVCP-w or QNCP-w policy as
    186 // specified in ETSI EN 319 411-2.
    187 bool CertHas1QWACPolicy(Input cert) {
    188  // QEVCP-w is itu-t(0) identified-organization(4) etsi(0)
    189  //   qualified-certificate-policies(194112) policy-identifiers(1) qcp-web (4)
    190  // python DottedOIDToCode.py qevcp-w 0.4.0.194112.1.4
    191  static const uint8_t qevcp_w[] = {0x04, 0x00, 0x8b, 0xec, 0x40, 0x01, 0x04};
    192 
    193  // QNCP-w is itu-t(0) identified-organization(4) etsi(0)
    194  //   qualified-certificate-policies(194112) policy-identifiers(1) qncp-web (5)
    195  // python DottedOIDToCode.py qncp-w 0.4.0.194112.1.5
    196  static const uint8_t qncp_w[] = {0x04, 0x00, 0x8b, 0xec, 0x40, 0x01, 0x05};
    197 
    198  return CertHasPolicyFrom(cert, {Input(qevcp_w), Input(qncp_w)});
    199 }
    200 
    201 // For 2-QWACs, ETSI TS 119 411-5 V2.1.1 clause 6.1.2 ("Validation of QWACs")
    202 // item 5 references clause 4.2.2, which references clause 4.2.1, which states
    203 // that such certificates must have the QNCP-w-gen policy as specified in ETSI
    204 // EN 319 411-2.
    205 bool CertHas2QWACPolicy(Input cert) {
    206  // QEVCP-w-gen is itu-t(0) identified-organization(4) etsi(0)
    207  //   qualified-certificate-policies(194112) policy-identifiers(1)
    208  //   qncp-web-gen (6)
    209  // python DottedOIDToCode.py qevcp-w-gen 0.4.0.194112.1.6
    210  static const uint8_t qevcp_w_gen[] = {0x04, 0x00, 0x8b, 0xec,
    211                                        0x40, 0x01, 0x06};
    212 
    213  return CertHasPolicyFrom(cert, {Input(qevcp_w_gen)});
    214 }
    215 
    216 // ETSI TS 119 411-5 V2.1.1 states that "The 2-QWAC certificate shall be issued
    217 // in accordance with ETSI EN 319 412-4 [4] for the relevant certificate policy
    218 // as identified in clause 4.2.1 of the present document, except as described
    219 // below:
    220 //   * the extKeyUsage value shall only assert the extendedKeyUsage purpose of
    221 //     id-kp-tls-binding as specified in Annex A."
    222 // This is interpreted to mean the 2-QWAC certificate must have an
    223 // extendedKeyUsage extension and it must contain only id-kp-tls-binding, and
    224 // that there are no particular restrictions or requirements of the other
    225 // certificates in the chain with regard to EKU extensions.
    226 bool CertOnlyHasTLSBindingEKU(Input cert) {
    227  using namespace mozilla::pkix::der;
    228 
    229  // ETSI TS 119 411-5 V2.1.1 Annex A:
    230  // id-tlsBinding OBJECT IDENTIFIER ::= { itu-t(0) identified-organization(4)
    231  //   etsi(0) id-qwacImplementation(194115) tls-binding (1) }
    232  // id-kp-tls-binding OBJECT IDENTIFIER ::= { id-tlsBinding
    233  //   id-kp-tls-binding(0) }
    234  // python DottedOIDToCode.py id-kp-tls-binding 0.4.0.194115.1.0
    235  static const uint8_t id_kp_tls_binding[] = {0x04, 0x00, 0x8b, 0xec,
    236                                              0x43, 0x01, 0x00};
    237 
    238  BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
    239  if (backCert.Init() != Success) {
    240    return false;
    241  }
    242  const Input* ekuInput(backCert.GetExtKeyUsage());
    243  if (!ekuInput) {
    244    return false;
    245  }
    246  Reader eku(*ekuInput);
    247  // Normally, the extended key usage extension is defined like so:
    248  //   ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
    249  //   KeyPurposeId ::= OBJECT IDENTIFIER
    250  // That is, it consists of a SEQUENCE of OBJECT IDENTIFIERs, where each OID
    251  // identifies a key purpose. However, for 2-QWACs, the EKU must consist of
    252  // exactly one key purpose ID of id-kp-tls-binding.
    253  mozilla::pkix::Result rv = Nested(eku, SEQUENCE, OIDTag, [&](Reader& r) {
    254    if (r.MatchRest(id_kp_tls_binding)) {
    255      return Success;
    256    }
    257    return mozilla::pkix::Result::ERROR_INADEQUATE_CERT_TYPE;
    258  });
    259  if (rv != Success) {
    260    return false;
    261  }
    262  return eku.AtEnd();
    263 }
    264 
    265 nsresult VerifyQWACTask::CalculateResult() {
    266  mozilla::psm::QWACTrustDomain trustDomain(mCollectedCerts);
    267  nsTArray<uint8_t> certDER;
    268  nsresult rv = mCert->GetRawDER(certDER);
    269  if (NS_FAILED(rv)) {
    270    return rv;
    271  }
    272  Input cert;
    273  if (cert.Init(certDER.Elements(), certDER.Length()) != Success) {
    274    return NS_ERROR_FAILURE;
    275  }
    276  if (!CertHasQWACSQCStatements(cert)) {
    277    return NS_OK;
    278  }
    279  if (mType == nsIX509CertDB::QWACType::OneQWAC) {
    280    if (!CertHas1QWACPolicy(cert)) {
    281      return NS_OK;
    282    }
    283  } else if (mType == nsIX509CertDB::QWACType::TwoQWAC) {
    284    if (!CertHas2QWACPolicy(cert)) {
    285      return NS_OK;
    286    }
    287    if (!CertOnlyHasTLSBindingEKU(cert)) {
    288      return NS_OK;
    289    }
    290  } else {
    291    MOZ_ASSERT_UNREACHABLE("unhandled QWAC type");
    292    return NS_ERROR_FAILURE;
    293  }
    294 
    295  if (BuildCertChain(trustDomain, cert, Now(), EndEntityOrCA::MustBeEndEntity,
    296                     KeyUsage::noParticularKeyUsageRequired,
    297                     KeyPurposeId::anyExtendedKeyUsage, CertPolicyId::anyPolicy,
    298                     nullptr) != Success) {
    299    return NS_OK;
    300  }
    301 
    302  // For 1-QWACs, the hostname should have already been validated in the TLS
    303  // handshake. However, this operation is not expensive, and it ensures all
    304  // required checks have been done, in case 1-QWACs are ever re-used in a
    305  // different context.
    306  Input hostname;
    307  if (hostname.Init(mozilla::BitwiseCast<const uint8_t*, const char*>(
    308                        mHostname.BeginReading()),
    309                    mHostname.Length()) != Success) {
    310    return NS_OK;
    311  }
    312  // According to ETSI EN 319 412-4 V1.4.1 section 4, certificates following
    313  // EVCP or QEVCP-w (which includes 1-QWACs) are subject to the CA/Browser
    314  // Forum's EV Guidelines, which incorporates the Baseline Requirements.
    315  // Certificates following QNCP-w-gen (which includes 2-QWACs) are subject to
    316  // the Baseline Requirements with respect to the subject alternative name
    317  // extension.
    318  if (CheckCertHostname(cert, hostname) != Success) {
    319    return NS_OK;
    320  }
    321 
    322  mVerified = true;
    323  return NS_OK;
    324 }
    325 
    326 void VerifyQWACTask::CallCallback(nsresult rv) {
    327  if (NS_FAILED(rv)) {
    328    mPromise->MaybeReject(rv);
    329  } else {
    330    mPromise->MaybeResolve(mVerified);
    331  }
    332 }
    333 
    334 NS_IMETHODIMP
    335 nsNSSCertificateDB::AsyncVerifyQWAC(
    336    QWACType aType, nsIX509Cert* aCert, const nsACString& aHostname,
    337    const nsTArray<RefPtr<nsIX509Cert>>& aCollectedCerts, JSContext* aCx,
    338    mozilla::dom::Promise** aPromise) {
    339  NS_ENSURE_ARG_POINTER(aCx);
    340 
    341  nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
    342  if (!globalObject) {
    343    return NS_ERROR_UNEXPECTED;
    344  }
    345  mozilla::ErrorResult result;
    346  RefPtr<Promise> promise = Promise::Create(globalObject, result);
    347  if (result.Failed()) {
    348    return result.StealNSResult();
    349  }
    350 
    351  RefPtr<VerifyQWACTask> task(
    352      new VerifyQWACTask(aType, aCert, aHostname, aCollectedCerts, promise));
    353  nsresult rv = task->Dispatch();
    354  if (NS_FAILED(rv)) {
    355    return rv;
    356  }
    357 
    358  promise.forget(aPromise);
    359  return NS_OK;
    360 }