Bug 1935611 - Fix libyuv/libpng link failed for loongarch64. r=glandium,tnikkel,ng
[gecko.git] / security / manager / ssl / TLSClientAuthCertSelection.cpp
blob7e833c26798b793326f491c3b81620f4996465b2
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
7 // Implements the client authentication certificate selection callback for NSS.
8 // nsNSSIOLayer.cpp sets the callback by calling SSL_GetClientAuthDataHook and
9 // identifying SSLGetClientAuthDataHook as the function to call when a TLS
10 // server requests a client authentication certificate.
12 // In the general case, SSLGetClientAuthDataHook (running on the socket thread),
13 // dispatches an event to the main thread to ask the user to select a client
14 // authentication certificate. Meanwhile, it returns SECWouldBlock so that other
15 // network I/O can occur. When the user selects a client certificate (or opts
16 // not to send one), an event is dispatched to the socket thread that gives NSS
17 // the appropriate information to proceed with the TLS connection.
19 // If networking is being done on the socket process, SSLGetClientAuthDataHook
20 // sends an IPC call to the parent process to ask the user to select a
21 // certificate. Meanwhile, it again returns SECWouldBlock so other network I/O
22 // can occur. When a certificate (or no certificate) has been selected, the
23 // parent process sends an IPC call back to the socket process, which causes an
24 // event to be dispatched to the socket thread to continue to the TLS
25 // connection.
27 #include "TLSClientAuthCertSelection.h"
28 #include "cert_storage/src/cert_storage.h"
29 #include "mozilla/Logging.h"
30 #include "mozilla/dom/BrowsingContext.h"
31 #include "mozilla/ipc/Endpoint.h"
32 #include "mozilla/net/SocketProcessBackgroundChild.h"
33 #include "mozilla/psm/SelectTLSClientAuthCertChild.h"
34 #include "mozilla/psm/SelectTLSClientAuthCertParent.h"
35 #include "nsArray.h"
36 #include "nsArrayUtils.h"
37 #include "nsNSSComponent.h"
38 #include "nsIClientAuthDialogService.h"
39 #include "nsIMutableArray.h"
40 #include "nsINSSComponent.h"
41 #include "NSSCertDBTrustDomain.h"
42 #include "nsIClientAuthRememberService.h"
43 #include "nsIX509CertDB.h"
44 #include "nsNSSHelper.h"
45 #include "mozpkix/pkixnss.h"
46 #include "mozpkix/pkixutil.h"
47 #include "mozpkix/pkix.h"
48 #include "secerr.h"
49 #include "sslerr.h"
51 using namespace mozilla;
52 using namespace mozilla::pkix;
53 using namespace mozilla::psm;
55 extern LazyLogModule gPIPNSSLog;
57 mozilla::pkix::Result BuildChainForCertificate(
58 nsTArray<uint8_t>& certBytes, nsTArray<nsTArray<uint8_t>>& certChainBytes,
59 const nsTArray<nsTArray<uint8_t>>& caNames,
60 const nsTArray<nsTArray<uint8_t>>& enterpriseCertificates);
62 // Possible behaviors for choosing a cert for client auth.
63 enum class UserCertChoice {
64 // Ask the user to choose a cert.
65 Ask = 0,
66 // Automatically choose a cert.
67 Auto = 1,
70 // Returns the most appropriate user cert choice based on the value of the
71 // security.default_personal_cert preference.
72 UserCertChoice nsGetUserCertChoice() {
73 nsAutoCString value;
74 nsresult rv =
75 Preferences::GetCString("security.default_personal_cert", value);
76 if (NS_FAILED(rv)) {
77 return UserCertChoice::Ask;
80 // There are three cases for what the preference could be set to:
81 // 1. "Select Automatically" -> Auto.
82 // 2. "Ask Every Time" -> Ask.
83 // 3. Something else -> Ask. This might be a nickname from a migrated cert,
84 // but we no longer support this case.
85 return value.EqualsLiteral("Select Automatically") ? UserCertChoice::Auto
86 : UserCertChoice::Ask;
89 static bool hasExplicitKeyUsageNonRepudiation(CERTCertificate* cert) {
90 // There is no extension, v1 or v2 certificate
91 if (!cert->extensions) return false;
93 SECStatus srv;
94 SECItem keyUsageItem;
95 keyUsageItem.data = nullptr;
97 srv = CERT_FindKeyUsageExtension(cert, &keyUsageItem);
98 if (srv == SECFailure) return false;
100 unsigned char keyUsage = keyUsageItem.data[0];
101 PORT_Free(keyUsageItem.data);
103 return !!(keyUsage & KU_NON_REPUDIATION);
106 ClientAuthInfo::ClientAuthInfo(const nsACString& hostName,
107 const OriginAttributes& originAttributes,
108 int32_t port, uint32_t providerFlags,
109 uint32_t providerTlsFlags)
110 : mHostName(hostName),
111 mOriginAttributes(originAttributes),
112 mPort(port),
113 mProviderFlags(providerFlags),
114 mProviderTlsFlags(providerTlsFlags) {}
116 ClientAuthInfo::ClientAuthInfo(ClientAuthInfo&& aOther) noexcept
117 : mHostName(std::move(aOther.mHostName)),
118 mOriginAttributes(std::move(aOther.mOriginAttributes)),
119 mPort(aOther.mPort),
120 mProviderFlags(aOther.mProviderFlags),
121 mProviderTlsFlags(aOther.mProviderTlsFlags) {}
123 const nsACString& ClientAuthInfo::HostName() const { return mHostName; }
125 const OriginAttributes& ClientAuthInfo::OriginAttributesRef() const {
126 return mOriginAttributes;
129 int32_t ClientAuthInfo::Port() const { return mPort; }
131 uint32_t ClientAuthInfo::ProviderFlags() const { return mProviderFlags; }
133 uint32_t ClientAuthInfo::ProviderTlsFlags() const { return mProviderTlsFlags; }
135 nsTArray<nsTArray<uint8_t>> CollectCANames(CERTDistNames* caNames) {
136 MOZ_ASSERT(caNames);
138 nsTArray<nsTArray<uint8_t>> collectedCANames;
139 if (!caNames) {
140 return collectedCANames;
143 for (int i = 0; i < caNames->nnames; i++) {
144 nsTArray<uint8_t> caName;
145 caName.AppendElements(caNames->names[i].data, caNames->names[i].len);
146 collectedCANames.AppendElement(std::move(caName));
148 return collectedCANames;
151 // This TrustDomain only exists to facilitate the mozilla::pkix path building
152 // algorithm. It considers any certificate with an issuer distinguished name in
153 // the set of given CA names to be a trust anchor. It does essentially no
154 // validation or verification (in particular, the signature checking function
155 // always returns "Success").
156 class ClientAuthCertNonverifyingTrustDomain final : public TrustDomain {
157 public:
158 ClientAuthCertNonverifyingTrustDomain(
159 const nsTArray<nsTArray<uint8_t>>& caNames,
160 const nsTArray<nsTArray<uint8_t>>& thirdPartyCertificates)
161 : mCANames(caNames),
162 mCertStorage(do_GetService(NS_CERT_STORAGE_CID)),
163 mThirdPartyCertificates(thirdPartyCertificates) {}
165 virtual mozilla::pkix::Result GetCertTrust(
166 pkix::EndEntityOrCA endEntityOrCA, const pkix::CertPolicyId& policy,
167 pkix::Input candidateCertDER,
168 /*out*/ pkix::TrustLevel& trustLevel) override;
169 virtual mozilla::pkix::Result FindIssuer(pkix::Input encodedIssuerName,
170 IssuerChecker& checker,
171 pkix::Time time) override;
173 virtual mozilla::pkix::Result CheckRevocation(
174 EndEntityOrCA endEntityOrCA, const pkix::CertID& certID, Time time,
175 mozilla::pkix::Duration validityDuration,
176 /*optional*/ const Input* stapledOCSPresponse,
177 /*optional*/ const Input* aiaExtension,
178 /*optional*/ const Input* sctExtension) override {
179 return pkix::Success;
182 virtual mozilla::pkix::Result IsChainValid(
183 const pkix::DERArray& certChain, pkix::Time time,
184 const pkix::CertPolicyId& requiredPolicy) override;
186 virtual mozilla::pkix::Result CheckSignatureDigestAlgorithm(
187 pkix::DigestAlgorithm digestAlg, pkix::EndEntityOrCA endEntityOrCA,
188 pkix::Time notBefore) override {
189 return pkix::Success;
191 virtual mozilla::pkix::Result CheckRSAPublicKeyModulusSizeInBits(
192 pkix::EndEntityOrCA endEntityOrCA,
193 unsigned int modulusSizeInBits) override {
194 return pkix::Success;
196 virtual mozilla::pkix::Result VerifyRSAPKCS1SignedData(
197 pkix::Input data, pkix::DigestAlgorithm, pkix::Input signature,
198 pkix::Input subjectPublicKeyInfo) override {
199 return pkix::Success;
201 virtual mozilla::pkix::Result VerifyRSAPSSSignedData(
202 pkix::Input data, pkix::DigestAlgorithm, pkix::Input signature,
203 pkix::Input subjectPublicKeyInfo) override {
204 return pkix::Success;
206 virtual mozilla::pkix::Result CheckECDSACurveIsAcceptable(
207 pkix::EndEntityOrCA endEntityOrCA, pkix::NamedCurve curve) override {
208 return pkix::Success;
210 virtual mozilla::pkix::Result VerifyECDSASignedData(
211 pkix::Input data, pkix::DigestAlgorithm, pkix::Input signature,
212 pkix::Input subjectPublicKeyInfo) override {
213 return pkix::Success;
215 virtual mozilla::pkix::Result CheckValidityIsAcceptable(
216 pkix::Time notBefore, pkix::Time notAfter,
217 pkix::EndEntityOrCA endEntityOrCA,
218 pkix::KeyPurposeId keyPurpose) override {
219 return pkix::Success;
221 virtual mozilla::pkix::Result NetscapeStepUpMatchesServerAuth(
222 pkix::Time notBefore,
223 /*out*/ bool& matches) override {
224 matches = true;
225 return pkix::Success;
227 virtual void NoteAuxiliaryExtension(pkix::AuxiliaryExtension extension,
228 pkix::Input extensionData) override {}
229 virtual mozilla::pkix::Result DigestBuf(pkix::Input item,
230 pkix::DigestAlgorithm digestAlg,
231 /*out*/ uint8_t* digestBuf,
232 size_t digestBufLen) override {
233 return pkix::DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
236 nsTArray<nsTArray<uint8_t>> TakeBuiltChain() {
237 return std::move(mBuiltChain);
240 private:
241 const nsTArray<nsTArray<uint8_t>>& mCANames; // non-owning
242 nsCOMPtr<nsICertStorage> mCertStorage;
243 const nsTArray<nsTArray<uint8_t>>& mThirdPartyCertificates; // non-owning
244 nsTArray<nsTArray<uint8_t>> mBuiltChain;
247 mozilla::pkix::Result ClientAuthCertNonverifyingTrustDomain::GetCertTrust(
248 pkix::EndEntityOrCA endEntityOrCA, const pkix::CertPolicyId& policy,
249 pkix::Input candidateCertDER,
250 /*out*/ pkix::TrustLevel& trustLevel) {
251 // If the server did not specify any CA names, all client certificates are
252 // acceptable.
253 if (mCANames.Length() == 0) {
254 trustLevel = pkix::TrustLevel::TrustAnchor;
255 return pkix::Success;
257 BackCert cert(candidateCertDER, endEntityOrCA, nullptr);
258 mozilla::pkix::Result rv = cert.Init();
259 if (rv != pkix::Success) {
260 return rv;
262 // If this certificate's issuer distinguished name is in the set of acceptable
263 // CA names, we say this is a trust anchor so that the client certificate
264 // issued from this certificate will be presented as an option for the user.
265 // We also check the certificate's subject distinguished name to account for
266 // the case where client certificates that have the id-kp-OCSPSigning EKU
267 // can't be trust anchors according to mozilla::pkix, and thus we may be
268 // looking directly at the issuer.
269 pkix::Input issuer(cert.GetIssuer());
270 pkix::Input subject(cert.GetSubject());
271 for (const auto& caName : mCANames) {
272 pkix::Input caNameInput;
273 rv = caNameInput.Init(caName.Elements(), caName.Length());
274 if (rv != pkix::Success) {
275 continue; // probably too big
277 if (InputsAreEqual(issuer, caNameInput) ||
278 InputsAreEqual(subject, caNameInput)) {
279 trustLevel = pkix::TrustLevel::TrustAnchor;
280 return pkix::Success;
283 trustLevel = pkix::TrustLevel::InheritsTrust;
284 return pkix::Success;
287 // In theory this implementation should only need to consider intermediate
288 // certificates, since in theory it should only need to look at the issuer
289 // distinguished name of each certificate to determine if the client
290 // certificate is considered acceptable to the server.
291 // However, because we need to account for client certificates with the
292 // id-kp-OCSPSigning EKU, and because mozilla::pkix doesn't allow such
293 // certificates to be trust anchors, we need to consider the issuers of such
294 // certificates directly. These issuers could be roots, so we have to consider
295 // roots here.
296 mozilla::pkix::Result ClientAuthCertNonverifyingTrustDomain::FindIssuer(
297 pkix::Input encodedIssuerName, IssuerChecker& checker, pkix::Time time) {
298 // First try all relevant certificates known to Gecko, which avoids calling
299 // CERT_CreateSubjectCertList, because that can be expensive.
300 Vector<pkix::Input> geckoCandidates;
301 if (!mCertStorage) {
302 return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
304 nsTArray<uint8_t> subject;
305 subject.AppendElements(encodedIssuerName.UnsafeGetData(),
306 encodedIssuerName.GetLength());
307 nsTArray<nsTArray<uint8_t>> certs;
308 nsresult rv = mCertStorage->FindCertsBySubject(subject, certs);
309 if (NS_FAILED(rv)) {
310 return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
312 for (auto& cert : certs) {
313 pkix::Input certDER;
314 mozilla::pkix::Result rv = certDER.Init(cert.Elements(), cert.Length());
315 if (rv != pkix::Success) {
316 continue; // probably too big
318 if (!geckoCandidates.append(certDER)) {
319 return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY;
323 for (const auto& thirdPartyCertificate : mThirdPartyCertificates) {
324 pkix::Input thirdPartyCertificateInput;
325 mozilla::pkix::Result rv = thirdPartyCertificateInput.Init(
326 thirdPartyCertificate.Elements(), thirdPartyCertificate.Length());
327 if (rv != pkix::Success) {
328 continue; // probably too big
330 if (!geckoCandidates.append(thirdPartyCertificateInput)) {
331 return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY;
335 bool keepGoing = true;
336 for (pkix::Input candidate : geckoCandidates) {
337 mozilla::pkix::Result rv = checker.Check(candidate, nullptr, keepGoing);
338 if (rv != pkix::Success) {
339 return rv;
341 if (!keepGoing) {
342 return pkix::Success;
346 SECItem encodedIssuerNameItem =
347 pkix::UnsafeMapInputToSECItem(encodedIssuerName);
348 // NSS seems not to differentiate between "no potential issuers found" and
349 // "there was an error trying to retrieve the potential issuers." We assume
350 // there was no error if CERT_CreateSubjectCertList returns nullptr.
351 UniqueCERTCertList candidates(CERT_CreateSubjectCertList(
352 nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameItem, 0, false));
353 Vector<pkix::Input> nssCandidates;
354 if (candidates) {
355 for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
356 !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
357 pkix::Input certDER;
358 mozilla::pkix::Result rv =
359 certDER.Init(n->cert->derCert.data, n->cert->derCert.len);
360 if (rv != pkix::Success) {
361 continue; // probably too big
363 if (!nssCandidates.append(certDER)) {
364 return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY;
369 for (pkix::Input candidate : nssCandidates) {
370 mozilla::pkix::Result rv = checker.Check(candidate, nullptr, keepGoing);
371 if (rv != pkix::Success) {
372 return rv;
374 if (!keepGoing) {
375 return pkix::Success;
378 return pkix::Success;
381 mozilla::pkix::Result ClientAuthCertNonverifyingTrustDomain::IsChainValid(
382 const pkix::DERArray& certArray, pkix::Time, const pkix::CertPolicyId&) {
383 mBuiltChain.Clear();
385 size_t numCerts = certArray.GetLength();
386 for (size_t i = 0; i < numCerts; ++i) {
387 nsTArray<uint8_t> certBytes;
388 const pkix::Input* certInput = certArray.GetDER(i);
389 MOZ_ASSERT(certInput != nullptr);
390 if (!certInput) {
391 return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
393 certBytes.AppendElements(certInput->UnsafeGetData(),
394 certInput->GetLength());
395 mBuiltChain.AppendElement(std::move(certBytes));
398 return pkix::Success;
401 nsTArray<nsTArray<uint8_t>> GetEnterpriseCertificates() {
402 nsTArray<nsTArray<uint8_t>> enterpriseCertificates;
403 nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
404 if (!component) {
405 return nsTArray<nsTArray<uint8_t>>{};
407 nsresult rv = component->GetEnterpriseIntermediates(enterpriseCertificates);
408 if (NS_FAILED(rv)) {
409 return nsTArray<nsTArray<uint8_t>>{};
411 nsTArray<nsTArray<uint8_t>> enterpriseRoots;
412 rv = component->GetEnterpriseRoots(enterpriseRoots);
413 if (NS_FAILED(rv)) {
414 return nsTArray<nsTArray<uint8_t>>{};
416 enterpriseCertificates.AppendElements(std::move(enterpriseRoots));
417 return enterpriseCertificates;
420 bool FindRememberedDecision(
421 const ClientAuthInfo& clientAuthInfo,
422 const nsTArray<nsTArray<uint8_t>>& caNames,
423 const nsTArray<nsTArray<uint8_t>>& enterpriseCertificates,
424 nsTArray<uint8_t>& rememberedCertBytes,
425 nsTArray<nsTArray<uint8_t>>& rememberedCertChainBytes) {
426 rememberedCertBytes.Clear();
427 rememberedCertChainBytes.Clear();
429 if (clientAuthInfo.ProviderTlsFlags() != 0) {
430 return false;
433 nsCOMPtr<nsIClientAuthRememberService> clientAuthRememberService(
434 do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID));
435 if (!clientAuthRememberService) {
436 return false;
439 nsCString rememberedDBKey;
440 bool found;
441 nsresult rv = clientAuthRememberService->HasRememberedDecision(
442 clientAuthInfo.HostName(), clientAuthInfo.OriginAttributesRef(),
443 rememberedDBKey, &found);
444 if (NS_FAILED(rv)) {
445 return false;
447 if (!found) {
448 return false;
450 // An empty dbKey indicates that the user chose not to use a certificate
451 // and chose to remember this decision
452 if (rememberedDBKey.IsEmpty()) {
453 return true;
455 nsCOMPtr<nsIX509CertDB> certdb(do_GetService(NS_X509CERTDB_CONTRACTID));
456 if (!certdb) {
457 return false;
459 nsCOMPtr<nsIX509Cert> foundCert;
460 rv = certdb->FindCertByDBKey(rememberedDBKey, getter_AddRefs(foundCert));
461 if (NS_FAILED(rv)) {
462 return false;
464 if (!foundCert) {
465 return false;
467 rv = foundCert->GetRawDER(rememberedCertBytes);
468 if (NS_FAILED(rv)) {
469 return false;
471 if (BuildChainForCertificate(rememberedCertBytes, rememberedCertChainBytes,
472 caNames, enterpriseCertificates) != Success) {
473 return false;
475 return true;
478 // Filter potential client certificates by the specified CA names, if any. This
479 // operation potentially builds a certificate chain for each candidate client
480 // certificate. Keeping those chains around means they don't have to be
481 // re-built later when the user selects a particular client certificate.
482 void FilterPotentialClientCertificatesByCANames(
483 UniqueCERTCertList& potentialClientCertificates,
484 const nsTArray<nsTArray<uint8_t>>& caNames,
485 const nsTArray<nsTArray<uint8_t>>& enterpriseCertificates,
486 nsTArray<nsTArray<nsTArray<uint8_t>>>& potentialClientCertificateChains) {
487 if (!potentialClientCertificates) {
488 return;
491 CERTCertListNode* n = CERT_LIST_HEAD(potentialClientCertificates);
492 while (!CERT_LIST_END(n, potentialClientCertificates)) {
493 nsTArray<nsTArray<uint8_t>> builtChain;
494 nsTArray<uint8_t> certBytes;
495 certBytes.AppendElements(n->cert->derCert.data, n->cert->derCert.len);
496 mozilla::pkix::Result result = BuildChainForCertificate(
497 certBytes, builtChain, caNames, enterpriseCertificates);
498 if (result != pkix::Success) {
499 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
500 ("removing cert '%s'", n->cert->subjectName));
501 CERTCertListNode* toRemove = n;
502 n = CERT_LIST_NEXT(n);
503 CERT_RemoveCertListNode(toRemove);
504 continue;
506 potentialClientCertificateChains.AppendElement(std::move(builtChain));
507 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
508 ("keeping cert '%s'\n", n->cert->subjectName));
509 n = CERT_LIST_NEXT(n);
513 void ClientAuthCertificateSelectedBase::SetSelectedClientAuthData(
514 nsTArray<uint8_t>&& selectedCertBytes,
515 nsTArray<nsTArray<uint8_t>>&& selectedCertChainBytes) {
516 mSelectedCertBytes = std::move(selectedCertBytes);
517 mSelectedCertChainBytes = std::move(selectedCertChainBytes);
520 NS_IMETHODIMP
521 ClientAuthCertificateSelected::Run() {
522 mSocketInfo->ClientAuthCertificateSelected(mSelectedCertBytes,
523 mSelectedCertChainBytes);
524 return NS_OK;
527 void SelectClientAuthCertificate::DispatchContinuation(
528 nsTArray<uint8_t>&& selectedCertBytes) {
529 nsTArray<nsTArray<uint8_t>> selectedCertChainBytes;
530 // Attempt to find a pre-built certificate chain corresponding to the
531 // selected certificate.
532 for (const auto& clientCertificateChain : mPotentialClientCertificateChains) {
533 if (clientCertificateChain.Length() > 0 &&
534 clientCertificateChain[0] == selectedCertBytes) {
535 for (const auto& certificateBytes : clientCertificateChain) {
536 selectedCertChainBytes.AppendElement(certificateBytes.Clone());
538 break;
541 mContinuation->SetSelectedClientAuthData(std::move(selectedCertBytes),
542 std::move(selectedCertChainBytes));
543 nsCOMPtr<nsIEventTarget> socketThread(
544 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
545 if (socketThread) {
546 (void)socketThread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
550 // Helper function to build a certificate chain from the given certificate to a
551 // trust anchor in the set indicated by the peer (mCANames). This is essentially
552 // best-effort, so no signature verification occurs.
553 mozilla::pkix::Result BuildChainForCertificate(
554 nsTArray<uint8_t>& certBytes, nsTArray<nsTArray<uint8_t>>& certChainBytes,
555 const nsTArray<nsTArray<uint8_t>>& caNames,
556 const nsTArray<nsTArray<uint8_t>>& enterpriseCertificates) {
557 ClientAuthCertNonverifyingTrustDomain trustDomain(caNames,
558 enterpriseCertificates);
559 pkix::Input certDER;
560 mozilla::pkix::Result result =
561 certDER.Init(certBytes.Elements(), certBytes.Length());
562 if (result != pkix::Success) {
563 return result;
565 // Client certificates shouldn't be CAs, but for interoperability reasons we
566 // attempt to build a path with each certificate as an end entity and then as
567 // a CA if that fails.
568 const pkix::EndEntityOrCA kEndEntityOrCAParams[] = {
569 pkix::EndEntityOrCA::MustBeEndEntity, pkix::EndEntityOrCA::MustBeCA};
570 // mozilla::pkix rejects certificates with id-kp-OCSPSigning unless it is
571 // specifically required. A client certificate should never have this EKU.
572 // Unfortunately, there are some client certificates in private PKIs that
573 // have this EKU. For interoperability, we attempt to work around this
574 // restriction in mozilla::pkix by first building the certificate chain with
575 // no particular EKU required and then again with id-kp-OCSPSigning required
576 // if that fails.
577 const pkix::KeyPurposeId kKeyPurposeIdParams[] = {
578 pkix::KeyPurposeId::anyExtendedKeyUsage,
579 pkix::KeyPurposeId::id_kp_OCSPSigning};
580 for (const auto& endEntityOrCAParam : kEndEntityOrCAParams) {
581 for (const auto& keyPurposeIdParam : kKeyPurposeIdParams) {
582 mozilla::pkix::Result result = BuildCertChain(
583 trustDomain, certDER, Now(), endEntityOrCAParam,
584 KeyUsage::noParticularKeyUsageRequired, keyPurposeIdParam,
585 pkix::CertPolicyId::anyPolicy, nullptr);
586 if (result == pkix::Success) {
587 certChainBytes = trustDomain.TakeBuiltChain();
588 return pkix::Success;
592 return mozilla::pkix::Result::ERROR_UNKNOWN_ISSUER;
595 class ClientAuthDialogCallback : public nsIClientAuthDialogCallback {
596 public:
597 NS_DECL_ISUPPORTS
598 NS_DECL_NSICLIENTAUTHDIALOGCALLBACK
600 explicit ClientAuthDialogCallback(
601 SelectClientAuthCertificate* selectClientAuthCertificate)
602 : mSelectClientAuthCertificate(selectClientAuthCertificate) {}
604 private:
605 virtual ~ClientAuthDialogCallback() = default;
607 RefPtr<SelectClientAuthCertificate> mSelectClientAuthCertificate;
610 NS_IMPL_ISUPPORTS(ClientAuthDialogCallback, nsIClientAuthDialogCallback)
612 NS_IMETHODIMP
613 ClientAuthDialogCallback::CertificateChosen(nsIX509Cert* cert,
614 bool rememberDecision) {
615 MOZ_ASSERT(mSelectClientAuthCertificate);
616 if (!mSelectClientAuthCertificate) {
617 return NS_ERROR_FAILURE;
619 const ClientAuthInfo& info = mSelectClientAuthCertificate->Info();
620 nsCOMPtr<nsIClientAuthRememberService> clientAuthRememberService(
621 do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID));
622 if (info.ProviderTlsFlags() == 0 && rememberDecision &&
623 clientAuthRememberService) {
624 (void)clientAuthRememberService->RememberDecision(
625 info.HostName(), info.OriginAttributesRef(), cert);
627 nsTArray<uint8_t> selectedCertBytes;
628 if (cert) {
629 nsresult rv = cert->GetRawDER(selectedCertBytes);
630 if (NS_FAILED(rv)) {
631 selectedCertBytes.Clear();
632 mSelectClientAuthCertificate->DispatchContinuation(
633 std::move(selectedCertBytes));
634 return rv;
637 mSelectClientAuthCertificate->DispatchContinuation(
638 std::move(selectedCertBytes));
639 return NS_OK;
642 NS_IMETHODIMP
643 SelectClientAuthCertificate::Run() {
644 // We check the value of a pref, so this should only be run on the main
645 // thread.
646 MOZ_ASSERT(NS_IsMainThread());
648 nsTArray<uint8_t> selectedCertBytes;
649 if (!mPotentialClientCertificates ||
650 CERT_LIST_EMPTY(mPotentialClientCertificates)) {
651 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
652 ("no potential client certificates available"));
653 DispatchContinuation(std::move(selectedCertBytes));
654 return NS_OK;
657 // find valid user cert and key pair
658 if (nsGetUserCertChoice() == UserCertChoice::Auto) {
659 // automatically find the right cert
660 UniqueCERTCertificate lowPrioNonrepCert;
661 // loop through the list until we find a cert with a key
662 for (CERTCertListNode* node = CERT_LIST_HEAD(mPotentialClientCertificates);
663 !CERT_LIST_END(node, mPotentialClientCertificates);
664 node = CERT_LIST_NEXT(node)) {
665 UniqueSECKEYPrivateKey tmpKey(PK11_FindKeyByAnyCert(node->cert, nullptr));
666 if (tmpKey) {
667 if (hasExplicitKeyUsageNonRepudiation(node->cert)) {
668 // Not a preferred cert
669 if (!lowPrioNonrepCert) { // did not yet find a low prio cert
670 lowPrioNonrepCert.reset(CERT_DupCertificate(node->cert));
672 } else {
673 // this is a good cert to present
674 selectedCertBytes.AppendElements(node->cert->derCert.data,
675 node->cert->derCert.len);
676 DispatchContinuation(std::move(selectedCertBytes));
677 return NS_OK;
680 if (PR_GetError() == SEC_ERROR_BAD_PASSWORD) {
681 // problem with password: bail
682 break;
686 if (lowPrioNonrepCert) {
687 selectedCertBytes.AppendElements(lowPrioNonrepCert->derCert.data,
688 lowPrioNonrepCert->derCert.len);
690 DispatchContinuation(std::move(selectedCertBytes));
691 return NS_OK;
694 // Not Auto => ask the user to select a certificate
695 nsTArray<RefPtr<nsIX509Cert>> certArray;
696 for (CERTCertListNode* node = CERT_LIST_HEAD(mPotentialClientCertificates);
697 !CERT_LIST_END(node, mPotentialClientCertificates);
698 node = CERT_LIST_NEXT(node)) {
699 RefPtr<nsIX509Cert> tempCert(new nsNSSCertificate(node->cert));
700 certArray.AppendElement(tempCert);
703 nsCOMPtr<nsIClientAuthDialogService> clientAuthDialogService(
704 do_GetService(NS_CLIENTAUTHDIALOGSERVICE_CONTRACTID));
705 if (!clientAuthDialogService) {
706 DispatchContinuation(std::move(selectedCertBytes));
707 return NS_ERROR_FAILURE;
709 nsCOMPtr<nsILoadContext> loadContext = nullptr;
710 if (mBrowserId != 0) {
711 loadContext =
712 mozilla::dom::BrowsingContext::GetCurrentTopByBrowserId(mBrowserId);
714 RefPtr<nsIClientAuthDialogCallback> callback(
715 new ClientAuthDialogCallback(this));
716 nsresult rv = clientAuthDialogService->ChooseCertificate(
717 mInfo.HostName(), certArray, loadContext, callback);
718 if (NS_FAILED(rv)) {
719 DispatchContinuation(std::move(selectedCertBytes));
720 return rv;
722 return NS_OK;
725 SECStatus SSLGetClientAuthDataHook(void* arg, PRFileDesc* socket,
726 CERTDistNames* caNamesDecoded,
727 CERTCertificate** pRetCert,
728 SECKEYPrivateKey** pRetKey) {
729 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
730 ("[%p] SSLGetClientAuthDataHook", socket));
732 if (!arg || !socket || !caNamesDecoded || !pRetCert || !pRetKey) {
733 PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
734 return SECFailure;
737 *pRetCert = nullptr;
738 *pRetKey = nullptr;
740 RefPtr<NSSSocketControl> info(static_cast<NSSSocketControl*>(arg));
741 glean::security::client_auth_cert_usage.Get("requested"_ns).Add(1);
743 if (info->GetDenyClientCert()) {
744 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
745 ("[%p] Not returning client cert due to denyClientCert attribute",
746 socket));
747 return SECSuccess;
750 if (info->GetJoined()) {
751 // We refuse to send a client certificate when there are multiple hostnames
752 // joined on this connection, because we only show the user one hostname
753 // (mHostName) in the client certificate UI.
754 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
755 ("[%p] Not returning client cert due to previous join", socket));
756 return SECSuccess;
759 UniqueCERTCertificate serverCert(SSL_PeerCertificate(socket));
760 if (!serverCert) {
761 PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
762 return SECFailure;
765 uint64_t browserId;
766 if (NS_FAILED(info->GetBrowserId(&browserId))) {
767 PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
768 return SECFailure;
771 nsTArray<nsTArray<uint8_t>> caNames(CollectCANames(caNamesDecoded));
773 // Currently, the IPC client certs module only refreshes its view of
774 // available certificates and keys if the platform issues a search for all
775 // certificates or keys. In the socket process, such a search may not have
776 // happened, so this ensures it has.
777 // Additionally, instantiating certificates in NSS is not thread-safe and has
778 // performance implications, so search for them here (on the socket thread)
779 // when not in the socket process.
780 UniqueCERTCertList potentialClientCertificates(
781 FindClientCertificatesWithPrivateKeys());
783 RefPtr<ClientAuthCertificateSelected> continuation(
784 new ClientAuthCertificateSelected(info));
785 // If this is the socket process, dispatch an IPC call to select a client
786 // authentication certificate in the parent process.
787 // Otherwise, dispatch an event to the main thread to do the selection.
788 // When those events finish, they will run the continuation, which gives the
789 // appropriate information to the NSSSocketControl, which then calls
790 // SSL_ClientCertCallbackComplete to continue the connection.
791 if (XRE_IsSocketProcess()) {
792 RefPtr<SelectTLSClientAuthCertChild> selectClientAuthCertificate(
793 new SelectTLSClientAuthCertChild(continuation));
794 nsAutoCString hostname(info->GetHostName());
795 nsTArray<uint8_t> serverCertBytes;
796 nsTArray<ByteArray> caNamesBytes;
797 for (const auto& caName : caNames) {
798 caNamesBytes.AppendElement(ByteArray(std::move(caName)));
800 serverCertBytes.AppendElements(serverCert->derCert.data,
801 serverCert->derCert.len);
802 OriginAttributes originAttributes(info->GetOriginAttributes());
803 int32_t port(info->GetPort());
804 uint32_t providerFlags(info->GetProviderFlags());
805 uint32_t providerTlsFlags(info->GetProviderTlsFlags());
806 nsCOMPtr<nsIRunnable> remoteSelectClientAuthCertificate(
807 NS_NewRunnableFunction(
808 "RemoteSelectClientAuthCertificate",
809 [selectClientAuthCertificate(
810 std::move(selectClientAuthCertificate)),
811 hostname(std::move(hostname)),
812 originAttributes(std::move(originAttributes)), port, providerFlags,
813 providerTlsFlags, serverCertBytes(std::move(serverCertBytes)),
814 caNamesBytes(std::move(caNamesBytes)),
815 browserId(browserId)]() mutable {
816 ipc::Endpoint<PSelectTLSClientAuthCertParent> parentEndpoint;
817 ipc::Endpoint<PSelectTLSClientAuthCertChild> childEndpoint;
818 PSelectTLSClientAuthCert::CreateEndpoints(&parentEndpoint,
819 &childEndpoint);
820 if (NS_FAILED(net::SocketProcessBackgroundChild::WithActor(
821 "SendInitSelectTLSClientAuthCert",
822 [endpoint = std::move(parentEndpoint),
823 hostname(std::move(hostname)),
824 originAttributes(std::move(originAttributes)), port,
825 providerFlags, providerTlsFlags,
826 serverCertBytes(std::move(serverCertBytes)),
827 caNamesBytes(std::move(caNamesBytes)), browserId](
828 net::SocketProcessBackgroundChild* aActor) mutable {
829 Unused << aActor->SendInitSelectTLSClientAuthCert(
830 std::move(endpoint), hostname, originAttributes,
831 port, providerFlags, providerTlsFlags,
832 ByteArray(serverCertBytes), caNamesBytes,
833 browserId);
834 }))) {
835 return;
838 if (!childEndpoint.Bind(selectClientAuthCertificate)) {
839 return;
841 }));
842 info->SetPendingSelectClientAuthCertificate(
843 std::move(remoteSelectClientAuthCertificate));
844 PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
845 return SECWouldBlock;
848 ClientAuthInfo authInfo(info->GetHostName(), info->GetOriginAttributes(),
849 info->GetPort(), info->GetProviderFlags(),
850 info->GetProviderTlsFlags());
851 nsTArray<nsTArray<uint8_t>> enterpriseCertificates(
852 GetEnterpriseCertificates());
853 nsTArray<uint8_t> rememberedCertBytes;
854 nsTArray<nsTArray<uint8_t>> rememberedCertChainBytes;
855 if (FindRememberedDecision(authInfo, caNames, enterpriseCertificates,
856 rememberedCertBytes, rememberedCertChainBytes)) {
857 continuation->SetSelectedClientAuthData(
858 std::move(rememberedCertBytes), std::move(rememberedCertChainBytes));
859 nsresult rv = NS_DispatchToCurrentThread(continuation);
860 if (NS_FAILED(rv)) {
861 PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
862 return SECFailure;
864 PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
865 return SECWouldBlock;
868 nsTArray<nsTArray<nsTArray<uint8_t>>> potentialClientCertificateChains;
869 FilterPotentialClientCertificatesByCANames(potentialClientCertificates,
870 caNames, enterpriseCertificates,
871 potentialClientCertificateChains);
872 if (!potentialClientCertificates ||
873 CERT_LIST_EMPTY(potentialClientCertificates)) {
874 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
875 ("[%p] no client certificates available after filtering by CA",
876 socket));
877 return SECSuccess;
879 nsCOMPtr<nsIRunnable> selectClientAuthCertificate(
880 new SelectClientAuthCertificate(
881 std::move(authInfo), std::move(serverCert),
882 std::move(potentialClientCertificates),
883 std::move(potentialClientCertificateChains), continuation,
884 browserId));
885 info->SetPendingSelectClientAuthCertificate(
886 std::move(selectClientAuthCertificate));
888 // Meanwhile, tell NSS this connection is blocking for now.
889 PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
890 return SECWouldBlock;
893 // Helper continuation for when a client authentication certificate has been
894 // selected in the parent process and the information needs to be sent to the
895 // socket process.
896 class RemoteClientAuthCertificateSelected
897 : public ClientAuthCertificateSelectedBase {
898 public:
899 explicit RemoteClientAuthCertificateSelected(
900 SelectTLSClientAuthCertParent* selectTLSClientAuthCertParent)
901 : mSelectTLSClientAuthCertParent(selectTLSClientAuthCertParent),
902 mEventTarget(GetCurrentSerialEventTarget()) {}
904 NS_IMETHOD Run() override;
906 private:
907 RefPtr<SelectTLSClientAuthCertParent> mSelectTLSClientAuthCertParent;
908 nsCOMPtr<nsISerialEventTarget> mEventTarget;
911 NS_IMETHODIMP
912 RemoteClientAuthCertificateSelected::Run() {
913 // When this runs, it dispatches an event to the IPC thread it originally came
914 // from in order to send the IPC call to the socket process that a client
915 // authentication certificate has been selected.
916 return mEventTarget->Dispatch(
917 NS_NewRunnableFunction(
918 "psm::RemoteClientAuthCertificateSelected::Run",
919 [parent(mSelectTLSClientAuthCertParent),
920 certBytes(std::move(mSelectedCertBytes)),
921 builtCertChain(std::move(mSelectedCertChainBytes))]() mutable {
922 parent->TLSClientAuthCertSelected(certBytes,
923 std::move(builtCertChain));
925 NS_DISPATCH_NORMAL);
928 namespace mozilla::psm {
930 // Given some information from the socket process about a connection that
931 // requested a client authentication certificate, this function dispatches an
932 // event to the main thread to ask the user to select one. When the user does so
933 // (or selects no certificate), the continuation runs and sends the information
934 // back via IPC.
935 bool SelectTLSClientAuthCertParent::Dispatch(
936 const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
937 const int32_t& aPort, const uint32_t& aProviderFlags,
938 const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
939 nsTArray<ByteArray>&& aCANames, const uint64_t& aBrowserId) {
940 RefPtr<ClientAuthCertificateSelectedBase> continuation(
941 new RemoteClientAuthCertificateSelected(this));
942 ClientAuthInfo authInfo(aHostName, aOriginAttributes, aPort, aProviderFlags,
943 aProviderTlsFlags);
944 nsCOMPtr<nsIEventTarget> socketThread =
945 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
946 if (NS_WARN_IF(!socketThread)) {
947 return false;
949 // Dispatch the work of instantiating a CERTCertificate and searching for
950 // client certificates to the socket thread.
951 nsresult rv = socketThread->Dispatch(NS_NewRunnableFunction(
952 "SelectTLSClientAuthCertParent::Dispatch",
953 [authInfo(std::move(authInfo)), continuation(std::move(continuation)),
954 serverCertBytes(aServerCertBytes), caNames(std::move(aCANames)),
955 browserId(aBrowserId)]() mutable {
956 SECItem serverCertItem{
957 siBuffer,
958 const_cast<uint8_t*>(serverCertBytes.data().Elements()),
959 static_cast<unsigned int>(serverCertBytes.data().Length()),
961 UniqueCERTCertificate serverCert(CERT_NewTempCertificate(
962 CERT_GetDefaultCertDB(), &serverCertItem, nullptr, false, true));
963 if (!serverCert) {
964 return;
966 nsTArray<nsTArray<uint8_t>> caNamesArray;
967 for (auto& caName : caNames) {
968 caNamesArray.AppendElement(std::move(caName.data()));
970 nsTArray<nsTArray<uint8_t>> enterpriseCertificates(
971 GetEnterpriseCertificates());
972 nsTArray<uint8_t> rememberedCertBytes;
973 nsTArray<nsTArray<uint8_t>> rememberedCertChainBytes;
974 if (FindRememberedDecision(authInfo, caNamesArray,
975 enterpriseCertificates, rememberedCertBytes,
976 rememberedCertChainBytes)) {
977 continuation->SetSelectedClientAuthData(
978 std::move(rememberedCertBytes),
979 std::move(rememberedCertChainBytes));
980 (void)NS_DispatchToCurrentThread(continuation);
981 return;
983 UniqueCERTCertList potentialClientCertificates(
984 FindClientCertificatesWithPrivateKeys());
985 nsTArray<nsTArray<nsTArray<uint8_t>>> potentialClientCertificateChains;
986 FilterPotentialClientCertificatesByCANames(
987 potentialClientCertificates, caNamesArray, enterpriseCertificates,
988 potentialClientCertificateChains);
989 RefPtr<SelectClientAuthCertificate> selectClientAuthCertificate(
990 new SelectClientAuthCertificate(
991 std::move(authInfo), std::move(serverCert),
992 std::move(potentialClientCertificates),
993 std::move(potentialClientCertificateChains), continuation,
994 browserId));
995 Unused << NS_DispatchToMainThread(selectClientAuthCertificate);
996 }));
997 return NS_SUCCEEDED(rv);
1000 void SelectTLSClientAuthCertParent::TLSClientAuthCertSelected(
1001 const nsTArray<uint8_t>& aSelectedCertBytes,
1002 nsTArray<nsTArray<uint8_t>>&& aSelectedCertChainBytes) {
1003 if (!CanSend()) {
1004 return;
1007 nsTArray<ByteArray> selectedCertChainBytes;
1008 for (auto& certBytes : aSelectedCertChainBytes) {
1009 selectedCertChainBytes.AppendElement(ByteArray(certBytes));
1012 Unused << SendTLSClientAuthCertSelected(aSelectedCertBytes,
1013 selectedCertChainBytes);
1014 Close();
1017 void SelectTLSClientAuthCertParent::ActorDestroy(
1018 mozilla::ipc::IProtocol::ActorDestroyReason aWhy) {}
1020 SelectTLSClientAuthCertChild::SelectTLSClientAuthCertChild(
1021 ClientAuthCertificateSelected* continuation)
1022 : mContinuation(continuation) {}
1024 // When the user has selected (or not) a client authentication certificate in
1025 // the parent, this function receives that information in the socket process and
1026 // dispatches a continuation to the socket process to continue the connection.
1027 ipc::IPCResult SelectTLSClientAuthCertChild::RecvTLSClientAuthCertSelected(
1028 ByteArray&& aSelectedCertBytes,
1029 nsTArray<ByteArray>&& aSelectedCertChainBytes) {
1030 nsTArray<uint8_t> selectedCertBytes(std::move(aSelectedCertBytes.data()));
1031 nsTArray<nsTArray<uint8_t>> selectedCertChainBytes;
1032 for (auto& certBytes : aSelectedCertChainBytes) {
1033 selectedCertChainBytes.AppendElement(std::move(certBytes.data()));
1035 mContinuation->SetSelectedClientAuthData(std::move(selectedCertBytes),
1036 std::move(selectedCertChainBytes));
1038 nsCOMPtr<nsIEventTarget> socketThread =
1039 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
1040 if (NS_WARN_IF(!socketThread)) {
1041 return IPC_OK();
1043 nsresult rv = socketThread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
1044 Unused << NS_WARN_IF(NS_FAILED(rv));
1046 return IPC_OK();
1049 } // namespace mozilla::psm