Changeset 260970 in webkit


Ignore:
Timestamp:
Apr 30, 2020 3:27:02 PM (4 years ago)
Author:
jiewen_tan@apple.com
Message:

[WebAuthn] Optimize LocalAuthenticator
https://bugs.webkit.org/show_bug.cgi?id=183534
<rdar://problem/43357408>

Reviewed by Brent Fulgham.

Source/WebCore:

Covered by new API tests.

  • en.lproj/Localizable.strings:
  • platform/LocalizedStrings.cpp:

(WebCore::getAssertionTouchIDPromptTitle):
(WebCore::genericTouchIDPromptTitle):

  • platform/LocalizedStrings.h:

Improving the LocalAuthentication dialog titles for iOS.

Source/WebKit:

This patch implements the following small optimizations:

  1. Replacing local constants with ones from FidoConstants.h;
  2. Merging m_assertionResponses and m_existingCredentials by replacing HashSet with Vector in Authenticator::Observer::selectAssertionResponse;
  3. Using Base64 encoded strings as the keys of the HashSet in produceHashSet() instead of the old casting hack;
  4. Invaliding the LAContext in LocalConnection::~LocalConnection() such that any displaying LocalAuthentication dialogs can be dismissed after the object is destroyed;
  5. Sorting existing credentials according to LRU before returning to UI clients;
  6. Improving the LocalAuthentication dialog titles for iOS.
  • UIProcess/WebAuthentication/Authenticator.h:
  • UIProcess/WebAuthentication/AuthenticatorManager.cpp:

(WebKit::AuthenticatorManager::selectAssertionResponse):

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

(WebKit::LocalAuthenticatorInternal::produceHashSet):
(WebKit::LocalAuthenticatorInternal::toNSData):
(WebKit::LocalAuthenticatorInternal::getExistingCredentials):
(WebKit::LocalAuthenticator::makeCredential):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterUserVerification):
(WebKit::LocalAuthenticator::getAssertion):
(WebKit::LocalAuthenticator::continueGetAssertionAfterUserVerification):

  • UIProcess/WebAuthentication/Cocoa/LocalConnection.h:

(WebKit::LocalConnection::filterResponses const):

  • UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:

(WebKit::WebCore::bundleName):
(WebKit::LocalConnection::~LocalConnection):
(WebKit::LocalConnection::verifyUser):
(WebKit::LocalConnection::verifyUser const): Deleted.

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

(WebKit::MockLocalConnection::verifyUser):
(WebKit::MockLocalConnection::filterResponses const):
(WebKit::MockLocalConnection::verifyUser const): Deleted.

  • UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp:

(WebKit::CtapAuthenticator::continueGetAssertionAfterResponseReceived):
(WebKit::CtapAuthenticator::continueGetNextAssertionAfterResponseReceived):

  • UIProcess/WebAuthentication/fido/CtapAuthenticator.h:

Tools:

  • TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:

(-[TestWebAuthenticationPanelDelegate panel:selectAssertionResponse:source:completionHandler:]):
(TestWebKitAPI::TEST):
Adds a new test case for the LRU.

Location:
trunk
Files:
18 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r260969 r260970  
     12020-04-30  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Optimize LocalAuthenticator
     4        https://bugs.webkit.org/show_bug.cgi?id=183534
     5        <rdar://problem/43357408>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Covered by new API tests.
     10
     11        * en.lproj/Localizable.strings:
     12        * platform/LocalizedStrings.cpp:
     13        (WebCore::getAssertionTouchIDPromptTitle):
     14        (WebCore::genericTouchIDPromptTitle):
     15        * platform/LocalizedStrings.h:
     16        Improving the LocalAuthentication dialog titles for iOS.
     17
    1182020-04-30  Kate Cheney  <katherine_cheney@apple.com>
    219
  • trunk/Source/WebCore/en.lproj/Localizable.strings

    r258961 r260970  
    233233"Continue" = "Continue";
    234234
     235/* Continue with Touch ID */
     236"Continue with Touch ID." = "Continue with Touch ID.";
     237
    235238/* Media Controls context menu item */
    236239"Controls" = "Controls";
     
    869872"This website may try to trick you into installing software that harms your browsing experience, like changing your settings without your permission or showing you unwanted ads. Once installed, it may be difficult to remove." = "This website may try to trick you into installing software that harms your browsing experience, like changing your settings without your permission or showing you unwanted ads. Once installed, it may be difficult to remove.";
    870873
    871 /* This website would like to use Touch ID */
    872 "This website would like to use Touch ID." = "This website would like to use Touch ID.";
    873 
    874874/* Informative text for requesting cross-site cookie and website data access. */
    875875"This will allow “%@” to track your activity." = "This will allow “%@” to track your activity.";
     
    890890"To view this page, you must log in to this area on %@:" = "To view this page, you must log in to this area on %@:";
    891891
    892 /* Use Touch ID to sign in to this website */
    893 "Touch ID to sign in to this website." = "Touch ID to sign in to this website.";
    894 
    895892/* Transformations context sub-menu item */
    896893"Transformations" = "Transformations";
  • trunk/Source/WebCore/platform/LocalizedStrings.cpp

    r258961 r260970  
    12111211// On macOS, Touch ID prompt is not guaranteed to show on top of the UI client, and therefore additional
    12121212// information is provided to help users to make decisions.
    1213 #if PLATFORM(MAC)
    12141213String makeCredentialTouchIDPromptTitle(const String& bundleName, const String& domain)
    12151214{
     
    12211220    return formatLocalizedString(WEB_UI_CFSTRING("“%@” would like to sign in to “%@”.", "Allow the specified bundle to sign in to the specified website"), bundleName.createCFString().get(), domain.createCFString().get());
    12221221}
    1223 #else
    1224 String makeCredentialTouchIDPromptTitle(const String&, const String&)
    1225 {
    1226     return WEB_UI_STRING("This website would like to use Touch ID.", "This website would like to use Touch ID");
    1227 }
    1228 
    1229 String getAssertionTouchIDPromptTitle(const String&, const String&)
    1230 {
    1231     return WEB_UI_STRING("Touch ID to sign in to this website.", "Use Touch ID to sign in to this website");
    1232 }
    1233 #endif // PLATFORM(MAC)
     1222
     1223String genericTouchIDPromptTitle()
     1224{
     1225    return WEB_UI_STRING("Continue with Touch ID.", "Continue with Touch ID.");
     1226}
    12341227#endif // ENABLE(WEB_AUTHN)
    12351228
  • trunk/Source/WebCore/platform/LocalizedStrings.h

    r258961 r260970  
    343343    WEBCORE_EXPORT String makeCredentialTouchIDPromptTitle(const String& bundleName, const String& domain);
    344344    WEBCORE_EXPORT String getAssertionTouchIDPromptTitle(const String& bundleName, const String& domain);
     345    WEBCORE_EXPORT String genericTouchIDPromptTitle();
    345346#endif
    346347
  • trunk/Source/WebKit/ChangeLog

    r260968 r260970  
     12020-04-30  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Optimize LocalAuthenticator
     4        https://bugs.webkit.org/show_bug.cgi?id=183534
     5        <rdar://problem/43357408>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        This patch implements the following small optimizations:
     10        1. Replacing local constants with ones from FidoConstants.h;
     11        2. Merging m_assertionResponses and m_existingCredentials by replacing HashSet with Vector in Authenticator::Observer::selectAssertionResponse;
     12        3. Using Base64 encoded strings as the keys of the HashSet in produceHashSet() instead of the old casting hack;
     13        4. Invaliding the LAContext in LocalConnection::~LocalConnection() such that any displaying LocalAuthentication dialogs can be dismissed after the object is destroyed;
     14        5. Sorting existing credentials according to LRU before returning to UI clients;
     15        6. Improving the LocalAuthentication dialog titles for iOS.
     16
     17        * UIProcess/WebAuthentication/Authenticator.h:
     18        * UIProcess/WebAuthentication/AuthenticatorManager.cpp:
     19        (WebKit::AuthenticatorManager::selectAssertionResponse):
     20        * UIProcess/WebAuthentication/AuthenticatorManager.h:
     21        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h:
     22        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:
     23        (WebKit::LocalAuthenticatorInternal::produceHashSet):
     24        (WebKit::LocalAuthenticatorInternal::toNSData):
     25        (WebKit::LocalAuthenticatorInternal::getExistingCredentials):
     26        (WebKit::LocalAuthenticator::makeCredential):
     27        (WebKit::LocalAuthenticator::continueMakeCredentialAfterUserVerification):
     28        (WebKit::LocalAuthenticator::getAssertion):
     29        (WebKit::LocalAuthenticator::continueGetAssertionAfterUserVerification):
     30        * UIProcess/WebAuthentication/Cocoa/LocalConnection.h:
     31        (WebKit::LocalConnection::filterResponses const):
     32        * UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:
     33        (WebKit::WebCore::bundleName):
     34        (WebKit::LocalConnection::~LocalConnection):
     35        (WebKit::LocalConnection::verifyUser):
     36        (WebKit::LocalConnection::verifyUser const): Deleted.
     37        * UIProcess/WebAuthentication/Mock/MockLocalConnection.h:
     38        * UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:
     39        (WebKit::MockLocalConnection::verifyUser):
     40        (WebKit::MockLocalConnection::filterResponses const):
     41        (WebKit::MockLocalConnection::verifyUser const): Deleted.
     42        * UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp:
     43        (WebKit::CtapAuthenticator::continueGetAssertionAfterResponseReceived):
     44        (WebKit::CtapAuthenticator::continueGetNextAssertionAfterResponseReceived):
     45        * UIProcess/WebAuthentication/fido/CtapAuthenticator.h:
     46
    1472020-04-30  Kate Cheney  <katherine_cheney@apple.com>
    248
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Authenticator.h

    r259680 r260970  
    5656        virtual void authenticatorStatusUpdated(WebAuthenticationStatus) = 0;
    5757        virtual void requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&&) = 0;
    58         virtual void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, WebAuthenticationSource, CompletionHandler<void(WebCore::AuthenticatorAssertionResponse*)>&&) = 0;
     58        virtual void selectAssertionResponse(Vector<Ref<WebCore::AuthenticatorAssertionResponse>>&&, WebAuthenticationSource, CompletionHandler<void(WebCore::AuthenticatorAssertionResponse*)>&&) = 0;
    5959        virtual void decidePolicyForLocalAuthenticator(CompletionHandler<void(LocalAuthenticatorPolicy)>&&) = 0;
    6060        virtual void cancelRequest() = 0;
  • trunk/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp

    r260182 r260970  
    274274}
    275275
    276 void AuthenticatorManager::selectAssertionResponse(const HashSet<Ref<AuthenticatorAssertionResponse>>& responses, WebAuthenticationSource source, CompletionHandler<void(AuthenticatorAssertionResponse*)>&& completionHandler)
    277 {
    278     Vector<Ref<AuthenticatorAssertionResponse>> responseVector;
    279     responseVector.reserveInitialCapacity(responses.size());
    280     for (auto& response : responses)
    281         responseVector.uncheckedAppend(response.copyRef());
    282 
    283     dispatchPanelClientCall([responses = WTFMove(responseVector), source, completionHandler = WTFMove(completionHandler)] (const API::WebAuthenticationPanel& panel) mutable {
     276void AuthenticatorManager::selectAssertionResponse(Vector<Ref<WebCore::AuthenticatorAssertionResponse>>&& responses, WebAuthenticationSource source, CompletionHandler<void(AuthenticatorAssertionResponse*)>&& completionHandler)
     277{
     278    dispatchPanelClientCall([responses = WTFMove(responses), source, completionHandler = WTFMove(completionHandler)] (const API::WebAuthenticationPanel& panel) mutable {
    284279        panel.client().selectAssertionResponse(WTFMove(responses), source, WTFMove(completionHandler));
    285280    });
  • trunk/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h

    r259680 r260970  
    8383    void authenticatorStatusUpdated(WebAuthenticationStatus) final;
    8484    void requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&&) final;
    85     void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, WebAuthenticationSource, CompletionHandler<void(WebCore::AuthenticatorAssertionResponse*)>&&) final;
     85    void selectAssertionResponse(Vector<Ref<WebCore::AuthenticatorAssertionResponse>>&&, WebAuthenticationSource, CompletionHandler<void(WebCore::AuthenticatorAssertionResponse*)>&&) final;
    8686    void decidePolicyForLocalAuthenticator(CompletionHandler<void(LocalAuthenticatorPolicy)>&&) final;
    8787    void cancelRequest() final;
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h

    r259680 r260970  
    7373    State m_state { State::Init };
    7474    UniqueRef<LocalConnection> m_connection;
    75     // FIXME(183534): Combine these two.
    76     HashSet<Ref<WebCore::AuthenticatorAssertionResponse>> m_assertionResponses;
    7775    Vector<Ref<WebCore::AuthenticatorAssertionResponse>> m_existingCredentials;
    7876};
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm

    r260366 r260970  
    3535#import <WebCore/CBORWriter.h>
    3636#import <WebCore/ExceptionData.h>
     37#import <WebCore/FidoConstants.h>
    3738#import <WebCore/PublicKeyCredentialCreationOptions.h>
    3839#import <WebCore/PublicKeyCredentialRequestOptions.h>
     
    4041#import <WebCore/WebAuthenticationUtils.h>
    4142#import <pal/crypto/CryptoDigest.h>
    42 #import <wtf/HashSet.h>
    4343#import <wtf/RetainPtr.h>
    4444#import <wtf/RunLoop.h>
    4545#import <wtf/Vector.h>
    4646#import <wtf/spi/cocoa/SecuritySPI.h>
     47#import <wtf/text/Base64.h>
    4748#import <wtf/text/StringHash.h>
    4849
     
    5859// Credential ID is currently SHA-1 of the corresponding public key.
    5960const uint16_t credentialIdLength = 20;
    60 const char* const userEntityIdKey = "id";
    61 const char* const userEntityNameKey = "name";
    6261const uint64_t counter = 0;
    6362
     
    6766}
    6867
    69 // FIXME(183534): Find a better way of comparing credential id. Doing it with array seems fine given the list should be small.
     68// A Base64 encoded string of the Credential ID is used as the key of the hash set.
    7069static inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors)
    7170{
     
    7574            && credentialDescriptor.type == PublicKeyCredentialType::PublicKey
    7675            && credentialDescriptor.idVector.size() == credentialIdLength)
    77             result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size()));
     76            result.add(base64Encode(credentialDescriptor.idVector.data(), credentialDescriptor.idVector.size()));
    7877    }
    7978    return result;
     
    8988static inline RetainPtr<NSData> toNSData(const Vector<uint8_t>& data)
    9089{
    91     // FIXME(183534): Consider using initWithBytesNoCopy.
    9290    return adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]);
    9391}
     
    9694{
    9795    ASSERT(buffer);
    98     // FIXME(183534): Consider using initWithBytesNoCopy.
    9996    return adoptNS([[NSData alloc] initWithBytes:buffer->data() length:buffer->byteLength()]);
    10097}
     
    141138        return WTF::nullopt;
    142139    auto retainAttributesArray = adoptCF(attributesArrayRef);
    143     NSArray *nsAttributesArray = (NSArray *)attributesArrayRef;
     140    NSArray *sortedAttributesArray = [(NSArray *)attributesArrayRef sortedArrayUsingComparator:^(NSDictionary *a, NSDictionary *b) {
     141        return [b[(id)kSecAttrModificationDate] compare:a[(id)kSecAttrModificationDate]];
     142    }];
    144143
    145144    Vector<Ref<AuthenticatorAssertionResponse>> result;
    146     result.reserveInitialCapacity(nsAttributesArray.count);
    147     for (NSDictionary *attributes in nsAttributesArray) {
     145    result.reserveInitialCapacity(sortedAttributesArray.count);
     146    for (NSDictionary *attributes in sortedAttributesArray) {
    148147        auto decodedResponse = cbor::CBORReader::read(toVector(attributes[(id)kSecAttrApplicationTag]));
    149148        if (!decodedResponse || !decodedResponse->isMap()) {
     
    153152        auto& responseMap = decodedResponse->getMap();
    154153
    155         auto it = responseMap.find(CBOR(userEntityIdKey));
     154        auto it = responseMap.find(CBOR(kEntityIdMapKey));
    156155        if (it == responseMap.end() || !it->second.isByteString()) {
    157156            ASSERT_NOT_REACHED();
     
    160159        auto& userHandle = it->second.getByteString();
    161160
    162         it = responseMap.find(CBOR(userEntityNameKey));
     161        it = responseMap.find(CBOR(kEntityNameMapKey));
    163162        if (it == responseMap.end() || !it->second.isString()) {
    164163            ASSERT_NOT_REACHED();
     
    212211            auto* rawId = credential->rawId();
    213212            ASSERT(rawId);
    214             return excludeCredentialIds.contains(String(reinterpret_cast<const char*>(rawId->data()), rawId->byteLength()));
     213            return excludeCredentialIds.contains(base64Encode(rawId->data(), rawId->byteLength()));
    215214        })) {
    216215            receiveException({ NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s }, WebAuthenticationStatus::LAExcludeCredentialsMatched);
     
    287286
    288287    cbor::CBORValue::MapValue userEntityMap;
    289     userEntityMap[cbor::CBORValue(userEntityIdKey)] = cbor::CBORValue(creationOptions.user.idVector);
    290     userEntityMap[cbor::CBORValue(userEntityNameKey)] = cbor::CBORValue(creationOptions.user.name);
     288    userEntityMap[cbor::CBORValue(kEntityIdMapKey)] = cbor::CBORValue(creationOptions.user.idVector);
     289    userEntityMap[cbor::CBORValue(kEntityNameMapKey)] = cbor::CBORValue(creationOptions.user.name);
    291290    auto userEntity = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(userEntityMap)));
    292291    ASSERT(userEntity);
     
    436435    m_existingCredentials = WTFMove(*existingCredentials);
    437436
     437    Vector<Ref<WebCore::AuthenticatorAssertionResponse>> assertionResponses;
     438    assertionResponses.reserveInitialCapacity(m_existingCredentials.size());
    438439    for (auto& credential : m_existingCredentials) {
    439440        if (allowCredentialIds.isEmpty()) {
    440             auto addResult = m_assertionResponses.add(credential.copyRef());
    441             ASSERT_UNUSED(addResult, addResult.isNewEntry);
     441            assertionResponses.uncheckedAppend(credential.copyRef());
    442442            continue;
    443443        }
    444444
    445445        auto* rawId = credential->rawId();
    446         if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(rawId->data()), rawId->byteLength()))) {
    447             auto addResult = m_assertionResponses.add(credential.copyRef());
    448             ASSERT_UNUSED(addResult, addResult.isNewEntry);
    449         }
    450     }
    451     if (m_assertionResponses.isEmpty()) {
     446        if (allowCredentialIds.contains(base64Encode(rawId->data(), rawId->byteLength())))
     447            assertionResponses.uncheckedAppend(credential.copyRef());
     448    }
     449    if (assertionResponses.isEmpty()) {
    452450        receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
    453451        return;
     
    455453
    456454    // Step 6-7. User consent is implicitly acquired by selecting responses.
    457     m_connection->filterResponses(m_assertionResponses);
     455    m_connection->filterResponses(assertionResponses);
    458456
    459457    if (auto* observer = this->observer()) {
     
    463461                return;
    464462
    465             auto returnResponse = m_assertionResponses.take(response);
    466             if (!returnResponse)
     463            auto result = m_existingCredentials.findMatching([expectedResponse = response] (auto& response) {
     464                return response.ptr() == expectedResponse;
     465            });
     466            if (result == notFound)
    467467                return;
    468             continueGetAssertionAfterResponseSelected(WTFMove(*returnResponse));
     468            continueGetAssertionAfterResponseSelected(m_existingCredentials[result].copyRef());
    469469        };
    470         observer->selectAssertionResponse(m_assertionResponses, WebAuthenticationSource::Local, WTFMove(callback));
     470        observer->selectAssertionResponse(WTFMove(assertionResponses), WebAuthenticationSource::Local, WTFMove(callback));
    471471    }
    472472}
     
    503503
    504504    // Step 10.
    505     auto authData = buildAuthData(WTF::get<PublicKeyCredentialRequestOptions>(requestData().options).rpId, getAssertionFlags, counter, { });
     505    auto requestOptions = WTF::get<PublicKeyCredentialRequestOptions>(requestData().options);
     506    auto authData = buildAuthData(requestOptions.rpId, getAssertionFlags, counter, { });
    506507
    507508    // Step 11.
    508509    RetainPtr<CFDataRef> signature;
     510    auto nsCredentialId = toNSData(response->rawId());
    509511    {
    510512        NSDictionary *query = @{
    511513            (id)kSecClass: (id)kSecClassKey,
    512514            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    513             (id)kSecAttrApplicationLabel: toNSData(response->rawId()).get(),
     515            (id)kSecAttrApplicationLabel: nsCredentialId.get(),
    514516            (id)kSecUseAuthenticationContext: context,
    515517            (id)kSecReturnRef: @YES,
     
    545547    response->setSignature(toArrayBuffer((NSData *)signature.get()));
    546548    receiveRespond(WTFMove(response));
     549
     550    // Extra step: update the Keychain item with the same value to update its modification date such that LRU can be used
     551    // for selectAssertionResponse
     552    NSDictionary *updateQuery = @{
     553        (id)kSecClass: (id)kSecClassKey,
     554        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
     555        (id)kSecAttrApplicationLabel: nsCredentialId.get(),
     556#if HAVE(DATA_PROTECTION_KEYCHAIN)
     557        (id)kSecUseDataProtectionKeychain: @YES
     558#else
     559        (id)kSecAttrNoLegacy: @YES
     560#endif
     561    };
     562    NSDictionary *updateParams = @{
     563        (id)kSecAttrLabel: requestOptions.rpId,
     564    };
     565    auto status = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateParams);
     566    if (status)
     567        LOG_ERROR("Couldn't update the Keychain item: %d", status);
    547568}
    548569
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.h

    r259680 r260970  
    6262
    6363    LocalConnection() = default;
    64     // FIXME(183534): Invalidate the LAContext.
    65     virtual ~LocalConnection() = default;
     64    virtual ~LocalConnection();
    6665
    6766    // Overrided by MockLocalConnection.
    68     virtual void verifyUser(const String& rpId, WebCore::ClientDataType, SecAccessControlRef, UserVerificationCallback&&) const;
     67    virtual void verifyUser(const String& rpId, WebCore::ClientDataType, SecAccessControlRef, UserVerificationCallback&&);
    6968    virtual RetainPtr<SecKeyRef> createCredentialPrivateKey(LAContext *, SecAccessControlRef, const String& secAttrLabel, NSData *secAttrApplicationTag) const;
    7069    virtual void getAttestation(SecKeyRef, NSData *authData, NSData *hash, AttestationCallback&&) const;
    71     virtual void filterResponses(HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&) const { };
     70    virtual void filterResponses(Vector<Ref<WebCore::AuthenticatorAssertionResponse>>&) const { };
     71
     72private:
     73    RetainPtr<LAContext> m_context;
    7274};
    7375
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.mm

    r259680 r260970  
    4343
    4444namespace {
    45 static String bundleName()
     45#if PLATFORM(MAC)
     46static inline String bundleName()
    4647{
    47     String bundleName;
    48 
    49 #if PLATFORM(MAC)
    50     bundleName = [[NSRunningApplication currentApplication] localizedName];
     48    return [[NSRunningApplication currentApplication] localizedName];
     49}
    5150#endif
    52 
    53     return bundleName;
    54 }
    5551} // namespace
    5652
    57 void LocalConnection::verifyUser(const String& rpId, ClientDataType type, SecAccessControlRef accessControl, UserVerificationCallback&& completionHandler) const
     53LocalConnection::~LocalConnection()
    5854{
    59     String title;
     55    // Dismiss any showing LocalAuthentication dialogs.
     56    [m_context invalidate];
     57}
     58
     59void LocalConnection::verifyUser(const String& rpId, ClientDataType type, SecAccessControlRef accessControl, UserVerificationCallback&& completionHandler)
     60{
     61    String title = genericTouchIDPromptTitle();
     62#if PLATFORM(MAC)
    6063    switch (type) {
    6164    case ClientDataType::Create:
     
    6871        ASSERT_NOT_REACHED();
    6972    }
     73#endif
    7074
    71     auto context = adoptNS([allocLAContextInstance() init]);
     75    m_context = [allocLAContextInstance() init];
    7276
    7377    auto options = adoptNS([[NSMutableDictionary alloc] init]);
    74     if ([context biometryType] == LABiometryTypeTouchID) {
     78    if ([m_context biometryType] == LABiometryTypeTouchID) {
    7579        [options setObject:title forKey:@(LAOptionAuthenticationTitle)];
    7680        [options setObject:@NO forKey:@(LAOptionFallbackVisible)];
    7781    }
    7882
    79     auto reply = makeBlockPtr([context, completionHandler = WTFMove(completionHandler)] (NSDictionary *, NSError *error) mutable {
    80         ASSERT(!RunLoop::isMain());
    81 
     83    auto reply = makeBlockPtr([context = m_context, completionHandler = WTFMove(completionHandler)] (NSDictionary *, NSError *error) mutable {
    8284        UserVerification verification = UserVerification::Yes;
    8385        if (error) {
     
    8789                verification = UserVerification::Cancel;
    8890        }
     91
     92        // This block can be executed in another thread.
    8993        RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), verification, context = WTFMove(context)] () mutable {
    9094            completionHandler(verification, context.get());
     
    9296    });
    9397
    94     [context evaluateAccessControl:accessControl operation:LAAccessControlOperationUseKeySign options:options.get() reply:reply.get()];
     98    [m_context evaluateAccessControl:accessControl operation:LAAccessControlOperationUseKeySign options:options.get() reply:reply.get()];
    9599}
    96100
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h

    r258961 r260970  
    3838
    3939private:
    40     void verifyUser(const String&, WebCore::ClientDataType, SecAccessControlRef, UserVerificationCallback&&) const final;
     40    void verifyUser(const String&, WebCore::ClientDataType, SecAccessControlRef, UserVerificationCallback&&) 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     void filterResponses(HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&) const final;
     43    void filterResponses(Vector<Ref<WebCore::AuthenticatorAssertionResponse>>&) const final;
    4444
    4545    WebCore::MockWebAuthenticationConfiguration m_configuration;
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.mm

    r260182 r260970  
    3232#import <WebCore/AuthenticatorAssertionResponse.h>
    3333#import <WebCore/ExceptionData.h>
    34 #import <wtf/HashSet.h>
    3534#import <wtf/RunLoop.h>
    3635#import <wtf/spi/cocoa/SecuritySPI.h>
     
    4847}
    4948
    50 void MockLocalConnection::verifyUser(const String&, ClientDataType, SecAccessControlRef, UserVerificationCallback&& callback) const
     49void MockLocalConnection::verifyUser(const String&, ClientDataType, SecAccessControlRef, UserVerificationCallback&& callback)
    5150{
    5251    // Mock async operations.
     
    125124}
    126125
    127 void MockLocalConnection::filterResponses(HashSet<Ref<AuthenticatorAssertionResponse>>& responses) const
     126void MockLocalConnection::filterResponses(Vector<Ref<AuthenticatorAssertionResponse>>& responses) const
    128127{
    129128    const auto& preferredCredentialIdBase64 = m_configuration.local->preferredCredentialIdBase64;
     
    139138            break;
    140139    }
    141     auto response = responses.take(itr);
    142     ASSERT(response);
     140    auto response = itr->copyRef();
    143141    responses.clear();
    144     responses.add(WTFMove(*response));
     142    responses.append(WTFMove(response));
    145143}
    146144
  • trunk/Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp

    r257954 r260970  
    174174
    175175    m_remainingAssertionResponses = response->numberOfCredentials() - 1;
    176     auto addResult = m_assertionResponses.add(response.releaseNonNull());
    177     ASSERT_UNUSED(addResult, addResult.isNewEntry);
     176    m_assertionResponses.reserveInitialCapacity(response->numberOfCredentials());
     177    m_assertionResponses.uncheckedAppend(response.releaseNonNull());
    178178    driver().transact(encodeEmptyAuthenticatorRequest(CtapRequestCommand::kAuthenticatorGetNextAssertion), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
    179179        ASSERT(RunLoop::isMain());
     
    193193    }
    194194    m_remainingAssertionResponses--;
    195     auto addResult = m_assertionResponses.add(response.releaseNonNull());
    196     ASSERT_UNUSED(addResult, addResult.isNewEntry);
     195    m_assertionResponses.uncheckedAppend(response.releaseNonNull());
    197196
    198197    if (!m_remainingAssertionResponses) {
    199198        if (auto* observer = this->observer()) {
    200             observer->selectAssertionResponse(m_assertionResponses, WebAuthenticationSource::External, [this, weakThis = makeWeakPtr(*this)] (AuthenticatorAssertionResponse* response) {
     199            Vector<Ref<AuthenticatorAssertionResponse>> responsesCopy;
     200            responsesCopy.reserveInitialCapacity(m_assertionResponses.size());
     201            for (auto& response : m_assertionResponses)
     202                responsesCopy.uncheckedAppend(response.copyRef());
     203
     204            observer->selectAssertionResponse(WTFMove(responsesCopy), WebAuthenticationSource::External, [this, weakThis = makeWeakPtr(*this)] (AuthenticatorAssertionResponse* response) {
    201205                ASSERT(RunLoop::isMain());
    202206                if (!weakThis)
    203207                    return;
    204                 auto returnResponse = m_assertionResponses.take(response);
    205                 if (!returnResponse)
     208                auto result = m_assertionResponses.findMatching([expectedResponse = response] (auto& response) {
     209                    return response.ptr() == expectedResponse;
     210                });
     211                if (result == notFound)
    206212                    return;
    207                 receiveRespond(WTFMove(*returnResponse));
     213                receiveRespond(m_assertionResponses[result].copyRef());
    208214            });
    209215        }
  • trunk/Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.h

    r256062 r260970  
    7474    bool m_isDowngraded { false };
    7575    size_t m_remainingAssertionResponses { 0 };
    76     HashSet<Ref<WebCore::AuthenticatorAssertionResponse>> m_assertionResponses;
     76    Vector<Ref<WebCore::AuthenticatorAssertionResponse>> m_assertionResponses;
    7777    Vector<uint8_t> m_pinAuth;
    7878};
  • trunk/Tools/ChangeLog

    r260968 r260970  
     12020-04-30  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Optimize LocalAuthenticator
     4        https://bugs.webkit.org/show_bug.cgi?id=183534
     5        <rdar://problem/43357408>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        * TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
     10        (-[TestWebAuthenticationPanelDelegate panel:selectAssertionResponse:source:completionHandler:]):
     11        (TestWebKitAPI::TEST):
     12        Adds a new test case for the LRU.
     13
    1142020-04-30  Kate Cheney  <katherine_cheney@apple.com>
    215
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm

    r260554 r260970  
    6363    "PH76c0+WFOzZKslPyyFse4goGIW2R7k9VHLPEZl5nfnBgEVFh5zev+/xpHQIvuq6"
    6464    "RQ==";
    65 static String testUserEntityBundleBase64 = "omJpZEoAAQIDBAUGBwgJZG5hbWVwQUFFQ0F3UUZCZ2NJQ1E9PQ==";
     65static String testUserEntityBundleBase64 = "omJpZEoAAQIDBAUGBwgJZG5hbWVkSm9obg=="; // { "id": h'00010203040506070809', "name": "John" }
     66static String webAuthenticationPanelSelectedCredentialName;
    6667
    6768@interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
     
    137138    if (responses.count == 1) {
    138139        completionHandler(responses[0]);
     140        return;
     141    }
     142
     143    // Responses returned from LocalAuthenticator is in the order of LRU. Therefore, we use the last item to populate it to
     144    // the first to test its correctness.
     145    if (source == _WKWebAuthenticationSourceLocal) {
     146        webAuthenticationPanelSelectedCredentialName = responses.lastObject.name;
     147        completionHandler(responses.lastObject);
    139148        return;
    140149    }
     
    301310    webAuthenticationPanelNullUserHandle = NO;
    302311    localAuthenticatorPolicy = _WKLocalAuthenticatorPolicyDisallow;
     312    webAuthenticationPanelSelectedCredentialName = emptyString();
    303313}
    304314
     
    12951305// Skip the test because of <rdar://problem/59635486>.
    12961306#if PLATFORM(MAC)
     1307
    12971308TEST(WebAuthenticationPanel, LAGetAssertion)
    12981309{
     
    13141325    cleanUpKeychain("");
    13151326}
     1327
     1328TEST(WebAuthenticationPanel, LAGetAssertionMultipleOrder)
     1329{
     1330    reset();
     1331    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-la" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
     1332
     1333    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
     1334    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
     1335    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationLocalAuthenticatorExperimentalFeature()];
     1336
     1337    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
     1338    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
     1339    [webView setUIDelegate:delegate.get()];
     1340
     1341    ASSERT_TRUE(addKeyToKeychain(testES256PrivateKeyBase64, "", testUserEntityBundleBase64));
     1342    ASSERT_TRUE(addKeyToKeychain("BBRoi2JbR0IXTeJmvXUp1YIuM4sph/Lu3eGf75F7n+HojHKG70a4R0rB2PQce5/SJle6T7OO5Cqet/LJZVM6NQ8yDDxWvayf71GTDp2yUtuIbqJLFVbpWymlj9WRizgX3A==", "", "omJpZEoAAQIDBAUGBwgJZG5hbWVkSmFuZQ=="/* { "id": h'00010203040506070809', "name": "Jane" } */));
     1343
     1344    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1345    [webView waitForMessage:@"Succeeded!"];
     1346    EXPECT_WK_STREQ(webAuthenticationPanelSelectedCredentialName, "John");
     1347
     1348    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     1349    [webView waitForMessage:@"Succeeded!"];
     1350    EXPECT_WK_STREQ(webAuthenticationPanelSelectedCredentialName, "Jane");
     1351
     1352    cleanUpKeychain("");
     1353}
     1354
    13161355#endif
    13171356
Note: See TracChangeset for help on using the changeset viewer.