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
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"
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"
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.
66 // Automatically choose a cert.
70 // Returns the most appropriate user cert choice based on the value of the
71 // security.default_personal_cert preference.
72 UserCertChoice
nsGetUserCertChoice() {
75 Preferences::GetCString("security.default_personal_cert", value
);
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;
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
),
113 mProviderFlags(providerFlags
),
114 mProviderTlsFlags(providerTlsFlags
) {}
116 ClientAuthInfo::ClientAuthInfo(ClientAuthInfo
&& aOther
) noexcept
117 : mHostName(std::move(aOther
.mHostName
)),
118 mOriginAttributes(std::move(aOther
.mOriginAttributes
)),
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
) {
138 nsTArray
<nsTArray
<uint8_t>> collectedCANames
;
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
{
158 ClientAuthCertNonverifyingTrustDomain(
159 const nsTArray
<nsTArray
<uint8_t>>& caNames
,
160 const nsTArray
<nsTArray
<uint8_t>>& thirdPartyCertificates
)
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
{
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
);
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
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
) {
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
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
;
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
);
310 return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE
;
312 for (auto& cert
: certs
) {
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
) {
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
;
355 for (CERTCertListNode
* n
= CERT_LIST_HEAD(candidates
);
356 !CERT_LIST_END(n
, candidates
); n
= CERT_LIST_NEXT(n
)) {
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
) {
375 return pkix::Success
;
378 return pkix::Success
;
381 mozilla::pkix::Result
ClientAuthCertNonverifyingTrustDomain::IsChainValid(
382 const pkix::DERArray
& certArray
, pkix::Time
, const pkix::CertPolicyId
&) {
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);
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
));
405 return nsTArray
<nsTArray
<uint8_t>>{};
407 nsresult rv
= component
->GetEnterpriseIntermediates(enterpriseCertificates
);
409 return nsTArray
<nsTArray
<uint8_t>>{};
411 nsTArray
<nsTArray
<uint8_t>> enterpriseRoots
;
412 rv
= component
->GetEnterpriseRoots(enterpriseRoots
);
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) {
433 nsCOMPtr
<nsIClientAuthRememberService
> clientAuthRememberService(
434 do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID
));
435 if (!clientAuthRememberService
) {
439 nsCString rememberedDBKey
;
441 nsresult rv
= clientAuthRememberService
->HasRememberedDecision(
442 clientAuthInfo
.HostName(), clientAuthInfo
.OriginAttributesRef(),
443 rememberedDBKey
, &found
);
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()) {
455 nsCOMPtr
<nsIX509CertDB
> certdb(do_GetService(NS_X509CERTDB_CONTRACTID
));
459 nsCOMPtr
<nsIX509Cert
> foundCert
;
460 rv
= certdb
->FindCertByDBKey(rememberedDBKey
, getter_AddRefs(foundCert
));
467 rv
= foundCert
->GetRawDER(rememberedCertBytes
);
471 if (BuildChainForCertificate(rememberedCertBytes
, rememberedCertChainBytes
,
472 caNames
, enterpriseCertificates
) != Success
) {
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
) {
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
);
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
);
521 ClientAuthCertificateSelected::Run() {
522 mSocketInfo
->ClientAuthCertificateSelected(mSelectedCertBytes
,
523 mSelectedCertChainBytes
);
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());
541 mContinuation
->SetSelectedClientAuthData(std::move(selectedCertBytes
),
542 std::move(selectedCertChainBytes
));
543 nsCOMPtr
<nsIEventTarget
> socketThread(
544 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID
));
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
);
560 mozilla::pkix::Result result
=
561 certDER
.Init(certBytes
.Elements(), certBytes
.Length());
562 if (result
!= pkix::Success
) {
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
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
{
598 NS_DECL_NSICLIENTAUTHDIALOGCALLBACK
600 explicit ClientAuthDialogCallback(
601 SelectClientAuthCertificate
* selectClientAuthCertificate
)
602 : mSelectClientAuthCertificate(selectClientAuthCertificate
) {}
605 virtual ~ClientAuthDialogCallback() = default;
607 RefPtr
<SelectClientAuthCertificate
> mSelectClientAuthCertificate
;
610 NS_IMPL_ISUPPORTS(ClientAuthDialogCallback
, nsIClientAuthDialogCallback
)
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
;
629 nsresult rv
= cert
->GetRawDER(selectedCertBytes
);
631 selectedCertBytes
.Clear();
632 mSelectClientAuthCertificate
->DispatchContinuation(
633 std::move(selectedCertBytes
));
637 mSelectClientAuthCertificate
->DispatchContinuation(
638 std::move(selectedCertBytes
));
643 SelectClientAuthCertificate::Run() {
644 // We check the value of a pref, so this should only be run on the main
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
));
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));
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
));
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
));
680 if (PR_GetError() == SEC_ERROR_BAD_PASSWORD
) {
681 // problem with password: bail
686 if (lowPrioNonrepCert
) {
687 selectedCertBytes
.AppendElements(lowPrioNonrepCert
->derCert
.data
,
688 lowPrioNonrepCert
->derCert
.len
);
690 DispatchContinuation(std::move(selectedCertBytes
));
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) {
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
);
719 DispatchContinuation(std::move(selectedCertBytes
));
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);
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",
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
));
759 UniqueCERTCertificate
serverCert(SSL_PeerCertificate(socket
));
761 PR_SetError(SSL_ERROR_NO_CERTIFICATE
, 0);
766 if (NS_FAILED(info
->GetBrowserId(&browserId
))) {
767 PR_SetError(SEC_ERROR_LIBRARY_FAILURE
, 0);
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
,
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
,
838 if (!childEndpoint
.Bind(selectClientAuthCertificate
)) {
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
);
861 PR_SetError(SEC_ERROR_LIBRARY_FAILURE
, 0);
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",
879 nsCOMPtr
<nsIRunnable
> selectClientAuthCertificate(
880 new SelectClientAuthCertificate(
881 std::move(authInfo
), std::move(serverCert
),
882 std::move(potentialClientCertificates
),
883 std::move(potentialClientCertificateChains
), continuation
,
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
896 class RemoteClientAuthCertificateSelected
897 : public ClientAuthCertificateSelectedBase
{
899 explicit RemoteClientAuthCertificateSelected(
900 SelectTLSClientAuthCertParent
* selectTLSClientAuthCertParent
)
901 : mSelectTLSClientAuthCertParent(selectTLSClientAuthCertParent
),
902 mEventTarget(GetCurrentSerialEventTarget()) {}
904 NS_IMETHOD
Run() override
;
907 RefPtr
<SelectTLSClientAuthCertParent
> mSelectTLSClientAuthCertParent
;
908 nsCOMPtr
<nsISerialEventTarget
> mEventTarget
;
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
));
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
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
,
944 nsCOMPtr
<nsIEventTarget
> socketThread
=
945 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID
);
946 if (NS_WARN_IF(!socketThread
)) {
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
{
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));
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
);
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
,
995 Unused
<< NS_DispatchToMainThread(selectClientAuthCertificate
);
997 return NS_SUCCEEDED(rv
);
1000 void SelectTLSClientAuthCertParent::TLSClientAuthCertSelected(
1001 const nsTArray
<uint8_t>& aSelectedCertBytes
,
1002 nsTArray
<nsTArray
<uint8_t>>&& aSelectedCertChainBytes
) {
1007 nsTArray
<ByteArray
> selectedCertChainBytes
;
1008 for (auto& certBytes
: aSelectedCertChainBytes
) {
1009 selectedCertChainBytes
.AppendElement(ByteArray(certBytes
));
1012 Unused
<< SendTLSClientAuthCertSelected(aSelectedCertBytes
,
1013 selectedCertChainBytes
);
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
)) {
1043 nsresult rv
= socketThread
->Dispatch(mContinuation
, NS_DISPATCH_NORMAL
);
1044 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
1049 } // namespace mozilla::psm