blob: c5c394ad7eef05749d35e44bb2915973c05fdf88 [file] [log] [blame]
/*
* Copyright (C) 2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#if HAVE(UNIFIED_ASC_AUTH_UI)
#import "config.h"
#import "WebAuthenticatorCoordinatorProxy.h"
#import "LocalService.h"
#import "WKError.h"
#import "WebAuthenticationRequestData.h"
#import "WebPageProxy.h"
#import <AuthenticationServices/ASCOSEConstants.h>
#import <WebCore/AuthenticatorAttachment.h>
#import <WebCore/AuthenticatorResponseData.h>
#import <WebCore/BufferSource.h>
#import <WebCore/ExceptionData.h>
#import <WebCore/PublicKeyCredentialCreationOptions.h>
#import <wtf/BlockPtr.h>
#import "AuthenticationServicesCoreSoftLink.h"
namespace WebKit {
using namespace WebCore;
static inline Ref<ArrayBuffer> toArrayBuffer(NSData *data)
{
return ArrayBuffer::create(reinterpret_cast<const uint8_t*>(data.bytes), data.length);
}
static inline RetainPtr<NSData> toNSData(const Vector<uint8_t>& data)
{
return adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]);
}
static inline RetainPtr<NSString> toNSString(UserVerificationRequirement userVerificationRequirement)
{
switch (userVerificationRequirement) {
case UserVerificationRequirement::Required:
return @"required";
case UserVerificationRequirement::Discouraged:
return @"discouraged";
case UserVerificationRequirement::Preferred:
return @"preferred";
}
return @"preferred";
}
static inline RetainPtr<NSString> toNSString(AttestationConveyancePreference attestationConveyancePreference)
{
switch (attestationConveyancePreference) {
case AttestationConveyancePreference::Direct:
return @"direct";
case AttestationConveyancePreference::Indirect:
return @"indirect";
case AttestationConveyancePreference::None:
return @"none";
}
return @"none";
}
static inline ExceptionCode toExceptionCode(NSInteger nsErrorCode)
{
ExceptionCode exceptionCode = (ExceptionCode)nsErrorCode;
switch (exceptionCode) {
case IndexSizeError: FALLTHROUGH;
case HierarchyRequestError: FALLTHROUGH;
case WrongDocumentError: FALLTHROUGH;
case InvalidCharacterError: FALLTHROUGH;
case NoModificationAllowedError: FALLTHROUGH;
case NotFoundError: FALLTHROUGH;
case NotSupportedError: FALLTHROUGH;
case InUseAttributeError: FALLTHROUGH;
case InvalidStateError: FALLTHROUGH;
case SyntaxError: FALLTHROUGH;
case InvalidModificationError: FALLTHROUGH;
case NamespaceError: FALLTHROUGH;
case InvalidAccessError: FALLTHROUGH;
case TypeMismatchError: FALLTHROUGH;
case SecurityError: FALLTHROUGH;
case NetworkError: FALLTHROUGH;
case AbortError: FALLTHROUGH;
case URLMismatchError: FALLTHROUGH;
case QuotaExceededError: FALLTHROUGH;
case TimeoutError: FALLTHROUGH;
case InvalidNodeTypeError: FALLTHROUGH;
case DataCloneError: FALLTHROUGH;
case EncodingError: FALLTHROUGH;
case NotReadableError: FALLTHROUGH;
case UnknownError: FALLTHROUGH;
case ConstraintError: FALLTHROUGH;
case DataError: FALLTHROUGH;
case TransactionInactiveError: FALLTHROUGH;
case ReadonlyError: FALLTHROUGH;
case VersionError: FALLTHROUGH;
case OperationError: FALLTHROUGH;
case NotAllowedError: FALLTHROUGH;
case RangeError: FALLTHROUGH;
case TypeError: FALLTHROUGH;
case JSSyntaxError: FALLTHROUGH;
case StackOverflowError: FALLTHROUGH;
case OutOfMemoryError: FALLTHROUGH;
case ExistingExceptionError:
return exceptionCode;
}
return NotAllowedError;
}
static inline RetainPtr<ASCPublicKeyCredentialDescriptor> toASCDescriptor(PublicKeyCredentialDescriptor descriptor)
{
RetainPtr<NSMutableArray<NSString *>> transports;
size_t transportCount = descriptor.transports.size();
if (transportCount) {
transports = adoptNS([[NSMutableArray alloc] initWithCapacity:transportCount]);
for (AuthenticatorTransport transport : descriptor.transports) {
NSString *transportString = nil;
switch (transport) {
case AuthenticatorTransport::Usb:
transportString = @"usb";
break;
case AuthenticatorTransport::Nfc:
transportString = @"nfc";
break;
case AuthenticatorTransport::Ble:
transportString = @"ble";
break;
case AuthenticatorTransport::Internal:
transportString = @"internal";
break;
}
if (transportString)
[transports addObject:transportString];
}
}
return adoptNS([allocASCPublicKeyCredentialDescriptorInstance() initWithCredentialID:WebCore::toNSData(descriptor.id).get() transports:transports.get()]);
}
static inline RetainPtr<ASCWebAuthenticationExtensionsClientInputs> toASCExtensions(const AuthenticationExtensionsClientInputs& extensions)
{
if ([allocASCWebAuthenticationExtensionsClientInputsInstance() respondsToSelector:@selector(initWithAppID:isGoogleLegacyAppIDSupport:)])
return adoptNS([allocASCWebAuthenticationExtensionsClientInputsInstance() initWithAppID:extensions.appid isGoogleLegacyAppIDSupport:extensions.googleLegacyAppidSupport]);
return nil;
}
static RetainPtr<ASCCredentialRequestContext> configureRegistrationRequestContext(const PublicKeyCredentialCreationOptions& options, Vector<uint8_t> hash)
{
ASCCredentialRequestTypes requestTypes = ASCCredentialRequestTypePlatformPublicKeyRegistration | ASCCredentialRequestTypeSecurityKeyPublicKeyRegistration;
RetainPtr<NSString> userVerification;
bool shouldRequireResidentKey = false;
std::optional<PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria> authenticatorSelection = options.authenticatorSelection;
if (authenticatorSelection) {
std::optional<AuthenticatorAttachment> attachment = authenticatorSelection->authenticatorAttachment;
if (attachment == AuthenticatorAttachment::Platform)
requestTypes = ASCCredentialRequestTypePlatformPublicKeyRegistration;
else if (attachment == AuthenticatorAttachment::CrossPlatform)
requestTypes = ASCCredentialRequestTypeSecurityKeyPublicKeyRegistration;
userVerification = toNSString(authenticatorSelection->userVerification);
shouldRequireResidentKey = authenticatorSelection->requireResidentKey;
}
auto requestContext = adoptNS([allocASCCredentialRequestContextInstance() initWithRequestTypes:requestTypes]);
[requestContext setRelyingPartyIdentifier:options.rp.id];
auto credentialCreationOptions = adoptNS([allocASCPublicKeyCredentialCreationOptionsInstance() init]);
if ([credentialCreationOptions respondsToSelector:@selector(setClientDataHash:)])
[credentialCreationOptions setClientDataHash:toNSData(hash).get()];
else
[credentialCreationOptions setChallenge:WebCore::toNSData(options.challenge).get()];
[credentialCreationOptions setRelyingPartyIdentifier:options.rp.id];
[credentialCreationOptions setUserName:options.user.name];
[credentialCreationOptions setUserIdentifier:WebCore::toNSData(options.user.id).get()];
[credentialCreationOptions setUserDisplayName:options.user.displayName];
[credentialCreationOptions setUserVerificationPreference:userVerification.get()];
[credentialCreationOptions setShouldRequireResidentKey:shouldRequireResidentKey];
[credentialCreationOptions setAttestationPreference:toNSString(options.attestation).get()];
RetainPtr<NSMutableArray<NSNumber *>> supportedAlgorithmIdentifiers = adoptNS([[NSMutableArray alloc] initWithCapacity:options.pubKeyCredParams.size()]);
for (PublicKeyCredentialCreationOptions::Parameters algorithmParameter : options.pubKeyCredParams)
[supportedAlgorithmIdentifiers addObject:@(algorithmParameter.alg)];
[credentialCreationOptions setSupportedAlgorithmIdentifiers:supportedAlgorithmIdentifiers.get()];
size_t excludedCredentialCount = options.excludeCredentials.size();
if (excludedCredentialCount) {
RetainPtr<NSMutableArray<ASCPublicKeyCredentialDescriptor *>> excludedCredentials = adoptNS([[NSMutableArray alloc] initWithCapacity:excludedCredentialCount]);
for (PublicKeyCredentialDescriptor descriptor : options.excludeCredentials)
[excludedCredentials addObject:toASCDescriptor(descriptor).get()];
[credentialCreationOptions setExcludedCredentials:excludedCredentials.get()];
}
if (requestTypes & ASCCredentialRequestTypePlatformPublicKeyRegistration)
[requestContext setPlatformKeyCredentialCreationOptions:credentialCreationOptions.get()];
if (requestTypes & ASCCredentialRequestTypeSecurityKeyPublicKeyRegistration)
[requestContext setSecurityKeyCredentialCreationOptions:credentialCreationOptions.get()];
if (options.extensions && [credentialCreationOptions respondsToSelector:@selector(setExtensions:)])
[credentialCreationOptions setExtensions:toASCExtensions(*options.extensions).get()];
return requestContext;
}
static RetainPtr<ASCCredentialRequestContext> configurationAssertionRequestContext(const PublicKeyCredentialRequestOptions& options, Vector<uint8_t> hash)
{
ASCCredentialRequestTypes requestTypes = ASCCredentialRequestTypePlatformPublicKeyAssertion | ASCCredentialRequestTypeSecurityKeyPublicKeyAssertion;
RetainPtr<NSString> userVerification;
std::optional<AuthenticatorAttachment> attachment = options.authenticatorAttachment;
if (attachment == AuthenticatorAttachment::Platform)
requestTypes = ASCCredentialRequestTypePlatformPublicKeyAssertion;
else if (attachment == AuthenticatorAttachment::CrossPlatform)
requestTypes = ASCCredentialRequestTypeSecurityKeyPublicKeyAssertion;
userVerification = toNSString(options.userVerification);
size_t allowedCredentialCount = options.allowCredentials.size();
RetainPtr<NSMutableArray<ASCPublicKeyCredentialDescriptor *>> allowedCredentials;
if (allowedCredentialCount) {
allowedCredentials = adoptNS([[NSMutableArray alloc] initWithCapacity:allowedCredentialCount]);
for (PublicKeyCredentialDescriptor descriptor : options.allowCredentials)
[allowedCredentials addObject:toASCDescriptor(descriptor).get()];
}
auto requestContext = adoptNS([allocASCCredentialRequestContextInstance() initWithRequestTypes:requestTypes]);
[requestContext setRelyingPartyIdentifier:options.rpId];
if (requestTypes & ASCCredentialRequestTypePlatformPublicKeyAssertion) {
auto assertionOptions = adoptNS(allocASCPublicKeyCredentialAssertionOptionsInstance());
if ([assertionOptions respondsToSelector:@selector(initWithKind:relyingPartyIdentifier:clientDataHash:userVerificationPreference:allowedCredentials:)]) {
auto nsHash = toNSData(hash);
[assertionOptions initWithKind:ASCPublicKeyCredentialKindPlatform relyingPartyIdentifier:options.rpId clientDataHash:nsHash.get() userVerificationPreference:userVerification.get() allowedCredentials:allowedCredentials.get()];
} else {
auto challenge = WebCore::toNSData(options.challenge);
[assertionOptions initWithKind:ASCPublicKeyCredentialKindPlatform relyingPartyIdentifier:options.rpId challenge:challenge.get() userVerificationPreference:userVerification.get() allowedCredentials:allowedCredentials.get()];
}
if (options.extensions && [assertionOptions respondsToSelector:@selector(setExtensions:)])
[assertionOptions setExtensions:toASCExtensions(*options.extensions).get()];
[requestContext setPlatformKeyCredentialAssertionOptions:assertionOptions.get()];
}
if (requestTypes & ASCCredentialRequestTypeSecurityKeyPublicKeyAssertion) {
auto assertionOptions = adoptNS(allocASCPublicKeyCredentialAssertionOptionsInstance());
if ([assertionOptions respondsToSelector:@selector(initWithKind:relyingPartyIdentifier:clientDataHash:userVerificationPreference:allowedCredentials:)]) {
auto nsHash = toNSData(hash);
[assertionOptions initWithKind:ASCPublicKeyCredentialKindSecurityKey relyingPartyIdentifier:options.rpId clientDataHash:nsHash.get() userVerificationPreference:userVerification.get() allowedCredentials:allowedCredentials.get()];
} else {
auto challenge = WebCore::toNSData(options.challenge);
[assertionOptions initWithKind:ASCPublicKeyCredentialKindSecurityKey relyingPartyIdentifier:options.rpId challenge:challenge.get() userVerificationPreference:userVerification.get() allowedCredentials:allowedCredentials.get()];
}
if (options.extensions && [assertionOptions respondsToSelector:@selector(setExtensions:)])
[assertionOptions setExtensions:toASCExtensions(*options.extensions).get()];
[requestContext setSecurityKeyCredentialAssertionOptions:assertionOptions.get()];
}
return requestContext;
}
RetainPtr<ASCCredentialRequestContext> WebAuthenticatorCoordinatorProxy::contextForRequest(WebAuthenticationRequestData&& requestData)
{
RetainPtr<ASCCredentialRequestContext> result;
WTF::switchOn(requestData.options, [&](const PublicKeyCredentialCreationOptions& options) {
result = configureRegistrationRequestContext(options, requestData.hash);
}, [&](const PublicKeyCredentialRequestOptions& options) {
result = configurationAssertionRequestContext(options, requestData.hash);
});
return result;
}
void WebAuthenticatorCoordinatorProxy::performRequest(RetainPtr<ASCCredentialRequestContext> requestContext, RequestCompletionHandler&& handler)
{
auto proxy = adoptNS([allocASCAgentProxyInstance() init]);
RetainPtr<NSWindow> window = m_webPageProxy.platformWindow();
[proxy performAuthorizationRequestsForContext:requestContext.get() withClearanceHandler:makeBlockPtr([weakThis = WeakPtr { *this }, handler = WTFMove(handler), window = WTFMove(window), proxy = WTFMove(proxy)](NSXPCListenerEndpoint *daemonEndpoint, NSError *error) mutable {
callOnMainRunLoop([weakThis, handler = WTFMove(handler), window = WTFMove(window), proxy = WTFMove(proxy), daemonEndpoint = retainPtr(daemonEndpoint), error = retainPtr(error)] () mutable {
if (!weakThis || !daemonEndpoint) {
LOG_ERROR("Could not connect to authorization daemon: %@\n", error.get());
handler({ }, (AuthenticatorAttachment)0, ExceptionData { NotAllowedError, "Operation failed." });
return;
}
weakThis->m_presenter = adoptNS([allocASCAuthorizationRemotePresenterInstance() init]);
[weakThis->m_presenter presentWithWindow:window.get() daemonEndpoint:daemonEndpoint.get() completionHandler:makeBlockPtr([handler = WTFMove(handler), proxy = WTFMove(proxy)](id <ASCCredentialProtocol> credential, NSError *error) mutable {
AuthenticatorResponseData response = { };
AuthenticatorAttachment attachment;
ExceptionData exceptionData = { };
if ([credential isKindOfClass:getASCPlatformPublicKeyCredentialRegistrationClass()]) {
attachment = AuthenticatorAttachment::Platform;
response.isAuthenticatorAttestationResponse = true;
ASCPlatformPublicKeyCredentialRegistration *registrationCredential = credential;
response.rawId = toArrayBuffer(registrationCredential.credentialID);
response.attestationObject = toArrayBuffer(registrationCredential.attestationObject);
} else if ([credential isKindOfClass:getASCSecurityKeyPublicKeyCredentialRegistrationClass()]) {
attachment = AuthenticatorAttachment::CrossPlatform;
response.isAuthenticatorAttestationResponse = true;
ASCSecurityKeyPublicKeyCredentialRegistration *registrationCredential = credential;
response.rawId = toArrayBuffer(registrationCredential.credentialID);
response.attestationObject = toArrayBuffer(registrationCredential.attestationObject);
} else if ([credential isKindOfClass:getASCPlatformPublicKeyCredentialAssertionClass()]) {
attachment = AuthenticatorAttachment::Platform;
response.isAuthenticatorAttestationResponse = false;
ASCPlatformPublicKeyCredentialAssertion *assertionCredential = credential;
response.rawId = toArrayBuffer(assertionCredential.credentialID);
response.authenticatorData = toArrayBuffer(assertionCredential.authenticatorData);
response.signature = toArrayBuffer(assertionCredential.signature);
response.userHandle = toArrayBuffer(assertionCredential.userHandle);
} else if ([credential isKindOfClass:getASCSecurityKeyPublicKeyCredentialAssertionClass()]) {
attachment = AuthenticatorAttachment::CrossPlatform;
response.isAuthenticatorAttestationResponse = false;
ASCSecurityKeyPublicKeyCredentialAssertion *assertionCredential = credential;
response.rawId = toArrayBuffer(assertionCredential.credentialID);
response.authenticatorData = toArrayBuffer(assertionCredential.authenticatorData);
response.signature = toArrayBuffer(assertionCredential.signature);
response.userHandle = toArrayBuffer(assertionCredential.userHandle);
} else {
attachment = (AuthenticatorAttachment) 0;
ExceptionCode exceptionCode;
NSString *errorMessage = nil;
if ([error.domain isEqualToString:WKErrorDomain]) {
exceptionCode = toExceptionCode(error.code);
errorMessage = error.userInfo[NSLocalizedDescriptionKey];
} else {
exceptionCode = NotAllowedError;
if ([error.domain isEqualToString:ASCAuthorizationErrorDomain] && error.code == ASCAuthorizationErrorUserCanceled)
errorMessage = @"This request has been cancelled by the user.";
else
errorMessage = @"Operation failed.";
}
exceptionData = { exceptionCode, errorMessage };
}
handler(response, attachment, exceptionData);
}).get()];
});
}).get()];
}
void WebAuthenticatorCoordinatorProxy::isUserVerifyingPlatformAuthenticatorAvailable(QueryCompletionHandler&& handler)
{
if ([getASCWebKitSPISupportClass() shouldUseAlternateCredentialStore]) {
handler(true);
return;
}
handler(LocalService::isAvailable());
}
} // namespace WebKit
#endif // HAVE(UNIFIED_ASC_AUTH_UI)