Changeset 257269 in webkit


Ignore:
Timestamp:
Feb 24, 2020 3:51:17 PM (4 years ago)
Author:
jiewen_tan@apple.com
Message:

[WebAuthn] Implement SPI for the platform authenticator
https://bugs.webkit.org/show_bug.cgi?id=208087
<rdar://problem/59369305>

Reviewed by Brent Fulgham.

Source/WebCore:

Enhances AuthenticatorAssertionResponse to accommondate responses
returned from the platform authenticator.

Covered by API tests.

  • Modules/webauthn/AuthenticatorAssertionResponse.cpp:

(WebCore::AuthenticatorAssertionResponse::create):
(WebCore::AuthenticatorAssertionResponse::setAuthenticatorData):
(WebCore::AuthenticatorAssertionResponse::AuthenticatorAssertionResponse):

  • Modules/webauthn/AuthenticatorAssertionResponse.h:

(WebCore::AuthenticatorAssertionResponse::authenticatorData const):
(WebCore::AuthenticatorAssertionResponse::signature const):
(WebCore::AuthenticatorAssertionResponse::name const):
(WebCore::AuthenticatorAssertionResponse::displayName const):
(WebCore::AuthenticatorAssertionResponse::numberOfCredentials const):
(WebCore::AuthenticatorAssertionResponse::accessControl const):
(WebCore::AuthenticatorAssertionResponse::setSignature):
(WebCore::AuthenticatorAssertionResponse::setName):
(WebCore::AuthenticatorAssertionResponse::setDisplayName):
(WebCore::AuthenticatorAssertionResponse::setNumberOfCredentials):

Source/WebKit:

Here is the newly added SPI:
typedef NS_ENUM(NSInteger, _WKWebAuthenticationPanelUpdate) {

...
_WKWebAuthenticationPanelUpdateLAError,
_WKWebAuthenticationPanelUpdateLADuplicateCredential,
_WKWebAuthenticationPanelUpdateLANoCredential,

};

typedef NS_ENUM(NSInteger, _WKWebAuthenticationTransport) {

...
_WKWebAuthenticationTransportInternal,

};

@protocol _WKWebAuthenticationPanelDelegate <NSObject>
@optional
...

  • (void)panel:(_WKWebAuthenticationPanel *)panel verifyUserWithAccessControl:(SecAccessControlRef)accessControl completionHandler:(void ()(LAContext *))completionHandler;

@end

Illustrations:
1) _WKWebAuthenticationPanelUpdate: Three errors are added to help clients present meaningful error messages to users.
a) WKWebAuthenticationPanelUpdateLAError: An internal error, clients should inform users and terminate the platform
authentication process. This error can be returned at any time.
b) _WKWebAuthenticationPanelUpdateLADuplicateCredential: It means a credential is found to match an entry in the
excludeList. Clients should inform users and terminate the platform authentication process. This error will only be
returned during makeCredential and before verifyUserWithAccessControl delegate.
c) _WKWebAuthenticationPanelUpdateLANoCredential: It means no credentials are found. Clients should inform users and
terminate the platform authentication process. This error will only be returned during getAssertion and before
verifyUserWithAccessControl delegate.

2) _WKWebAuthenticationTransport: _WKWebAuthenticationTransportInternal is added such that clients can learn platform
authenticator will be used from _WKWebAuthenticationPanel.transports.

3) verifyUserWithAccessControl: A delegate that will be called during makeCredential or getAssertion when the platform
authenticator is involved. This delegate is used to obtain user verification from a LAContext. In addition, the LAContext
should evaluate the passed accessControl, such that the SEP protected credential private key can be used. A typical
example will be [LAContext evaluateAccessControl:accessControl operation:LAAccessControlOperationUseKeySign localizedReason:reply:].
Noted, for getAssertion, selectAssertionResponse will be called before verifyUserWithAccessControl. So users need to be
prompted to select a credential before the user verification.

In the scenario when both the platform authenticator and external authenticators are requested. Clients are advised to
wait until verifyUserWithAccessControl to show the combined UI. If any of the LAError states are received before
verifyUserWithAccessControl, clients should then only show the external authenticator UI. Also, platform authenticator and
external authenticators are being discovered at the same time, which means a user can plug in a security key at anytime.
If a valid response is received from the security key, the whole ceremony will be terminated.

Besides introducing the SPI, and all the necessary plumbing to make it happen. This patch also:
1) adds LocalAuthenticationSPI, which is used to check whether a given LAContext is unlocked or not;
2) improves MockLocalConnection such that mock testing can still be ran.

  • Platform/spi/Cocoa/LocalAuthenticationSPI.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h.
  • UIProcess/API/APIWebAuthenticationPanel.cpp:

(API::WebAuthenticationPanel::WebAuthenticationPanel):

  • UIProcess/API/APIWebAuthenticationPanelClient.h:

(API::WebAuthenticationPanelClient::verifyUser const):

  • UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
  • UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:

(wkWebAuthenticationTransport):

  • UIProcess/WebAuthentication/Authenticator.h:
  • UIProcess/WebAuthentication/AuthenticatorManager.cpp:

(WebKit::AuthenticatorManager::verifyUser):

  • UIProcess/WebAuthentication/AuthenticatorManager.h:
  • UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h:
  • UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h:
  • UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:

(WebKit::LocalAuthenticatorInternal::toNSData):
(WebKit::LocalAuthenticatorInternal::toArrayBuffer):
(WebKit::LocalAuthenticator::makeCredential):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterUserConsented):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
(WebKit::LocalAuthenticator::getAssertion):
(WebKit::LocalAuthenticator::continueGetAssertionAfterResponseSelected):
(WebKit::LocalAuthenticator::continueGetAssertionAfterUserConsented):
(WebKit::LocalAuthenticator::receiveException const):

  • UIProcess/WebAuthentication/Cocoa/LocalConnection.h:

(WebKit::LocalConnection::filterResponses const):

  • UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:

(WebKit::LocalConnection::isUnlocked const):
(WebKit::LocalConnection::getUserConsent const): Deleted.
(WebKit::LocalConnection::selectCredential const): Deleted.

  • UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h:
  • UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm:

(WebKit::WebAuthenticationPanelClient::WebAuthenticationPanelClient):
(WebKit::wkWebAuthenticationPanelUpdate):
(WebKit::WebAuthenticationPanelClient::selectAssertionResponse const):
(WebKit::WebAuthenticationPanelClient::verifyUser const):

  • UIProcess/WebAuthentication/Mock/MockLocalConnection.h:
  • UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:

(WebKit::MockLocalConnection::isUnlocked const):
(WebKit::MockLocalConnection::filterResponses const):
(WebKit::MockLocalConnection::getUserConsent const): Deleted.
(WebKit::MockLocalConnection::selectCredential const): Deleted.

  • UIProcess/WebAuthentication/WebAuthenticationFlags.h:
  • WebKit.xcodeproj/project.pbxproj:

Tools:

Besides adding API tests, this patch also teaches TestWebKitAPI to use restricted entitlements.

  • TestWebKitAPI/Configurations/TestWebKitAPI-macOS.entitlements:
  • TestWebKitAPI/Configurations/TestWebKitAPI.xcconfig:
  • TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
  • TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:

(-[TestWebAuthenticationPanelDelegate panel:updateWebAuthenticationPanel:]):
(-[TestWebAuthenticationPanelDelegate panel:selectAssertionResponse:completionHandler:]):
(-[TestWebAuthenticationPanelDelegate panel:verifyUserWithAccessControl:completionHandler:]):
(TestWebKitAPI::TEST):

  • TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-la.html: Copied from Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion.html.
  • TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-la-duplicate-credential.html: Added.
  • TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-la-error.html: Added.
  • TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-la.html: Added.
Location:
trunk
Files:
4 added
27 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r257268 r257269  
     12020-02-24  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Implement SPI for the platform authenticator
     4        https://bugs.webkit.org/show_bug.cgi?id=208087
     5        <rdar://problem/59369305>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Enhances AuthenticatorAssertionResponse to accommondate responses
     10        returned from the platform authenticator.
     11
     12        Covered by API tests.
     13
     14        * Modules/webauthn/AuthenticatorAssertionResponse.cpp:
     15        (WebCore::AuthenticatorAssertionResponse::create):
     16        (WebCore::AuthenticatorAssertionResponse::setAuthenticatorData):
     17        (WebCore::AuthenticatorAssertionResponse::AuthenticatorAssertionResponse):
     18        * Modules/webauthn/AuthenticatorAssertionResponse.h:
     19        (WebCore::AuthenticatorAssertionResponse::authenticatorData const):
     20        (WebCore::AuthenticatorAssertionResponse::signature const):
     21        (WebCore::AuthenticatorAssertionResponse::name const):
     22        (WebCore::AuthenticatorAssertionResponse::displayName const):
     23        (WebCore::AuthenticatorAssertionResponse::numberOfCredentials const):
     24        (WebCore::AuthenticatorAssertionResponse::accessControl const):
     25        (WebCore::AuthenticatorAssertionResponse::setSignature):
     26        (WebCore::AuthenticatorAssertionResponse::setName):
     27        (WebCore::AuthenticatorAssertionResponse::setDisplayName):
     28        (WebCore::AuthenticatorAssertionResponse::setNumberOfCredentials):
     29
    1302020-02-24  Andres Gonzalez  <andresg_22@apple.com>
    231
  • trunk/Source/WebCore/Modules/webauthn/AuthenticatorAssertionResponse.cpp

    r253398 r257269  
    4141}
    4242
    43 Ref<AuthenticatorAssertionResponse> AuthenticatorAssertionResponse::create(const Vector<uint8_t>& rawId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature,  const Vector<uint8_t>& userHandle)
     43Ref<AuthenticatorAssertionResponse> AuthenticatorAssertionResponse::create(const Vector<uint8_t>& rawId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle)
    4444{
    4545    RefPtr<ArrayBuffer> userhandleBuffer;
     
    4949}
    5050
     51Ref<AuthenticatorAssertionResponse> AuthenticatorAssertionResponse::create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, SecAccessControlRef accessControl)
     52{
     53    return adoptRef(*new AuthenticatorAssertionResponse(WTFMove(rawId), WTFMove(userHandle), accessControl));
     54}
     55
     56void AuthenticatorAssertionResponse::setAuthenticatorData(Vector<uint8_t>&& authenticatorData)
     57{
     58    m_authenticatorData = ArrayBuffer::create(authenticatorData.data(), authenticatorData.size());
     59}
     60
    5161AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& authenticatorData, Ref<ArrayBuffer>&& signature, RefPtr<ArrayBuffer>&& userHandle)
    5262    : AuthenticatorResponse(WTFMove(rawId))
     
    5464    , m_signature(WTFMove(signature))
    5565    , m_userHandle(WTFMove(userHandle))
     66{
     67}
     68
     69AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, SecAccessControlRef accessControl)
     70    : AuthenticatorResponse(WTFMove(rawId))
     71    , m_userHandle(WTFMove(userHandle))
     72    , m_accessControl(accessControl)
    5673{
    5774}
  • trunk/Source/WebCore/Modules/webauthn/AuthenticatorAssertionResponse.h

    r254554 r257269  
    2929
    3030#include "AuthenticatorResponse.h"
     31#include <wtf/RetainPtr.h>
     32#include <wtf/spi/cocoa/SecuritySPI.h>
    3133
    3234namespace WebCore {
     
    3638    static Ref<AuthenticatorAssertionResponse> create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& authenticatorData, Ref<ArrayBuffer>&& signature, RefPtr<ArrayBuffer>&& userHandle, Optional<AuthenticationExtensionsClientOutputs>&&);
    3739    WEBCORE_EXPORT static Ref<AuthenticatorAssertionResponse> create(const Vector<uint8_t>& rawId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature,  const Vector<uint8_t>& userHandle);
     40    WEBCORE_EXPORT static Ref<AuthenticatorAssertionResponse> create(Ref<ArrayBuffer>&& rawId, Ref<ArrayBuffer>&& userHandle, SecAccessControlRef);
    3841    virtual ~AuthenticatorAssertionResponse() = default;
    3942
    40     ArrayBuffer* authenticatorData() const { return m_authenticatorData.ptr(); }
    41     ArrayBuffer* signature() const { return m_signature.ptr(); }
     43    ArrayBuffer* authenticatorData() const { return m_authenticatorData.get(); }
     44    ArrayBuffer* signature() const { return m_signature.get(); }
    4245    ArrayBuffer* userHandle() const { return m_userHandle.get(); }
     46    const String& name() const { return m_name; }
     47    const String& displayName() const { return m_displayName; }
     48    size_t numberOfCredentials() const { return m_numberOfCredentials; }
     49    SecAccessControlRef accessControl() const { return m_accessControl.get(); }
    4350
     51    WEBCORE_EXPORT void setAuthenticatorData(Vector<uint8_t>&&);
     52    void setSignature(Ref<ArrayBuffer>&& signature) { m_signature = WTFMove(signature); }
    4453    void setName(const String& name) { m_name = name; }
    45     const String& name() const { return m_name; }
    4654    void setDisplayName(const String& displayName) { m_displayName = displayName; }
    47     const String& displayName() const { return m_displayName; }
    4855    void setNumberOfCredentials(size_t numberOfCredentials) { m_numberOfCredentials = numberOfCredentials; }
    49     size_t numberOfCredentials() const { return m_numberOfCredentials; }
    5056
    5157private:
    5258    AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, RefPtr<ArrayBuffer>&&);
     59    AuthenticatorAssertionResponse(Ref<ArrayBuffer>&&, Ref<ArrayBuffer>&&, SecAccessControlRef);
    5360
    5461    Type type() const final { return Type::Assertion; }
    5562    AuthenticatorResponseData data() const final;
    5663
    57     Ref<ArrayBuffer> m_authenticatorData;
    58     Ref<ArrayBuffer> m_signature;
     64    RefPtr<ArrayBuffer> m_authenticatorData;
     65    RefPtr<ArrayBuffer> m_signature;
    5966    RefPtr<ArrayBuffer> m_userHandle;
    6067
     
    6269    String m_displayName;
    6370    size_t m_numberOfCredentials { 0 };
     71    RetainPtr<SecAccessControlRef> m_accessControl;
    6472};
    6573
  • trunk/Source/WebKit/ChangeLog

    r257268 r257269  
     12020-02-24  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Implement SPI for the platform authenticator
     4        https://bugs.webkit.org/show_bug.cgi?id=208087
     5        <rdar://problem/59369305>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Here is the newly added SPI:
     10        typedef NS_ENUM(NSInteger, _WKWebAuthenticationPanelUpdate) {
     11            ...
     12            _WKWebAuthenticationPanelUpdateLAError,
     13            _WKWebAuthenticationPanelUpdateLADuplicateCredential,
     14            _WKWebAuthenticationPanelUpdateLANoCredential,
     15        };
     16
     17        typedef NS_ENUM(NSInteger, _WKWebAuthenticationTransport) {
     18            ...
     19            _WKWebAuthenticationTransportInternal,
     20        };
     21
     22        @protocol _WKWebAuthenticationPanelDelegate <NSObject>
     23        @optional
     24        ...
     25        - (void)panel:(_WKWebAuthenticationPanel *)panel verifyUserWithAccessControl:(SecAccessControlRef)accessControl completionHandler:(void (^)(LAContext *))completionHandler;
     26        @end
     27
     28        Illustrations:
     29        1) _WKWebAuthenticationPanelUpdate: Three errors are added to help clients present meaningful error messages to users.
     30        a) WKWebAuthenticationPanelUpdateLAError: An internal error, clients should inform users and terminate the platform
     31        authentication process. This error can be returned at any time.
     32        b) _WKWebAuthenticationPanelUpdateLADuplicateCredential: It means a credential is found to match an entry in the
     33        excludeList. Clients should inform users and terminate the platform authentication process. This error will only be
     34        returned during makeCredential and before verifyUserWithAccessControl delegate.
     35        c) _WKWebAuthenticationPanelUpdateLANoCredential: It means no credentials are found. Clients should inform users and
     36        terminate the platform authentication process. This error will only be returned during getAssertion and before
     37        verifyUserWithAccessControl delegate.
     38
     39        2) _WKWebAuthenticationTransport: _WKWebAuthenticationTransportInternal is added such that clients can learn platform
     40        authenticator will be used from _WKWebAuthenticationPanel.transports.
     41
     42        3) verifyUserWithAccessControl: A delegate that will be called during makeCredential or getAssertion when the platform
     43        authenticator is involved. This delegate is used to obtain user verification from a LAContext. In addition, the LAContext
     44        should evaluate the passed accessControl, such that the SEP protected credential private key can be used. A typical
     45        example will be [LAContext evaluateAccessControl:accessControl operation:LAAccessControlOperationUseKeySign localizedReason:reply:].
     46        Noted, for getAssertion, selectAssertionResponse will be called before verifyUserWithAccessControl. So users need to be
     47        prompted to select a credential before the user verification.
     48
     49        In the scenario when both the platform authenticator and external authenticators are requested. Clients are advised to
     50        wait until verifyUserWithAccessControl to show the combined UI. If any of the LAError states are received before
     51        verifyUserWithAccessControl, clients should then only show the external authenticator UI. Also, platform authenticator and
     52        external authenticators are being discovered at the same time, which means a user can plug in a security key at anytime.
     53        If a valid response is received from the security key, the whole ceremony will be terminated.
     54
     55        Besides introducing the SPI, and all the necessary plumbing to make it happen. This patch also:
     56        1) adds LocalAuthenticationSPI, which is used to check whether a given LAContext is unlocked or not;
     57        2) improves MockLocalConnection such that mock testing can still be ran.
     58
     59        * Platform/spi/Cocoa/LocalAuthenticationSPI.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h.
     60        * UIProcess/API/APIWebAuthenticationPanel.cpp:
     61        (API::WebAuthenticationPanel::WebAuthenticationPanel):
     62        * UIProcess/API/APIWebAuthenticationPanelClient.h:
     63        (API::WebAuthenticationPanelClient::verifyUser const):
     64        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
     65        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
     66        (wkWebAuthenticationTransport):
     67        * UIProcess/WebAuthentication/Authenticator.h:
     68        * UIProcess/WebAuthentication/AuthenticatorManager.cpp:
     69        (WebKit::AuthenticatorManager::verifyUser):
     70        * UIProcess/WebAuthentication/AuthenticatorManager.h:
     71        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h:
     72        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h:
     73        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:
     74        (WebKit::LocalAuthenticatorInternal::toNSData):
     75        (WebKit::LocalAuthenticatorInternal::toArrayBuffer):
     76        (WebKit::LocalAuthenticator::makeCredential):
     77        (WebKit::LocalAuthenticator::continueMakeCredentialAfterUserConsented):
     78        (WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
     79        (WebKit::LocalAuthenticator::getAssertion):
     80        (WebKit::LocalAuthenticator::continueGetAssertionAfterResponseSelected):
     81        (WebKit::LocalAuthenticator::continueGetAssertionAfterUserConsented):
     82        (WebKit::LocalAuthenticator::receiveException const):
     83        * UIProcess/WebAuthentication/Cocoa/LocalConnection.h:
     84        (WebKit::LocalConnection::filterResponses const):
     85        * UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:
     86        (WebKit::LocalConnection::isUnlocked const):
     87        (WebKit::LocalConnection::getUserConsent const): Deleted.
     88        (WebKit::LocalConnection::selectCredential const): Deleted.
     89        * UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h:
     90        * UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm:
     91        (WebKit::WebAuthenticationPanelClient::WebAuthenticationPanelClient):
     92        (WebKit::wkWebAuthenticationPanelUpdate):
     93        (WebKit::WebAuthenticationPanelClient::selectAssertionResponse const):
     94        (WebKit::WebAuthenticationPanelClient::verifyUser const):
     95        * UIProcess/WebAuthentication/Mock/MockLocalConnection.h:
     96        * UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:
     97        (WebKit::MockLocalConnection::isUnlocked const):
     98        (WebKit::MockLocalConnection::filterResponses const):
     99        (WebKit::MockLocalConnection::getUserConsent const): Deleted.
     100        (WebKit::MockLocalConnection::selectCredential const): Deleted.
     101        * UIProcess/WebAuthentication/WebAuthenticationFlags.h:
     102        * WebKit.xcodeproj/project.pbxproj:
     103
    11042020-02-24  Per Arne Vollan  <pvollan@apple.com>
    2105
  • trunk/Source/WebKit/Platform/spi/Cocoa/LocalAuthenticationSPI.h

    r257268 r257269  
    11/*
    2  * Copyright (C) 2018-2019 Apple Inc. All rights reserved.
     2 * Copyright (C) 2020 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2727
    2828#import <LocalAuthentication/LocalAuthentication.h>
    29 #import <wtf/SoftLinking.h>
    3029
    31 SOFT_LINK_FRAMEWORK_FOR_HEADER(WebKit, LocalAuthentication);
     30#if USE(APPLE_INTERNAL_SDK)
    3231
    33 SOFT_LINK_CLASS_FOR_HEADER(WebKit, LAContext);
     32#import <LocalAuthentication/LocalAuthentication_Private.h>
     33
     34#else
     35
     36typedef NS_ENUM(NSInteger, LAOption) {
     37    LAOptionNotInteractive,
     38};
     39
     40@interface LAContext(Private) <NSSecureCoding>
     41
     42- (NSDictionary *)evaluatePolicy:(LAPolicy)policy options:(NSDictionary *)options error:(NSError **)error;
     43
     44@end
     45
     46#endif // USE(APPLE_INTERNAL_SDK)
  • trunk/Source/WebKit/UIProcess/API/APIWebAuthenticationPanel.cpp

    r251762 r257269  
    5454    if (transports.contains(AuthenticatorTransport::Nfc))
    5555        m_transports.uncheckedAppend(AuthenticatorTransport::Nfc);
     56    if (transports.contains(AuthenticatorTransport::Internal))
     57        m_transports.uncheckedAppend(AuthenticatorTransport::Internal);
    5658}
    5759
  • trunk/Source/WebKit/UIProcess/API/APIWebAuthenticationPanelClient.h

    r256238 r257269  
    3131#include <wtf/HashSet.h>
    3232#include <wtf/RefCounted.h>
     33#include <wtf/spi/cocoa/SecuritySPI.h>
    3334#include <wtf/text/WTFString.h>
     35
     36OBJC_CLASS LAContext;
    3437
    3538namespace WebCore {
     
    5356    virtual void requestPin(uint64_t, CompletionHandler<void(const WTF::String&)>&& completionHandler) const { completionHandler(emptyString()); }
    5457    virtual void selectAssertionResponse(Vector<Ref<WebCore::AuthenticatorAssertionResponse>>&& responses, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&& completionHandler) const { ASSERT(!responses.isEmpty()); completionHandler(responses[0]); }
     58    virtual void verifyUser(SecAccessControlRef, CompletionHandler<void(LAContext *)>&& completionHandler) const { completionHandler(nullptr); }
    5559};
    5660
  • trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h

    r254554 r257269  
    3232NS_ASSUME_NONNULL_BEGIN
    3333
     34@class LAContext;
    3435@class _WKWebAuthenticationAssertionResponse;
    3536@class _WKWebAuthenticationPanel;
     
    4748    _WKWebAuthenticationPanelUpdatePINAuthBlocked,
    4849    _WKWebAuthenticationPanelUpdatePINInvalid,
     50    _WKWebAuthenticationPanelUpdateLAError,
     51    _WKWebAuthenticationPanelUpdateLAExcludeCredentialsMatched,
     52    _WKWebAuthenticationPanelUpdateLANoCredential,
    4953} WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
    5054
     
    5761    _WKWebAuthenticationTransportUSB,
    5862    _WKWebAuthenticationTransportNFC,
     63    _WKWebAuthenticationTransportInternal,
    5964} WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
    6065
     
    7277- (void)panel:(_WKWebAuthenticationPanel *)panel requestPINWithRemainingRetries:(NSUInteger)retries completionHandler:(void (^)(NSString *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
    7378- (void)panel:(_WKWebAuthenticationPanel *)panel selectAssertionResponse:(NSArray < _WKWebAuthenticationAssertionResponse *> *)responses completionHandler:(void (^)(_WKWebAuthenticationAssertionResponse *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
     79- (void)panel:(_WKWebAuthenticationPanel *)panel verifyUserWithAccessControl:(SecAccessControlRef)accessControl completionHandler:(void (^)(LAContext *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
    7480
    7581@end
  • trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm

    r254554 r257269  
    7474    case WebCore::AuthenticatorTransport::Nfc:
    7575        return _WKWebAuthenticationTransportNFC;
     76    case WebCore::AuthenticatorTransport::Internal:
     77        return _WKWebAuthenticationTransportInternal;
    7678    default:
    7779        ASSERT_NOT_REACHED();
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Authenticator.h

    r254554 r257269  
    3535#include <wtf/RefCounted.h>
    3636#include <wtf/WeakPtr.h>
     37#include <wtf/spi/cocoa/SecuritySPI.h>
     38
     39OBJC_CLASS LAContext;
    3740
    3841namespace WebCore {
     
    5457        virtual void requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&&) = 0;
    5558        virtual void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&&) = 0;
     59        virtual void verifyUser(SecAccessControlRef, CompletionHandler<void(LAContext *)>&&) = 0;
    5660    };
    5761
  • trunk/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp

    r256238 r257269  
    296296}
    297297
     298void AuthenticatorManager::verifyUser(SecAccessControlRef accessControlRef, CompletionHandler<void(LAContext *)>&& completionHandler)
     299{
     300    RetainPtr<SecAccessControlRef> accessControl = accessControlRef;
     301    dispatchPanelClientCall([accessControl = WTFMove(accessControl), completionHandler = WTFMove(completionHandler)] (const API::WebAuthenticationPanel& panel) mutable {
     302        panel.client().verifyUser(accessControl.get(), WTFMove(completionHandler));
     303    });
     304}
     305
    298306UniqueRef<AuthenticatorTransportService> AuthenticatorManager::createService(AuthenticatorTransport transport, AuthenticatorTransportService::Observer& observer) const
    299307{
  • trunk/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h

    r256238 r257269  
    8484    void requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&&) final;
    8585    void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&&) final;
     86    void verifyUser(SecAccessControlRef, CompletionHandler<void(LAContext *)>&&) final;
    8687
    8788    // Overriden by MockAuthenticatorManager.
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h

    r240631 r257269  
    2626#pragma once
    2727
    28 #import <LocalAuthentication/LocalAuthentication.h>
     28#import "LocalAuthenticationSPI.h"
    2929#import <wtf/SoftLinking.h>
    3030
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h

    r257085 r257269  
    4040    // Here is the FSM.
    4141    // MakeCredential: Init => RequestReceived => UserConsented => Attested => End
    42     // GetAssertion: Init => RequestReceived => UserConsented => End
     42    // GetAssertion: Init => RequestReceived => ResponseSelected => UserConsented => End
    4343    enum class State {
    4444        Init,
     
    4646        UserConsented,
    4747        Attested,
     48        ResponseSelected
    4849    };
    4950
     
    5758
    5859    void makeCredential() final;
    59     void continueMakeCredentialAfterUserConsented(SecAccessControlRef, LocalConnection::UserConsent, LAContext *);
     60    void continueMakeCredentialAfterUserConsented(SecAccessControlRef, LAContext *);
    6061    void continueMakeCredentialAfterAttested(SecKeyRef, Vector<uint8_t>&& credentialId, Vector<uint8_t>&& authData, NSArray *certificates, NSError *);
    6162
    6263    void getAssertion() final;
    63     void continueGetAssertionAfterUserConsented(LocalConnection::UserConsent, LAContext *, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& userhandle);
     64    void continueGetAssertionAfterResponseSelected(Ref<WebCore::AuthenticatorAssertionResponse>&&);
     65    void continueGetAssertionAfterUserConsented(LAContext *, Ref<WebCore::AuthenticatorAssertionResponse>&&);
     66
     67    void receiveException(WebCore::ExceptionData&&, WebAuthenticationStatus = WebAuthenticationStatus::LAError) const;
    6468
    6569    State m_state { State::Init };
    6670    UniqueRef<LocalConnection> m_connection;
     71    HashSet<Ref<WebCore::AuthenticatorAssertionResponse>> m_assertionResponses;
    6772};
    6873
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm

    r257085 r257269  
    8787}
    8888
     89static inline RetainPtr<NSData> toNSData(ArrayBuffer* buffer)
     90{
     91    ASSERT(buffer);
     92    // FIXME(183534): Consider using initWithBytesNoCopy.
     93    return adoptNS([[NSData alloc] initWithBytes:buffer->data() length:buffer->byteLength()]);
     94}
     95
     96static inline Ref<ArrayBuffer> toArrayBuffer(NSData *data)
     97{
     98    return ArrayBuffer::create(reinterpret_cast<const uint8_t*>(data.bytes), data.length);
     99}
     100
    89101} // LocalAuthenticatorInternal
    90102
     
    109121        return pubKeyCredParam.type == PublicKeyCredentialType::PublicKey && pubKeyCredParam.alg == COSE::ES256;
    110122    })) {
    111         receiveRespond(ExceptionData { NotSupportedError, "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."_s });
     123        receiveException({ NotSupportedError, "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."_s });
    112124        return;
    113125    }
     
    132144        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
    133145        if (status && status != errSecItemNotFound) {
    134             LOG_ERROR("Couldn't query Keychain: %d", status);
    135             receiveRespond(ExceptionData { UnknownError, makeString("Couldn't query Keychain: ", status) });
     146            receiveException({ UnknownError, makeString("Couldn't query Keychain: ", status) });
    136147            return;
    137148        }
    138149        auto retainAttributesArray = adoptCF(attributesArrayRef);
    139150
    140         // FIXME(rdar://problem/35900593): Need to obtain user consent and then return different error according to the result.
     151        // FIXME: Need to obtain user consent and then return different error according to the result.
    141152        for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
    142153            NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
    143154            if (excludeCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) {
    144                 receiveRespond(ExceptionData { NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s });
     155                receiveException({ NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s }, WebAuthenticationStatus::LAExcludeCredentialsMatched);
    145156                return;
    146157            }
     
    149160
    150161    // Step 6.
    151     // FIXME(rdar://problem/35900593): Update to a formal UI.
    152162    // Get user consent.
    153163    RetainPtr<SecAccessControlRef> accessControl;
     
    157167        auto retainError = adoptCF(errorRef);
    158168        if (errorRef) {
    159             LOG_ERROR("Couldn't create access control: %@", (NSError *)errorRef);
    160             receiveRespond(ExceptionData { UnknownError, makeString("Couldn't create access control: ", String(((NSError*)errorRef).localizedDescription)) });
     169            receiveException({ UnknownError, makeString("Couldn't create access control: ", String(((NSError*)errorRef).localizedDescription)) });
    161170            return;
    162171        }
    163172    }
    164173
    165     SecAccessControlRef accessControlRef = accessControl.get();
    166     auto callback = [accessControl = WTFMove(accessControl), weakThis = makeWeakPtr(*this)] (LocalConnection::UserConsent consent, LAContext *context) {
    167         ASSERT(RunLoop::isMain());
    168         if (!weakThis)
    169             return;
    170 
    171         weakThis->continueMakeCredentialAfterUserConsented(accessControl.get(), consent, context);
    172     };
    173     m_connection->getUserConsent(
    174         makeString("allow "_s, creationOptions.rp.id, " to create a public key credential for "_s, creationOptions.user.name),
    175         accessControlRef,
    176         WTFMove(callback));
    177 }
    178 
    179 void LocalAuthenticator::continueMakeCredentialAfterUserConsented(SecAccessControlRef accessControlRef, LocalConnection::UserConsent consent, LAContext *context)
     174    if (auto* observer = this->observer()) {
     175        SecAccessControlRef accessControlRef = accessControl.get();
     176        auto callback = [accessControl = WTFMove(accessControl), weakThis = makeWeakPtr(*this)] (LAContext *context) {
     177            ASSERT(RunLoop::isMain());
     178            if (!weakThis)
     179                return;
     180
     181            weakThis->continueMakeCredentialAfterUserConsented(accessControl.get(), context);
     182        };
     183        observer->verifyUser(accessControlRef, WTFMove(callback));
     184    }
     185}
     186
     187void LocalAuthenticator::continueMakeCredentialAfterUserConsented(SecAccessControlRef accessControlRef, LAContext *context)
    180188{
    181189    using namespace LocalAuthenticatorInternal;
     
    185193    auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
    186194
    187     if (consent == LocalConnection::UserConsent::No) {
     195    if (!m_connection->isUnlocked(context)) {
    188196        receiveRespond(ExceptionData { NotAllowedError, "Couldn't get user consent."_s });
    189197        return;
     
    214222    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
    215223    if (status && status != errSecItemNotFound) {
    216         LOG_ERROR("Couldn't delete older credential: %d", status);
    217         receiveRespond(ExceptionData { UnknownError, makeString("Couldn't delete older credential: ", status) });
     224        receiveException({ UnknownError, makeString("Couldn't delete older credential: ", status) });
    218225        return;
    219226    }
     
    223230    auto privateKey = m_connection->createCredentialPrivateKey(context, accessControlRef, secAttrLabel, secAttrApplicationTag.get());
    224231    if (!privateKey) {
    225         receiveRespond(ExceptionData { UnknownError, "Couldn't create private key."_s });
     232        receiveException({ UnknownError, "Couldn't create private key."_s });
    226233        return;
    227234    }
     
    244251        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, &attributesRef);
    245252        if (status) {
    246             LOG_ERROR("Couldn't get Credential ID: %d", status);
    247             receiveRespond(ExceptionData { UnknownError, makeString("Couldn't get Credential ID: ", status) });
     253            receiveException({ UnknownError, makeString("Couldn't get Credential ID: ", status) });
    248254            return;
    249255        }
     
    269275            auto retainError = adoptCF(errorRef);
    270276            if (errorRef) {
    271                 LOG_ERROR("Couldn't export the public key: %@", (NSError*)errorRef);
    272                 receiveRespond(ExceptionData { UnknownError, makeString("Couldn't export the public key: ", String(((NSError*)errorRef).localizedDescription)) });
     277                receiveException({ UnknownError, makeString("Couldn't export the public key: ", String(((NSError*)errorRef).localizedDescription)) });
    273278                return;
    274279            }
     
    310315
    311316    if (error) {
    312         LOG_ERROR("Couldn't attest: %@", error);
    313         receiveRespond(ExceptionData { UnknownError, makeString("Couldn't attest: ", String(error.localizedDescription)) });
     317        receiveException({ UnknownError, makeString("Couldn't attest: ", String(error.localizedDescription)) });
    314318        return;
    315319    }
     
    345349    // Step 12 is implicitly captured by all UnknownError exception callbacks.
    346350    // Step 3-5. Unlike the spec, if an allow list is provided and there is no intersection between existing ones and the allow list, we always return NotAllowedError.
    347     // FIXME(rdar://problem/35900593): Need to inform users.
    348351    auto allowCredentialIds = produceHashSet(requestOptions.allowCredentials);
    349352    if (!requestOptions.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) {
    350         receiveRespond(ExceptionData { NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s });
     353        receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
    351354        return;
    352355    }
     
    368371    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
    369372    if (status && status != errSecItemNotFound) {
    370         LOG_ERROR("Couldn't query Keychain: %d", status);
    371         receiveRespond(ExceptionData { UnknownError, makeString("Couldn't query Keychain: ", status) });
     373        receiveException({ UnknownError, makeString("Couldn't query Keychain: ", status) });
    372374        return;
    373375    }
     
    387389    }
    388390    if (!intersectedCredentialsAttributes.count) {
    389         receiveRespond(ExceptionData { NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s });
     391        receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
    390392        return;
    391393    }
    392394
    393395    // Step 6.
    394     auto *selectedCredentialAttributes = m_connection->selectCredential(intersectedCredentialsAttributes);
     396    for (NSDictionary *attribute : intersectedCredentialsAttributes) {
     397        auto addResult = m_assertionResponses.add(AuthenticatorAssertionResponse::create(
     398            toArrayBuffer(attribute[(id)kSecAttrApplicationLabel]),
     399            toArrayBuffer(attribute[(id)kSecAttrApplicationTag]),
     400            (__bridge SecAccessControlRef)attribute[(id)kSecAttrAccessControl]));
     401        ASSERT_UNUSED(addResult, addResult.isNewEntry);
     402    }
     403    m_connection->filterResponses(m_assertionResponses);
     404
     405    if (auto* observer = this->observer()) {
     406        auto callback = [this, weakThis = makeWeakPtr(*this)] (const AuthenticatorAssertionResponse& response) {
     407            ASSERT(RunLoop::isMain());
     408            if (!weakThis)
     409                return;
     410
     411            auto returnResponse = m_assertionResponses.take(const_cast<AuthenticatorAssertionResponse*>(&response));
     412            if (!returnResponse)
     413                return;
     414            continueGetAssertionAfterResponseSelected(WTFMove(*returnResponse));
     415        };
     416        observer->selectAssertionResponse(m_assertionResponses, WTFMove(callback));
     417    }
     418}
     419
     420void LocalAuthenticator::continueGetAssertionAfterResponseSelected(Ref<WebCore::AuthenticatorAssertionResponse>&& response)
     421{
     422    ASSERT(m_state == State::RequestReceived);
     423    m_state = State::ResponseSelected;
    395424
    396425    // Step 7. Get user consent.
    397     // FIXME(rdar://problem/35900593): Update to a formal UI.
    398     auto callback = [
    399         weakThis = makeWeakPtr(*this),
    400         credentialId = toVector(selectedCredentialAttributes[(id)kSecAttrApplicationLabel]),
    401         userhandle = toVector(selectedCredentialAttributes[(id)kSecAttrApplicationTag])
    402     ](LocalConnection::UserConsent consent, LAContext *context) {
    403         ASSERT(RunLoop::isMain());
    404         if (!weakThis)
    405             return;
    406 
    407         weakThis->continueGetAssertionAfterUserConsented(consent, context, credentialId, userhandle);
    408     };
    409     NSData *idData = selectedCredentialAttributes[(id)kSecAttrApplicationTag];
    410     StringView idStringView { static_cast<const UChar*>([idData bytes]), static_cast<unsigned>([idData length]) };
    411     m_connection->getUserConsent(
    412         makeString("log into ", requestOptions.rpId, " with ", idStringView),
    413         (__bridge SecAccessControlRef)selectedCredentialAttributes[(id)kSecAttrAccessControl],
    414         WTFMove(callback));
    415 }
    416 
    417 void LocalAuthenticator::continueGetAssertionAfterUserConsented(LocalConnection::UserConsent consent, LAContext *context, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& userhandle)
     426    if (auto* observer = this->observer()) {
     427        auto accessControlRef = response->accessControl();
     428        auto callback = [weakThis = makeWeakPtr(*this), response = WTFMove(response)] (LAContext *context) mutable {
     429            ASSERT(RunLoop::isMain());
     430            if (!weakThis)
     431                return;
     432
     433            weakThis->continueGetAssertionAfterUserConsented(context, WTFMove(response));
     434        };
     435        observer->verifyUser(accessControlRef, WTFMove(callback));
     436    }
     437}
     438
     439void LocalAuthenticator::continueGetAssertionAfterUserConsented(LAContext *context, Ref<WebCore::AuthenticatorAssertionResponse>&& response)
    418440{
    419441    using namespace LocalAuthenticatorInternal;
    420     ASSERT(m_state == State::RequestReceived);
     442    ASSERT(m_state == State::ResponseSelected);
    421443    m_state = State::UserConsented;
    422444
    423     if (consent == LocalConnection::UserConsent::No) {
     445    if (!m_connection->isUnlocked(context)) {
    424446        receiveRespond(ExceptionData { NotAllowedError, "Couldn't get user consent."_s });
    425447        return;
     
    433455
    434456    // Step 11.
    435     Vector<uint8_t> signature;
     457    RetainPtr<CFDataRef> signature;
    436458    {
    437         NSDictionary *query = @{
     459        auto query = adoptNS([[NSMutableDictionary alloc] init]);
     460        [query addEntriesFromDictionary:@{
    438461            (id)kSecClass: (id)kSecClassKey,
    439462            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    440             (id)kSecAttrApplicationLabel: toNSData(credentialId).get(),
    441             (id)kSecUseAuthenticationContext: context,
     463            (id)kSecAttrApplicationLabel: toNSData(response->rawId()).get(),
    442464            (id)kSecReturnRef: @YES,
    443465#if HAVE(DATA_PROTECTION_KEYCHAIN)
     
    446468            (id)kSecAttrNoLegacy: @YES
    447469#endif
    448         };
     470        }];
     471        // context is nullptr in mock testing.
     472        if (context)
     473            [query setObject:context forKey:(id)kSecUseAuthenticationContext];
    449474        CFTypeRef privateKeyRef = nullptr;
    450         OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &privateKeyRef);
     475        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query.get(), &privateKeyRef);
    451476        if (status) {
    452             LOG_ERROR("Couldn't get the private key reference: %d", status);
    453             receiveRespond(ExceptionData { UnknownError, makeString("Couldn't get the private key reference: ", status) });
     477            receiveException({ UnknownError, makeString("Couldn't get the private key reference: ", status) });
    454478            return;
    455479        }
     
    461485        CFErrorRef errorRef = nullptr;
    462486        // FIXME: Converting CFTypeRef to SecKeyRef is quite subtle here.
    463         auto signatureRef = adoptCF(SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef));
     487        signature = adoptCF(SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef));
    464488        auto retainError = adoptCF(errorRef);
    465489        if (errorRef) {
    466             LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef);
    467             receiveRespond(ExceptionData { UnknownError, makeString("Couldn't generate the signature: ", String(((NSError*)errorRef).localizedDescription)) });
     490            receiveException({ UnknownError, makeString("Couldn't generate the signature: ", String(((NSError*)errorRef).localizedDescription)) });
    468491            return;
    469492        }
    470         signature = toVector((NSData *)signatureRef.get());
    471493    }
    472494
    473495    // Step 13.
    474     receiveRespond(AuthenticatorAssertionResponse::create(credentialId, authData, signature, userhandle));
     496    response->setAuthenticatorData(WTFMove(authData));
     497    response->setSignature(toArrayBuffer((NSData *)signature.get()));
     498    receiveRespond(WTFMove(response));
     499}
     500
     501void LocalAuthenticator::receiveException(ExceptionData&& exception, WebAuthenticationStatus status) const
     502{
     503    LOG_ERROR(exception.message.utf8().data());
     504    if (auto* observer = this->observer())
     505        observer->authenticatorStatusUpdated(status);
     506    receiveRespond(WTFMove(exception));
     507    return;
    475508}
    476509
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.h

    r257085 r257269  
    4747    WTF_MAKE_NONCOPYABLE(LocalConnection);
    4848public:
    49     enum class UserConsent {
    50         No,
    51         Yes
    52     };
    53 
    5449    using AttestationCallback = CompletionHandler<void(NSArray *, NSError *)>;
    55     using UserConsentCallback = CompletionHandler<void(UserConsent)>;
    56     using UserConsentContextCallback = CompletionHandler<void(UserConsent, LAContext *)>;
    5750
    5851    LocalConnection() = default;
     
    6053
    6154    // Overrided by MockLocalConnection.
    62     virtual void getUserConsent(const String& reason, SecAccessControlRef, UserConsentContextCallback&&) const;
     55    virtual bool isUnlocked(LAContext *) const;
    6356    virtual RetainPtr<SecKeyRef> createCredentialPrivateKey(LAContext *, SecAccessControlRef, const String& secAttrLabel, NSData *secAttrApplicationTag) const;
    6457    virtual void getAttestation(SecKeyRef, NSData *authData, NSData *hash, AttestationCallback&&) const;
    65     virtual NSDictionary *selectCredential(const NSArray *) const;
     58    virtual void filterResponses(HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&) const { };
    6659};
    6760
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.mm

    r257085 r257269  
    4040namespace WebKit {
    4141
    42 void LocalConnection::getUserConsent(const String& reason, SecAccessControlRef accessControl, UserConsentContextCallback&& completionHandler) const
     42bool LocalConnection::isUnlocked(LAContext *context) const
    4343{
    44     auto context = adoptNS([allocLAContextInstance() init]);
    45     auto reply = makeBlockPtr([context, completionHandler = WTFMove(completionHandler)] (BOOL success, NSError *error) mutable {
    46         ASSERT(!RunLoop::isMain());
    47 
    48         UserConsent consent = UserConsent::Yes;
    49         if (!success || error) {
    50             LOG_ERROR("Couldn't authenticate with biometrics: %@", error);
    51             consent = UserConsent::No;
    52         }
    53         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), consent, context = WTFMove(context)]() mutable {
    54             completionHandler(consent, context.get());
    55         });
    56     });
    57     [context evaluateAccessControl:accessControl operation:LAAccessControlOperationUseKeySign localizedReason:reason reply:reply.get()];
     44    NSError *error = nil;
     45    auto result = [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics options:@{ @(LAOptionNotInteractive): @YES } error:&error];
     46    if (!result)
     47        LOG_ERROR("Couldn't get user consent: %@", error);
     48    return !!result;
    5849}
    5950
     
    8980}
    9081
    91 NSDictionary *LocalConnection::selectCredential(const NSArray *credentials) const
    92 {
    93     // FIXME(rdar://problem/35900534): We don't have an UI to prompt users for selecting intersectedCredentials, and therefore we always use the first one for now.
    94     return credentials[0];
    95 }
    96 
    9782} // namespace WebKit
    9883
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h

    r256238 r257269  
    5151    void requestPin(uint64_t, CompletionHandler<void(const WTF::String&)>&&) const final;
    5252    void selectAssertionResponse(Vector<Ref<WebCore::AuthenticatorAssertionResponse>>&&, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&&) const final;
     53    void verifyUser(SecAccessControlRef, CompletionHandler<void(LAContext *)>&&) const final;
    5354
    5455    _WKWebAuthenticationPanel *m_panel;
     
    5960        bool panelDismissWebAuthenticationPanelWithResult : 1;
    6061        bool panelRequestPinWithRemainingRetriesCompletionHandler : 1;
    61         bool panelselectAssertionResponseCompletionHandler : 1;
     62        bool panelSelectAssertionResponseCompletionHandler : 1;
     63        bool panelVerifyUserWithAccessControlCompletionHandler : 1;
    6264    } m_delegateMethods;
    6365};
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm

    r256238 r257269  
    3939#import <wtf/RunLoop.h>
    4040
     41#import "LocalAuthenticationSoftLink.h"
     42
    4143namespace WebKit {
    4244
     
    4850    m_delegateMethods.panelDismissWebAuthenticationPanelWithResult = [delegate respondsToSelector:@selector(panel:dismissWebAuthenticationPanelWithResult:)];
    4951    m_delegateMethods.panelRequestPinWithRemainingRetriesCompletionHandler = [delegate respondsToSelector:@selector(panel:requestPINWithRemainingRetries:completionHandler:)];
    50     m_delegateMethods.panelselectAssertionResponseCompletionHandler = [delegate respondsToSelector:@selector(panel:selectAssertionResponse:completionHandler:)];
     52    m_delegateMethods.panelSelectAssertionResponseCompletionHandler = [delegate respondsToSelector:@selector(panel:selectAssertionResponse:completionHandler:)];
     53    m_delegateMethods.panelVerifyUserWithAccessControlCompletionHandler = [delegate respondsToSelector:@selector(panel:verifyUserWithAccessControl:completionHandler:)];
    5154}
    5255
     
    6871    if (status == WebAuthenticationStatus::PinInvalid)
    6972        return _WKWebAuthenticationPanelUpdatePINInvalid;
     73    if (status == WebAuthenticationStatus::LAError)
     74        return _WKWebAuthenticationPanelUpdateLAError;
     75    if (status == WebAuthenticationStatus::LAExcludeCredentialsMatched)
     76        return _WKWebAuthenticationPanelUpdateLAExcludeCredentialsMatched;
     77    if (status == WebAuthenticationStatus::LANoCredential)
     78        return _WKWebAuthenticationPanelUpdateLANoCredential;
    7079    ASSERT_NOT_REACHED();
    7180    return _WKWebAuthenticationPanelUpdateMultipleNFCTagsPresent;
     
    134143    ASSERT(!responses.isEmpty());
    135144
    136     if (!m_delegateMethods.panelselectAssertionResponseCompletionHandler) {
     145    if (!m_delegateMethods.panelSelectAssertionResponseCompletionHandler) {
    137146        completionHandler(responses[0]);
    138147        return;
     
    159168}
    160169
     170void WebAuthenticationPanelClient::verifyUser(SecAccessControlRef accessControl, CompletionHandler<void(LAContext *)>&& completionHandler) const
     171{
     172    if (!m_delegateMethods.panelVerifyUserWithAccessControlCompletionHandler) {
     173        completionHandler(adoptNS([allocLAContextInstance() init]).get());
     174        return;
     175    }
     176
     177    auto delegate = m_delegate.get();
     178    if (!delegate) {
     179        completionHandler(adoptNS([allocLAContextInstance() init]).get());
     180        return;
     181    }
     182
     183    auto checker = CompletionHandlerCallChecker::create(delegate.get(), @selector(panel:verifyUserWithAccessControl:completionHandler:));
     184    [delegate panel:m_panel verifyUserWithAccessControl:accessControl completionHandler:makeBlockPtr([completionHandler = WTFMove(completionHandler), checker = WTFMove(checker)](LAContext *context) mutable {
     185        if (checker->completionHandlerHasBeenCalled())
     186            return;
     187        checker->didCallCompletionHandler();
     188        completionHandler(context);
     189    }).get()];
     190}
     191
    161192} // namespace WebKit
    162193
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h

    r257085 r257269  
    3838
    3939private:
    40     void getUserConsent(const String& reason, SecAccessControlRef, UserConsentContextCallback&&) const final;
     40    bool isUnlocked(LAContext *) const final;
    4141    RetainPtr<SecKeyRef> createCredentialPrivateKey(LAContext *, SecAccessControlRef, const String& secAttrLabel, NSData *secAttrApplicationTag) const final;
    4242    void getAttestation(SecKeyRef, NSData *authData, NSData *hash, AttestationCallback&&) const final;
    43     NSDictionary *selectCredential(const NSArray *) const final;
     43    void filterResponses(HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&) const final;
    4444
    4545    WebCore::MockWebAuthenticationConfiguration m_configuration;
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.mm

    r257085 r257269  
    3333#import <wtf/RunLoop.h>
    3434#import <wtf/spi/cocoa/SecuritySPI.h>
     35#import <wtf/text/Base64.h>
    3536#import <wtf/text/WTFString.h>
    3637
     
    4445}
    4546
    46 void MockLocalConnection::getUserConsent(const String&, SecAccessControlRef, UserConsentContextCallback&& callback) const
     47bool MockLocalConnection::isUnlocked(LAContext *context) const
    4748{
    48     // Mock async operations.
    49     RunLoop::main().dispatch([configuration = m_configuration, callback = WTFMove(callback)]() mutable {
    50         ASSERT(configuration.local);
    51         if (!configuration.local->acceptAuthentication) {
    52             callback(UserConsent::No, nil);
    53             return;
    54         }
    55         callback(UserConsent::Yes, adoptNS([allocLAContextInstance() init]).get());
    56     });
     49    return m_configuration.local->acceptAuthentication;
    5750}
    5851
     
    113106}
    114107
    115 NSDictionary *MockLocalConnection::selectCredential(const NSArray *credentials) const
     108void MockLocalConnection::filterResponses(HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses) const
    116109{
    117     auto preferredUserhandle = adoptNS([[NSData alloc] initWithBase64EncodedString:m_configuration.local->preferredUserhandleBase64 options:0]);
    118     for (NSDictionary *credential : credentials) {
    119         if ([credential[(id)kSecAttrApplicationTag] isEqualToData:preferredUserhandle.get()])
    120             return credential;
     110    const auto& preferredUserhandleBase64 = m_configuration.local->preferredUserhandleBase64;
     111    if (preferredUserhandleBase64.isEmpty())
     112        return;
     113
     114    auto itr = responses.begin();
     115    for (; itr != responses.end(); ++itr) {
     116        auto* userHandle = itr->get().userHandle();
     117        ASSERT(userHandle);
     118        auto userhandleBase64 = base64Encode(userHandle->data(), userHandle->byteLength());
     119        if (userhandleBase64 == preferredUserhandleBase64)
     120            break;
    121121    }
    122     ASSERT_NOT_REACHED();
    123     return nil;
     122    auto response = responses.take(itr);
     123    ASSERT(response);
     124    responses.clear();
     125    responses.add(WTFMove(*response));
    124126}
    125127
  • trunk/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationFlags.h

    r256062 r257269  
    4646    PinBlocked,
    4747    PinAuthBlocked,
    48     PinInvalid
     48    PinInvalid,
     49    LAError,
     50    LAExcludeCredentialsMatched,
     51    LANoCredential,
    4952};
    5053
  • trunk/Source/WebKit/WebKit.xcodeproj/project.pbxproj

    r257127 r257269  
    10971097                570DAACA230385FD00E8FC04 /* CtapNfcDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 570DAAC8230385FD00E8FC04 /* CtapNfcDriver.h */; };
    10981098                572FD44322265CE200A1ECC3 /* WebViewDidMoveToWindowObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 572FD44122265CE200A1ECC3 /* WebViewDidMoveToWindowObserver.h */; };
     1099                574217922400E286002B303D /* LocalAuthenticationSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 574217912400E098002B303D /* LocalAuthenticationSPI.h */; };
    10991100                574728D123456E98001700AF /* _WKWebAuthenticationPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 574728CF23456E98001700AF /* _WKWebAuthenticationPanel.h */; settings = {ATTRIBUTES = (Private, ); }; };
    11001101                574728D4234570AE001700AF /* _WKWebAuthenticationPanelInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 574728D3234570AE001700AF /* _WKWebAuthenticationPanelInternal.h */; };
     
    38123813                570DAAC9230385FD00E8FC04 /* CtapNfcDriver.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CtapNfcDriver.cpp; sourceTree = "<group>"; };
    38133814                572FD44122265CE200A1ECC3 /* WebViewDidMoveToWindowObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebViewDidMoveToWindowObserver.h; sourceTree = "<group>"; };
     3815                574217912400E098002B303D /* LocalAuthenticationSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalAuthenticationSPI.h; sourceTree = "<group>"; };
    38143816                574728CF23456E98001700AF /* _WKWebAuthenticationPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKWebAuthenticationPanel.h; sourceTree = "<group>"; };
    38153817                574728D023456E98001700AF /* _WKWebAuthenticationPanel.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKWebAuthenticationPanel.mm; sourceTree = "<group>"; };
     
    68406842                                37C21CAD1E994C0C0029D5F9 /* CorePredictionSPI.h */,
    68416843                                2DAADA8E2298C21000E36B0C /* DeviceManagementSPI.h */,
     6844                                574217912400E098002B303D /* LocalAuthenticationSPI.h */,
    68426845                                57B826402304EB3E00B72EB0 /* NearFieldSPI.h */,
    68436846                                3754D5441B3A29FD003A4C7F /* NSInvocationSPI.h */,
     
    1036310366                                2D1087611D2C573E00B85F82 /* LoadParameters.h in Headers */,
    1036410367                                578DC2982155A0020074E815 /* LocalAuthenticationSoftLink.h in Headers */,
     10368                                574217922400E286002B303D /* LocalAuthenticationSPI.h in Headers */,
    1036510369                                57DCEDAC214C60270016B847 /* LocalAuthenticator.h in Headers */,
    1036610370                                57DCEDAD214C602C0016B847 /* LocalConnection.h in Headers */,
  • trunk/Tools/ChangeLog

    r257257 r257269  
     12020-02-24  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Implement SPI for the platform authenticator
     4        https://bugs.webkit.org/show_bug.cgi?id=208087
     5        <rdar://problem/59369305>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Besides adding API tests, this patch also teaches TestWebKitAPI to use restricted entitlements.
     10
     11        * TestWebKitAPI/Configurations/TestWebKitAPI-macOS.entitlements:
     12        * TestWebKitAPI/Configurations/TestWebKitAPI.xcconfig:
     13        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
     14        * TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
     15        (-[TestWebAuthenticationPanelDelegate panel:updateWebAuthenticationPanel:]):
     16        (-[TestWebAuthenticationPanelDelegate panel:selectAssertionResponse:completionHandler:]):
     17        (-[TestWebAuthenticationPanelDelegate panel:verifyUserWithAccessControl:completionHandler:]):
     18        (TestWebKitAPI::TEST):
     19        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-la.html: Copied from Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion.html.
     20        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-la-duplicate-credential.html: Added.
     21        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-la-error.html: Added.
     22        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-la.html: Added.
     23
    1242020-02-24  Yusuke Suzuki  <ysuzuki@apple.com>
    225
  • trunk/Tools/TestWebKitAPI/Configurations/TestWebKitAPI-macOS.entitlements

    r256777 r257269  
    33<plist version="1.0">
    44<dict>
     5        <key>keychain-access-groups</key>
     6        <array>
     7                <string>com.apple.TestWebKitAPI</string>
     8        </array>
    59        <key>com.apple.security.temporary-exception.sbpl</key>
    610        <array>
  • trunk/Tools/TestWebKitAPI/Configurations/TestWebKitAPI.xcconfig

    r256777 r257269  
    7070
    7171STRIP_STYLE = debugging;
     72
     73CODE_SIGN_IDENTITY[sdk=macosx*] = $(CODE_SIGN_IDENTITY_$(CONFIGURATION));
     74CODE_SIGN_IDENTITY_Debug = $(CODE_SIGN_IDENTITY_$(USE_INTERNAL_SDK));
     75CODE_SIGN_IDENTITY_Release = $(CODE_SIGN_IDENTITY_$(USE_INTERNAL_SDK));
     76CODE_SIGN_IDENTITY_YES = $(WK_ENGINEERING_CODE_SIGN_IDENTITY);
     77CODE_SIGN_IDENTITY_Production = $(CODE_SIGN_IDENTITY_Production_$(USE_INTERNAL_SDK));
     78CODE_SIGN_IDENTITY_Production_YES = -;
  • trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj

    r257225 r257269  
    350350                573255A622139BC700396AE8 /* load-web-archive-1.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 573255A222139B8F00396AE8 /* load-web-archive-1.html */; };
    351351                573255A722139BC700396AE8 /* load-web-archive-2.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 573255A322139B9000396AE8 /* load-web-archive-2.html */; };
     352                574217882400AC25002B303D /* web-authentication-make-credential-la-error.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 574217872400ABFD002B303D /* web-authentication-make-credential-la-error.html */; };
     353                5742178A2400AED8002B303D /* web-authentication-make-credential-la-duplicate-credential.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 574217892400AED0002B303D /* web-authentication-make-credential-la-duplicate-credential.html */; };
     354                5742178C2400CD47002B303D /* web-authentication-get-assertion-la.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5742178B2400CD2D002B303D /* web-authentication-get-assertion-la.html */; };
     355                5742178E2400D2DF002B303D /* web-authentication-make-credential-la.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5742178D2400D26C002B303D /* web-authentication-make-credential-la.html */; };
    352356                574F55D2204D47F0002948C6 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 574F55D0204D471C002948C6 /* Security.framework */; };
    353357                5758597F23A2527A00C74572 /* CtapPinTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5758597E23A2527A00C74572 /* CtapPinTest.cpp */; };
     
    15191523                                570D26FC23C3F87000D5CF67 /* web-authentication-get-assertion-hid-pin.html in Copy Resources */,
    15201524                                57663DEC234F1F9300E85E09 /* web-authentication-get-assertion-hid.html in Copy Resources */,
     1525                                5742178C2400CD47002B303D /* web-authentication-get-assertion-la.html in Copy Resources */,
    15211526                                579833922368FA37008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html in Copy Resources */,
    15221527                                57663DEA234EA66D00E85E09 /* web-authentication-get-assertion-nfc.html in Copy Resources */,
     
    15331538                                570D26F623C3D33000D5CF67 /* web-authentication-make-credential-hid-pin.html in Copy Resources */,
    15341539                                5798337E236019A4008E5547 /* web-authentication-make-credential-hid.html in Copy Resources */,
     1540                                5742178A2400AED8002B303D /* web-authentication-make-credential-la-duplicate-credential.html in Copy Resources */,
     1541                                574217882400AC25002B303D /* web-authentication-make-credential-la-error.html in Copy Resources */,
     1542                                5742178E2400D2DF002B303D /* web-authentication-make-credential-la.html in Copy Resources */,
    15351543                                1C2B81861C89259D00A5529F /* webfont.html in Copy Resources */,
    15361544                                51714EB41CF8C78C004723C4 /* WebProcessKillIDBCleanup-1.html in Copy Resources */,
     
    19631971                573255A422139B9000396AE8 /* helloworld.webarchive */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = helloworld.webarchive; sourceTree = "<group>"; };
    19641972                5735F0251F3A4EA6000EE801 /* TestWebKitAPI-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "TestWebKitAPI-iOS.entitlements"; sourceTree = "<group>"; };
     1973                574217872400ABFD002B303D /* web-authentication-make-credential-la-error.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-make-credential-la-error.html"; sourceTree = "<group>"; };
     1974                574217892400AED0002B303D /* web-authentication-make-credential-la-duplicate-credential.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-make-credential-la-duplicate-credential.html"; sourceTree = "<group>"; };
     1975                5742178B2400CD2D002B303D /* web-authentication-get-assertion-la.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-get-assertion-la.html"; sourceTree = "<group>"; };
     1976                5742178D2400D26C002B303D /* web-authentication-make-credential-la.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-make-credential-la.html"; sourceTree = "<group>"; };
     1977                5742178F2400D54D002B303D /* web-authentication-make-credential.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-make-credential.html"; sourceTree = "<group>"; };
    19651978                574F55D0204D471C002948C6 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
    19661979                5758597D23A2527A00C74572 /* CtapPinTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CtapPinTest.h; sourceTree = "<group>"; };
     
    35833596                                570D26FB23C3F86500D5CF67 /* web-authentication-get-assertion-hid-pin.html */,
    35843597                                57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */,
     3598                                5742178B2400CD2D002B303D /* web-authentication-get-assertion-la.html */,
    35853599                                5798337B235EB65C008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html */,
    35863600                                57663DE9234EA60B00E85E09 /* web-authentication-get-assertion-nfc.html */,
     
    35973611                                570D26F523C3D32700D5CF67 /* web-authentication-make-credential-hid-pin.html */,
    35983612                                5798337D2360196D008E5547 /* web-authentication-make-credential-hid.html */,
     3613                                574217892400AED0002B303D /* web-authentication-make-credential-la-duplicate-credential.html */,
     3614                                574217872400ABFD002B303D /* web-authentication-make-credential-la-error.html */,
     3615                                5742178D2400D26C002B303D /* web-authentication-make-credential-la.html */,
     3616                                5742178F2400D54D002B303D /* web-authentication-make-credential.html */,
    35993617                                51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */,
    36003618                                51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm

    r256062 r257269  
    3333#import "TestWKWebView.h"
    3434#import "WKWebViewConfigurationExtras.h"
     35#import <LocalAuthentication/LocalAuthentication.h>
    3536#import <WebKit/WKPreferencesPrivate.h>
    3637#import <WebKit/WKUIDelegatePrivate.h>
     
    4041#import <wtf/BlockPtr.h>
    4142#import <wtf/RandomNumber.h>
     43#import <wtf/spi/cocoa/SecuritySPI.h>
    4244#import <wtf/text/StringConcatenateNumbers.h>
    4345
     
    5052static bool webAuthenticationPanelUpdatePINAuthBlocked = false;
    5153static bool webAuthenticationPanelUpdatePINInvalid = false;
     54static bool webAuthenticationPanelUpdateLAError = false;
     55static bool webAuthenticationPanelUpdateLAExcludeCredentialsMatched = false;
     56static bool webAuthenticationPanelUpdateLANoCredential = false;
    5257static bool webAuthenticationPanelCancelImmediately = false;
     58static bool webAuthenticationPanelVerifyUser = false;
    5359static String webAuthenticationPanelPin;
    5460static BOOL webAuthenticationPanelNullUserHandle = NO;
     61static String testES256PrivateKeyBase64 =
     62    "BDj/zxSkzKgaBuS3cdWDF558of8AaIpgFpsjF/Qm1749VBJPgqUIwfhWHJ91nb7U"
     63    "PH76c0+WFOzZKslPyyFse4goGIW2R7k9VHLPEZl5nfnBgEVFh5zev+/xpHQIvuq6"
     64    "RQ==";
     65static String testUserhandleBase64 = "AAECAwQFBgcICQ==";
    5566
    5667@interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
     
    8596        return;
    8697    }
     98    if (update == _WKWebAuthenticationPanelUpdateLAError) {
     99        webAuthenticationPanelUpdateLAError = true;
     100        return;
     101    }
     102    if (update == _WKWebAuthenticationPanelUpdateLAExcludeCredentialsMatched) {
     103        webAuthenticationPanelUpdateLAExcludeCredentialsMatched = true;
     104        return;
     105    }
     106    if (update == _WKWebAuthenticationPanelUpdateLANoCredential) {
     107        webAuthenticationPanelUpdateLANoCredential = true;
     108        return;
     109    }
    87110}
    88111
     
    112135- (void)panel:(_WKWebAuthenticationPanel *)panel selectAssertionResponse:(NSArray < _WKWebAuthenticationAssertionResponse *> *)responses completionHandler:(void (^)(_WKWebAuthenticationAssertionResponse *))completionHandler
    113136{
     137    if (responses.count == 1) {
     138        completionHandler(responses[0]);
     139        return;
     140    }
     141
    114142    EXPECT_EQ(responses.count, 2ul);
    115143    for (_WKWebAuthenticationAssertionResponse *response in responses) {
     
    122150    webAuthenticationPanelNullUserHandle = responses[index].userHandle ? NO : YES;
    123151    completionHandler(responses[index]);
     152}
     153
     154- (void)panel:(_WKWebAuthenticationPanel *)panel verifyUserWithAccessControl:(SecAccessControlRef)accessControl completionHandler:(void (^)(LAContext *))completionHandler
     155{
     156    webAuthenticationPanelVerifyUser = true;
     157    auto context = adoptNS([[LAContext alloc] init]);
     158    completionHandler(context.get());
    124159}
    125160
     
    232267}
    233268
     269#if USE(APPLE_INTERNAL_SDK) || PLATFORM(IOS)
     270static _WKExperimentalFeature *webAuthenticationLocalAuthenticatorExperimentalFeature()
     271{
     272    static RetainPtr<_WKExperimentalFeature> theFeature;
     273    if (theFeature)
     274        return theFeature.get();
     275
     276    NSArray *features = [WKPreferences _experimentalFeatures];
     277    for (_WKExperimentalFeature *feature in features) {
     278        if ([feature.key isEqual:@"WebAuthenticationLocalAuthenticatorEnabled"]) {
     279            theFeature = feature;
     280            break;
     281        }
     282    }
     283    return theFeature.get();
     284}
     285#endif // USE(APPLE_INTERNAL_SDK) || PLATFORM(IOS)
     286
    234287static void reset()
    235288{
     
    242295    webAuthenticationPanelUpdatePINAuthBlocked = false;
    243296    webAuthenticationPanelUpdatePINInvalid = false;
     297    webAuthenticationPanelUpdateLAError = false;
     298    webAuthenticationPanelUpdateLAExcludeCredentialsMatched = false;
     299    webAuthenticationPanelUpdateLANoCredential = false;
    244300    webAuthenticationPanelCancelImmediately = false;
    245301    webAuthenticationPanelPin = emptyString();
    246302    webAuthenticationPanelNullUserHandle = NO;
     303    webAuthenticationPanelVerifyUser = false;
    247304}
    248305
     
    277334    EXPECT_EQ(frame.webView, webView);
    278335}
     336
     337#if USE(APPLE_INTERNAL_SDK) || PLATFORM(IOS)
     338
     339bool addKeyToKeychain(const String& privateKeyBase64, const String& rpId, const String& userHandleBase64)
     340{
     341    NSDictionary* options = @{
     342        (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
     343        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
     344        (id)kSecAttrKeySizeInBits: @256,
     345    };
     346    CFErrorRef errorRef = nullptr;
     347    auto key = adoptCF(SecKeyCreateWithData(
     348        (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:privateKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
     349        (__bridge CFDictionaryRef)options,
     350        &errorRef
     351    ));
     352    if (errorRef)
     353        return false;
     354
     355    NSDictionary* addQuery = @{
     356        (id)kSecValueRef: (id)key.get(),
     357        (id)kSecClass: (id)kSecClassKey,
     358        (id)kSecAttrLabel: rpId,
     359        (id)kSecAttrApplicationTag: adoptNS([[NSData alloc] initWithBase64EncodedString:userHandleBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
     360        (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
     361#if HAVE(DATA_PROTECTION_KEYCHAIN)
     362        (id)kSecUseDataProtectionKeychain: @YES
     363#else
     364        (id)kSecAttrNoLegacy: @YES
     365#endif
     366    };
     367    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
     368    if (status)
     369        return false;
     370
     371    return true;
     372}
     373
     374void cleanUpKeychain(const String& rpId)
     375{
     376    NSDictionary* deleteQuery = @{
     377        (id)kSecClass: (id)kSecClassKey,
     378        (id)kSecAttrLabel: rpId,
     379        (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
     380#if HAVE(DATA_PROTECTION_KEYCHAIN)
     381        (id)kSecUseDataProtectionKeychain: @YES
     382#else
     383        (id)kSecAttrNoLegacy: @YES
     384#endif
     385    };
     386    SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
     387}
     388
     389#endif // USE(APPLE_INTERNAL_SDK) || PLATFORM(IOS)
    279390
    280391} // namesapce;
     
    10771188}
    10781189
     1190// For macOS, only internal builds can sign keychain entitlemnets
     1191// which are required to run local authenticator tests.
     1192#if USE(APPLE_INTERNAL_SDK) || PLATFORM(IOS)
     1193
     1194TEST(WebAuthenticationPanel, LAError)
     1195{
     1196    reset();
     1197    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-make-credential-la-error" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
     1198
     1199    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
     1200    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
     1201    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationLocalAuthenticatorExperimentalFeature()];
     1202
     1203    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
     1204    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
     1205    [webView setUIDelegate:delegate.get()];
     1206
     1207    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1208    Util::run(&webAuthenticationPanelUpdateLAError);
     1209}
     1210
     1211TEST(WebAuthenticationPanel, LADuplicateCredential)
     1212{
     1213    reset();
     1214    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-make-credential-la-duplicate-credential" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
     1215
     1216    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
     1217    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
     1218    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationLocalAuthenticatorExperimentalFeature()];
     1219
     1220    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
     1221    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
     1222    [webView setUIDelegate:delegate.get()];
     1223
     1224    ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserhandleBase64));
     1225    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1226    Util::run(&webAuthenticationPanelUpdateLAExcludeCredentialsMatched);
     1227    cleanUpKeychain("");
     1228}
     1229
     1230TEST(WebAuthenticationPanel, LANoCredential)
     1231{
     1232    reset();
     1233    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-la" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
     1234
     1235    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
     1236    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
     1237    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationLocalAuthenticatorExperimentalFeature()];
     1238
     1239    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
     1240    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
     1241    [webView setUIDelegate:delegate.get()];
     1242
     1243    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1244    Util::run(&webAuthenticationPanelUpdateLANoCredential);
     1245}
     1246
     1247TEST(WebAuthenticationPanel, LAMakeCredentialNullDelegate)
     1248{
     1249    reset();
     1250    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-make-credential-la" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
     1251
     1252    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
     1253    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
     1254    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationLocalAuthenticatorExperimentalFeature()];
     1255
     1256    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
     1257    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
     1258    [delegate setIsNull:true];
     1259    [webView setUIDelegate:delegate.get()];
     1260
     1261    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1262    [webView waitForMessage:@"Succeeded!"];
     1263    cleanUpKeychain("");
     1264}
     1265
     1266TEST(WebAuthenticationPanel, LAMakeCredential)
     1267{
     1268    reset();
     1269    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-make-credential-la" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
     1270
     1271    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
     1272    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
     1273    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationLocalAuthenticatorExperimentalFeature()];
     1274
     1275    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
     1276    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
     1277    [webView setUIDelegate:delegate.get()];
     1278
     1279    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1280    Util::run(&webAuthenticationPanelVerifyUser);
     1281    checkPanel([delegate panel], @"", @[adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportUSB]).get(), adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportInternal]).get()], _WKWebAuthenticationTypeCreate);
     1282    [webView waitForMessage:@"Succeeded!"];
     1283}
     1284
     1285TEST(WebAuthenticationPanel, LAGetAssertion)
     1286{
     1287    reset();
     1288    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-la" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
     1289
     1290    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
     1291    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
     1292    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationLocalAuthenticatorExperimentalFeature()];
     1293
     1294    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
     1295    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
     1296    [webView setUIDelegate:delegate.get()];
     1297
     1298    ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserhandleBase64));
     1299    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1300    Util::run(&webAuthenticationPanelVerifyUser);
     1301    checkPanel([delegate panel], @"", @[adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportUSB]).get(), adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportInternal]).get()], _WKWebAuthenticationTypeGet);
     1302    [webView waitForMessage:@"Succeeded!"];
     1303    cleanUpKeychain("");
     1304}
     1305
     1306#endif // USE(APPLE_INTERNAL_SDK) || PLATFORM(IOS)
     1307
    10791308} // namespace TestWebKitAPI
    10801309
Note: See TracChangeset for help on using the changeset viewer.