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 }