/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #import #include "MacOSWebAuthnService.h" #include "CFTypeRefPtr.h" #include "WebAuthnAutoFillEntry.h" #include "WebAuthnEnumStrings.h" #include "WebAuthnResult.h" #include "WebAuthnTransportIdentifiers.h" #include "mozilla/Maybe.h" #include "mozilla/StaticPrefs_security.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "nsCocoaUtils.h" #include "nsIWebAuthnPromise.h" #include "nsThreadUtils.h" // The documentation for the platform APIs used here can be found at: // https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys namespace { static mozilla::LazyLogModule gMacOSWebAuthnServiceLog("macoswebauthnservice"); } // namespace namespace mozilla::dom { class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService; } // namespace mozilla::dom // The following ASC* declarations are from the private framework // AuthenticationServicesCore. The full definitions can be found in WebKit's // source at Source/WebKit/Platform/spi/Cocoa/AuthenticationServicesCoreSPI.h. // Overriding ASAuthorizationController's _requestContextWithRequests is // currently the only way to provide the right information to the macOS // WebAuthn API (namely, the clientDataHash for requests made to physical // tokens). NS_ASSUME_NONNULL_BEGIN @class ASCPublicKeyCredentialDescriptor; @interface ASCPublicKeyCredentialDescriptor : NSObject - (instancetype)initWithCredentialID:(NSData*)credentialID transports: (nullable NSArray*)allowedTransports; @end @protocol ASCPublicKeyCredentialCreationOptions @property(nonatomic, copy) NSData* clientDataHash; @property(nonatomic, nullable, copy) NSData* challenge; @property(nonatomic, copy) NSArray* excludedCredentials; @end @protocol ASCPublicKeyCredentialAssertionOptions @property(nonatomic, copy) NSData* clientDataHash; @end @protocol ASCCredentialRequestContext @property(nonatomic, nullable, copy) id platformKeyCredentialCreationOptions; @property(nonatomic, nullable, copy) id securityKeyCredentialCreationOptions; @property(nonatomic, nullable, copy) id platformKeyCredentialAssertionOptions; @property(nonatomic, nullable, copy) id securityKeyCredentialAssertionOptions; @end @interface ASAuthorizationController (Secrets) - (id) _requestContextWithRequests:(NSArray*)requests error:(NSError**)outError; @end NSArray* TransportsByteToTransportsArray(const uint8_t aTransports) API_AVAILABLE(macos(13.3)) { NSMutableArray* transportsNS = [[NSMutableArray alloc] init]; if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) == MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) { [transportsNS addObject: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportUSB]; } if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) == MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) { [transportsNS addObject: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportNFC]; } if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) == MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) { [transportsNS addObject: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportBluetooth]; } // TODO (bug 1859367): the platform doesn't have a definition for "internal" // transport. When it does, this code should be updated to handle it. return transportsNS; } NSArray* CredentialListsToCredentialDescriptorArray( const nsTArray>& aCredentialList, const nsTArray& aCredentialListTransports, const Class credentialDescriptorClass) API_AVAILABLE(macos(13.3)) { MOZ_ASSERT(aCredentialList.Length() == aCredentialListTransports.Length()); NSMutableArray* credentials = [[NSMutableArray alloc] init]; for (size_t i = 0; i < aCredentialList.Length(); i++) { const nsTArray& credentialId = aCredentialList[i]; const uint8_t& credentialTransports = aCredentialListTransports[i]; NSData* credentialIdNS = [NSData dataWithBytes:credentialId.Elements() length:credentialId.Length()]; NSArray* credentialTransportsNS = TransportsByteToTransportsArray(credentialTransports); NSObject* credential = [[credentialDescriptorClass alloc] initWithCredentialID:credentialIdNS transports:credentialTransportsNS]; [credentials addObject:credential]; } return credentials; } // MacOSAuthorizationController is an ASAuthorizationController that overrides // _requestContextWithRequests so that the implementation can set some options // that aren't directly settable using the public API. API_AVAILABLE(macos(13.3)) @interface MacOSAuthorizationController : ASAuthorizationController @end @implementation MacOSAuthorizationController { nsTArray mClientDataHash; nsTArray> mCredentialList; nsTArray mCredentialListTransports; } - (void)setRegistrationOptions: (id)registrationOptions { registrationOptions.clientDataHash = [NSData dataWithBytes:mClientDataHash.Elements() length:mClientDataHash.Length()]; // Unset challenge so that the implementation uses clientDataHash (the API // returns an error otherwise). registrationOptions.challenge = nil; const Class publicKeyCredentialDescriptorClass = NSClassFromString(@"ASCPublicKeyCredentialDescriptor"); NSArray* excludedCredentials = CredentialListsToCredentialDescriptorArray( mCredentialList, mCredentialListTransports, publicKeyCredentialDescriptorClass); if ([excludedCredentials count] > 0) { registrationOptions.excludedCredentials = excludedCredentials; } } - (void)stashClientDataHash:(nsTArray&&)clientDataHash andCredentialList:(nsTArray>&&)credentialList andCredentialListTransports:(nsTArray&&)credentialListTransports { mClientDataHash = std::move(clientDataHash); mCredentialList = std::move(credentialList); mCredentialListTransports = std::move(credentialListTransports); } - (id) _requestContextWithRequests:(NSArray*)requests error:(NSError**)outError { id context = [super _requestContextWithRequests:requests error:outError]; if (context.platformKeyCredentialCreationOptions) { [self setRegistrationOptions:context.platformKeyCredentialCreationOptions]; } if (context.securityKeyCredentialCreationOptions) { [self setRegistrationOptions:context.securityKeyCredentialCreationOptions]; } if (context.platformKeyCredentialAssertionOptions) { id assertionOptions = context.platformKeyCredentialAssertionOptions; assertionOptions.clientDataHash = [NSData dataWithBytes:mClientDataHash.Elements() length:mClientDataHash.Length()]; context.platformKeyCredentialAssertionOptions = [assertionOptions copyWithZone:nil]; } if (context.securityKeyCredentialAssertionOptions) { id assertionOptions = context.securityKeyCredentialAssertionOptions; assertionOptions.clientDataHash = [NSData dataWithBytes:mClientDataHash.Elements() length:mClientDataHash.Length()]; context.securityKeyCredentialAssertionOptions = [assertionOptions copyWithZone:nil]; } return context; } @end // MacOSAuthenticatorRequestDelegate is an ASAuthorizationControllerDelegate, // which can be set as an ASAuthorizationController's delegate to be called // back when a request for a platform authenticator attestation or assertion // response completes either with an attestation or assertion // (didCompleteWithAuthorization) or with an error (didCompleteWithError). API_AVAILABLE(macos(13.3)) @interface MacOSAuthenticatorRequestDelegate : NSObject - (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback transactionId:(uint64_t)transactionId; @end // MacOSAuthenticatorPresentationContextProvider is an // ASAuthorizationControllerPresentationContextProviding, which can be set as // an ASAuthorizationController's presentationContextProvider, and provides a // presentation anchor for the ASAuthorizationController. Basically, this // provides the API a handle to the window that made the request. API_AVAILABLE(macos(13.3)) @interface MacOSAuthenticatorPresentationContextProvider : NSObject // We store the window number rather than a strong reference to the NSWindow so // that an in-flight (or stalled) passkey handshake never keeps the requesting // browser window alive past its close. A retained window that has been -close'd // lingers as an invisible "ghost" that still captures mouse events (bug // 2015460). The anchor is resolved lazily from the number; if the window has // since closed, -windowWithWindowNumber: returns nil, which is an acceptable // anchor. @property(nonatomic) NSInteger windowNumber; @end namespace mozilla::dom { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnullability-completeness" class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService final : public nsIWebAuthnService { public: MacOSWebAuthnService() = default; NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIWEBAUTHNSERVICE void FinishMakeCredential(uint64_t aTransactionId, nsTArray&& aRawAttestationObject, nsTArray&& aCredentialId, nsTArray&& aTransports, Maybe&& aAuthenticatorAttachment, Maybe aLargeBlobSupported, Maybe aPrfSupported, Maybe>&& aPrfFirst, Maybe>&& aPrfSecond); void FinishGetAssertion( uint64_t aTransactionId, nsTArray&& aCredentialId, nsTArray&& aSignature, nsTArray&& aAuthenticatorData, nsTArray&& aUserHandle, Maybe&& aAuthenticatorAttachment, Maybe aUsedAppId, Maybe>&& aLargeBlobValue, Maybe aLargeBlobWritten, Maybe>&& aPrfFirst, Maybe>&& aPrfSecond); void ReleasePlatformResources(); // Internal cleanup helper — does not check tid. Used when superseding stale // state during MakeCredential/GetAssertion. void AbortTransaction(nsresult aError); // Delegate-facing overload that checks the tid matches the active // transaction before doing anything. Releases platform resources on // completion. void AbortTransaction(uint64_t aTransactionId, nsresult aError); private: ~MacOSWebAuthnService() = default; void PerformRequests(uint64_t aTransactionId, NSArray* aRequests, nsTArray&& aClientDataHash, nsTArray>&& aCredentialList, nsTArray&& aCredentialListTransports, uint64_t aBrowsingContextId); // Main thread only: Maybe mTransactionId; ASAuthorizationWebBrowserPublicKeyCredentialManager* mCredentialManager = nil; nsCOMPtr mRegisterPromise; nsCOMPtr mSignPromise; MacOSAuthorizationController* mAuthorizationController = nil; MacOSAuthenticatorRequestDelegate* mRequestDelegate = nil; MacOSAuthenticatorPresentationContextProvider* mPresentationContextProvider = nil; }; #pragma clang diagnostic pop } // namespace mozilla::dom nsTArray NSDataToArray(NSData* data) { nsTArray array(reinterpret_cast(data.bytes), data.length); return array; } API_AVAILABLE(macos(15.0)) NSDictionary* _Nullable ConstructPrfEvalByCredentialEntries( const RefPtr& aArgs) { nsTArray> prfEvalByCredIds; nsTArray> prfEvalByCredFirsts; nsTArray prfEvalByCredSecondMaybes; nsTArray> prfEvalByCredSeconds; if (NS_FAILED(aArgs->GetPrfEvalByCredentialCredentialId(prfEvalByCredIds)) || NS_FAILED(aArgs->GetPrfEvalByCredentialEvalFirst(prfEvalByCredFirsts)) || NS_FAILED(aArgs->GetPrfEvalByCredentialEvalSecondMaybe( prfEvalByCredSecondMaybes)) || NS_FAILED( aArgs->GetPrfEvalByCredentialEvalSecond(prfEvalByCredSeconds)) || prfEvalByCredIds.Length() != prfEvalByCredFirsts.Length() || prfEvalByCredIds.Length() != prfEvalByCredSecondMaybes.Length() || prfEvalByCredIds.Length() != prfEvalByCredSeconds.Length()) { return nil; } uint32_t count = prfEvalByCredIds.Length(); NSMutableArray* keys = [NSMutableArray arrayWithCapacity:count]; NSMutableArray* objects = [NSMutableArray arrayWithCapacity:count]; for (size_t i = 0; i < count; i++) { NSData* saltInput1 = [NSData dataWithBytes:prfEvalByCredFirsts[i].Elements() length:prfEvalByCredFirsts[i].Length()]; NSData* saltInput2 = nil; if (prfEvalByCredSecondMaybes[i]) { saltInput2 = [NSData dataWithBytes:prfEvalByCredSeconds[i].Elements() length:prfEvalByCredSeconds[i].Length()]; } [keys addObject:[NSData dataWithBytes:prfEvalByCredIds[i].Elements() length:prfEvalByCredIds[i].Length()]]; [objects addObject:[[ASAuthorizationPublicKeyCredentialPRFAssertionInputValues alloc] initWithSaltInput1:saltInput1 saltInput2:saltInput2]]; } return [NSDictionary dictionaryWithObjects:objects forKeys:keys]; } @implementation MacOSAuthenticatorRequestDelegate { RefPtr mCallback; uint64_t mTransactionId; } - (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback transactionId:(uint64_t)transactionId { mCallback = callback; mTransactionId = transactionId; } - (void)authorizationController:(ASAuthorizationController*)controller didCompleteWithAuthorization:(ASAuthorization*)authorization { if ([authorization.credential conformsToProtocol: @protocol(ASAuthorizationPublicKeyCredentialRegistration)]) { MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug, ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: " "got registration")); id credential = (id) authorization.credential; nsTArray rawAttestationObject( NSDataToArray(credential.rawAttestationObject)); nsTArray credentialId(NSDataToArray(credential.credentialID)); nsTArray transports; mozilla::Maybe authenticatorAttachment; mozilla::Maybe largeBlobSupported; mozilla::Maybe prfSupported; mozilla::Maybe> prfFirst; mozilla::Maybe> prfSecond; if ([credential isKindOfClass: [ASAuthorizationPlatformPublicKeyCredentialRegistration class]]) { transports.AppendElement(u"hybrid"_ns); transports.AppendElement(u"internal"_ns); ASAuthorizationPlatformPublicKeyCredentialRegistration* platformCredential = (ASAuthorizationPlatformPublicKeyCredentialRegistration*) credential; if (__builtin_available(macos 13.5, *)) { switch (platformCredential.attachment) { case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform: authenticatorAttachment.emplace(u"cross-platform"_ns); break; case ASAuthorizationPublicKeyCredentialAttachmentPlatform: authenticatorAttachment.emplace(u"platform"_ns); break; default: break; } } if (__builtin_available(macos 14.0, *)) { if (platformCredential.largeBlob) { largeBlobSupported.emplace(platformCredential.largeBlob.isSupported); } } if (__builtin_available(macos 15.0, *)) { if (platformCredential.prf) { prfSupported.emplace(platformCredential.prf.isSupported); if (platformCredential.prf.first) { prfFirst.emplace(NSDataToArray(platformCredential.prf.first)); } if (platformCredential.prf.second) { prfSecond.emplace(NSDataToArray(platformCredential.prf.second)); } } } } else if ( [credential isKindOfClass: [ASAuthorizationSecurityKeyPublicKeyCredentialRegistration class]]) { // The platform didn't tell us what transport was used, but we know it // wasn't the internal transport. The transport response is not signed by // the authenticator. It represents the "transports that the authenticator // is believed to support, or an empty sequence if the information is // unavailable". We believe macOS supports usb, so we return usb. transports.AppendElement(u"usb"_ns); authenticatorAttachment.emplace(u"cross-platform"_ns); if (__builtin_available(macos 26.4, *)) { ASAuthorizationSecurityKeyPublicKeyCredentialRegistration* securityKeyCredential = (ASAuthorizationSecurityKeyPublicKeyCredentialRegistration*) credential; if (securityKeyCredential.prf) { prfSupported.emplace(securityKeyCredential.prf.isSupported); if (securityKeyCredential.prf.first) { prfFirst.emplace(NSDataToArray(securityKeyCredential.prf.first)); } if (securityKeyCredential.prf.second) { prfSecond.emplace(NSDataToArray(securityKeyCredential.prf.second)); } } } } mCallback->FinishMakeCredential( mTransactionId, std::move(rawAttestationObject), std::move(credentialId), std::move(transports), std::move(authenticatorAttachment), largeBlobSupported, prfSupported, std::move(prfFirst), std::move(prfSecond)); } else if ([authorization.credential conformsToProtocol: @protocol(ASAuthorizationPublicKeyCredentialAssertion)]) { MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug, ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: " "got assertion")); id credential = (id) authorization.credential; nsTArray credentialId(NSDataToArray(credential.credentialID)); nsTArray signature(NSDataToArray(credential.signature)); nsTArray rawAuthenticatorData( NSDataToArray(credential.rawAuthenticatorData)); nsTArray userHandle(NSDataToArray(credential.userID)); mozilla::Maybe authenticatorAttachment; mozilla::Maybe usedAppId; mozilla::Maybe> largeBlobValue; mozilla::Maybe largeBlobWritten; mozilla::Maybe> prfFirst; mozilla::Maybe> prfSecond; if ([credential isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion class]]) { ASAuthorizationPlatformPublicKeyCredentialAssertion* platformCredential = (ASAuthorizationPlatformPublicKeyCredentialAssertion*)credential; if (__builtin_available(macos 13.5, *)) { switch (platformCredential.attachment) { case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform: authenticatorAttachment.emplace(u"cross-platform"_ns); break; case ASAuthorizationPublicKeyCredentialAttachmentPlatform: authenticatorAttachment.emplace(u"platform"_ns); break; default: break; } } if (__builtin_available(macos 14.0, *)) { if (platformCredential.largeBlob) { if (platformCredential.largeBlob.readData) { largeBlobValue.emplace( NSDataToArray(platformCredential.largeBlob.readData)); } else { largeBlobWritten.emplace(platformCredential.largeBlob.didWrite); } } } if (__builtin_available(macos 15.0, *)) { if (platformCredential.prf) { if (platformCredential.prf.first) { prfFirst.emplace(NSDataToArray(platformCredential.prf.first)); } if (platformCredential.prf.second) { prfSecond.emplace(NSDataToArray(platformCredential.prf.second)); } } } } else if ([credential isKindOfClass: [ASAuthorizationSecurityKeyPublicKeyCredentialAssertion class]]) { ASAuthorizationSecurityKeyPublicKeyCredentialAssertion* securityKeyCredential = (ASAuthorizationSecurityKeyPublicKeyCredentialAssertion*) credential; if (__builtin_available(macos 14.5, *)) { usedAppId.emplace(securityKeyCredential.appID); } if (__builtin_available(macos 26.4, *)) { if (securityKeyCredential.prf) { if (securityKeyCredential.prf.first) { prfFirst.emplace(NSDataToArray(securityKeyCredential.prf.first)); } if (securityKeyCredential.prf.second) { prfSecond.emplace(NSDataToArray(securityKeyCredential.prf.second)); } } } authenticatorAttachment.emplace(u"cross-platform"_ns); } mCallback->FinishGetAssertion( mTransactionId, std::move(credentialId), std::move(signature), std::move(rawAuthenticatorData), std::move(userHandle), std::move(authenticatorAttachment), usedAppId, std::move(largeBlobValue), largeBlobWritten, std::move(prfFirst), std::move(prfSecond)); } else { MOZ_LOG( gMacOSWebAuthnServiceLog, mozilla::LogLevel::Error, ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: " "authorization.credential is neither registration nor assertion!")); MOZ_ASSERT_UNREACHABLE( "should have ASAuthorizationPublicKeyCredentialRegistration or " "ASAuthorizationPublicKeyCredentialAssertion"); } mCallback = nullptr; } - (void)authorizationController:(ASAuthorizationController*)controller didCompleteWithError:(NSError*)error { nsAutoString errorDescription; nsCocoaUtils::GetStringForNSString(error.localizedDescription, errorDescription); nsAutoString errorDomain; nsCocoaUtils::GetStringForNSString(error.domain, errorDomain); MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Warning, ("MacOSAuthenticatorRequestDelegate::didCompleteWithError: domain " "'%s' code %ld (%s)", NS_ConvertUTF16toUTF8(errorDomain).get(), error.code, NS_ConvertUTF16toUTF8(errorDescription).get())); nsresult rv = NS_ERROR_DOM_NOT_ALLOWED_ERR; // On older macOS versions, the excluded-credential error is reported in the // "WKErrorDomain" domain with code 8 (WKErrorDuplicateCredential) rather // than as ASAuthorizationErrorMatchedExcludedCredential. const NSInteger WKErrorDuplicateCredential = 8; if (errorDomain.EqualsLiteral("WKErrorDomain") && error.code == WKErrorDuplicateCredential) { rv = NS_ERROR_DOM_INVALID_STATE_ERR; } else if (error.domain == ASAuthorizationErrorDomain) { switch (error.code) { case ASAuthorizationErrorCanceled: rv = NS_ERROR_DOM_NOT_ALLOWED_ERR; break; case ASAuthorizationErrorMatchedExcludedCredential: rv = NS_ERROR_DOM_INVALID_STATE_ERR; break; case ASAuthorizationErrorFailed: // The message is right, but it's not about indexeddb. // See https://webidl.spec.whatwg.org/#constrainterror rv = NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; break; case ASAuthorizationErrorUnknown: rv = NS_ERROR_DOM_UNKNOWN_ERR; break; default: // rv already has a default value break; } } mCallback->AbortTransaction(mTransactionId, rv); mCallback = nullptr; } @end @implementation MacOSAuthenticatorPresentationContextProvider @synthesize windowNumber = windowNumber; - (ASPresentationAnchor)presentationAnchorForAuthorizationController: (ASAuthorizationController*)controller { return [NSApp windowWithWindowNumber:windowNumber]; } @end namespace mozilla::dom { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnullability-completeness" // Given a browsingContextId, attempts to determine the NSWindow associated // with that browser. nsresult BrowsingContextIdToNSWindow(uint64_t browsingContextId, NSWindow** window) { *window = nullptr; RefPtr browsingContext( BrowsingContext::Get(browsingContextId)); if (!browsingContext) { return NS_ERROR_NOT_AVAILABLE; } CanonicalBrowsingContext* canonicalBrowsingContext = browsingContext->Canonical(); if (!canonicalBrowsingContext) { return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr widget( canonicalBrowsingContext->GetParentProcessWidgetContaining()); if (!widget) { return NS_ERROR_NOT_AVAILABLE; } *window = static_cast(widget->GetNativeData(NS_NATIVE_WINDOW)); return NS_OK; } #pragma clang diagnostic pop already_AddRefed NewMacOSWebAuthnServiceIfAvailable() { MOZ_ASSERT(XRE_IsParentProcess()); if (!StaticPrefs::security_webauthn_enable_macos_passkeys()) { MOZ_LOG( gMacOSWebAuthnServiceLog, LogLevel::Debug, ("macOS platform support for webauthn (passkeys) disabled by pref")); return nullptr; } // This code checks for the entitlement // 'com.apple.developer.web-browser.public-key-credential', which must be // true to be able to use the platform APIs. CFTypeRefPtr entitlementTask( CFTypeRefPtr::WrapUnderCreateRule( SecTaskCreateFromSelf(nullptr))); CFTypeRefPtr entitlementValue( CFTypeRefPtr::WrapUnderCreateRule( reinterpret_cast(SecTaskCopyValueForEntitlement( entitlementTask.get(), CFSTR("com.apple.developer.web-browser.public-key-credential"), nullptr)))); if (!entitlementValue || !CFBooleanGetValue(entitlementValue.get())) { MOZ_LOG( gMacOSWebAuthnServiceLog, LogLevel::Warning, ("entitlement com.apple.developer.web-browser.public-key-credential " "not present: platform passkey APIs will not be available")); return nullptr; } nsCOMPtr service(new MacOSWebAuthnService()); return service.forget(); } void MacOSWebAuthnService::AbortTransaction(nsresult aError) { MOZ_ASSERT(NS_IsMainThread()); if (mRegisterPromise) { (void)mRegisterPromise->Reject(aError); mRegisterPromise = nullptr; } if (mSignPromise) { (void)mSignPromise->Reject(aError); mSignPromise = nullptr; } ReleasePlatformResources(); } void MacOSWebAuthnService::AbortTransaction(uint64_t aTransactionId, nsresult aError) { // This is invoked from the platform delegate callback, which may not be // running on the main thread. NS_DispatchToMainThread(NS_NewRunnableFunction( "MacOSWebAuthnService::AbortTransaction", [self = RefPtr{this}, aTransactionId, aError]() { MOZ_ASSERT(NS_IsMainThread()); if (self->mTransactionId.isNothing() || self->mTransactionId.ref() != aTransactionId) { // The transaction was superseded or cancelled before this // delegate callback fired; the result belongs to a stale // controller. return; } self->AbortTransaction(aError); })); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnullability-completeness" NS_IMPL_ISUPPORTS(MacOSWebAuthnService, nsIWebAuthnService) #pragma clang diagnostic pop NS_IMETHODIMP MacOSWebAuthnService::MakeCredential(uint64_t aTransactionId, uint64_t aBrowsingContextId, nsIWebAuthnRegisterArgs* aArgs, nsIWebAuthnRegisterPromise* aPromise) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mTransactionId.isNothing(), "WebAuthnService should reset the platform service before " "dispatching MakeCredential"); mTransactionId = Some(aTransactionId); mRegisterPromise = aPromise; nsAutoString rpId; (void)aArgs->GetRpId(rpId); NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId); nsTArray challenge; (void)aArgs->GetChallenge(challenge); NSData* challengeNS = [NSData dataWithBytes:challenge.Elements() length:challenge.Length()]; nsTArray userId; (void)aArgs->GetUserId(userId); NSData* userIdNS = [NSData dataWithBytes:userId.Elements() length:userId.Length()]; nsAutoString userName; (void)aArgs->GetUserName(userName); NSString* userNameNS = nsCocoaUtils::ToNSString(userName); nsAutoString userDisplayName; (void)aArgs->GetUserDisplayName(userDisplayName); NSString* userDisplayNameNS = nsCocoaUtils::ToNSString(userDisplayName); nsTArray coseAlgs; (void)aArgs->GetCoseAlgs(coseAlgs); NSMutableArray* credentialParameters = [[NSMutableArray alloc] init]; for (const auto& coseAlg : coseAlgs) { ASAuthorizationPublicKeyCredentialParameters* credentialParameter = [[ASAuthorizationPublicKeyCredentialParameters alloc] initWithAlgorithm:coseAlg]; [credentialParameters addObject:credentialParameter]; } nsTArray> excludeList; (void)aArgs->GetExcludeList(excludeList); nsTArray excludeListTransports; (void)aArgs->GetExcludeListTransports(excludeListTransports); if (excludeList.Length() != excludeListTransports.Length()) { AbortTransaction(NS_ERROR_INVALID_ARG); return NS_OK; } Maybe userVerificationPreference = Nothing(); nsAutoString userVerification; (void)aArgs->GetUserVerification(userVerification); // This mapping needs to be reviewed if values are added to the // UserVerificationRequirement enum. static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3); if (userVerification.EqualsLiteral( MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) { userVerificationPreference.emplace( ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired); } else if (userVerification.EqualsLiteral( MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) { userVerificationPreference.emplace( ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred); } else if (userVerification.EqualsLiteral( MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) { userVerificationPreference.emplace( ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged); } // The API doesn't support attestation for platform passkeys, so this is // only used for security keys. ASAuthorizationPublicKeyCredentialAttestationKind attestationPreference; nsAutoString mozAttestationPreference; (void)aArgs->GetAttestationConveyancePreference(mozAttestationPreference); if (mozAttestationPreference.EqualsLiteral( MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT)) { attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKindIndirect; } else if (mozAttestationPreference.EqualsLiteral( MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT)) { attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKindDirect; } else if (mozAttestationPreference.EqualsLiteral( MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE)) { attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKindEnterprise; } else { attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKindNone; } ASAuthorizationPublicKeyCredentialResidentKeyPreference residentKeyPreference; nsAutoString mozResidentKey; (void)aArgs->GetResidentKey(mozResidentKey); // This mapping needs to be reviewed if values are added to the // ResidentKeyRequirement enum. static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3); if (mozResidentKey.EqualsLiteral( MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED)) { residentKeyPreference = ASAuthorizationPublicKeyCredentialResidentKeyPreferenceRequired; } else if (mozResidentKey.EqualsLiteral( MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED)) { residentKeyPreference = ASAuthorizationPublicKeyCredentialResidentKeyPreferencePreferred; } else { MOZ_ASSERT(mozResidentKey.EqualsLiteral( MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED)); residentKeyPreference = ASAuthorizationPublicKeyCredentialResidentKeyPreferenceDiscouraged; } // Initialize the platform provider with the rpId. ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider = [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc] initWithRelyingPartyIdentifier:rpIdNS]; // Make a credential registration request with the challenge, userName, // and userId. ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest* platformRegistrationRequest = [platformProvider createCredentialRegistrationRequestWithChallenge:challengeNS name:userNameNS userID:userIdNS]; [platformProvider release]; // The API doesn't support attestation for platform passkeys platformRegistrationRequest.attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKindNone; if (userVerificationPreference.isSome()) { platformRegistrationRequest.userVerificationPreference = *userVerificationPreference; } // Initialize the cross-platform provider with the rpId. ASAuthorizationSecurityKeyPublicKeyCredentialProvider* crossPlatformProvider = [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc] initWithRelyingPartyIdentifier:rpIdNS]; // Make a credential registration request with the challenge, // userDisplayName, userName, and userId. ASAuthorizationSecurityKeyPublicKeyCredentialRegistrationRequest* crossPlatformRegistrationRequest = [crossPlatformProvider createCredentialRegistrationRequestWithChallenge:challengeNS displayName:userDisplayNameNS name:userNameNS userID:userIdNS]; [crossPlatformProvider release]; crossPlatformRegistrationRequest.attestationPreference = attestationPreference; crossPlatformRegistrationRequest.credentialParameters = credentialParameters; crossPlatformRegistrationRequest.residentKeyPreference = residentKeyPreference; if (userVerificationPreference.isSome()) { crossPlatformRegistrationRequest.userVerificationPreference = *userVerificationPreference; } if (__builtin_available(macos 13.5, *)) { // Show the hybrid transport unless we have a non-empty hint list and // none of the hints are for the hybrid transport. bool hasHybridHint = false; nsTArray hints; (void)aArgs->GetHints(hints); for (nsString& hint : hints) { if (hint.Equals(u"hybrid"_ns)) { hasHybridHint = true; } } platformRegistrationRequest.shouldShowHybridTransport = hints.Length() == 0 || hasHybridHint; } if (__builtin_available(macos 14.0, *)) { bool largeBlobSupportRequired; nsresult rv = aArgs->GetLargeBlobSupportRequired(&largeBlobSupportRequired); if (rv != NS_ERROR_NOT_AVAILABLE) { if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } ASAuthorizationPublicKeyCredentialLargeBlobSupportRequirement largeBlobRequirement = largeBlobSupportRequired ? ASAuthorizationPublicKeyCredentialLargeBlobSupportRequirementRequired : ASAuthorizationPublicKeyCredentialLargeBlobSupportRequirementPreferred; platformRegistrationRequest.largeBlob = [[ASAuthorizationPublicKeyCredentialLargeBlobRegistrationInput alloc] initWithSupportRequirement:largeBlobRequirement]; } } if (__builtin_available(macos 15.0, *)) { bool requestedPrf; (void)aArgs->GetPrf(&requestedPrf); if (requestedPrf) { NSData* saltInput1 = nil; NSData* saltInput2 = nil; nsTArray prfInput1; nsresult rv = aArgs->GetPrfEvalFirst(prfInput1); if (rv != NS_ERROR_NOT_AVAILABLE) { if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } saltInput1 = [NSData dataWithBytes:prfInput1.Elements() length:prfInput1.Length()]; } nsTArray prfInput2; rv = aArgs->GetPrfEvalSecond(prfInput2); if (rv != NS_ERROR_NOT_AVAILABLE) { if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } saltInput2 = [NSData dataWithBytes:prfInput2.Elements() length:prfInput2.Length()]; } ASAuthorizationPublicKeyCredentialPRFAssertionInputValues* prfInputs = [[ASAuthorizationPublicKeyCredentialPRFAssertionInputValues alloc] initWithSaltInput1:saltInput1 saltInput2:saltInput2]; ASAuthorizationPublicKeyCredentialPRFRegistrationInput* prfRegistrationInput = [[ASAuthorizationPublicKeyCredentialPRFRegistrationInput alloc] initWithInputValues:prfInputs]; platformRegistrationRequest.prf = prfRegistrationInput; if (__builtin_available(macos 26.4, *)) { crossPlatformRegistrationRequest.prf = prfRegistrationInput; } } } nsTArray clientDataHash; nsresult rv = aArgs->GetClientDataHash(clientDataHash); if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } nsAutoString authenticatorAttachment; rv = aArgs->GetAuthenticatorAttachment(authenticatorAttachment); if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { AbortTransaction(rv); return NS_OK; } NSMutableArray* requests = [[NSMutableArray alloc] init]; if (authenticatorAttachment.EqualsLiteral( MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) { [requests addObject:platformRegistrationRequest]; } else if (authenticatorAttachment.EqualsLiteral( MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) { [requests addObject:crossPlatformRegistrationRequest]; } else { // Regarding the value of authenticator attachment, according to the // specification, "client platforms MUST ignore unknown values, // treating an unknown value as if the member does not exist". [requests addObject:platformRegistrationRequest]; [requests addObject:crossPlatformRegistrationRequest]; } PerformRequests(aTransactionId, requests, std::move(clientDataHash), std::move(excludeList), std::move(excludeListTransports), aBrowsingContextId); return NS_OK; } void MacOSWebAuthnService::PerformRequests( uint64_t aTransactionId, NSArray* aRequests, nsTArray&& aClientDataHash, nsTArray>&& aCredentialList, nsTArray&& aCredentialListTransports, uint64_t aBrowsingContextId) { MOZ_ASSERT(NS_IsMainThread()); // Create a MacOSAuthorizationController and initialize it with the requests. MOZ_ASSERT(!mAuthorizationController); mAuthorizationController = [[MacOSAuthorizationController alloc] initWithAuthorizationRequests:aRequests]; [mAuthorizationController stashClientDataHash:std::move(aClientDataHash) andCredentialList:std::move(aCredentialList) andCredentialListTransports:std::move(aCredentialListTransports)]; // Set up the delegate to run when the operation completes. MOZ_ASSERT(!mRequestDelegate); mRequestDelegate = [[MacOSAuthenticatorRequestDelegate alloc] init]; [mRequestDelegate setCallback:this transactionId:aTransactionId]; mAuthorizationController.delegate = mRequestDelegate; // Create a presentation context provider so the API knows which window // made the request. NSWindow* window = nullptr; nsresult rv = BrowsingContextIdToNSWindow(aBrowsingContextId, &window); if (NS_FAILED(rv)) { AbortTransaction(NS_ERROR_DOM_INVALID_STATE_ERR); return; } MOZ_ASSERT(!mPresentationContextProvider); mPresentationContextProvider = [[MacOSAuthenticatorPresentationContextProvider alloc] init]; mPresentationContextProvider.windowNumber = window.windowNumber; mAuthorizationController.presentationContextProvider = mPresentationContextProvider; // Finally, perform the request. [mAuthorizationController performRequests]; } void MacOSWebAuthnService::FinishMakeCredential( uint64_t aTransactionId, nsTArray&& aRawAttestationObject, nsTArray&& aCredentialId, nsTArray&& aTransports, Maybe&& aAuthenticatorAttachment, Maybe aLargeBlobSupported, Maybe aPrfSupported, Maybe>&& aPrfFirst, Maybe>&& aPrfSecond) { // This is invoked from the platform delegate callback, which may not be // running on the main thread. NS_DispatchToMainThread(NS_NewRunnableFunction( "MacOSWebAuthnService::FinishMakeCredential", [self = RefPtr{this}, aTransactionId, rawAttestationObject = std::move(aRawAttestationObject), credentialId = std::move(aCredentialId), transports = std::move(aTransports), authenticatorAttachment = std::move(aAuthenticatorAttachment), largeBlobSupported = aLargeBlobSupported, prfSupported = aPrfSupported, prfFirst = std::move(aPrfFirst), prfSecond = std::move(aPrfSecond)]() { MOZ_ASSERT(NS_IsMainThread()); if (self->mTransactionId.isNothing() || self->mTransactionId.ref() != aTransactionId) { // The transaction was superseded or cancelled before this // delegate callback fired; the result belongs to a stale // controller. return; } if (!self->mRegisterPromise) { return; } RefPtr result(new WebAuthnRegisterResult( rawAttestationObject, Nothing(), credentialId, transports, authenticatorAttachment, largeBlobSupported, prfSupported, prfFirst, prfSecond)); (void)self->mRegisterPromise->Resolve(result); self->mRegisterPromise = nullptr; self->ReleasePlatformResources(); })); } NS_IMETHODIMP MacOSWebAuthnService::GetAssertion(uint64_t aTransactionId, uint64_t aBrowsingContextId, nsIWebAuthnSignArgs* aArgs, nsIWebAuthnSignPromise* aPromise) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mTransactionId.isNothing(), "WebAuthnService should reset the platform service before " "dispatching GetAssertion"); mTransactionId = Some(aTransactionId); mSignPromise = aPromise; nsAutoString rpId; (void)aArgs->GetRpId(rpId); NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId); nsTArray challenge; (void)aArgs->GetChallenge(challenge); NSData* challengeNS = [NSData dataWithBytes:challenge.Elements() length:challenge.Length()]; nsTArray> allowList; nsTArray allowListTransports; (void)aArgs->GetAllowList(allowList); (void)aArgs->GetAllowListTransports(allowListTransports); // Compute the union of the transport sets. uint8_t transports = 0; for (uint8_t credTransports : allowListTransports) { if (credTransports == 0) { // treat the empty transport set as "all transports". transports = ~0; break; } transports |= credTransports; } NSMutableArray* platformAllowedCredentials = [[NSMutableArray alloc] init]; for (const auto& allowedCredentialId : allowList) { NSData* allowedCredentialIdNS = [NSData dataWithBytes:allowedCredentialId.Elements() length:allowedCredentialId.Length()]; ASAuthorizationPlatformPublicKeyCredentialDescriptor* allowedCredential = [[ASAuthorizationPlatformPublicKeyCredentialDescriptor alloc] initWithCredentialID:allowedCredentialIdNS]; [platformAllowedCredentials addObject:allowedCredential]; } const Class securityKeyPublicKeyCredentialDescriptorClass = NSClassFromString( @"ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor"); NSArray* crossPlatformAllowedCredentials = CredentialListsToCredentialDescriptorArray( allowList, allowListTransports, securityKeyPublicKeyCredentialDescriptorClass); Maybe userVerificationPreference = Nothing(); nsAutoString userVerification; (void)aArgs->GetUserVerification(userVerification); // This mapping needs to be reviewed if values are added to the // UserVerificationRequirement enum. static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3); if (userVerification.EqualsLiteral( MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) { userVerificationPreference.emplace( ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired); } else if (userVerification.EqualsLiteral( MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) { userVerificationPreference.emplace( ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred); } else if (userVerification.EqualsLiteral( MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) { userVerificationPreference.emplace( ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged); } // Initialize the platform provider with the rpId. ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider = [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc] initWithRelyingPartyIdentifier:rpIdNS]; // Make a credential assertion request with the challenge. ASAuthorizationPlatformPublicKeyCredentialAssertionRequest* platformAssertionRequest = [platformProvider createCredentialAssertionRequestWithChallenge:challengeNS]; [platformProvider release]; platformAssertionRequest.allowedCredentials = platformAllowedCredentials; if (userVerificationPreference.isSome()) { platformAssertionRequest.userVerificationPreference = *userVerificationPreference; } if (__builtin_available(macos 13.5, *)) { // Show the hybrid transport option if (1) none of the allowlist // credentials list transports, or (2) at least one allow list entry // lists the hybrid transport, or (3) the request has the hybrid hint. bool shouldShowHybridTransport = !transports || (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID); nsTArray hints; (void)aArgs->GetHints(hints); for (nsString& hint : hints) { if (hint.Equals(u"hybrid"_ns)) { shouldShowHybridTransport = true; } } platformAssertionRequest.shouldShowHybridTransport = shouldShowHybridTransport; } // Initialize the cross-platform provider with the rpId. ASAuthorizationSecurityKeyPublicKeyCredentialProvider* crossPlatformProvider = [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc] initWithRelyingPartyIdentifier:rpIdNS]; // Make a credential assertion request with the challenge. ASAuthorizationSecurityKeyPublicKeyCredentialAssertionRequest* crossPlatformAssertionRequest = [crossPlatformProvider createCredentialAssertionRequestWithChallenge:challengeNS]; [crossPlatformProvider release]; crossPlatformAssertionRequest.allowedCredentials = crossPlatformAllowedCredentials; if (userVerificationPreference.isSome()) { crossPlatformAssertionRequest.userVerificationPreference = *userVerificationPreference; } if (__builtin_available(macos 14.0, *)) { nsTArray largeBlobWrite; bool largeBlobRead; nsresult rv = aArgs->GetLargeBlobRead(&largeBlobRead); if (rv != NS_ERROR_NOT_AVAILABLE) { if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } if (largeBlobRead) { platformAssertionRequest .largeBlob = [[ASAuthorizationPublicKeyCredentialLargeBlobAssertionInput alloc] initWithOperation: ASAuthorizationPublicKeyCredentialLargeBlobAssertionOperationRead]; } else { rv = aArgs->GetLargeBlobWrite(largeBlobWrite); if (rv != NS_ERROR_NOT_AVAILABLE) { if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } ASAuthorizationPublicKeyCredentialLargeBlobAssertionInput* largeBlobAssertionInput = [[ASAuthorizationPublicKeyCredentialLargeBlobAssertionInput alloc] initWithOperation: ASAuthorizationPublicKeyCredentialLargeBlobAssertionOperationWrite]; // We need to fully form the input before assigning it to // platformAssertionRequest.largeBlob. See // https://bugs.webkit.org/show_bug.cgi?id=276961 largeBlobAssertionInput.dataToWrite = [NSData dataWithBytes:largeBlobWrite.Elements() length:largeBlobWrite.Length()]; platformAssertionRequest.largeBlob = largeBlobAssertionInput; } } } } if (__builtin_available(macos 14.5, *)) { nsString appId; nsresult rv = aArgs->GetAppId(appId); if (rv != NS_ERROR_NOT_AVAILABLE) { // AppID is set if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } crossPlatformAssertionRequest.appID = nsCocoaUtils::ToNSString(appId); } } if (__builtin_available(macos 15.0, *)) { bool requestedPrf; (void)aArgs->GetPrf(&requestedPrf); if (requestedPrf) { NSData* saltInput1 = nil; NSData* saltInput2 = nil; nsTArray prfInput1; nsresult rv = aArgs->GetPrfEvalFirst(prfInput1); if (rv != NS_ERROR_NOT_AVAILABLE) { if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } saltInput1 = [NSData dataWithBytes:prfInput1.Elements() length:prfInput1.Length()]; } nsTArray prfInput2; rv = aArgs->GetPrfEvalSecond(prfInput2); if (rv != NS_ERROR_NOT_AVAILABLE) { if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } saltInput2 = [NSData dataWithBytes:prfInput2.Elements() length:prfInput2.Length()]; } ASAuthorizationPublicKeyCredentialPRFAssertionInputValues* prfInputs = [[ASAuthorizationPublicKeyCredentialPRFAssertionInputValues alloc] initWithSaltInput1:saltInput1 saltInput2:saltInput2]; NSDictionary* prfPerCredentialInputs = ConstructPrfEvalByCredentialEntries(aArgs); ASAuthorizationPublicKeyCredentialPRFAssertionInput* prfAssertionInput = [[ASAuthorizationPublicKeyCredentialPRFAssertionInput alloc] initWithInputValues:prfInputs perCredentialInputValues:prfPerCredentialInputs]; platformAssertionRequest.prf = prfAssertionInput; if (__builtin_available(macos 26.4, *)) { crossPlatformAssertionRequest.prf = prfAssertionInput; } } } nsTArray clientDataHash; nsresult rv = aArgs->GetClientDataHash(clientDataHash); if (NS_FAILED(rv)) { AbortTransaction(rv); return NS_OK; } // allowList and allowListTransports won't actually get used. PerformRequests(aTransactionId, @[ platformAssertionRequest, crossPlatformAssertionRequest ], std::move(clientDataHash), std::move(allowList), std::move(allowListTransports), aBrowsingContextId); return NS_OK; } void MacOSWebAuthnService::FinishGetAssertion( uint64_t aTransactionId, nsTArray&& aCredentialId, nsTArray&& aSignature, nsTArray&& aAuthenticatorData, nsTArray&& aUserHandle, Maybe&& aAuthenticatorAttachment, Maybe aUsedAppId, Maybe>&& aLargeBlobValue, Maybe aLargeBlobWritten, Maybe>&& aPrfFirst, Maybe>&& aPrfSecond) { // This is invoked from the platform delegate callback, which may not be // running on the main thread. NS_DispatchToMainThread(NS_NewRunnableFunction( "MacOSWebAuthnService::FinishGetAssertion", [self = RefPtr{this}, aTransactionId, credentialId = std::move(aCredentialId), signature = std::move(aSignature), authenticatorData = std::move(aAuthenticatorData), userHandle = std::move(aUserHandle), authenticatorAttachment = std::move(aAuthenticatorAttachment), usedAppId = aUsedAppId, largeBlobValue = std::move(aLargeBlobValue), largeBlobWritten = aLargeBlobWritten, prfFirst = std::move(aPrfFirst), prfSecond = std::move(aPrfSecond)]() { MOZ_ASSERT(NS_IsMainThread()); if (self->mTransactionId.isNothing() || self->mTransactionId.ref() != aTransactionId) { // The transaction was superseded or cancelled before this // delegate callback fired; the result belongs to a stale // controller. return; } if (!self->mSignPromise) { return; } RefPtr result(new WebAuthnSignResult( authenticatorData, Nothing(), credentialId, signature, userHandle, authenticatorAttachment, usedAppId, largeBlobValue, largeBlobWritten, prfFirst, prfSecond)); (void)self->mSignPromise->Resolve(result); self->mSignPromise = nullptr; self->ReleasePlatformResources(); })); } void MacOSWebAuthnService::ReleasePlatformResources() { MOZ_ASSERT(NS_IsMainThread()); [mCredentialManager release]; mCredentialManager = nil; [mAuthorizationController release]; mAuthorizationController = nil; [mRequestDelegate release]; mRequestDelegate = nil; [mPresentationContextProvider release]; mPresentationContextProvider = nil; mTransactionId.reset(); } NS_IMETHODIMP MacOSWebAuthnService::Reset() { MOZ_ASSERT(NS_IsMainThread()); if (mAuthorizationController) { [mAuthorizationController cancel]; } AbortTransaction(NS_ERROR_DOM_ABORT_ERR); return NS_OK; } NS_IMETHODIMP MacOSWebAuthnService::GetIsUVPAA(bool* aAvailable) { *aAvailable = true; return NS_OK; } NS_IMETHODIMP MacOSWebAuthnService::Cancel(uint64_t aTransactionId) { MOZ_ASSERT(NS_IsMainThread()); if (mTransactionId.isSome() && mTransactionId.ref() == aTransactionId) { Reset(); } return NS_OK; } NS_IMETHODIMP MacOSWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId, const nsAString& aOrigin, uint64_t* aRv) { MOZ_ASSERT(NS_IsMainThread()); *aRv = 0; return NS_OK; } NS_IMETHODIMP MacOSWebAuthnService::GetAutoFillEntries( uint64_t aTransactionId, nsIWebAuthnAutoFillEntriesCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); aCallback->Reject(NS_ERROR_NOT_AVAILABLE); return NS_OK; } NS_IMETHODIMP MacOSWebAuthnService::GetAutoFillEntriesForRpId( const nsAString& aRpId, const nsTArray>& aAllowList, nsIWebAuthnAutoFillEntriesCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); // Apple's completion blocks aren't guaranteed to run on the main thread, // but the callback is a JS-implemented XPCOM object that must only be // released (and invoked) on the main thread. Wrap it in a main-thread // pointer handle and dispatch the resolve/reject back to the main thread. nsMainThreadPtrHandle callback( new nsMainThreadPtrHolder( "MacOSWebAuthnService::GetAutoFillEntriesForRpId::callback", aCallback)); nsString rpId(aRpId); auto credentialsCompletionHandler = ^(NSArray* credentials) { nsTArray> entries; for (NSUInteger i = 0; i < credentials.count; i++) { const auto& credential = credentials[i]; nsAutoString userName; nsCocoaUtils::GetStringForNSString(credential.name, userName); nsAutoString credRpId; nsCocoaUtils::GetStringForNSString(credential.relyingParty, credRpId); entries.AppendElement(new WebAuthnAutoFillEntry( nsIWebAuthnAutoFillEntry::PROVIDER_PLATFORM_MACOS, userName, credRpId, NSDataToArray(credential.credentialID))); } NS_DispatchToMainThread(NS_NewRunnableFunction( "MacOSWebAuthnService::GetAutoFillEntriesForRpId::resolve", [callback, entries = std::move(entries)]() mutable { callback->Resolve(entries); })); }; RefPtr self = this; auto authorizationHandler = ^( ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState authorizationState) { if (authorizationState == ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationStateAuthorized) { [self->mCredentialManager platformCredentialsForRelyingParty:nsCocoaUtils::ToNSString(rpId) completionHandler:credentialsCompletionHandler]; } else { NS_DispatchToMainThread(NS_NewRunnableFunction( "MacOSWebAuthnService::GetAutoFillEntriesForRpId::reject", [callback]() { callback->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR); })); } }; if (!mCredentialManager) { mCredentialManager = [[ASAuthorizationWebBrowserPublicKeyCredentialManager alloc] init]; } [mCredentialManager requestAuthorizationForPublicKeyCredentials:authorizationHandler]; return NS_OK; } NS_IMETHODIMP MacOSWebAuthnService::SelectAutoFillEntry( uint64_t aTransactionId, const nsTArray& aCredentialId) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP MacOSWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP MacOSWebAuthnService::PinCallback(uint64_t aTransactionId, const nsACString& aPin) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId, bool aHasConsent) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::SelectionCallback(uint64_t aTransactionId, uint64_t aIndex) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::AddVirtualAuthenticator( const nsACString& aProtocol, const nsACString& aTransport, bool aHasResidentKey, bool aHasUserVerification, bool aIsUserConsenting, bool aIsUserVerified, nsACString& aRetval) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::HasVirtualAuthenticator( const nsACString& aAuthenticatorId, bool* aRetval) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::RemoveVirtualAuthenticator( const nsACString& aAuthenticatorId) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::AddCredential(const nsACString& aAuthenticatorId, const nsACString& aCredentialId, bool aIsResidentCredential, const nsACString& aRpId, const nsACString& aPrivateKey, const nsACString& aUserHandle, uint32_t aSignCount) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::GetCredentials( const nsACString& aAuthenticatorId, nsTArray>& _aRetval) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::RemoveCredential(const nsACString& aAuthenticatorId, const nsACString& aCredentialId) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::RemoveAllCredentials(const nsACString& aAuthenticatorId) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::SetUserVerified(const nsACString& aAuthenticatorId, bool aIsUserVerified) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::Listen() { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MacOSWebAuthnService::RunCommand(const nsACString& aCmd) { MOZ_ASSERT(NS_IsMainThread()); return NS_ERROR_NOT_IMPLEMENTED; } } // namespace mozilla::dom NS_ASSUME_NONNULL_END