1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #import <AuthenticationServices/AuthenticationServices.h>
9 #include "MacOSWebAuthnService.h"
11 #include "CFTypeRefPtr.h"
12 #include "WebAuthnAutoFillEntry.h"
13 #include "WebAuthnEnumStrings.h"
14 #include "WebAuthnResult.h"
15 #include "WebAuthnTransportIdentifiers.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/StaticPrefs_security.h"
18 #include "mozilla/dom/CanonicalBrowsingContext.h"
19 #include "nsCocoaUtils.h"
20 #include "nsIWebAuthnPromise.h"
21 #include "nsThreadUtils.h"
23 // The documentation for the platform APIs used here can be found at:
24 // https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys
27 static mozilla::LazyLogModule gMacOSWebAuthnServiceLog("macoswebauthnservice");
30 namespace mozilla::dom {
31 class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService;
32 } // namespace mozilla::dom
34 // The following ASC* declarations are from the private framework
35 // AuthenticationServicesCore. The full definitions can be found in WebKit's
36 // source at Source/WebKit/Platform/spi/Cocoa/AuthenticationServicesCoreSPI.h.
37 // Overriding ASAuthorizationController's _requestContextWithRequests is
38 // currently the only way to provide the right information to the macOS
39 // WebAuthn API (namely, the clientDataHash for requests made to physical
42 NS_ASSUME_NONNULL_BEGIN
44 @class ASCPublicKeyCredentialDescriptor;
45 @interface ASCPublicKeyCredentialDescriptor : NSObject <NSSecureCoding>
46 - (instancetype)initWithCredentialID:(NSData*)credentialID
48 (nullable NSArray<NSString*>*)allowedTransports;
51 @protocol ASCPublicKeyCredentialCreationOptions
52 @property(nonatomic, copy) NSData* clientDataHash;
53 @property(nonatomic, nullable, copy) NSData* challenge;
54 @property(nonatomic, copy)
55 NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials;
58 @protocol ASCPublicKeyCredentialAssertionOptions <NSCopying>
59 @property(nonatomic, copy) NSData* clientDataHash;
62 @protocol ASCCredentialRequestContext
63 @property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
64 platformKeyCredentialCreationOptions;
65 @property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
66 platformKeyCredentialAssertionOptions;
69 @interface ASAuthorizationController (Secrets)
70 - (id<ASCCredentialRequestContext>)
71 _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
72 error:(NSError**)outError;
75 NSArray<NSString*>* TransportsByteToTransportsArray(const uint8_t aTransports)
76 API_AVAILABLE(macos(13.3)) {
77 NSMutableArray<NSString*>* transportsNS = [[NSMutableArray alloc] init];
78 if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) ==
79 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
82 ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportUSB];
84 if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) ==
85 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
88 ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportNFC];
90 if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) ==
91 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
94 ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportBluetooth];
96 // TODO (bug 1859367): the platform doesn't have a definition for "internal"
97 // transport. When it does, this code should be updated to handle it.
101 NSArray* CredentialListsToCredentialDescriptorArray(
102 const nsTArray<nsTArray<uint8_t>>& aCredentialList,
103 const nsTArray<uint8_t>& aCredentialListTransports,
104 const Class credentialDescriptorClass) API_AVAILABLE(macos(13.3)) {
105 MOZ_ASSERT(aCredentialList.Length() == aCredentialListTransports.Length());
106 NSMutableArray* credentials = [[NSMutableArray alloc] init];
107 for (size_t i = 0; i < aCredentialList.Length(); i++) {
108 const nsTArray<uint8_t>& credentialId = aCredentialList[i];
109 const uint8_t& credentialTransports = aCredentialListTransports[i];
110 NSData* credentialIdNS = [NSData dataWithBytes:credentialId.Elements()
111 length:credentialId.Length()];
112 NSArray<NSString*>* credentialTransportsNS =
113 TransportsByteToTransportsArray(credentialTransports);
114 NSObject* credential = [[credentialDescriptorClass alloc]
115 initWithCredentialID:credentialIdNS
116 transports:credentialTransportsNS];
117 [credentials addObject:credential];
122 // MacOSAuthorizationController is an ASAuthorizationController that overrides
123 // _requestContextWithRequests so that the implementation can set some options
124 // that aren't directly settable using the public API.
125 API_AVAILABLE(macos(13.3))
126 @interface MacOSAuthorizationController : ASAuthorizationController
129 @implementation MacOSAuthorizationController {
130 nsTArray<uint8_t> mClientDataHash;
131 nsTArray<nsTArray<uint8_t>> mCredentialList;
132 nsTArray<uint8_t> mCredentialListTransports;
135 - (void)stashClientDataHash:(nsTArray<uint8_t>&&)clientDataHash
136 andCredentialList:(nsTArray<nsTArray<uint8_t>>&&)credentialList
137 andCredentialListTransports:(nsTArray<uint8_t>&&)credentialListTransports {
138 mClientDataHash = std::move(clientDataHash);
139 mCredentialList = std::move(credentialList);
140 mCredentialListTransports = std::move(credentialListTransports);
143 - (id<ASCCredentialRequestContext>)
144 _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
145 error:(NSError**)outError {
146 id<ASCCredentialRequestContext> context =
147 [super _requestContextWithRequests:requests error:outError];
149 id<ASCPublicKeyCredentialCreationOptions> registrationOptions =
150 context.platformKeyCredentialCreationOptions;
151 if (registrationOptions) {
152 registrationOptions.clientDataHash =
153 [NSData dataWithBytes:mClientDataHash.Elements()
154 length:mClientDataHash.Length()];
155 // Unset challenge so that the implementation uses clientDataHash (the API
156 // returns an error otherwise).
157 registrationOptions.challenge = nil;
158 const Class publicKeyCredentialDescriptorClass =
159 NSClassFromString(@"ASCPublicKeyCredentialDescriptor");
160 NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials =
161 CredentialListsToCredentialDescriptorArray(
162 mCredentialList, mCredentialListTransports,
163 publicKeyCredentialDescriptorClass);
164 if ([excludedCredentials count] > 0) {
165 registrationOptions.excludedCredentials = excludedCredentials;
169 id<ASCPublicKeyCredentialAssertionOptions> signOptions =
170 context.platformKeyCredentialAssertionOptions;
172 signOptions.clientDataHash =
173 [NSData dataWithBytes:mClientDataHash.Elements()
174 length:mClientDataHash.Length()];
175 context.platformKeyCredentialAssertionOptions =
176 [signOptions copyWithZone:nil];
183 // MacOSAuthenticatorRequestDelegate is an ASAuthorizationControllerDelegate,
184 // which can be set as an ASAuthorizationController's delegate to be called
185 // back when a request for a platform authenticator attestation or assertion
186 // response completes either with an attestation or assertion
187 // (didCompleteWithAuthorization) or with an error (didCompleteWithError).
188 API_AVAILABLE(macos(13.3))
189 @interface MacOSAuthenticatorRequestDelegate
190 : NSObject <ASAuthorizationControllerDelegate>
191 - (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback;
194 // MacOSAuthenticatorPresentationContextProvider is an
195 // ASAuthorizationControllerPresentationContextProviding, which can be set as
196 // an ASAuthorizationController's presentationContextProvider, and provides a
197 // presentation anchor for the ASAuthorizationController. Basically, this
198 // provides the API a handle to the window that made the request.
199 API_AVAILABLE(macos(13.3))
200 @interface MacOSAuthenticatorPresentationContextProvider
201 : NSObject <ASAuthorizationControllerPresentationContextProviding>
202 @property(nonatomic, strong) NSWindow* window;
205 namespace mozilla::dom {
207 #pragma clang diagnostic push
208 #pragma clang diagnostic ignored "-Wnullability-completeness"
209 class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService final
210 : public nsIWebAuthnService {
212 MacOSWebAuthnService()
213 : mTransactionState(Nothing(),
214 "MacOSWebAuthnService::mTransactionState") {}
216 NS_DECL_THREADSAFE_ISUPPORTS
217 NS_DECL_NSIWEBAUTHNSERVICE
219 void FinishMakeCredential(const nsTArray<uint8_t>& aRawAttestationObject,
220 const nsTArray<uint8_t>& aCredentialId,
221 const nsTArray<nsString>& aTransports,
222 const Maybe<nsString>& aAuthenticatorAttachment);
224 void FinishGetAssertion(const nsTArray<uint8_t>& aCredentialId,
225 const nsTArray<uint8_t>& aSignature,
226 const nsTArray<uint8_t>& aAuthenticatorData,
227 const nsTArray<uint8_t>& aUserHandle,
228 const Maybe<nsString>& aAuthenticatorAttachment);
229 void ReleasePlatformResources();
230 void AbortTransaction(nsresult aError);
233 ~MacOSWebAuthnService() = default;
235 void PerformRequests(NSArray<ASAuthorizationRequest*>* aRequests,
236 nsTArray<uint8_t>&& aClientDataHash,
237 nsTArray<nsTArray<uint8_t>>&& aCredentialList,
238 nsTArray<uint8_t>&& aCredentialListTransports,
239 uint64_t aBrowsingContextId);
241 struct TransactionState {
242 uint64_t transactionId;
243 uint64_t browsingContextId;
244 Maybe<RefPtr<nsIWebAuthnSignArgs>> pendingSignArgs;
245 Maybe<RefPtr<nsIWebAuthnSignPromise>> pendingSignPromise;
246 Maybe<nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>> autoFillEntries;
249 using TransactionStateMutex = DataMutex<Maybe<TransactionState>>;
250 void DoGetAssertion(Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
251 const TransactionStateMutex::AutoLock& aGuard);
253 TransactionStateMutex mTransactionState;
256 ASAuthorizationWebBrowserPublicKeyCredentialManager* mCredentialManager = nil;
257 nsCOMPtr<nsIWebAuthnRegisterPromise> mRegisterPromise;
258 nsCOMPtr<nsIWebAuthnSignPromise> mSignPromise;
259 MacOSAuthorizationController* mAuthorizationController = nil;
260 MacOSAuthenticatorRequestDelegate* mRequestDelegate = nil;
261 MacOSAuthenticatorPresentationContextProvider* mPresentationContextProvider =
264 #pragma clang diagnostic pop
266 } // namespace mozilla::dom
268 nsTArray<uint8_t> NSDataToArray(NSData* data) {
269 nsTArray<uint8_t> array(reinterpret_cast<const uint8_t*>(data.bytes),
274 @implementation MacOSAuthenticatorRequestDelegate {
275 RefPtr<mozilla::dom::MacOSWebAuthnService> mCallback;
278 - (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback {
279 mCallback = callback;
282 - (void)authorizationController:(ASAuthorizationController*)controller
283 didCompleteWithAuthorization:(ASAuthorization*)authorization {
284 if ([authorization.credential
286 @protocol(ASAuthorizationPublicKeyCredentialRegistration)]) {
287 MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
288 ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
289 "got registration"));
290 id<ASAuthorizationPublicKeyCredentialRegistration> credential =
291 (id<ASAuthorizationPublicKeyCredentialRegistration>)
292 authorization.credential;
293 nsTArray<uint8_t> rawAttestationObject(
294 NSDataToArray(credential.rawAttestationObject));
295 nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
296 nsTArray<nsString> transports;
297 mozilla::Maybe<nsString> authenticatorAttachment;
298 if ([credential isKindOfClass:
299 [ASAuthorizationPlatformPublicKeyCredentialRegistration
301 transports.AppendElement(u"internal"_ns);
302 #if defined(MAC_OS_VERSION_13_5) && \
303 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
304 if (__builtin_available(macos 13.5, *)) {
305 ASAuthorizationPlatformPublicKeyCredentialRegistration*
307 (ASAuthorizationPlatformPublicKeyCredentialRegistration*)
309 switch (platformCredential.attachment) {
310 case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
311 authenticatorAttachment.emplace(u"cross-platform"_ns);
313 case ASAuthorizationPublicKeyCredentialAttachmentPlatform:
314 authenticatorAttachment.emplace(u"platform"_ns);
322 authenticatorAttachment.emplace(u"cross-platform"_ns);
324 mCallback->FinishMakeCredential(rawAttestationObject, credentialId,
325 transports, authenticatorAttachment);
326 } else if ([authorization.credential
328 @protocol(ASAuthorizationPublicKeyCredentialAssertion)]) {
329 MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
330 ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
332 id<ASAuthorizationPublicKeyCredentialAssertion> credential =
333 (id<ASAuthorizationPublicKeyCredentialAssertion>)
334 authorization.credential;
335 nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
336 nsTArray<uint8_t> signature(NSDataToArray(credential.signature));
337 nsTArray<uint8_t> rawAuthenticatorData(
338 NSDataToArray(credential.rawAuthenticatorData));
339 nsTArray<uint8_t> userHandle(NSDataToArray(credential.userID));
340 mozilla::Maybe<nsString> authenticatorAttachment;
342 isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion
344 #if defined(MAC_OS_VERSION_13_5) && \
345 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
346 if (__builtin_available(macos 13.5, *)) {
347 ASAuthorizationPlatformPublicKeyCredentialAssertion*
349 (ASAuthorizationPlatformPublicKeyCredentialAssertion*)
351 switch (platformCredential.attachment) {
352 case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
353 authenticatorAttachment.emplace(u"cross-platform"_ns);
355 case ASAuthorizationPublicKeyCredentialAttachmentPlatform:
356 authenticatorAttachment.emplace(u"platform"_ns);
364 authenticatorAttachment.emplace(u"cross-platform"_ns);
366 mCallback->FinishGetAssertion(credentialId, signature, rawAuthenticatorData,
367 userHandle, authenticatorAttachment);
370 gMacOSWebAuthnServiceLog, mozilla::LogLevel::Error,
371 ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
372 "authorization.credential is neither registration nor assertion!"));
373 MOZ_ASSERT_UNREACHABLE(
374 "should have ASAuthorizationPublicKeyCredentialRegistration or "
375 "ASAuthorizationPublicKeyCredentialAssertion");
377 mCallback->ReleasePlatformResources();
381 - (void)authorizationController:(ASAuthorizationController*)controller
382 didCompleteWithError:(NSError*)error {
383 nsAutoString errorDescription;
384 nsCocoaUtils::GetStringForNSString(error.localizedDescription,
386 MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Warning,
387 ("MacOSAuthenticatorRequestDelegate::didCompleteWithError: %ld (%s)",
388 error.code, NS_ConvertUTF16toUTF8(errorDescription).get()));
390 switch (error.code) {
391 case ASAuthorizationErrorCanceled:
392 rv = NS_ERROR_DOM_ABORT_ERR;
394 case ASAuthorizationErrorFailed:
395 // The message is right, but it's not about indexeddb.
396 // See https://webidl.spec.whatwg.org/#constrainterror
397 rv = NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
399 case ASAuthorizationErrorUnknown:
400 rv = NS_ERROR_DOM_UNKNOWN_ERR;
403 rv = NS_ERROR_DOM_NOT_ALLOWED_ERR;
406 mCallback->AbortTransaction(rv);
411 @implementation MacOSAuthenticatorPresentationContextProvider
412 @synthesize window = window;
414 - (ASPresentationAnchor)presentationAnchorForAuthorizationController:
415 (ASAuthorizationController*)controller {
420 namespace mozilla::dom {
422 #pragma clang diagnostic push
423 #pragma clang diagnostic ignored "-Wnullability-completeness"
424 // Given a browsingContextId, attempts to determine the NSWindow associated
425 // with that browser.
426 nsresult BrowsingContextIdToNSWindow(uint64_t browsingContextId,
429 RefPtr<BrowsingContext> browsingContext(
430 BrowsingContext::Get(browsingContextId));
431 if (!browsingContext) {
432 return NS_ERROR_NOT_AVAILABLE;
434 CanonicalBrowsingContext* canonicalBrowsingContext =
435 browsingContext->Canonical();
436 if (!canonicalBrowsingContext) {
437 return NS_ERROR_NOT_AVAILABLE;
439 nsCOMPtr<nsIWidget> widget(
440 canonicalBrowsingContext->GetParentProcessWidgetContaining());
442 return NS_ERROR_NOT_AVAILABLE;
444 *window = static_cast<NSWindow*>(widget->GetNativeData(NS_NATIVE_WINDOW));
447 #pragma clang diagnostic pop
449 already_AddRefed<nsIWebAuthnService> NewMacOSWebAuthnServiceIfAvailable() {
450 MOZ_ASSERT(XRE_IsParentProcess());
451 if (!StaticPrefs::security_webauthn_enable_macos_passkeys()) {
453 gMacOSWebAuthnServiceLog, LogLevel::Debug,
454 ("macOS platform support for webauthn (passkeys) disabled by pref"));
457 // This code checks for the entitlement
458 // 'com.apple.developer.web-browser.public-key-credential', which must be
459 // true to be able to use the platform APIs.
460 CFTypeRefPtr<SecTaskRef> entitlementTask(
461 CFTypeRefPtr<SecTaskRef>::WrapUnderCreateRule(
462 SecTaskCreateFromSelf(nullptr)));
463 CFTypeRefPtr<CFBooleanRef> entitlementValue(
464 CFTypeRefPtr<CFBooleanRef>::WrapUnderCreateRule(
465 reinterpret_cast<CFBooleanRef>(SecTaskCopyValueForEntitlement(
466 entitlementTask.get(),
467 CFSTR("com.apple.developer.web-browser.public-key-credential"),
469 if (!entitlementValue || !CFBooleanGetValue(entitlementValue.get())) {
471 gMacOSWebAuthnServiceLog, LogLevel::Warning,
472 ("entitlement com.apple.developer.web-browser.public-key-credential "
473 "not present: platform passkey APIs will not be available"));
476 nsCOMPtr<nsIWebAuthnService> service(new MacOSWebAuthnService());
477 return service.forget();
480 void MacOSWebAuthnService::AbortTransaction(nsresult aError) {
481 MOZ_ASSERT(NS_IsMainThread());
482 if (mRegisterPromise) {
483 Unused << mRegisterPromise->Reject(aError);
484 mRegisterPromise = nullptr;
487 Unused << mSignPromise->Reject(aError);
488 mSignPromise = nullptr;
490 ReleasePlatformResources();
493 #pragma clang diagnostic push
494 #pragma clang diagnostic ignored "-Wnullability-completeness"
495 NS_IMPL_ISUPPORTS(MacOSWebAuthnService, nsIWebAuthnService)
496 #pragma clang diagnostic pop
499 MacOSWebAuthnService::MakeCredential(uint64_t aTransactionId,
500 uint64_t aBrowsingContextId,
501 nsIWebAuthnRegisterArgs* aArgs,
502 nsIWebAuthnRegisterPromise* aPromise) {
504 auto guard = mTransactionState.Lock();
505 *guard = Some(TransactionState{
512 NS_DispatchToMainThread(NS_NewRunnableFunction(
513 "MacOSWebAuthnService::MakeCredential",
514 [self = RefPtr{this}, browsingContextId(aBrowsingContextId),
515 aArgs = nsCOMPtr{aArgs}, aPromise = nsCOMPtr{aPromise}]() {
516 self->mRegisterPromise = aPromise;
519 Unused << aArgs->GetRpId(rpId);
520 NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId);
522 nsTArray<uint8_t> challenge;
523 Unused << aArgs->GetChallenge(challenge);
524 NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
525 length:challenge.Length()];
527 nsTArray<uint8_t> userId;
528 Unused << aArgs->GetUserId(userId);
529 NSData* userIdNS = [NSData dataWithBytes:userId.Elements()
530 length:userId.Length()];
532 nsAutoString userName;
533 Unused << aArgs->GetUserName(userName);
534 NSString* userNameNS = nsCocoaUtils::ToNSString(userName);
536 nsAutoString userDisplayName;
537 Unused << aArgs->GetUserName(userDisplayName);
538 NSString* userDisplayNameNS = nsCocoaUtils::ToNSString(userDisplayName);
540 nsTArray<int32_t> coseAlgs;
541 Unused << aArgs->GetCoseAlgs(coseAlgs);
542 NSMutableArray* credentialParameters = [[NSMutableArray alloc] init];
543 for (const auto& coseAlg : coseAlgs) {
544 ASAuthorizationPublicKeyCredentialParameters* credentialParameter =
545 [[ASAuthorizationPublicKeyCredentialParameters alloc]
546 initWithAlgorithm:coseAlg];
547 [credentialParameters addObject:credentialParameter];
550 nsTArray<nsTArray<uint8_t>> excludeList;
551 Unused << aArgs->GetExcludeList(excludeList);
552 nsTArray<uint8_t> excludeListTransports;
553 Unused << aArgs->GetExcludeListTransports(excludeListTransports);
554 if (excludeList.Length() != excludeListTransports.Length()) {
555 self->mRegisterPromise->Reject(NS_ERROR_INVALID_ARG);
559 Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
560 userVerificationPreference = Nothing();
561 nsAutoString userVerification;
562 Unused << aArgs->GetUserVerification(userVerification);
563 if (userVerification.EqualsLiteral(
564 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
565 userVerificationPreference.emplace(
566 ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired);
567 } else if (userVerification.EqualsLiteral(
568 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
569 userVerificationPreference.emplace(
570 ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred);
572 userVerification.EqualsLiteral(
573 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) {
574 userVerificationPreference.emplace(
575 ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged);
578 // The API doesn't support attestation for platform passkeys and shows
579 // no consent UI for non-none attestation for cross-platform devices,
580 // so this must always be none.
581 ASAuthorizationPublicKeyCredentialAttestationKind
582 attestationPreference =
583 ASAuthorizationPublicKeyCredentialAttestationKindNone;
585 // Initialize the platform provider with the rpId.
586 ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider =
587 [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
588 initWithRelyingPartyIdentifier:rpIdNS];
589 // Make a credential registration request with the challenge, userName,
591 ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest*
592 platformRegistrationRequest = [platformProvider
593 createCredentialRegistrationRequestWithChallenge:challengeNS
596 [platformProvider release];
597 platformRegistrationRequest.attestationPreference =
598 attestationPreference;
599 if (userVerificationPreference.isSome()) {
600 platformRegistrationRequest.userVerificationPreference =
601 *userVerificationPreference;
604 // Initialize the cross-platform provider with the rpId.
605 ASAuthorizationSecurityKeyPublicKeyCredentialProvider*
606 crossPlatformProvider =
607 [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc]
608 initWithRelyingPartyIdentifier:rpIdNS];
609 // Make a credential registration request with the challenge,
610 // userDisplayName, userName, and userId.
611 ASAuthorizationSecurityKeyPublicKeyCredentialRegistrationRequest*
612 crossPlatformRegistrationRequest = [crossPlatformProvider
613 createCredentialRegistrationRequestWithChallenge:challengeNS
618 [crossPlatformProvider release];
619 crossPlatformRegistrationRequest.attestationPreference =
620 attestationPreference;
621 crossPlatformRegistrationRequest.credentialParameters =
622 credentialParameters;
623 if (userVerificationPreference.isSome()) {
624 crossPlatformRegistrationRequest.userVerificationPreference =
625 *userVerificationPreference;
627 nsTArray<uint8_t> clientDataHash;
628 nsresult rv = aArgs->GetClientDataHash(clientDataHash);
630 self->mRegisterPromise->Reject(rv);
633 nsAutoString authenticatorAttachment;
634 rv = aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
635 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
636 self->mRegisterPromise->Reject(rv);
639 NSMutableArray* requests = [[NSMutableArray alloc] init];
640 if (authenticatorAttachment.EqualsLiteral(
641 MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
642 [requests addObject:platformRegistrationRequest];
643 } else if (authenticatorAttachment.EqualsLiteral(
644 MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
645 [requests addObject:crossPlatformRegistrationRequest];
647 // Regarding the value of authenticator attachment, according to the
648 // specification, "client platforms MUST ignore unknown values,
649 // treating an unknown value as if the member does not exist".
650 [requests addObject:platformRegistrationRequest];
651 [requests addObject:crossPlatformRegistrationRequest];
653 self->PerformRequests(
654 requests, std::move(clientDataHash), std::move(excludeList),
655 std::move(excludeListTransports), browsingContextId);
660 void MacOSWebAuthnService::PerformRequests(
661 NSArray<ASAuthorizationRequest*>* aRequests,
662 nsTArray<uint8_t>&& aClientDataHash,
663 nsTArray<nsTArray<uint8_t>>&& aCredentialList,
664 nsTArray<uint8_t>&& aCredentialListTransports,
665 uint64_t aBrowsingContextId) {
666 MOZ_ASSERT(NS_IsMainThread());
667 // Create a MacOSAuthorizationController and initialize it with the requests.
668 MOZ_ASSERT(!mAuthorizationController);
669 mAuthorizationController = [[MacOSAuthorizationController alloc]
670 initWithAuthorizationRequests:aRequests];
671 [mAuthorizationController
672 stashClientDataHash:std::move(aClientDataHash)
673 andCredentialList:std::move(aCredentialList)
674 andCredentialListTransports:std::move(aCredentialListTransports)];
676 // Set up the delegate to run when the operation completes.
677 MOZ_ASSERT(!mRequestDelegate);
678 mRequestDelegate = [[MacOSAuthenticatorRequestDelegate alloc] init];
679 [mRequestDelegate setCallback:this];
680 mAuthorizationController.delegate = mRequestDelegate;
682 // Create a presentation context provider so the API knows which window
684 NSWindow* window = nullptr;
685 nsresult rv = BrowsingContextIdToNSWindow(aBrowsingContextId, &window);
687 AbortTransaction(NS_ERROR_DOM_INVALID_STATE_ERR);
690 MOZ_ASSERT(!mPresentationContextProvider);
691 mPresentationContextProvider =
692 [[MacOSAuthenticatorPresentationContextProvider alloc] init];
693 mPresentationContextProvider.window = window;
694 mAuthorizationController.presentationContextProvider =
695 mPresentationContextProvider;
697 // Finally, perform the request.
698 [mAuthorizationController performRequests];
701 void MacOSWebAuthnService::FinishMakeCredential(
702 const nsTArray<uint8_t>& aRawAttestationObject,
703 const nsTArray<uint8_t>& aCredentialId,
704 const nsTArray<nsString>& aTransports,
705 const Maybe<nsString>& aAuthenticatorAttachment) {
706 MOZ_ASSERT(NS_IsMainThread());
707 if (!mRegisterPromise) {
711 RefPtr<WebAuthnRegisterResult> result(new WebAuthnRegisterResult(
712 aRawAttestationObject, Nothing(), aCredentialId, aTransports,
713 aAuthenticatorAttachment));
714 Unused << mRegisterPromise->Resolve(result);
715 mRegisterPromise = nullptr;
719 MacOSWebAuthnService::GetAssertion(uint64_t aTransactionId,
720 uint64_t aBrowsingContextId,
721 nsIWebAuthnSignArgs* aArgs,
722 nsIWebAuthnSignPromise* aPromise) {
725 auto guard = mTransactionState.Lock();
726 *guard = Some(TransactionState{
730 Some(RefPtr{aPromise}),
734 bool conditionallyMediated;
735 Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
736 if (!conditionallyMediated) {
737 DoGetAssertion(Nothing(), guard);
741 // Using conditional mediation, so dispatch a task to collect any available
743 NS_DispatchToMainThread(NS_NewRunnableFunction(
744 "platformCredentialsForRelyingParty",
745 [self = RefPtr{this}, aTransactionId, aArgs = nsCOMPtr{aArgs}]() {
746 // This handler is called when platformCredentialsForRelyingParty
748 auto credentialsCompletionHandler = ^(
749 NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*
751 nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>> autoFillEntries;
752 for (NSUInteger i = 0; i < credentials.count; i++) {
753 const auto& credential = credentials[i];
754 nsAutoString userName;
755 nsCocoaUtils::GetStringForNSString(credential.name, userName);
757 nsCocoaUtils::GetStringForNSString(credential.relyingParty, rpId);
758 autoFillEntries.AppendElement(new WebAuthnAutoFillEntry(
759 nsIWebAuthnAutoFillEntry::PROVIDER_PLATFORM_MACOS, userName,
760 rpId, NSDataToArray(credential.credentialID)));
762 auto guard = self->mTransactionState.Lock();
763 if (guard->isSome() && guard->ref().transactionId == aTransactionId) {
764 guard->ref().autoFillEntries.emplace(std::move(autoFillEntries));
767 // This handler is called when
768 // requestAuthorizationForPublicKeyCredentials completes.
769 auto authorizationHandler = ^(
770 ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState
771 authorizationState) {
772 // If authorized, list any available passkeys.
773 if (authorizationState ==
774 ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationStateAuthorized) {
776 Unused << aArgs->GetRpId(rpId);
777 [self->mCredentialManager
778 platformCredentialsForRelyingParty:nsCocoaUtils::ToNSString(
781 credentialsCompletionHandler];
784 if (!self->mCredentialManager) {
785 self->mCredentialManager =
786 [[ASAuthorizationWebBrowserPublicKeyCredentialManager alloc]
789 // Request authorization to examine any available passkeys. This will
790 // cause a permission prompt to appear once.
791 [self->mCredentialManager
792 requestAuthorizationForPublicKeyCredentials:authorizationHandler];
798 void MacOSWebAuthnService::DoGetAssertion(
799 Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
800 const TransactionStateMutex::AutoLock& aGuard) {
801 if (aGuard->isNothing() || aGuard->ref().pendingSignArgs.isNothing() ||
802 aGuard->ref().pendingSignPromise.isNothing()) {
806 // Take the pending Args and Promise to prevent repeated calls to
807 // DoGetAssertion for this transaction.
808 RefPtr<nsIWebAuthnSignArgs> aArgs = aGuard->ref().pendingSignArgs.extract();
809 RefPtr<nsIWebAuthnSignPromise> aPromise =
810 aGuard->ref().pendingSignPromise.extract();
811 uint64_t aBrowsingContextId = aGuard->ref().browsingContextId;
813 NS_DispatchToMainThread(NS_NewRunnableFunction(
814 "MacOSWebAuthnService::MakeCredential",
815 [self = RefPtr{this}, browsingContextId(aBrowsingContextId), aArgs,
817 aSelectedCredentialId = std::move(aSelectedCredentialId)]() mutable {
818 self->mSignPromise = aPromise;
821 Unused << aArgs->GetRpId(rpId);
822 NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId);
824 nsTArray<uint8_t> challenge;
825 Unused << aArgs->GetChallenge(challenge);
826 NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
827 length:challenge.Length()];
829 nsTArray<nsTArray<uint8_t>> allowList;
830 nsTArray<uint8_t> allowListTransports;
831 if (aSelectedCredentialId.isSome()) {
832 allowList.AppendElement(aSelectedCredentialId.extract());
833 allowListTransports.AppendElement(
834 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL);
836 Unused << aArgs->GetAllowList(allowList);
837 Unused << aArgs->GetAllowListTransports(allowListTransports);
839 NSMutableArray* platformAllowedCredentials =
840 [[NSMutableArray alloc] init];
841 for (const auto& allowedCredentialId : allowList) {
842 NSData* allowedCredentialIdNS =
843 [NSData dataWithBytes:allowedCredentialId.Elements()
844 length:allowedCredentialId.Length()];
845 ASAuthorizationPlatformPublicKeyCredentialDescriptor*
847 [[ASAuthorizationPlatformPublicKeyCredentialDescriptor alloc]
848 initWithCredentialID:allowedCredentialIdNS];
849 [platformAllowedCredentials addObject:allowedCredential];
851 const Class securityKeyPublicKeyCredentialDescriptorClass =
853 @"ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor");
854 NSArray<ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor*>*
855 crossPlatformAllowedCredentials =
856 CredentialListsToCredentialDescriptorArray(
857 allowList, allowListTransports,
858 securityKeyPublicKeyCredentialDescriptorClass);
860 Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
861 userVerificationPreference = Nothing();
862 nsAutoString userVerification;
863 Unused << aArgs->GetUserVerification(userVerification);
864 if (userVerification.EqualsLiteral(
865 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
866 userVerificationPreference.emplace(
867 ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired);
868 } else if (userVerification.EqualsLiteral(
869 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
870 userVerificationPreference.emplace(
871 ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred);
873 userVerification.EqualsLiteral(
874 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) {
875 userVerificationPreference.emplace(
876 ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged);
879 // Initialize the platform provider with the rpId.
880 ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider =
881 [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
882 initWithRelyingPartyIdentifier:rpIdNS];
883 // Make a credential assertion request with the challenge.
884 ASAuthorizationPlatformPublicKeyCredentialAssertionRequest*
885 platformAssertionRequest = [platformProvider
886 createCredentialAssertionRequestWithChallenge:challengeNS];
887 [platformProvider release];
888 platformAssertionRequest.allowedCredentials =
889 platformAllowedCredentials;
890 if (userVerificationPreference.isSome()) {
891 platformAssertionRequest.userVerificationPreference =
892 *userVerificationPreference;
895 // Initialize the cross-platform provider with the rpId.
896 ASAuthorizationSecurityKeyPublicKeyCredentialProvider*
897 crossPlatformProvider =
898 [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc]
899 initWithRelyingPartyIdentifier:rpIdNS];
900 // Make a credential assertion request with the challenge.
901 ASAuthorizationSecurityKeyPublicKeyCredentialAssertionRequest*
902 crossPlatformAssertionRequest = [crossPlatformProvider
903 createCredentialAssertionRequestWithChallenge:challengeNS];
904 [crossPlatformProvider release];
905 crossPlatformAssertionRequest.allowedCredentials =
906 crossPlatformAllowedCredentials;
907 if (userVerificationPreference.isSome()) {
908 crossPlatformAssertionRequest.userVerificationPreference =
909 *userVerificationPreference;
911 nsTArray<uint8_t> clientDataHash;
912 nsresult rv = aArgs->GetClientDataHash(clientDataHash);
914 self->mSignPromise->Reject(rv);
917 nsTArray<nsTArray<uint8_t>> unusedCredentialIds;
918 nsTArray<uint8_t> unusedTransports;
919 // allowList and allowListTransports won't actually get used.
920 self->PerformRequests(
921 @[ platformAssertionRequest, crossPlatformAssertionRequest ],
922 std::move(clientDataHash), std::move(allowList),
923 std::move(allowListTransports), browsingContextId);
927 void MacOSWebAuthnService::FinishGetAssertion(
928 const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aSignature,
929 const nsTArray<uint8_t>& aAuthenticatorData,
930 const nsTArray<uint8_t>& aUserHandle,
931 const Maybe<nsString>& aAuthenticatorAttachment) {
932 MOZ_ASSERT(NS_IsMainThread());
937 RefPtr<WebAuthnSignResult> result(new WebAuthnSignResult(
938 aAuthenticatorData, Nothing(), aCredentialId, aSignature, aUserHandle,
939 aAuthenticatorAttachment));
940 Unused << mSignPromise->Resolve(result);
941 mSignPromise = nullptr;
944 void MacOSWebAuthnService::ReleasePlatformResources() {
945 MOZ_ASSERT(NS_IsMainThread());
946 [mCredentialManager release];
947 mCredentialManager = nil;
948 [mAuthorizationController release];
949 mAuthorizationController = nil;
950 [mRequestDelegate release];
951 mRequestDelegate = nil;
952 [mPresentationContextProvider release];
953 mPresentationContextProvider = nil;
957 MacOSWebAuthnService::Reset() {
958 auto guard = mTransactionState.Lock();
959 if (guard->isSome()) {
960 if (guard->ref().pendingSignPromise.isSome()) {
961 // This request was never dispatched to the platform API, so
962 // we need to reject the promise ourselves.
963 guard->ref().pendingSignPromise.ref()->Reject(
964 NS_ERROR_DOM_NOT_ALLOWED_ERR);
968 NS_DispatchToMainThread(NS_NewRunnableFunction(
969 "MacOSWebAuthnService::Cancel", [self = RefPtr{this}] {
970 // cancel results in the delegate's didCompleteWithError method being
971 // called, which will release all platform resources.
972 [self->mAuthorizationController cancel];
978 MacOSWebAuthnService::GetIsUVPAA(bool* aAvailable) {
984 MacOSWebAuthnService::Cancel(uint64_t aTransactionId) {
985 return NS_ERROR_NOT_IMPLEMENTED;
989 MacOSWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
990 const nsAString& aOrigin,
992 auto guard = mTransactionState.Lock();
993 if (guard->isNothing() ||
994 guard->ref().browsingContextId != aBrowsingContextId ||
995 guard->ref().pendingSignArgs.isNothing()) {
1001 Unused << guard->ref().pendingSignArgs.ref()->GetOrigin(origin);
1002 if (origin != aOrigin) {
1007 *aRv = guard->ref().transactionId;
1012 MacOSWebAuthnService::GetAutoFillEntries(
1013 uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
1014 auto guard = mTransactionState.Lock();
1015 if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
1016 guard->ref().pendingSignArgs.isNothing() ||
1017 guard->ref().autoFillEntries.isNothing()) {
1018 return NS_ERROR_NOT_AVAILABLE;
1020 aRv.Assign(guard->ref().autoFillEntries.ref());
1025 MacOSWebAuthnService::SelectAutoFillEntry(
1026 uint64_t aTransactionId, const nsTArray<uint8_t>& aCredentialId) {
1027 auto guard = mTransactionState.Lock();
1028 if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
1029 guard->ref().pendingSignArgs.isNothing()) {
1030 return NS_ERROR_NOT_AVAILABLE;
1033 nsTArray<nsTArray<uint8_t>> allowList;
1034 Unused << guard->ref().pendingSignArgs.ref()->GetAllowList(allowList);
1035 if (!allowList.IsEmpty() && !allowList.Contains(aCredentialId)) {
1036 return NS_ERROR_FAILURE;
1039 Maybe<nsTArray<uint8_t>> id;
1041 id.ref().Assign(aCredentialId);
1042 DoGetAssertion(std::move(id), guard);
1048 MacOSWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
1049 auto guard = mTransactionState.Lock();
1050 if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
1051 guard->ref().pendingSignArgs.isNothing()) {
1052 return NS_ERROR_NOT_AVAILABLE;
1054 DoGetAssertion(Nothing(), guard);
1059 MacOSWebAuthnService::PinCallback(uint64_t aTransactionId,
1060 const nsACString& aPin) {
1061 return NS_ERROR_NOT_IMPLEMENTED;
1065 MacOSWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
1066 bool aForceNoneAttestation) {
1067 return NS_ERROR_NOT_IMPLEMENTED;
1071 MacOSWebAuthnService::SelectionCallback(uint64_t aTransactionId,
1073 return NS_ERROR_NOT_IMPLEMENTED;
1077 MacOSWebAuthnService::AddVirtualAuthenticator(
1078 const nsACString& protocol, const nsACString& transport,
1079 bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
1080 bool isUserVerified, uint64_t* _retval) {
1081 return NS_ERROR_NOT_IMPLEMENTED;
1085 MacOSWebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
1086 return NS_ERROR_NOT_IMPLEMENTED;
1090 MacOSWebAuthnService::AddCredential(uint64_t authenticatorId,
1091 const nsACString& credentialId,
1092 bool isResidentCredential,
1093 const nsACString& rpId,
1094 const nsACString& privateKey,
1095 const nsACString& userHandle,
1096 uint32_t signCount) {
1097 return NS_ERROR_NOT_IMPLEMENTED;
1101 MacOSWebAuthnService::GetCredentials(
1102 uint64_t authenticatorId,
1103 nsTArray<RefPtr<nsICredentialParameters>>& _retval) {
1104 return NS_ERROR_NOT_IMPLEMENTED;
1108 MacOSWebAuthnService::RemoveCredential(uint64_t authenticatorId,
1109 const nsACString& credentialId) {
1110 return NS_ERROR_NOT_IMPLEMENTED;
1114 MacOSWebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
1115 return NS_ERROR_NOT_IMPLEMENTED;
1119 MacOSWebAuthnService::SetUserVerified(uint64_t authenticatorId,
1120 bool isUserVerified) {
1121 return NS_ERROR_NOT_IMPLEMENTED;
1125 MacOSWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
1128 MacOSWebAuthnService::RunCommand(const nsACString& cmd) {
1129 return NS_ERROR_NOT_IMPLEMENTED;
1132 } // namespace mozilla::dom
1134 NS_ASSUME_NONNULL_END