Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/webauthn/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 61 kB image not shown  

Quelle  MacOSWebAuthnService.mm   Sprache: unbekannt

 
Spracherkennung für: .mm vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

/* 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 <AuthenticationServices/AuthenticationServices.h>

#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 <NSSecureCoding>
- (instancetype)initWithCredentialID:(NSData*)credentialID
                          transports:
                              (nullable NSArray<NSString*>*)allowedTransports;
@end

@protocol ASCPublicKeyCredentialCreationOptions
@property(nonatomic, copy) NSData* clientDataHash;
@property(nonatomic, nullable, copy) NSData* challenge;
@property(nonatomic, copy)
    NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials;
@end

@protocol ASCPublicKeyCredentialAssertionOptions <NSCopying>
@property(nonatomic, copy) NSData* clientDataHash;
@end

@protocol ASCCredentialRequestContext
@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
    platformKeyCredentialCreationOptions;
@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
    securityKeyCredentialCreationOptions;
@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
    platformKeyCredentialAssertionOptions;
@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
    securityKeyCredentialAssertionOptions;
@end

@interface ASAuthorizationController (Secrets)
- (id<ASCCredentialRequestContext>)
    _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
                          error:(NSError**)outError;
@end

NSArray<NSString*>* TransportsByteToTransportsArray(const uint8_t aTransports)
    API_AVAILABLE(macos(13.3)) {
  NSMutableArray<NSString*>* 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<nsTArray<uint8_t>>& aCredentialList,
    const nsTArray<uint8_t>& 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<uint8_t>& credentialId = aCredentialList[i];
    const uint8_t& credentialTransports = aCredentialListTransports[i];
    NSData* credentialIdNS = [NSData dataWithBytes:credentialId.Elements()
                                            length:credentialId.Length()];
    NSArray<NSString*>* 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<uint8_t> mClientDataHash;
  nsTArray<nsTArray<uint8_t>> mCredentialList;
  nsTArray<uint8_t> mCredentialListTransports;
}

- (void)setRegistrationOptions:
    (id<ASCPublicKeyCredentialCreationOptions>)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<ASCPublicKeyCredentialDescriptor*>* excludedCredentials =
      CredentialListsToCredentialDescriptorArray(
          mCredentialList, mCredentialListTransports,
          publicKeyCredentialDescriptorClass);
  if ([excludedCredentials count] > 0) {
    registrationOptions.excludedCredentials = excludedCredentials;
  }
}

- (void)stashClientDataHash:(nsTArray<uint8_t>&&)clientDataHash
              andCredentialList:(nsTArray<nsTArray<uint8_t>>&&)credentialList
    andCredentialListTransports:(nsTArray<uint8_t>&&)credentialListTransports {
  mClientDataHash = std::move(clientDataHash);
  mCredentialList = std::move(credentialList);
  mCredentialListTransports = std::move(credentialListTransports);
}

- (id<ASCCredentialRequestContext>)
    _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
                          error:(NSError**)outError {
  id<ASCCredentialRequestContext> context =
      [super _requestContextWithRequests:requests error:outError];

  if (context.platformKeyCredentialCreationOptions) {
    [self setRegistrationOptions:context.platformKeyCredentialCreationOptions];
  }
  if (context.securityKeyCredentialCreationOptions) {
    [self setRegistrationOptions:context.securityKeyCredentialCreationOptions];
  }

  if (context.platformKeyCredentialAssertionOptions) {
    id<ASCPublicKeyCredentialAssertionOptions> assertionOptions =
        context.platformKeyCredentialAssertionOptions;
    assertionOptions.clientDataHash =
        [NSData dataWithBytes:mClientDataHash.Elements()
                       length:mClientDataHash.Length()];
    context.platformKeyCredentialAssertionOptions =
        [assertionOptions copyWithZone:nil];
  }
  if (context.securityKeyCredentialAssertionOptions) {
    id<ASCPublicKeyCredentialAssertionOptions> 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 <ASAuthorizationControllerDelegate>
- (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 <ASAuthorizationControllerPresentationContextProviding>
@property(nonatomic, strong) NSWindow* window;
@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<uint8_t>&& aRawAttestationObject,
                            nsTArray<uint8_t>&& aCredentialId,
                            nsTArray<nsString>&& aTransports,
                            Maybe<nsString>&& aAuthenticatorAttachment,
                            Maybe<bool> aLargeBlobSupported,
                            Maybe<bool> aPrfSupported,
                            Maybe<nsTArray<uint8_t>>&& aPrfFirst,
                            Maybe<nsTArray<uint8_t>>&& aPrfSecond);

  void FinishGetAssertion(
      uint64_t aTransactionId, nsTArray<uint8_t>&& aCredentialId,
      nsTArray<uint8_t>&& aSignature, nsTArray<uint8_t>&& aAuthenticatorData,
      nsTArray<uint8_t>&& aUserHandle,
      Maybe<nsString>&& aAuthenticatorAttachment, Maybe<bool> aUsedAppId,
      Maybe<nsTArray<uint8_t>>&& aLargeBlobValue, Maybe<bool> aLargeBlobWritten,
      Maybe<nsTArray<uint8_t>>&& aPrfFirst,
      Maybe<nsTArray<uint8_t>>&& 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<ASAuthorizationRequest*>* aRequests,
                       nsTArray<uint8_t>&& aClientDataHash,
                       nsTArray<nsTArray<uint8_t>>&& aCredentialList,
                       nsTArray<uint8_t>&& aCredentialListTransports,
                       uint64_t aBrowsingContextId);

  // Main thread only:
  Maybe<uint64_t> mTransactionId;
  ASAuthorizationWebBrowserPublicKeyCredentialManager* mCredentialManager = nil;
  nsCOMPtr<nsIWebAuthnRegisterPromise> mRegisterPromise;
  nsCOMPtr<nsIWebAuthnSignPromise> mSignPromise;
  MacOSAuthorizationController* mAuthorizationController = nil;
  MacOSAuthenticatorRequestDelegate* mRequestDelegate = nil;
  MacOSAuthenticatorPresentationContextProvider* mPresentationContextProvider =
      nil;
};
#pragma clang diagnostic pop

}  // namespace mozilla::dom

nsTArray<uint8_t> NSDataToArray(NSData* data) {
  nsTArray<uint8_t> array(reinterpret_cast<const uint8_t*>(data.bytes),
                          data.length);
  return array;
}

API_AVAILABLE(macos(15.0))
NSDictionary<NSData*, ASAuthorizationPublicKeyCredentialPRFAssertionInputValues*>* _Nullable ConstructPrfEvalByCredentialEntries(
    const RefPtr<nsIWebAuthnSignArgs>& aArgs) {
  nsTArray<nsTArray<uint8_t>> prfEvalByCredIds;
  nsTArray<nsTArray<uint8_t>> prfEvalByCredFirsts;
  nsTArray<bool> prfEvalByCredSecondMaybes;
  nsTArray<nsTArray<uint8_t>> 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<NSData*>* keys = [NSMutableArray arrayWithCapacity:count];
  NSMutableArray<ASAuthorizationPublicKeyCredentialPRFAssertionInputValues*>*
      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<mozilla::dom::MacOSWebAuthnService> 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<ASAuthorizationPublicKeyCredentialRegistration> credential =
        (id<ASAuthorizationPublicKeyCredentialRegistration>)
            authorization.credential;
    nsTArray<uint8_t> rawAttestationObject(
        NSDataToArray(credential.rawAttestationObject));
    nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
    nsTArray<nsString> transports;
    mozilla::Maybe<nsString> authenticatorAttachment;
    mozilla::Maybe<bool> largeBlobSupported;
    mozilla::Maybe<bool> prfSupported;
    mozilla::Maybe<nsTArray<uint8_t>> prfFirst;
    mozilla::Maybe<nsTArray<uint8_t>> 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<ASAuthorizationPublicKeyCredentialAssertion> credential =
        (id<ASAuthorizationPublicKeyCredentialAssertion>)
            authorization.credential;
    nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
    nsTArray<uint8_t> signature(NSDataToArray(credential.signature));
    nsTArray<uint8_t> rawAuthenticatorData(
        NSDataToArray(credential.rawAuthenticatorData));
    nsTArray<uint8_t> userHandle(NSDataToArray(credential.userID));
    mozilla::Maybe<nsString> authenticatorAttachment;
    mozilla::Maybe<bool> usedAppId;
    mozilla::Maybe<nsTArray<uint8_t>> largeBlobValue;
    mozilla::Maybe<bool> largeBlobWritten;
    mozilla::Maybe<nsTArray<uint8_t>> prfFirst;
    mozilla::Maybe<nsTArray<uint8_t>> 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;
  // For some reason, the error for "the credential used in a registration was
  // on the exclude list" is in the "WKErrorDomain" domain with code 8, which
  // is presumably WKErrorDuplicateCredential.
  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 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 window = window;

- (ASPresentationAnchor)presentationAnchorForAuthorizationController:
    (ASAuthorizationController*)controller {
  return window;
}
@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(
      BrowsingContext::Get(browsingContextId));
  if (!browsingContext) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  CanonicalBrowsingContext* canonicalBrowsingContext =
      browsingContext->Canonical();
  if (!canonicalBrowsingContext) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  nsCOMPtr<nsIWidget> widget(
      canonicalBrowsingContext->GetParentProcessWidgetContaining());
  if (!widget) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  *window = static_cast<NSWindow*>(widget->GetNativeData(NS_NATIVE_WINDOW));
  return NS_OK;
}
#pragma clang diagnostic pop

already_AddRefed<nsIWebAuthnService> 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<SecTaskRef> entitlementTask(
      CFTypeRefPtr<SecTaskRef>::WrapUnderCreateRule(
          SecTaskCreateFromSelf(nullptr)));
  CFTypeRefPtr<CFBooleanRef> entitlementValue(
      CFTypeRefPtr<CFBooleanRef>::WrapUnderCreateRule(
          reinterpret_cast<CFBooleanRef>(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<nsIWebAuthnService> 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<uint8_t> challenge;
  (void)aArgs->GetChallenge(challenge);
  NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
                                       length:challenge.Length()];

  nsTArray<uint8_t> 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<int32_t> 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<nsTArray<uint8_t>> excludeList;
  (void)aArgs->GetExcludeList(excludeList);
  nsTArray<uint8_t> excludeListTransports;
  (void)aArgs->GetExcludeListTransports(excludeListTransports);
  if (excludeList.Length() != excludeListTransports.Length()) {
    AbortTransaction(NS_ERROR_INVALID_ARG);
    return NS_OK;
  }

  Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
      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<nsString> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<ASAuthorizationRequest*>* aRequests,
    nsTArray<uint8_t>&& aClientDataHash,
    nsTArray<nsTArray<uint8_t>>&& aCredentialList,
    nsTArray<uint8_t>&& 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.window = window;
  mAuthorizationController.presentationContextProvider =
      mPresentationContextProvider;

  // Finally, perform the request.
  [mAuthorizationController performRequests];
}

void MacOSWebAuthnService::FinishMakeCredential(
    uint64_t aTransactionId, nsTArray<uint8_t>&& aRawAttestationObject,
    nsTArray<uint8_t>&& aCredentialId, nsTArray<nsString>&& aTransports,
    Maybe<nsString>&& aAuthenticatorAttachment, Maybe<bool> aLargeBlobSupported,
    Maybe<bool> aPrfSupported, Maybe<nsTArray<uint8_t>>&& aPrfFirst,
    Maybe<nsTArray<uint8_t>>&& 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<WebAuthnRegisterResult> 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<uint8_t> challenge;
  (void)aArgs->GetChallenge(challenge);
  NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
                                       length:challenge.Length()];

  nsTArray<nsTArray<uint8_t>> allowList;
  nsTArray<uint8_t> 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<ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor*>*
      crossPlatformAllowedCredentials =
          CredentialListsToCredentialDescriptorArray(
              allowList, allowListTransports,
              securityKeyPublicKeyCredentialDescriptorClass);

  Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
      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<nsString> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<NSData*,
                   ASAuthorizationPublicKeyCredentialPRFAssertionInputValues*>*
          prfPerCredentialInputs = ConstructPrfEvalByCredentialEntries(aArgs);
      ASAuthorizationPublicKeyCredentialPRFAssertionInput* prfAssertionInput =
          [[ASAuthorizationPublicKeyCredentialPRFAssertionInput alloc]
                   initWithInputValues:prfInputs
              perCredentialInputValues:prfPerCredentialInputs];
      platformAssertionRequest.prf = prfAssertionInput;
      if (__builtin_available(macos 26.4, *)) {
        crossPlatformAssertionRequest.prf = prfAssertionInput;
      }
    }
  }

  nsTArray<uint8_t> 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<uint8_t>&& aCredentialId,
    nsTArray<uint8_t>&& aSignature, nsTArray<uint8_t>&& aAuthenticatorData,
    nsTArray<uint8_t>&& aUserHandle, Maybe<nsString>&& aAuthenticatorAttachment,
    Maybe<bool> aUsedAppId, Maybe<nsTArray<uint8_t>>&& aLargeBlobValue,
    Maybe<bool> aLargeBlobWritten, Maybe<nsTArray<uint8_t>>&& aPrfFirst,
    Maybe<nsTArray<uint8_t>>&& 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<WebAuthnSignResult> 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<nsTArray<uint8_t>>& 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<nsIWebAuthnAutoFillEntriesCallback> callback(
      new nsMainThreadPtrHolder<nsIWebAuthnAutoFillEntriesCallback>(
          "MacOSWebAuthnService::GetAutoFillEntriesForRpId::callback",
          aCallback));
  nsString rpId(aRpId);
  auto credentialsCompletionHandler =
      ^(NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*
            credentials) {
        nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>> 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<MacOSWebAuthnService> 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<uint8_t>& 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::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<RefPtr<nsICredentialParameters>>& _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

[Dauer der Verarbeitung: 0.27 Sekunden, vorverarbeitet 2026-08-24]