910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/credential_provider_extension/passkey_request_details.h"

#import <AuthenticationServices/AuthenticationServices.h>

#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "components/webauthn/core/browser/passkey_model_utils.h"
#import "ios/chrome/common/credential_provider/credential.h"
#import "ios/chrome/credential_provider_extension/passkey_util.h"
#import "ios/chrome/credential_provider_extension/passkey_util_swift.h"
#import "ios/chrome/credential_provider_extension/ui/feature_flags.h"

@interface PasskeyRequestDetails ()

// Hash of client data for credential provider to sign as part of the operation.
@property(strong, nonatomic, readwrite) NSData* clientDataHash;

// A preference for whether the authenticator should attempt to verify that it
// is being used by its owner.
@property(nonatomic, readwrite) BOOL userVerificationRequired;

// The relying party identifier for this request.
@property(copy, nonatomic, readwrite) NSString* relyingPartyIdentifier;

// A list of allowed credential IDs for this request. An empty list means all
// credentials are allowed.
@property(strong, nonatomic, readwrite) NSArray<NSData*>* allowedCredentials;

// A list of excluded credentials for this request. An empty list means no
// credentials are excluded.
@property(strong, nonatomic, readwrite) NSArray<NSData*>* excludedCredentials;

// Whether at least one signing algorithm is supported by the relying party.
// Unused by assertion requests.
@property(nonatomic, readwrite) BOOL algorithmIsSupported;

// The user name of the passkey credential.
@property(copy, nonatomic, readwrite) NSString* userName;

// The user handle of the passkey credential.
@property(strong, nonatomic, readwrite) NSData* userHandle;

@end

@implementation PasskeyRequestDetails {
  PRFData* _prf;
  // Caches whether the registration request supports the large blob extension.
  BOOL _largeBlobCheckSupported;
}

// Checks if Large Blob support is requested from the registration input.
// This is determined by the presence of the 'largeBlob' property on the input
// object which indicates that support is either required or preferred.
+ (BOOL)isLargeBlobSupportRequestedFromRegistrationInput:
    (ASPasskeyRegistrationCredentialExtensionInput*)registrationInput
    API_AVAILABLE(ios(18.0)) {
  if (!IsPasskeyLargeBlobEnabled()) {
    return NO;
  }
  // The presence of the Large Blob input means support is either required or
  // preferred.
  return registrationInput.largeBlob ? YES : NO;
}

- (instancetype)initWithParameters:(ASPasskeyCredentialRequestParameters*)
                                       passkeyCredentialRequestParameters
    isBiometricAuthenticationEnabled:(BOOL)isBiometricAuthenticationEnabled {
  CHECK(passkeyCredentialRequestParameters);

  self = [super init];
  if (self) {
    self.clientDataHash = passkeyCredentialRequestParameters.clientDataHash;
    self.userVerificationRequired = ShouldPerformUserVerificationForPreference(
        passkeyCredentialRequestParameters.userVerificationPreference,
        isBiometricAuthenticationEnabled, /*is_conditional_create=*/NO);
    self.relyingPartyIdentifier =
        passkeyCredentialRequestParameters.relyingPartyIdentifier;
    self.allowedCredentials =
        passkeyCredentialRequestParameters.allowedCredentials;
    self.excludedCredentials = nil;
    self.algorithmIsSupported = NO;
    self.userName = nil;
    self.userHandle = nil;

    if (@available(iOS 18.0, *)) {
      if (IsPasskeyPRFEnabled()) {
        _prf = [PRFData fromParameters:passkeyCredentialRequestParameters];
      }
    }
  }
  return self;
}

- (instancetype)initWithRequest:(id<ASCredentialRequest>)credentialRequest
    isBiometricAuthenticationEnabled:(BOOL)isBiometricAuthenticationEnabled
                 isConditionalCreate:(BOOL)isConditionalCreate {
  CHECK(credentialRequest);

  self = [super init];
  if (self) {
    ASPasskeyCredentialRequest* passkeyCredentialRequest =
        base::apple::ObjCCastStrict<ASPasskeyCredentialRequest>(
            credentialRequest);

    self.clientDataHash = passkeyCredentialRequest.clientDataHash;
    self.userVerificationRequired = ShouldPerformUserVerificationForPreference(
        passkeyCredentialRequest.userVerificationPreference,
        isBiometricAuthenticationEnabled, isConditionalCreate);

    NSArray<NSNumber*>* supportedAlgorithms = [passkeyCredentialRequest
                                                   .supportedAlgorithms
        filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
                                                     NSNumber* algorithm,
                                                     NSDictionary* bindings) {
          return webauthn::passkey_model_utils::IsSupportedAlgorithm(
              algorithm.intValue);
        }]];
    self.algorithmIsSupported = supportedAlgorithms.count > 0;

    ASPasskeyCredentialIdentity* identity =
        base::apple::ObjCCastStrict<ASPasskeyCredentialIdentity>(
            passkeyCredentialRequest.credentialIdentity);

    self.relyingPartyIdentifier = identity.relyingPartyIdentifier;
    self.userName = identity.userName;
    self.userHandle = identity.userHandle;
    self.allowedCredentials = nil;
    self.excludedCredentials = nil;

    if (@available(iOS 18.0, *)) {
      if (passkeyCredentialRequest.excludedCredentials.count) {
        NSMutableArray<NSData*>* excludedCredentials = [NSMutableArray array];
        for (ASAuthorizationPlatformPublicKeyCredentialDescriptor* credential in
                 passkeyCredentialRequest.excludedCredentials) {
          [excludedCredentials addObject:credential.credentialID];
        }
        self.excludedCredentials = [excludedCredentials copy];
      }

      if (IsPasskeyPRFEnabled()) {
        _prf = [PRFData fromRequest:passkeyCredentialRequest];
      }

      // Registration side large blob extension.
      _largeBlobCheckSupported = [PasskeyRequestDetails
          isLargeBlobSupportRequestedFromRegistrationInput:
              passkeyCredentialRequest.registrationExtensionInput];
    }
  }
  return self;
}

- (ASPasskeyRegistrationCredential*)
           createPasskeyForGaia:(NSString*)gaia
          securityDomainSecrets:(NSArray<NSData*>*)securityDomainSecrets
    didCompleteUserVerification:(BOOL)didCompleteUserVerification {
  NSArray<NSData*>* prfInputs = nil;
  if (@available(iOS 18.0, *)) {
    if (_prf.inputValues) {
      prfInputs = [NSArray arrayWithObjects:_prf.inputValues.saltInput1,
                                            _prf.inputValues.saltInput2, nil];
    } else if (_prf.checkForSupport) {
      // Initialize prfInputs with a non nil empty array to check for support.
      prfInputs = [NSArray array];
    }
  }
  PasskeyCreationOutput passkeyCreationOutput = PerformPasskeyCreation(
      self.clientDataHash, self.relyingPartyIdentifier, self.userName,
      self.userHandle, gaia, securityDomainSecrets, prfInputs,
      didCompleteUserVerification);
  if (@available(iOS 18.0, *)) {
    if (passkeyCreationOutput.credential) {
      if ([passkeyCreationOutput.prf_outputs count]) {
        PRFOutputValues* prfOutputValues =
            [PRFOutputValues fromValues:passkeyCreationOutput.prf_outputs];
        [passkeyCreationOutput.credential
            setPRFFromOutputValues:prfOutputValues];
      } else if (_prf.checkForSupport) {
        [passkeyCreationOutput.credential setPRFIsSupported];
      }
      if (_largeBlobCheckSupported) {
        [passkeyCreationOutput.credential setLargeBlobIsSupported];
      }
    }
  }
  return passkeyCreationOutput.credential;
}

- (ASPasskeyAssertionCredential*)
        assertPasskeyCredential:(id<Credential>)credential
          securityDomainSecrets:(NSArray<NSData*>*)securityDomainSecrets
    didCompleteUserVerification:(BOOL)didCompleteUserVerification {
  NSArray<NSData*>* prfInputs = nil;
  PRFInputValues* inputValues = nil;
  if (@available(iOS 18.0, *)) {
    if (_prf) {
      // Check if there's per credential values available.
      inputValues = _prf.perCredentialInputValues[credential.credentialId];
      if (!inputValues) {
        // If there are no per credential values, use the generic values.
        inputValues = _prf.inputValues;
      }
      if (inputValues) {
        prfInputs = [NSArray arrayWithObjects:inputValues.saltInput1,
                                              inputValues.saltInput2, nil];
      }
    }
  }
  PasskeyAssertionOutput passkeyAssertionOutput = PerformPasskeyAssertion(
      credential, self.clientDataHash, self.allowedCredentials,
      securityDomainSecrets, prfInputs, didCompleteUserVerification);
  if (@available(iOS 18.0, *)) {
    if (passkeyAssertionOutput.credential &&
        [passkeyAssertionOutput.prf_outputs count]) {
      PRFOutputValues* prfOutputValues =
          [PRFOutputValues fromValues:passkeyAssertionOutput.prf_outputs];
      [passkeyAssertionOutput.credential
          setPRFFromOutputValues:prfOutputValues];
    }
  }
  return passkeyAssertionOutput.credential;
}

- (BOOL)hasMatchingPassword:(NSArray<id<Credential>>*)credentials {
  if (!credentials.count) {
    return NO;
  }

  NSString* rpID = self.relyingPartyIdentifier;
  NSUInteger credentialIndex =
      [credentials indexOfObjectPassingTest:^BOOL(id<Credential> credential,
                                                  NSUInteger idx, BOOL* stop) {
        NSString* domainSuffix = [NSString
            stringWithFormat:@".%@", credential.registryControlledDomain];
        BOOL matchingDomain =
            [rpID isEqualToString:credential.registryControlledDomain] ||
            [rpID hasSuffix:domainSuffix];
        return !credential.isPasskey && matchingDomain &&
               [credential.username isEqualToString:self.userName];
      }];
  return credentialIndex != NSNotFound;
}

- (BOOL)hasExcludedPasskey:(NSArray<id<Credential>>*)credentials {
  if (!credentials.count || !self.excludedCredentials.count) {
    return NO;
  }

  NSUInteger credentialIndex = [credentials indexOfObjectPassingTest:^BOOL(
                                                id<Credential> credential,
                                                NSUInteger idx, BOOL* stop) {
    return credential.isPasskey &&
           [credential.rpId isEqualToString:self.relyingPartyIdentifier] &&
           [self.excludedCredentials containsObject:credential.credentialId];
  }];
  return credentialIndex != NSNotFound;
}

#pragma mark - PasskeyRequestDetails (Testing)

- (instancetype)initWithURL:(NSString*)url
                   username:(NSString*)username
        excludedCredentials:(NSArray<NSData*>*)excludedCredentials {
  self = [super init];
  if (self) {
    self.relyingPartyIdentifier = url;
    self.userName = username;
    self.excludedCredentials = excludedCredentials;
  }
  return self;
}

@end