Bug 1874684 - Part 6: Limit day length calculations to safe integers. r=mgaudet
[gecko.git] / dom / webauthn / MacOSWebAuthnService.mm
blobfc08ee1a48c2acdb8ed03df43ef8171dd683b344
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
26 namespace {
27 static mozilla::LazyLogModule gMacOSWebAuthnServiceLog("macoswebauthnservice");
28 }  // namespace
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
40 // tokens).
42 NS_ASSUME_NONNULL_BEGIN
44 @class ASCPublicKeyCredentialDescriptor;
45 @interface ASCPublicKeyCredentialDescriptor : NSObject <NSSecureCoding>
46 - (instancetype)initWithCredentialID:(NSData*)credentialID
47                           transports:
48                               (nullable NSArray<NSString*>*)allowedTransports;
49 @end
51 @protocol ASCPublicKeyCredentialCreationOptions
52 @property(nonatomic, copy) NSData* clientDataHash;
53 @property(nonatomic, nullable, copy) NSData* challenge;
54 @property(nonatomic, copy)
55     NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials;
56 @end
58 @protocol ASCPublicKeyCredentialAssertionOptions <NSCopying>
59 @property(nonatomic, copy) NSData* clientDataHash;
60 @end
62 @protocol ASCCredentialRequestContext
63 @property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
64     platformKeyCredentialCreationOptions;
65 @property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
66     securityKeyCredentialCreationOptions;
67 @property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
68     platformKeyCredentialAssertionOptions;
69 @property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
70     securityKeyCredentialAssertionOptions;
71 @end
73 @interface ASAuthorizationController (Secrets)
74 - (id<ASCCredentialRequestContext>)
75     _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
76                           error:(NSError**)outError;
77 @end
79 NSArray<NSString*>* TransportsByteToTransportsArray(const uint8_t aTransports)
80     API_AVAILABLE(macos(13.3)) {
81   NSMutableArray<NSString*>* transportsNS = [[NSMutableArray alloc] init];
82   if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) ==
83       MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
84     [transportsNS
85         addObject:
86             ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportUSB];
87   }
88   if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) ==
89       MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
90     [transportsNS
91         addObject:
92             ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportNFC];
93   }
94   if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) ==
95       MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
96     [transportsNS
97         addObject:
98             ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportBluetooth];
99   }
100   // TODO (bug 1859367): the platform doesn't have a definition for "internal"
101   // transport. When it does, this code should be updated to handle it.
102   return transportsNS;
105 NSArray* CredentialListsToCredentialDescriptorArray(
106     const nsTArray<nsTArray<uint8_t>>& aCredentialList,
107     const nsTArray<uint8_t>& aCredentialListTransports,
108     const Class credentialDescriptorClass) API_AVAILABLE(macos(13.3)) {
109   MOZ_ASSERT(aCredentialList.Length() == aCredentialListTransports.Length());
110   NSMutableArray* credentials = [[NSMutableArray alloc] init];
111   for (size_t i = 0; i < aCredentialList.Length(); i++) {
112     const nsTArray<uint8_t>& credentialId = aCredentialList[i];
113     const uint8_t& credentialTransports = aCredentialListTransports[i];
114     NSData* credentialIdNS = [NSData dataWithBytes:credentialId.Elements()
115                                             length:credentialId.Length()];
116     NSArray<NSString*>* credentialTransportsNS =
117         TransportsByteToTransportsArray(credentialTransports);
118     NSObject* credential = [[credentialDescriptorClass alloc]
119         initWithCredentialID:credentialIdNS
120                   transports:credentialTransportsNS];
121     [credentials addObject:credential];
122   }
123   return credentials;
126 // MacOSAuthorizationController is an ASAuthorizationController that overrides
127 // _requestContextWithRequests so that the implementation can set some options
128 // that aren't directly settable using the public API.
129 API_AVAILABLE(macos(13.3))
130 @interface MacOSAuthorizationController : ASAuthorizationController
131 @end
133 @implementation MacOSAuthorizationController {
134   nsTArray<uint8_t> mClientDataHash;
135   nsTArray<nsTArray<uint8_t>> mCredentialList;
136   nsTArray<uint8_t> mCredentialListTransports;
139 - (void)setRegistrationOptions:
140     (id<ASCPublicKeyCredentialCreationOptions>)registrationOptions {
141   registrationOptions.clientDataHash =
142       [NSData dataWithBytes:mClientDataHash.Elements()
143                      length:mClientDataHash.Length()];
144   // Unset challenge so that the implementation uses clientDataHash (the API
145   // returns an error otherwise).
146   registrationOptions.challenge = nil;
147   const Class publicKeyCredentialDescriptorClass =
148       NSClassFromString(@"ASCPublicKeyCredentialDescriptor");
149   NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials =
150       CredentialListsToCredentialDescriptorArray(
151           mCredentialList, mCredentialListTransports,
152           publicKeyCredentialDescriptorClass);
153   if ([excludedCredentials count] > 0) {
154     registrationOptions.excludedCredentials = excludedCredentials;
155   }
158 - (void)stashClientDataHash:(nsTArray<uint8_t>&&)clientDataHash
159               andCredentialList:(nsTArray<nsTArray<uint8_t>>&&)credentialList
160     andCredentialListTransports:(nsTArray<uint8_t>&&)credentialListTransports {
161   mClientDataHash = std::move(clientDataHash);
162   mCredentialList = std::move(credentialList);
163   mCredentialListTransports = std::move(credentialListTransports);
166 - (id<ASCCredentialRequestContext>)
167     _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
168                           error:(NSError**)outError {
169   id<ASCCredentialRequestContext> context =
170       [super _requestContextWithRequests:requests error:outError];
172   if (context.platformKeyCredentialCreationOptions) {
173     [self setRegistrationOptions:context.platformKeyCredentialCreationOptions];
174   }
175   if (context.securityKeyCredentialCreationOptions) {
176     [self setRegistrationOptions:context.securityKeyCredentialCreationOptions];
177   }
179   if (context.platformKeyCredentialAssertionOptions) {
180     id<ASCPublicKeyCredentialAssertionOptions> assertionOptions =
181         context.platformKeyCredentialAssertionOptions;
182     assertionOptions.clientDataHash =
183         [NSData dataWithBytes:mClientDataHash.Elements()
184                        length:mClientDataHash.Length()];
185     context.platformKeyCredentialAssertionOptions =
186         [assertionOptions copyWithZone:nil];
187   }
188   if (context.securityKeyCredentialAssertionOptions) {
189     id<ASCPublicKeyCredentialAssertionOptions> assertionOptions =
190         context.securityKeyCredentialAssertionOptions;
191     assertionOptions.clientDataHash =
192         [NSData dataWithBytes:mClientDataHash.Elements()
193                        length:mClientDataHash.Length()];
194     context.securityKeyCredentialAssertionOptions =
195         [assertionOptions copyWithZone:nil];
196   }
198   return context;
200 @end
202 // MacOSAuthenticatorRequestDelegate is an ASAuthorizationControllerDelegate,
203 // which can be set as an ASAuthorizationController's delegate to be called
204 // back when a request for a platform authenticator attestation or assertion
205 // response completes either with an attestation or assertion
206 // (didCompleteWithAuthorization) or with an error (didCompleteWithError).
207 API_AVAILABLE(macos(13.3))
208 @interface MacOSAuthenticatorRequestDelegate
209     : NSObject <ASAuthorizationControllerDelegate>
210 - (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback;
211 @end
213 // MacOSAuthenticatorPresentationContextProvider is an
214 // ASAuthorizationControllerPresentationContextProviding, which can be set as
215 // an ASAuthorizationController's presentationContextProvider, and provides a
216 // presentation anchor for the ASAuthorizationController. Basically, this
217 // provides the API a handle to the window that made the request.
218 API_AVAILABLE(macos(13.3))
219 @interface MacOSAuthenticatorPresentationContextProvider
220     : NSObject <ASAuthorizationControllerPresentationContextProviding>
221 @property(nonatomic, strong) NSWindow* window;
222 @end
224 namespace mozilla::dom {
226 #pragma clang diagnostic push
227 #pragma clang diagnostic ignored "-Wnullability-completeness"
228 class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService final
229     : public nsIWebAuthnService {
230  public:
231   MacOSWebAuthnService()
232       : mTransactionState(Nothing(),
233                           "MacOSWebAuthnService::mTransactionState") {}
235   NS_DECL_THREADSAFE_ISUPPORTS
236   NS_DECL_NSIWEBAUTHNSERVICE
238   void FinishMakeCredential(const nsTArray<uint8_t>& aRawAttestationObject,
239                             const nsTArray<uint8_t>& aCredentialId,
240                             const nsTArray<nsString>& aTransports,
241                             const Maybe<nsString>& aAuthenticatorAttachment);
243   void FinishGetAssertion(const nsTArray<uint8_t>& aCredentialId,
244                           const nsTArray<uint8_t>& aSignature,
245                           const nsTArray<uint8_t>& aAuthenticatorData,
246                           const nsTArray<uint8_t>& aUserHandle,
247                           const Maybe<nsString>& aAuthenticatorAttachment);
248   void ReleasePlatformResources();
249   void AbortTransaction(nsresult aError);
251  private:
252   ~MacOSWebAuthnService() = default;
254   void PerformRequests(NSArray<ASAuthorizationRequest*>* aRequests,
255                        nsTArray<uint8_t>&& aClientDataHash,
256                        nsTArray<nsTArray<uint8_t>>&& aCredentialList,
257                        nsTArray<uint8_t>&& aCredentialListTransports,
258                        uint64_t aBrowsingContextId);
260   struct TransactionState {
261     uint64_t transactionId;
262     uint64_t browsingContextId;
263     Maybe<RefPtr<nsIWebAuthnSignArgs>> pendingSignArgs;
264     Maybe<RefPtr<nsIWebAuthnSignPromise>> pendingSignPromise;
265     Maybe<nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>> autoFillEntries;
266   };
268   using TransactionStateMutex = DataMutex<Maybe<TransactionState>>;
269   void DoGetAssertion(Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
270                       const TransactionStateMutex::AutoLock& aGuard);
272   TransactionStateMutex mTransactionState;
274   // Main thread only:
275   ASAuthorizationWebBrowserPublicKeyCredentialManager* mCredentialManager = nil;
276   nsCOMPtr<nsIWebAuthnRegisterPromise> mRegisterPromise;
277   nsCOMPtr<nsIWebAuthnSignPromise> mSignPromise;
278   MacOSAuthorizationController* mAuthorizationController = nil;
279   MacOSAuthenticatorRequestDelegate* mRequestDelegate = nil;
280   MacOSAuthenticatorPresentationContextProvider* mPresentationContextProvider =
281       nil;
283 #pragma clang diagnostic pop
285 }  // namespace mozilla::dom
287 nsTArray<uint8_t> NSDataToArray(NSData* data) {
288   nsTArray<uint8_t> array(reinterpret_cast<const uint8_t*>(data.bytes),
289                           data.length);
290   return array;
293 @implementation MacOSAuthenticatorRequestDelegate {
294   RefPtr<mozilla::dom::MacOSWebAuthnService> mCallback;
297 - (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback {
298   mCallback = callback;
301 - (void)authorizationController:(ASAuthorizationController*)controller
302     didCompleteWithAuthorization:(ASAuthorization*)authorization {
303   if ([authorization.credential
304           conformsToProtocol:
305               @protocol(ASAuthorizationPublicKeyCredentialRegistration)]) {
306     MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
307             ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
308              "got registration"));
309     id<ASAuthorizationPublicKeyCredentialRegistration> credential =
310         (id<ASAuthorizationPublicKeyCredentialRegistration>)
311             authorization.credential;
312     nsTArray<uint8_t> rawAttestationObject(
313         NSDataToArray(credential.rawAttestationObject));
314     nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
315     nsTArray<nsString> transports;
316     mozilla::Maybe<nsString> authenticatorAttachment;
317     if ([credential isKindOfClass:
318                         [ASAuthorizationPlatformPublicKeyCredentialRegistration
319                             class]]) {
320       transports.AppendElement(u"internal"_ns);
321 #if defined(MAC_OS_VERSION_13_5) && \
322     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
323       if (__builtin_available(macos 13.5, *)) {
324         ASAuthorizationPlatformPublicKeyCredentialRegistration*
325             platformCredential =
326                 (ASAuthorizationPlatformPublicKeyCredentialRegistration*)
327                     credential;
328         switch (platformCredential.attachment) {
329           case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
330             authenticatorAttachment.emplace(u"cross-platform"_ns);
331             break;
332           case ASAuthorizationPublicKeyCredentialAttachmentPlatform:
333             authenticatorAttachment.emplace(u"platform"_ns);
334             break;
335           default:
336             break;
337         }
338       }
339 #endif
340     } else {
341       // The platform didn't tell us what transport was used, but we know it
342       // wasn't the internal transport. The transport response is not signed by
343       // the authenticator. It represents the "transports that the authenticator
344       // is believed to support, or an empty sequence if the information is
345       // unavailable". We believe macOS supports usb, so we return usb.
346       transports.AppendElement(u"usb"_ns);
347       authenticatorAttachment.emplace(u"cross-platform"_ns);
348     }
349     mCallback->FinishMakeCredential(rawAttestationObject, credentialId,
350                                     transports, authenticatorAttachment);
351   } else if ([authorization.credential
352                  conformsToProtocol:
353                      @protocol(ASAuthorizationPublicKeyCredentialAssertion)]) {
354     MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
355             ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
356              "got assertion"));
357     id<ASAuthorizationPublicKeyCredentialAssertion> credential =
358         (id<ASAuthorizationPublicKeyCredentialAssertion>)
359             authorization.credential;
360     nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
361     nsTArray<uint8_t> signature(NSDataToArray(credential.signature));
362     nsTArray<uint8_t> rawAuthenticatorData(
363         NSDataToArray(credential.rawAuthenticatorData));
364     nsTArray<uint8_t> userHandle(NSDataToArray(credential.userID));
365     mozilla::Maybe<nsString> authenticatorAttachment;
366     if ([credential
367             isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion
368                               class]]) {
369 #if defined(MAC_OS_VERSION_13_5) && \
370     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
371       if (__builtin_available(macos 13.5, *)) {
372         ASAuthorizationPlatformPublicKeyCredentialAssertion*
373             platformCredential =
374                 (ASAuthorizationPlatformPublicKeyCredentialAssertion*)
375                     credential;
376         switch (platformCredential.attachment) {
377           case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
378             authenticatorAttachment.emplace(u"cross-platform"_ns);
379             break;
380           case ASAuthorizationPublicKeyCredentialAttachmentPlatform:
381             authenticatorAttachment.emplace(u"platform"_ns);
382             break;
383           default:
384             break;
385         }
386       }
387 #endif
388     } else {
389       authenticatorAttachment.emplace(u"cross-platform"_ns);
390     }
391     mCallback->FinishGetAssertion(credentialId, signature, rawAuthenticatorData,
392                                   userHandle, authenticatorAttachment);
393   } else {
394     MOZ_LOG(
395         gMacOSWebAuthnServiceLog, mozilla::LogLevel::Error,
396         ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
397          "authorization.credential is neither registration nor assertion!"));
398     MOZ_ASSERT_UNREACHABLE(
399         "should have ASAuthorizationPublicKeyCredentialRegistration or "
400         "ASAuthorizationPublicKeyCredentialAssertion");
401   }
402   mCallback->ReleasePlatformResources();
403   mCallback = nullptr;
406 - (void)authorizationController:(ASAuthorizationController*)controller
407            didCompleteWithError:(NSError*)error {
408   nsAutoString errorDescription;
409   nsCocoaUtils::GetStringForNSString(error.localizedDescription,
410                                      errorDescription);
411   nsAutoString errorDomain;
412   nsCocoaUtils::GetStringForNSString(error.domain, errorDomain);
413   MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Warning,
414           ("MacOSAuthenticatorRequestDelegate::didCompleteWithError: domain "
415            "'%s' code %ld (%s)",
416            NS_ConvertUTF16toUTF8(errorDomain).get(), error.code,
417            NS_ConvertUTF16toUTF8(errorDescription).get()));
418   nsresult rv = NS_ERROR_DOM_NOT_ALLOWED_ERR;
419   // For some reason, the error for "the credential used in a registration was
420   // on the exclude list" is in the "WKErrorDomain" domain with code 8, which
421   // is presumably WKErrorDuplicateCredential.
422   const NSInteger WKErrorDuplicateCredential = 8;
423   if (errorDomain.EqualsLiteral("WKErrorDomain") &&
424       error.code == WKErrorDuplicateCredential) {
425     rv = NS_ERROR_DOM_INVALID_STATE_ERR;
426   } else if (error.domain == ASAuthorizationErrorDomain) {
427     switch (error.code) {
428       case ASAuthorizationErrorCanceled:
429         rv = NS_ERROR_DOM_ABORT_ERR;
430         break;
431       case ASAuthorizationErrorFailed:
432         // The message is right, but it's not about indexeddb.
433         // See https://webidl.spec.whatwg.org/#constrainterror
434         rv = NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
435         break;
436       case ASAuthorizationErrorUnknown:
437         rv = NS_ERROR_DOM_UNKNOWN_ERR;
438         break;
439       default:
440         // rv already has a default value
441         break;
442     }
443   }
444   mCallback->AbortTransaction(rv);
445   mCallback = nullptr;
447 @end
449 @implementation MacOSAuthenticatorPresentationContextProvider
450 @synthesize window = window;
452 - (ASPresentationAnchor)presentationAnchorForAuthorizationController:
453     (ASAuthorizationController*)controller {
454   return window;
456 @end
458 namespace mozilla::dom {
460 #pragma clang diagnostic push
461 #pragma clang diagnostic ignored "-Wnullability-completeness"
462 // Given a browsingContextId, attempts to determine the NSWindow associated
463 // with that browser.
464 nsresult BrowsingContextIdToNSWindow(uint64_t browsingContextId,
465                                      NSWindow** window) {
466   *window = nullptr;
467   RefPtr<BrowsingContext> browsingContext(
468       BrowsingContext::Get(browsingContextId));
469   if (!browsingContext) {
470     return NS_ERROR_NOT_AVAILABLE;
471   }
472   CanonicalBrowsingContext* canonicalBrowsingContext =
473       browsingContext->Canonical();
474   if (!canonicalBrowsingContext) {
475     return NS_ERROR_NOT_AVAILABLE;
476   }
477   nsCOMPtr<nsIWidget> widget(
478       canonicalBrowsingContext->GetParentProcessWidgetContaining());
479   if (!widget) {
480     return NS_ERROR_NOT_AVAILABLE;
481   }
482   *window = static_cast<NSWindow*>(widget->GetNativeData(NS_NATIVE_WINDOW));
483   return NS_OK;
485 #pragma clang diagnostic pop
487 already_AddRefed<nsIWebAuthnService> NewMacOSWebAuthnServiceIfAvailable() {
488   MOZ_ASSERT(XRE_IsParentProcess());
489   if (!StaticPrefs::security_webauthn_enable_macos_passkeys()) {
490     MOZ_LOG(
491         gMacOSWebAuthnServiceLog, LogLevel::Debug,
492         ("macOS platform support for webauthn (passkeys) disabled by pref"));
493     return nullptr;
494   }
495   // This code checks for the entitlement
496   // 'com.apple.developer.web-browser.public-key-credential', which must be
497   // true to be able to use the platform APIs.
498   CFTypeRefPtr<SecTaskRef> entitlementTask(
499       CFTypeRefPtr<SecTaskRef>::WrapUnderCreateRule(
500           SecTaskCreateFromSelf(nullptr)));
501   CFTypeRefPtr<CFBooleanRef> entitlementValue(
502       CFTypeRefPtr<CFBooleanRef>::WrapUnderCreateRule(
503           reinterpret_cast<CFBooleanRef>(SecTaskCopyValueForEntitlement(
504               entitlementTask.get(),
505               CFSTR("com.apple.developer.web-browser.public-key-credential"),
506               nullptr))));
507   if (!entitlementValue || !CFBooleanGetValue(entitlementValue.get())) {
508     MOZ_LOG(
509         gMacOSWebAuthnServiceLog, LogLevel::Warning,
510         ("entitlement com.apple.developer.web-browser.public-key-credential "
511          "not present: platform passkey APIs will not be available"));
512     return nullptr;
513   }
514   nsCOMPtr<nsIWebAuthnService> service(new MacOSWebAuthnService());
515   return service.forget();
518 void MacOSWebAuthnService::AbortTransaction(nsresult aError) {
519   MOZ_ASSERT(NS_IsMainThread());
520   if (mRegisterPromise) {
521     Unused << mRegisterPromise->Reject(aError);
522     mRegisterPromise = nullptr;
523   }
524   if (mSignPromise) {
525     Unused << mSignPromise->Reject(aError);
526     mSignPromise = nullptr;
527   }
528   ReleasePlatformResources();
531 #pragma clang diagnostic push
532 #pragma clang diagnostic ignored "-Wnullability-completeness"
533 NS_IMPL_ISUPPORTS(MacOSWebAuthnService, nsIWebAuthnService)
534 #pragma clang diagnostic pop
536 NS_IMETHODIMP
537 MacOSWebAuthnService::MakeCredential(uint64_t aTransactionId,
538                                      uint64_t aBrowsingContextId,
539                                      nsIWebAuthnRegisterArgs* aArgs,
540                                      nsIWebAuthnRegisterPromise* aPromise) {
541   Reset();
542   auto guard = mTransactionState.Lock();
543   *guard = Some(TransactionState{
544       aTransactionId,
545       aBrowsingContextId,
546       Nothing(),
547       Nothing(),
548       Nothing(),
549   });
550   NS_DispatchToMainThread(NS_NewRunnableFunction(
551       "MacOSWebAuthnService::MakeCredential",
552       [self = RefPtr{this}, browsingContextId(aBrowsingContextId),
553        aArgs = nsCOMPtr{aArgs}, aPromise = nsCOMPtr{aPromise}]() {
554         // Bug 1884574 - The Reset() call above should have cancelled any
555         // transactions that were dispatched to the platform, the platform
556         // should have called didCompleteWithError, and didCompleteWithError
557         // should have rejected the pending promise. However, in some scenarios,
558         // the platform fails to call the callback, and this leads to a
559         // diagnostic assertion failure when we drop `mRegisterPromise`. Avoid
560         // this by aborting the transaction here.
561         if (self->mRegisterPromise) {
562           MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
563                   ("MacOSAuthenticatorRequestDelegate::MakeCredential: "
564                    "platform failed to call callback"));
565           self->AbortTransaction(NS_ERROR_DOM_ABORT_ERR);
566         }
567         self->mRegisterPromise = aPromise;
569         nsAutoString rpId;
570         Unused << aArgs->GetRpId(rpId);
571         NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId);
573         nsTArray<uint8_t> challenge;
574         Unused << aArgs->GetChallenge(challenge);
575         NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
576                                              length:challenge.Length()];
578         nsTArray<uint8_t> userId;
579         Unused << aArgs->GetUserId(userId);
580         NSData* userIdNS = [NSData dataWithBytes:userId.Elements()
581                                           length:userId.Length()];
583         nsAutoString userName;
584         Unused << aArgs->GetUserName(userName);
585         NSString* userNameNS = nsCocoaUtils::ToNSString(userName);
587         nsAutoString userDisplayName;
588         Unused << aArgs->GetUserName(userDisplayName);
589         NSString* userDisplayNameNS = nsCocoaUtils::ToNSString(userDisplayName);
591         nsTArray<int32_t> coseAlgs;
592         Unused << aArgs->GetCoseAlgs(coseAlgs);
593         NSMutableArray* credentialParameters = [[NSMutableArray alloc] init];
594         for (const auto& coseAlg : coseAlgs) {
595           ASAuthorizationPublicKeyCredentialParameters* credentialParameter =
596               [[ASAuthorizationPublicKeyCredentialParameters alloc]
597                   initWithAlgorithm:coseAlg];
598           [credentialParameters addObject:credentialParameter];
599         }
601         nsTArray<nsTArray<uint8_t>> excludeList;
602         Unused << aArgs->GetExcludeList(excludeList);
603         nsTArray<uint8_t> excludeListTransports;
604         Unused << aArgs->GetExcludeListTransports(excludeListTransports);
605         if (excludeList.Length() != excludeListTransports.Length()) {
606           self->mRegisterPromise->Reject(NS_ERROR_INVALID_ARG);
607           return;
608         }
610         Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
611             userVerificationPreference = Nothing();
612         nsAutoString userVerification;
613         Unused << aArgs->GetUserVerification(userVerification);
614         // This mapping needs to be reviewed if values are added to the
615         // UserVerificationRequirement enum.
616         static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
617         if (userVerification.EqualsLiteral(
618                 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
619           userVerificationPreference.emplace(
620               ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired);
621         } else if (userVerification.EqualsLiteral(
622                        MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
623           userVerificationPreference.emplace(
624               ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred);
625         } else if (
626             userVerification.EqualsLiteral(
627                 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) {
628           userVerificationPreference.emplace(
629               ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged);
630         }
632         // The API doesn't support attestation for platform passkeys, so this is
633         // only used for security keys.
634         ASAuthorizationPublicKeyCredentialAttestationKind attestationPreference;
635         nsAutoString mozAttestationPreference;
636         Unused << aArgs->GetAttestationConveyancePreference(
637             mozAttestationPreference);
638         if (mozAttestationPreference.EqualsLiteral(
639                 MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT)) {
640           attestationPreference =
641               ASAuthorizationPublicKeyCredentialAttestationKindIndirect;
642         } else if (mozAttestationPreference.EqualsLiteral(
643                        MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT)) {
644           attestationPreference =
645               ASAuthorizationPublicKeyCredentialAttestationKindDirect;
646         } else if (
647             mozAttestationPreference.EqualsLiteral(
648                 MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE)) {
649           attestationPreference =
650               ASAuthorizationPublicKeyCredentialAttestationKindEnterprise;
651         } else {
652           attestationPreference =
653               ASAuthorizationPublicKeyCredentialAttestationKindNone;
654         }
656         ASAuthorizationPublicKeyCredentialResidentKeyPreference
657             residentKeyPreference;
658         nsAutoString mozResidentKey;
659         Unused << aArgs->GetResidentKey(mozResidentKey);
660         // This mapping needs to be reviewed if values are added to the
661         // ResidentKeyRequirement enum.
662         static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
663         if (mozResidentKey.EqualsLiteral(
664                 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED)) {
665           residentKeyPreference =
666               ASAuthorizationPublicKeyCredentialResidentKeyPreferenceRequired;
667         } else if (mozResidentKey.EqualsLiteral(
668                        MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED)) {
669           residentKeyPreference =
670               ASAuthorizationPublicKeyCredentialResidentKeyPreferencePreferred;
671         } else {
672           MOZ_ASSERT(mozResidentKey.EqualsLiteral(
673               MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED));
674           residentKeyPreference =
675               ASAuthorizationPublicKeyCredentialResidentKeyPreferenceDiscouraged;
676         }
678         // Initialize the platform provider with the rpId.
679         ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider =
680             [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
681                 initWithRelyingPartyIdentifier:rpIdNS];
682         // Make a credential registration request with the challenge, userName,
683         // and userId.
684         ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest*
685             platformRegistrationRequest = [platformProvider
686                 createCredentialRegistrationRequestWithChallenge:challengeNS
687                                                             name:userNameNS
688                                                           userID:userIdNS];
689         [platformProvider release];
691         // The API doesn't support attestation for platform passkeys
692         platformRegistrationRequest.attestationPreference =
693             ASAuthorizationPublicKeyCredentialAttestationKindNone;
694         if (userVerificationPreference.isSome()) {
695           platformRegistrationRequest.userVerificationPreference =
696               *userVerificationPreference;
697         }
699         // Initialize the cross-platform provider with the rpId.
700         ASAuthorizationSecurityKeyPublicKeyCredentialProvider*
701             crossPlatformProvider =
702                 [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc]
703                     initWithRelyingPartyIdentifier:rpIdNS];
704         // Make a credential registration request with the challenge,
705         // userDisplayName, userName, and userId.
706         ASAuthorizationSecurityKeyPublicKeyCredentialRegistrationRequest*
707             crossPlatformRegistrationRequest = [crossPlatformProvider
708                 createCredentialRegistrationRequestWithChallenge:challengeNS
709                                                      displayName:
710                                                          userDisplayNameNS
711                                                             name:userNameNS
712                                                           userID:userIdNS];
713         [crossPlatformProvider release];
714         crossPlatformRegistrationRequest.attestationPreference =
715             attestationPreference;
716         crossPlatformRegistrationRequest.credentialParameters =
717             credentialParameters;
718         crossPlatformRegistrationRequest.residentKeyPreference =
719             residentKeyPreference;
720         if (userVerificationPreference.isSome()) {
721           crossPlatformRegistrationRequest.userVerificationPreference =
722               *userVerificationPreference;
723         }
724         nsTArray<uint8_t> clientDataHash;
725         nsresult rv = aArgs->GetClientDataHash(clientDataHash);
726         if (NS_FAILED(rv)) {
727           self->mRegisterPromise->Reject(rv);
728           return;
729         }
730         nsAutoString authenticatorAttachment;
731         rv = aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
732         if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
733           self->mRegisterPromise->Reject(rv);
734           return;
735         }
736         NSMutableArray* requests = [[NSMutableArray alloc] init];
737         if (authenticatorAttachment.EqualsLiteral(
738                 MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
739           [requests addObject:platformRegistrationRequest];
740         } else if (authenticatorAttachment.EqualsLiteral(
741                        MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
742           [requests addObject:crossPlatformRegistrationRequest];
743         } else {
744           // Regarding the value of authenticator attachment, according to the
745           // specification, "client platforms MUST ignore unknown values,
746           // treating an unknown value as if the member does not exist".
747           [requests addObject:platformRegistrationRequest];
748           [requests addObject:crossPlatformRegistrationRequest];
749         }
750         self->PerformRequests(
751             requests, std::move(clientDataHash), std::move(excludeList),
752             std::move(excludeListTransports), browsingContextId);
753       }));
754   return NS_OK;
757 void MacOSWebAuthnService::PerformRequests(
758     NSArray<ASAuthorizationRequest*>* aRequests,
759     nsTArray<uint8_t>&& aClientDataHash,
760     nsTArray<nsTArray<uint8_t>>&& aCredentialList,
761     nsTArray<uint8_t>&& aCredentialListTransports,
762     uint64_t aBrowsingContextId) {
763   MOZ_ASSERT(NS_IsMainThread());
764   // Create a MacOSAuthorizationController and initialize it with the requests.
765   MOZ_ASSERT(!mAuthorizationController);
766   mAuthorizationController = [[MacOSAuthorizationController alloc]
767       initWithAuthorizationRequests:aRequests];
768   [mAuthorizationController
769               stashClientDataHash:std::move(aClientDataHash)
770                 andCredentialList:std::move(aCredentialList)
771       andCredentialListTransports:std::move(aCredentialListTransports)];
773   // Set up the delegate to run when the operation completes.
774   MOZ_ASSERT(!mRequestDelegate);
775   mRequestDelegate = [[MacOSAuthenticatorRequestDelegate alloc] init];
776   [mRequestDelegate setCallback:this];
777   mAuthorizationController.delegate = mRequestDelegate;
779   // Create a presentation context provider so the API knows which window
780   // made the request.
781   NSWindow* window = nullptr;
782   nsresult rv = BrowsingContextIdToNSWindow(aBrowsingContextId, &window);
783   if (NS_FAILED(rv)) {
784     AbortTransaction(NS_ERROR_DOM_INVALID_STATE_ERR);
785     return;
786   }
787   MOZ_ASSERT(!mPresentationContextProvider);
788   mPresentationContextProvider =
789       [[MacOSAuthenticatorPresentationContextProvider alloc] init];
790   mPresentationContextProvider.window = window;
791   mAuthorizationController.presentationContextProvider =
792       mPresentationContextProvider;
794   // Finally, perform the request.
795   [mAuthorizationController performRequests];
798 void MacOSWebAuthnService::FinishMakeCredential(
799     const nsTArray<uint8_t>& aRawAttestationObject,
800     const nsTArray<uint8_t>& aCredentialId,
801     const nsTArray<nsString>& aTransports,
802     const Maybe<nsString>& aAuthenticatorAttachment) {
803   MOZ_ASSERT(NS_IsMainThread());
804   if (!mRegisterPromise) {
805     return;
806   }
808   RefPtr<WebAuthnRegisterResult> result(new WebAuthnRegisterResult(
809       aRawAttestationObject, Nothing(), aCredentialId, aTransports,
810       aAuthenticatorAttachment));
811   Unused << mRegisterPromise->Resolve(result);
812   mRegisterPromise = nullptr;
815 NS_IMETHODIMP
816 MacOSWebAuthnService::GetAssertion(uint64_t aTransactionId,
817                                    uint64_t aBrowsingContextId,
818                                    nsIWebAuthnSignArgs* aArgs,
819                                    nsIWebAuthnSignPromise* aPromise) {
820   Reset();
822   auto guard = mTransactionState.Lock();
823   *guard = Some(TransactionState{
824       aTransactionId,
825       aBrowsingContextId,
826       Some(RefPtr{aArgs}),
827       Some(RefPtr{aPromise}),
828       Nothing(),
829   });
831   bool conditionallyMediated;
832   Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
833   if (!conditionallyMediated) {
834     DoGetAssertion(Nothing(), guard);
835     return NS_OK;
836   }
838   // Using conditional mediation, so dispatch a task to collect any available
839   // passkeys.
840   NS_DispatchToMainThread(NS_NewRunnableFunction(
841       "platformCredentialsForRelyingParty",
842       [self = RefPtr{this}, aTransactionId, aArgs = nsCOMPtr{aArgs}]() {
843         // This handler is called when platformCredentialsForRelyingParty
844         // completes.
845         auto credentialsCompletionHandler = ^(
846             NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*
847                 credentials) {
848           nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>> autoFillEntries;
849           for (NSUInteger i = 0; i < credentials.count; i++) {
850             const auto& credential = credentials[i];
851             nsAutoString userName;
852             nsCocoaUtils::GetStringForNSString(credential.name, userName);
853             nsAutoString rpId;
854             nsCocoaUtils::GetStringForNSString(credential.relyingParty, rpId);
855             autoFillEntries.AppendElement(new WebAuthnAutoFillEntry(
856                 nsIWebAuthnAutoFillEntry::PROVIDER_PLATFORM_MACOS, userName,
857                 rpId, NSDataToArray(credential.credentialID)));
858           }
859           auto guard = self->mTransactionState.Lock();
860           if (guard->isSome() && guard->ref().transactionId == aTransactionId) {
861             guard->ref().autoFillEntries.emplace(std::move(autoFillEntries));
862           }
863         };
864         // This handler is called when
865         // requestAuthorizationForPublicKeyCredentials completes.
866         auto authorizationHandler = ^(
867             ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState
868                 authorizationState) {
869           // If authorized, list any available passkeys.
870           if (authorizationState ==
871               ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationStateAuthorized) {
872             nsAutoString rpId;
873             Unused << aArgs->GetRpId(rpId);
874             [self->mCredentialManager
875                 platformCredentialsForRelyingParty:nsCocoaUtils::ToNSString(
876                                                        rpId)
877                                  completionHandler:
878                                      credentialsCompletionHandler];
879           }
880         };
881         if (!self->mCredentialManager) {
882           self->mCredentialManager =
883               [[ASAuthorizationWebBrowserPublicKeyCredentialManager alloc]
884                   init];
885         }
886         // Request authorization to examine any available passkeys. This will
887         // cause a permission prompt to appear once.
888         [self->mCredentialManager
889             requestAuthorizationForPublicKeyCredentials:authorizationHandler];
890       }));
892   return NS_OK;
895 void MacOSWebAuthnService::DoGetAssertion(
896     Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
897     const TransactionStateMutex::AutoLock& aGuard) {
898   if (aGuard->isNothing() || aGuard->ref().pendingSignArgs.isNothing() ||
899       aGuard->ref().pendingSignPromise.isNothing()) {
900     return;
901   }
903   // Take the pending Args and Promise to prevent repeated calls to
904   // DoGetAssertion for this transaction.
905   RefPtr<nsIWebAuthnSignArgs> aArgs = aGuard->ref().pendingSignArgs.extract();
906   RefPtr<nsIWebAuthnSignPromise> aPromise =
907       aGuard->ref().pendingSignPromise.extract();
908   uint64_t aBrowsingContextId = aGuard->ref().browsingContextId;
910   NS_DispatchToMainThread(NS_NewRunnableFunction(
911       "MacOSWebAuthnService::MakeCredential",
912       [self = RefPtr{this}, browsingContextId(aBrowsingContextId), aArgs,
913        aPromise,
914        aSelectedCredentialId = std::move(aSelectedCredentialId)]() mutable {
915         // Bug 1884574 - This AbortTransaction call is necessary.
916         // See comment in MacOSWebAuthnService::MakeCredential.
917         if (self->mSignPromise) {
918           MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
919                   ("MacOSAuthenticatorRequestDelegate::DoGetAssertion: "
920                    "platform failed to call callback"));
921           self->AbortTransaction(NS_ERROR_DOM_ABORT_ERR);
922         }
923         self->mSignPromise = aPromise;
925         nsAutoString rpId;
926         Unused << aArgs->GetRpId(rpId);
927         NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId);
929         nsTArray<uint8_t> challenge;
930         Unused << aArgs->GetChallenge(challenge);
931         NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
932                                              length:challenge.Length()];
934         nsTArray<nsTArray<uint8_t>> allowList;
935         nsTArray<uint8_t> allowListTransports;
936         if (aSelectedCredentialId.isSome()) {
937           allowList.AppendElement(aSelectedCredentialId.extract());
938           allowListTransports.AppendElement(
939               MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL);
940         } else {
941           Unused << aArgs->GetAllowList(allowList);
942           Unused << aArgs->GetAllowListTransports(allowListTransports);
943         }
944         NSMutableArray* platformAllowedCredentials =
945             [[NSMutableArray alloc] init];
946         for (const auto& allowedCredentialId : allowList) {
947           NSData* allowedCredentialIdNS =
948               [NSData dataWithBytes:allowedCredentialId.Elements()
949                              length:allowedCredentialId.Length()];
950           ASAuthorizationPlatformPublicKeyCredentialDescriptor*
951               allowedCredential =
952                   [[ASAuthorizationPlatformPublicKeyCredentialDescriptor alloc]
953                       initWithCredentialID:allowedCredentialIdNS];
954           [platformAllowedCredentials addObject:allowedCredential];
955         }
956         const Class securityKeyPublicKeyCredentialDescriptorClass =
957             NSClassFromString(
958                 @"ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor");
959         NSArray<ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor*>*
960             crossPlatformAllowedCredentials =
961                 CredentialListsToCredentialDescriptorArray(
962                     allowList, allowListTransports,
963                     securityKeyPublicKeyCredentialDescriptorClass);
965         Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
966             userVerificationPreference = Nothing();
967         nsAutoString userVerification;
968         Unused << aArgs->GetUserVerification(userVerification);
969         // This mapping needs to be reviewed if values are added to the
970         // UserVerificationRequirement enum.
971         static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
972         if (userVerification.EqualsLiteral(
973                 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
974           userVerificationPreference.emplace(
975               ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired);
976         } else if (userVerification.EqualsLiteral(
977                        MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
978           userVerificationPreference.emplace(
979               ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred);
980         } else if (
981             userVerification.EqualsLiteral(
982                 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) {
983           userVerificationPreference.emplace(
984               ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged);
985         }
987         // Initialize the platform provider with the rpId.
988         ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider =
989             [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
990                 initWithRelyingPartyIdentifier:rpIdNS];
991         // Make a credential assertion request with the challenge.
992         ASAuthorizationPlatformPublicKeyCredentialAssertionRequest*
993             platformAssertionRequest = [platformProvider
994                 createCredentialAssertionRequestWithChallenge:challengeNS];
995         [platformProvider release];
996         platformAssertionRequest.allowedCredentials =
997             platformAllowedCredentials;
998         if (userVerificationPreference.isSome()) {
999           platformAssertionRequest.userVerificationPreference =
1000               *userVerificationPreference;
1001         }
1003         // Initialize the cross-platform provider with the rpId.
1004         ASAuthorizationSecurityKeyPublicKeyCredentialProvider*
1005             crossPlatformProvider =
1006                 [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc]
1007                     initWithRelyingPartyIdentifier:rpIdNS];
1008         // Make a credential assertion request with the challenge.
1009         ASAuthorizationSecurityKeyPublicKeyCredentialAssertionRequest*
1010             crossPlatformAssertionRequest = [crossPlatformProvider
1011                 createCredentialAssertionRequestWithChallenge:challengeNS];
1012         [crossPlatformProvider release];
1013         crossPlatformAssertionRequest.allowedCredentials =
1014             crossPlatformAllowedCredentials;
1015         if (userVerificationPreference.isSome()) {
1016           crossPlatformAssertionRequest.userVerificationPreference =
1017               *userVerificationPreference;
1018         }
1019         nsTArray<uint8_t> clientDataHash;
1020         nsresult rv = aArgs->GetClientDataHash(clientDataHash);
1021         if (NS_FAILED(rv)) {
1022           self->mSignPromise->Reject(rv);
1023           return;
1024         }
1025         nsTArray<nsTArray<uint8_t>> unusedCredentialIds;
1026         nsTArray<uint8_t> unusedTransports;
1027         // allowList and allowListTransports won't actually get used.
1028         self->PerformRequests(
1029             @[ platformAssertionRequest, crossPlatformAssertionRequest ],
1030             std::move(clientDataHash), std::move(allowList),
1031             std::move(allowListTransports), browsingContextId);
1032       }));
1035 void MacOSWebAuthnService::FinishGetAssertion(
1036     const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aSignature,
1037     const nsTArray<uint8_t>& aAuthenticatorData,
1038     const nsTArray<uint8_t>& aUserHandle,
1039     const Maybe<nsString>& aAuthenticatorAttachment) {
1040   MOZ_ASSERT(NS_IsMainThread());
1041   if (!mSignPromise) {
1042     return;
1043   }
1045   RefPtr<WebAuthnSignResult> result(new WebAuthnSignResult(
1046       aAuthenticatorData, Nothing(), aCredentialId, aSignature, aUserHandle,
1047       aAuthenticatorAttachment));
1048   Unused << mSignPromise->Resolve(result);
1049   mSignPromise = nullptr;
1052 void MacOSWebAuthnService::ReleasePlatformResources() {
1053   MOZ_ASSERT(NS_IsMainThread());
1054   [mCredentialManager release];
1055   mCredentialManager = nil;
1056   [mAuthorizationController release];
1057   mAuthorizationController = nil;
1058   [mRequestDelegate release];
1059   mRequestDelegate = nil;
1060   [mPresentationContextProvider release];
1061   mPresentationContextProvider = nil;
1064 NS_IMETHODIMP
1065 MacOSWebAuthnService::Reset() {
1066   auto guard = mTransactionState.Lock();
1067   if (guard->isSome()) {
1068     if (guard->ref().pendingSignPromise.isSome()) {
1069       // This request was never dispatched to the platform API, so
1070       // we need to reject the promise ourselves.
1071       guard->ref().pendingSignPromise.ref()->Reject(
1072           NS_ERROR_DOM_NOT_ALLOWED_ERR);
1073     }
1074     guard->reset();
1075   }
1076   NS_DispatchToMainThread(NS_NewRunnableFunction(
1077       "MacOSWebAuthnService::Cancel", [self = RefPtr{this}] {
1078         // cancel results in the delegate's didCompleteWithError method being
1079         // called, which will release all platform resources.
1080         [self->mAuthorizationController cancel];
1081       }));
1082   return NS_OK;
1085 NS_IMETHODIMP
1086 MacOSWebAuthnService::GetIsUVPAA(bool* aAvailable) {
1087   *aAvailable = true;
1088   return NS_OK;
1091 NS_IMETHODIMP
1092 MacOSWebAuthnService::Cancel(uint64_t aTransactionId) {
1093   return NS_ERROR_NOT_IMPLEMENTED;
1096 NS_IMETHODIMP
1097 MacOSWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
1098                                                const nsAString& aOrigin,
1099                                                uint64_t* aRv) {
1100   auto guard = mTransactionState.Lock();
1101   if (guard->isNothing() ||
1102       guard->ref().browsingContextId != aBrowsingContextId ||
1103       guard->ref().pendingSignArgs.isNothing()) {
1104     *aRv = 0;
1105     return NS_OK;
1106   }
1108   nsString origin;
1109   Unused << guard->ref().pendingSignArgs.ref()->GetOrigin(origin);
1110   if (origin != aOrigin) {
1111     *aRv = 0;
1112     return NS_OK;
1113   }
1115   *aRv = guard->ref().transactionId;
1116   return NS_OK;
1119 NS_IMETHODIMP
1120 MacOSWebAuthnService::GetAutoFillEntries(
1121     uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
1122   auto guard = mTransactionState.Lock();
1123   if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
1124       guard->ref().pendingSignArgs.isNothing() ||
1125       guard->ref().autoFillEntries.isNothing()) {
1126     return NS_ERROR_NOT_AVAILABLE;
1127   }
1128   aRv.Assign(guard->ref().autoFillEntries.ref());
1129   return NS_OK;
1132 NS_IMETHODIMP
1133 MacOSWebAuthnService::SelectAutoFillEntry(
1134     uint64_t aTransactionId, const nsTArray<uint8_t>& aCredentialId) {
1135   auto guard = mTransactionState.Lock();
1136   if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
1137       guard->ref().pendingSignArgs.isNothing()) {
1138     return NS_ERROR_NOT_AVAILABLE;
1139   }
1141   nsTArray<nsTArray<uint8_t>> allowList;
1142   Unused << guard->ref().pendingSignArgs.ref()->GetAllowList(allowList);
1143   if (!allowList.IsEmpty() && !allowList.Contains(aCredentialId)) {
1144     return NS_ERROR_FAILURE;
1145   }
1147   Maybe<nsTArray<uint8_t>> id;
1148   id.emplace();
1149   id.ref().Assign(aCredentialId);
1150   DoGetAssertion(std::move(id), guard);
1152   return NS_OK;
1155 NS_IMETHODIMP
1156 MacOSWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
1157   auto guard = mTransactionState.Lock();
1158   if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
1159       guard->ref().pendingSignArgs.isNothing()) {
1160     return NS_ERROR_NOT_AVAILABLE;
1161   }
1162   DoGetAssertion(Nothing(), guard);
1163   return NS_OK;
1166 NS_IMETHODIMP
1167 MacOSWebAuthnService::PinCallback(uint64_t aTransactionId,
1168                                   const nsACString& aPin) {
1169   return NS_ERROR_NOT_IMPLEMENTED;
1172 NS_IMETHODIMP
1173 MacOSWebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
1174                                                bool aHasConsent) {
1175   return NS_ERROR_NOT_IMPLEMENTED;
1178 NS_IMETHODIMP
1179 MacOSWebAuthnService::SelectionCallback(uint64_t aTransactionId,
1180                                         uint64_t aIndex) {
1181   return NS_ERROR_NOT_IMPLEMENTED;
1184 NS_IMETHODIMP
1185 MacOSWebAuthnService::AddVirtualAuthenticator(
1186     const nsACString& protocol, const nsACString& transport,
1187     bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
1188     bool isUserVerified, uint64_t* _retval) {
1189   return NS_ERROR_NOT_IMPLEMENTED;
1192 NS_IMETHODIMP
1193 MacOSWebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
1194   return NS_ERROR_NOT_IMPLEMENTED;
1197 NS_IMETHODIMP
1198 MacOSWebAuthnService::AddCredential(uint64_t authenticatorId,
1199                                     const nsACString& credentialId,
1200                                     bool isResidentCredential,
1201                                     const nsACString& rpId,
1202                                     const nsACString& privateKey,
1203                                     const nsACString& userHandle,
1204                                     uint32_t signCount) {
1205   return NS_ERROR_NOT_IMPLEMENTED;
1208 NS_IMETHODIMP
1209 MacOSWebAuthnService::GetCredentials(
1210     uint64_t authenticatorId,
1211     nsTArray<RefPtr<nsICredentialParameters>>& _retval) {
1212   return NS_ERROR_NOT_IMPLEMENTED;
1215 NS_IMETHODIMP
1216 MacOSWebAuthnService::RemoveCredential(uint64_t authenticatorId,
1217                                        const nsACString& credentialId) {
1218   return NS_ERROR_NOT_IMPLEMENTED;
1221 NS_IMETHODIMP
1222 MacOSWebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
1223   return NS_ERROR_NOT_IMPLEMENTED;
1226 NS_IMETHODIMP
1227 MacOSWebAuthnService::SetUserVerified(uint64_t authenticatorId,
1228                                       bool isUserVerified) {
1229   return NS_ERROR_NOT_IMPLEMENTED;
1232 NS_IMETHODIMP
1233 MacOSWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
1235 NS_IMETHODIMP
1236 MacOSWebAuthnService::RunCommand(const nsACString& cmd) {
1237   return NS_ERROR_NOT_IMPLEMENTED;
1240 }  // namespace mozilla::dom
1242 NS_ASSUME_NONNULL_END