Changeset 230012 in webkit


Ignore:
Timestamp:
Mar 27, 2018 3:42:45 PM (6 years ago)
Author:
jiewen_tan@apple.com
Message:

[WebAuthN] Implement authenticatorGetAssertion
https://bugs.webkit.org/show_bug.cgi?id=183881
<rdar://problem/37258628>

Reviewed by Brent Fulgham.

Source/WebCore:

This patch does the following few things:
1) It implements the spec: https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017.
2) It tweaks encoding/decoding of PublicKeyCredentialRequestOptions such that options can be passed
between UI and Web processes.
3) It refines the way how LocalAuthenticator::makeCredential find intersection between
excludeCredentialDescriptorList and existing credentials in the authenticator, such that it is faster.
Basically, it takes the CredentialID from the list and treat it as an ASCII string and put it into a
HashSet<String>. It should not matter if a duplicated CredentialID is added. If the hash set is not
empty, the algorithm then queries Keychain for all CredentialIDs related to the current RP ID once.
For every queried CredentialID, the algorithm then treats it as an ASCII string as well and look for
a match in the hash set to produce the intersetction. The new way is also employed in
LocalAuthenticator::getAssertion as well.
4) It abstracts the way to produce authData and thus reorders a bit of code in
LocalAuthenticator::makeCredential.

Covered by API tests.

  • Modules/webauthn/AuthenticatorManager.cpp:

(WebCore::AuthenticatorManager::create const):
(WebCore::AuthenticatorManager::discoverFromExternalSource const):

  • Modules/webauthn/PublicKeyCredentialCreationOptions.h:
  • Modules/webauthn/PublicKeyCredentialRequestOptions.h:

(WebCore::PublicKeyCredentialRequestOptions::encode const):
(WebCore::PublicKeyCredentialRequestOptions::decode):

  • Modules/webauthn/cocoa/LocalAuthenticator.h:
  • Modules/webauthn/cocoa/LocalAuthenticator.mm:

(WebCore::LocalAuthenticatorInternal::buildAuthData):
(WebCore::LocalAuthenticatorInternal::produceHashSet):
(WebCore::LocalAuthenticator::makeCredential):
(WebCore::LocalAuthenticator::getAssertion):
(WebCore::LocalAuthenticator::issueClientCertificate const):

  • WebCore.xcodeproj/project.pbxproj:

Source/WebKit:

  • Shared/WebPreferences.yaml:
  • UIProcess/CredentialManagement/WebCredentialsMessengerProxy.cpp:

(WebKit::WebCredentialsMessengerProxy::makeCredential):
(WebKit::WebCredentialsMessengerProxy::getAssertion):
(WebKit::WebCredentialsMessengerProxy::getAssertionReply):

  • UIProcess/CredentialManagement/WebCredentialsMessengerProxy.h:
  • UIProcess/CredentialManagement/WebCredentialsMessengerProxy.messages.in:
  • WebProcess/CredentialManagement/WebCredentialsMessenger.cpp:

(WebKit::WebCredentialsMessenger::getAssertion):
(WebKit::WebCredentialsMessenger::getAssertionReply):

  • WebProcess/CredentialManagement/WebCredentialsMessenger.messages.in:

Tools:

  • TestWebKitAPI/Tests/ios/LocalAuthenticator.mm:

(TestWebKitAPI::getTestKey):
(TestWebKitAPI::addTestKeyToKeychain):
(TestWebKitAPI::LAEvaluatePolicyFailedSwizzler::evaluatePolicyFailed):
(TestWebKitAPI::LAEvaluatePolicyPassedSwizzler::evaluatePolicyPassed):
(TestWebKitAPI::LAEvaluateAccessControlFailedSwizzler::LAEvaluateAccessControlFailedSwizzler):
(TestWebKitAPI::LAEvaluateAccessControlFailedSwizzler::evaluateAccessControlFailed):
(TestWebKitAPI::LAEvaluateAccessControlPassedSwizzler::LAEvaluateAccessControlPassedSwizzler):
(TestWebKitAPI::LAEvaluateAccessControlPassedSwizzler::evaluateAccessControlPassed):
(TestWebKitAPI::TEST):

Location:
trunk
Files:
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r230009 r230012  
     12018-03-27  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthN] Implement authenticatorGetAssertion
     4        https://bugs.webkit.org/show_bug.cgi?id=183881
     5        <rdar://problem/37258628>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        This patch does the following few things:
     10        1) It implements the spec: https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017.
     11        2) It tweaks encoding/decoding of PublicKeyCredentialRequestOptions such that options can be passed
     12        between UI and Web processes.
     13        3) It refines the way how LocalAuthenticator::makeCredential find intersection between
     14        excludeCredentialDescriptorList and existing credentials in the authenticator, such that it is faster.
     15        Basically, it takes the CredentialID from the list and treat it as an ASCII string and put it into a
     16        HashSet<String>. It should not matter if a duplicated CredentialID is added. If the hash set is not
     17        empty, the algorithm then queries Keychain for all CredentialIDs related to the current RP ID once.
     18        For every queried CredentialID, the algorithm then treats it as an ASCII string as well and look for
     19        a match in the hash set to produce the intersetction. The new way is also employed in
     20        LocalAuthenticator::getAssertion as well.
     21        4) It abstracts the way to produce authData and thus reorders a bit of code in
     22        LocalAuthenticator::makeCredential.
     23
     24        Covered by API tests.
     25
     26        * Modules/webauthn/AuthenticatorManager.cpp:
     27        (WebCore::AuthenticatorManager::create const):
     28        (WebCore::AuthenticatorManager::discoverFromExternalSource const):
     29        * Modules/webauthn/PublicKeyCredentialCreationOptions.h:
     30        * Modules/webauthn/PublicKeyCredentialRequestOptions.h:
     31        (WebCore::PublicKeyCredentialRequestOptions::encode const):
     32        (WebCore::PublicKeyCredentialRequestOptions::decode):
     33        * Modules/webauthn/cocoa/LocalAuthenticator.h:
     34        * Modules/webauthn/cocoa/LocalAuthenticator.mm:
     35        (WebCore::LocalAuthenticatorInternal::buildAuthData):
     36        (WebCore::LocalAuthenticatorInternal::produceHashSet):
     37        (WebCore::LocalAuthenticator::makeCredential):
     38        (WebCore::LocalAuthenticator::getAssertion):
     39        (WebCore::LocalAuthenticator::issueClientCertificate const):
     40        * WebCore.xcodeproj/project.pbxproj:
     41
    1422018-03-27  Chris Dumez  <cdumez@apple.com>
    243
  • trunk/Source/WebCore/Modules/webauthn/AuthenticatorManager.cpp

    r228572 r230012  
    161161    // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
    162162    // Also, resident keys, user verifications and direct attestation are enforced at this tage.
    163     // For better performance, no filtering is done here regarding to options.excludeCredentials.
     163    // For better performance, transports of options.excludeCredentials are checked in LocalAuthenticator.
    164164    if (!m_messenger)  {
    165165        promise.reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") });
     
    220220    // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
    221221    // Also, resident keys, user verifications and direct attestation are enforced at this tage.
    222     // For better performance, no filtering is done here regarding to options.allowCredentials.
     222    // For better performance, filtering of options.allowCredentials is done in LocalAuthenticator.
    223223    if (!m_messenger)  {
    224224        promise.reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") });
  • trunk/Source/WebCore/Modules/webauthn/PublicKeyCredentialCreationOptions.h

    r229699 r230012  
    4848    struct UserEntity : public Entity {
    4949        BufferSource id; // id becomes idVector once it is passed to UIProcess.
    50         mutable Vector<uint8_t> idVector;
     50        Vector<uint8_t> idVector;
    5151        String displayName;
    5252    };
     
    141141        result.user.icon = source.user.icon.isolatedCopy();
    142142        result.user.displayName = source.user.displayName.isolatedCopy();
    143         result.user.idVector = WTFMove(source.user.idVector);
     143        result.user.idVector = source.user.idVector;
    144144        return result;
    145145    }
  • trunk/Source/WebCore/Modules/webauthn/PublicKeyCredentialRequestOptions.h

    r227764 r230012  
    3939    mutable String rpId;
    4040    Vector<PublicKeyCredentialDescriptor> allowCredentials;
     41
     42    template<class Encoder> void encode(Encoder&) const;
     43    template<class Decoder> static std::optional<PublicKeyCredentialRequestOptions> decode(Decoder&);
    4144};
     45
     46// Not every member is encoded.
     47template<class Encoder>
     48void PublicKeyCredentialRequestOptions::encode(Encoder& encoder) const
     49{
     50    encoder << rpId << allowCredentials;
     51}
     52
     53template<class Decoder>
     54std::optional<PublicKeyCredentialRequestOptions> PublicKeyCredentialRequestOptions::decode(Decoder& decoder)
     55{
     56    PublicKeyCredentialRequestOptions result;
     57    if (!decoder.decode(result.rpId))
     58        return std::nullopt;
     59    if (!decoder.decode(result.allowCredentials))
     60        return std::nullopt;
     61    return result;
     62}
    4263
    4364} // namespace WebCore
  • trunk/Source/WebCore/Modules/webauthn/cocoa/LocalAuthenticator.h

    r229699 r230012  
    3636struct ExceptionData;
    3737struct PublicKeyCredentialCreationOptions;
     38struct PublicKeyCredentialRequestOptions;
    3839
    3940using CreationCallback = Function<void(const Vector<uint8_t>&, const Vector<uint8_t>&)>;
     41using RequestCallback = Function<void(const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&)>;
    4042using ExceptionCallback = Function<void(const WebCore::ExceptionData&)>;
    4143
     
    5052
    5153    void makeCredential(const Vector<uint8_t>& hash, const PublicKeyCredentialCreationOptions&, CreationCallback&&, ExceptionCallback&&);
     54    void getAssertion(const Vector<uint8_t>& hash, const PublicKeyCredentialRequestOptions&, RequestCallback&&, ExceptionCallback&&);
    5255    bool isAvailable() const;
    5356
  • trunk/Source/WebCore/Modules/webauthn/cocoa/LocalAuthenticator.mm

    r229699 r230012  
    3333#import "ExceptionData.h"
    3434#import "PublicKeyCredentialCreationOptions.h"
     35#import "PublicKeyCredentialRequestOptions.h"
    3536#import <Security/SecItem.h>
    3637#import <pal/crypto/CryptoDigest.h>
    3738#import <pal/spi/cocoa/DeviceIdentitySPI.h>
    3839#import <wtf/BlockPtr.h>
     40#import <wtf/HashSet.h>
    3941#import <wtf/MainThread.h>
    4042#import <wtf/RetainPtr.h>
    4143#import <wtf/Vector.h>
     44#import <wtf/text/StringHash.h>
    4245
    4346#import "LocalAuthenticationSoftLink.h"
     
    4750namespace LocalAuthenticatorInternal {
    4851
    49 // UP, UV and AT are set. See https://www.w3.org/TR/webauthn/#flags.
    50 const uint8_t authenticatorDataFlags = 69;
     52// See https://www.w3.org/TR/webauthn/#flags.
     53const uint8_t makeCredentialFlags = 0b01000101; // UP, UV and AT are set.
     54const uint8_t getAssertionFlags = 0b00000101; // UP and UV are set.
    5155// FIXME(rdar://problem/38320512): Define Apple AAGUID.
    5256const uint8_t AAGUID[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 16 bytes
    53 const size_t maxCredentialIdLength = 0xffff; // 2 bytes
     57// Credential ID is currently SHA-1 of the corresponding public key.
     58// FIXME(183534): Assume little endian here.
     59const union {
     60    uint16_t integer;
     61    uint8_t bytes[2];
     62} credentialIdLength = {0x0014};
    5463const size_t ES256KeySizeInBytes = 32;
     64const size_t authDataPrefixFixedSize = 37; // hash(32) + flags(1) + counter(4)
     65
     66#if PLATFORM(IOS)
     67// https://www.w3.org/TR/webauthn/#sec-authenticator-data
     68static Vector<uint8_t> buildAuthData(const String& rpId, const uint8_t flags, const uint32_t counter, const Vector<uint8_t>& optionalAttestedCredentialData)
     69{
     70    Vector<uint8_t> authData;
     71    authData.reserveCapacity(authDataPrefixFixedSize + optionalAttestedCredentialData.size());
     72
     73    // RP ID hash
     74    auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
     75    // FIXME(183534): Test IDN.
     76    ASSERT(rpId.isAllASCII());
     77    auto asciiRpId = rpId.ascii();
     78    crypto->addBytes(asciiRpId.data(), asciiRpId.length());
     79    authData = crypto->computeHash();
     80
     81    // FLAGS
     82    authData.append(flags);
     83
     84    // COUNTER
     85    // FIXME(183534): Assume little endian here.
     86    union {
     87        uint32_t integer;
     88        uint8_t bytes[4];
     89    } counterUnion;
     90    counterUnion.integer = counter;
     91    authData.append(counterUnion.bytes, sizeof(counterUnion.bytes));
     92
     93    // ATTESTED CRED. DATA
     94    authData.appendVector(optionalAttestedCredentialData);
     95
     96    return authData;
     97}
     98
     99inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors)
     100{
     101    HashSet<String> result;
     102    for (auto& credentialDescriptor : credentialDescriptors) {
     103        if (credentialDescriptor.transports.isEmpty() && credentialDescriptor.type == PublicKeyCredentialType::PublicKey && credentialDescriptor.idVector.size() == credentialIdLength.integer)
     104            result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size()));
     105    }
     106    return result;
     107}
     108#endif // !PLATFORM(IOS)
    55109
    56110} // LocalAuthenticatorInternal
     
    63117    // FIXME(182772)
    64118    ASSERT_UNUSED(hash, hash == hash);
    65     ASSERT_UNUSED(options, options.rp.name.isEmpty());
     119    ASSERT_UNUSED(options, !options.rp.id.isEmpty());
    66120    ASSERT_UNUSED(callback, callback);
    67121    exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
     
    85139
    86140    // Step 3.
    87     for (auto& excludeCredential : options.excludeCredentials) {
    88         if (excludeCredential.type == PublicKeyCredentialType::PublicKey && excludeCredential.transports.isEmpty()) {
    89             // Search Keychain for the Credential ID and RP ID, which is stored in the kSecAttrApplicationLabel and kSecAttrLabel attribute respectively.
    90             NSDictionary *query = @{
    91                 (id)kSecClass: (id)kSecClassKey,
    92                 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    93                 (id)kSecAttrApplicationLabel: [NSData dataWithBytes:excludeCredential.idVector.data() length:excludeCredential.idVector.size()],
    94                 (id)kSecAttrLabel: options.rp.id
    95             };
    96             OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
    97             if (!status) {
     141    HashSet<String> excludeCredentialIds = produceHashSet(options.excludeCredentials);
     142    if (!excludeCredentialIds.isEmpty()) {
     143        // Search Keychain for the RP ID.
     144        NSDictionary *query = @{
     145            (id)kSecClass: (id)kSecClassKey,
     146            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
     147            (id)kSecAttrLabel: options.rp.id,
     148            (id)kSecReturnAttributes: @YES,
     149            (id)kSecMatchLimit: (id)kSecMatchLimitAll,
     150        };
     151        CFTypeRef attributesArrayRef = nullptr;
     152        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
     153        if (status && status != errSecItemNotFound) {
     154            LOG_ERROR("Couldn't query Keychain: %d", status);
     155            exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
     156            return;
     157        }
     158        auto retainAttributesArray = adoptCF(attributesArrayRef);
     159
     160        for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
     161            NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
     162            if (excludeCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) {
    98163                exceptionCallback({ NotAllowedError, ASCIILiteral("At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.") });
    99                 return;
    100             }
    101             if (status != errSecItemNotFound) {
    102                 LOG_ERROR("Couldn't query Keychain: %d", status);
    103                 exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
    104164                return;
    105165            }
     
    112172    auto context = adoptNS([allocLAContextInstance() init]);
    113173    NSError *error = nil;
    114     NSString *reason = [NSString stringWithFormat:@"Allow %@ to create a public key credential for %@", (id)options.rp.id, (id)options.user.name];
    115174    if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
    116175        LOG_ERROR("Couldn't evaluate authentication with biometrics policy: %@", error);
     
    120179    }
    121180
     181    NSString *reason = [NSString stringWithFormat:@"Allow %@ to create a public key credential for %@", (id)options.rp.id, (id)options.user.name];
    122182    // FIXME(183534): Optimize the following nested callbacks and threading.
    123183    [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:reason reply:BlockPtr<void(BOOL, NSError *)>::fromCallable([weakThis = m_weakFactory.createWeakPtr(*this), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), options = crossThreadCopy(options), hash] (BOOL success, NSError *error) mutable {
     
    158218            ASSERT(certificates && ([certificates count] == 2));
    159219
    160             // Step 7.2 - 7.4.
     220            // Step 7.2-7.4.
    161221            // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution
    162222            // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle.
     
    181241                    (id)kSecReturnAttributes: @YES
    182242                };
    183                 CFTypeRef attributesRef = NULL;
     243                CFTypeRef attributesRef = nullptr;
    184244                OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, &attributesRef);
    185245                if (status) {
     
    192252                NSDictionary *nsAttributes = (NSDictionary *)attributesRef;
    193253                NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
    194                 credentialId.append(static_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length);
     254                credentialId.append(reinterpret_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length);
    195255
    196256                NSDictionary *updateQuery = @{
     
    211271            }
    212272
    213             // Step 12.
    214             // Apple Attestation Cont'
    215             // Assemble the attestation object:
    216             // https://www.w3.org/TR/webauthn/#attestation-object
    217             // FIXME(183534): authData could throttle.
    218             Vector<uint8_t> authData;
     273            // Step 10.
     274            // FIXME(183533): store the counter.
     275            uint32_t counter = 0;
     276
     277            // FIXME(183534): attestedCredentialData could throttle.
     278            // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data
     279            Vector<uint8_t> attestedCredentialData;
    219280            {
    220                 // RP ID hash
    221                 auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
    222                 // FIXME(183534): Test IDN.
    223                 ASSERT(options.rp.id.isAllASCII());
    224                 auto asciiRpId = options.rp.id.ascii();
    225                 crypto->addBytes(asciiRpId.data(), asciiRpId.length());
    226                 authData = crypto->computeHash();
    227 
    228                 // FLAGS
    229                 authData.append(authenticatorDataFlags);
    230 
    231                 // Step. 10.
    232                 // COUNTER
    233                 // FIXME(183533): store the counter.
    234                 union {
    235                     uint32_t integer;
    236                     uint8_t bytes[4];
    237                 } counter = {0x00000000};
    238                 authData.append(counter.bytes, sizeof(counter.bytes));
    239 
    240                 // Step 11.
    241                 // AAGUID
    242                 authData.append(AAGUID, sizeof(AAGUID));
    243 
    244                 // L
    245                 ASSERT(credentialId.size() <= maxCredentialIdLength);
    246                 // Assume little endian here.
    247                 union {
    248                     uint16_t integer;
    249                     uint8_t bytes[2];
    250                 } credentialIdLength;
    251                 credentialIdLength.integer = static_cast<uint16_t>(credentialId.size());
    252                 authData.append(credentialIdLength.bytes, sizeof(uint16_t));
    253 
    254                 // CREDENTIAL ID
    255                 authData.appendVector(credentialId);
    256 
    257                 // CREDENTIAL PUBLIC KEY
    258                 CFDataRef publicKeyDataRef = NULL;
     281                // aaguid
     282                attestedCredentialData.append(AAGUID, sizeof(AAGUID));
     283
     284                // credentialIdLength
     285                ASSERT(credentialId.size() == credentialIdLength.integer);
     286                // FIXME(183534): Assume little endian here.
     287                attestedCredentialData.append(credentialIdLength.bytes, sizeof(uint16_t));
     288
     289                // credentialId
     290                attestedCredentialData.appendVector(credentialId);
     291
     292                // credentialPublicKey
     293                CFDataRef publicKeyDataRef = nullptr;
    259294                {
    260295                    auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey));
    261                     CFErrorRef errorRef = NULL;
     296                    CFErrorRef errorRef = nullptr;
    262297                    publicKeyDataRef = SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef);
    263298                    auto retainError = adoptCF(errorRef);
     
    289324                    return;
    290325                }
    291                 authData.appendVector(cosePublicKey.value());
    292             }
    293 
     326                attestedCredentialData.appendVector(cosePublicKey.value());
     327            }
     328
     329            // Step 12.
     330            auto authData = buildAuthData(options.rp.id, makeCredentialFlags, counter, attestedCredentialData);
     331
     332            // Step 13. Apple Attestation Cont'
     333            // Assemble the attestation object:
     334            // https://www.w3.org/TR/webauthn/#attestation-object
    294335            cbor::CBORValue::MapValue attestationStatementMap;
    295336            {
    296337                Vector<uint8_t> signature;
    297338                {
    298                     CFErrorRef errorRef = NULL;
     339                    CFErrorRef errorRef = nullptr;
    299340                    // FIXME(183652): Reduce prompt for biometrics
    300341                    CFDataRef signatureRef = SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:authData.data() length:authData.size()], &errorRef);
    301342                    auto retainError = adoptCF(errorRef);
    302343                    if (errorRef) {
    303                         LOG_ERROR("Couldn't export the public key: %@", (NSError*)errorRef);
     344                        LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef);
    304345                        exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
    305346                        return;
     
    307348                    auto retainSignature = adoptCF(signatureRef);
    308349                    NSData *nsSignature = (NSData *)signatureRef;
    309                     signature.append(static_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length);
     350                    signature.append(reinterpret_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length);
    310351                }
    311352                attestationStatementMap[cbor::CBORValue("alg")] = cbor::CBORValue(COSE::ES256);
     
    317358                    NSData *nsData = (NSData *)dataRef;
    318359                    Vector<uint8_t> data;
    319                     data.append(static_cast<const uint8_t*>(nsData.bytes), nsData.length);
     360                    data.append(reinterpret_cast<const uint8_t*>(nsData.bytes), nsData.length);
    320361                    cborArray.append(cbor::CBORValue(WTFMove(data)));
    321362                }
     
    337378        }).get());
    338379    }).get()];
     380#endif // !PLATFORM(IOS)
     381}
     382
     383void LocalAuthenticator::getAssertion(const Vector<uint8_t>& hash, const PublicKeyCredentialRequestOptions& options, RequestCallback&& callback, ExceptionCallback&& exceptionCallback)
     384{
     385    using namespace LocalAuthenticatorInternal;
     386
     387#if !PLATFORM(IOS)
     388    // FIXME(182772)
     389    ASSERT_UNUSED(hash, hash == hash);
     390    ASSERT_UNUSED(options, !options.rpId.isEmpty());
     391    ASSERT_UNUSED(callback, callback);
     392    exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
     393#else
     394    // The following implements https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017.
     395    // Skip Step 2 as requireUserVerification is enforced.
     396    // Skip Step 8 as extensions are not supported yet.
     397    // Step 12 is implicitly captured by all UnknownError exception callbacks.
     398    // 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.
     399    HashSet<String> allowCredentialIds = produceHashSet(options.allowCredentials);
     400    if (!options.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) {
     401        exceptionCallback({ NotAllowedError, ASCIILiteral("No matched credentials are found in the platform attached authenticator.") });
     402        return;
     403    }
     404
     405    // Search Keychain for the RP ID.
     406    NSDictionary *query = @{
     407        (id)kSecClass: (id)kSecClassKey,
     408        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
     409        (id)kSecAttrLabel: options.rpId,
     410        (id)kSecReturnAttributes: @YES,
     411        (id)kSecMatchLimit: (id)kSecMatchLimitAll,
     412    };
     413    CFTypeRef attributesArrayRef = nullptr;
     414    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
     415    if (status && status != errSecItemNotFound) {
     416        LOG_ERROR("Couldn't query Keychain: %d", status);
     417        exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
     418        return;
     419    }
     420    auto retainAttributesArray = adoptCF(attributesArrayRef);
     421
     422    NSArray *intersectedCredentialsAttributes = nil;
     423    if (options.allowCredentials.isEmpty())
     424        intersectedCredentialsAttributes = (NSArray *)attributesArrayRef;
     425    else {
     426        NSMutableArray *result = [NSMutableArray arrayWithCapacity:allowCredentialIds.size()];
     427        for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
     428            NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
     429            if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length)))
     430                [result addObject:nsAttributes];
     431        }
     432        intersectedCredentialsAttributes = result;
     433    }
     434    if (!intersectedCredentialsAttributes.count) {
     435        exceptionCallback({ NotAllowedError, ASCIILiteral("No matched credentials are found in the platform attached authenticator.") });
     436        return;
     437    }
     438
     439    // Step 6.
     440    // 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.
     441    NSDictionary *selectedCredentialAttributes = intersectedCredentialsAttributes[0];
     442
     443    // Step 7. Get user consent.
     444    // FIXME(183534): The lifetime of context is managed by reply and the early return, which is a bit subtle.
     445    LAContext *context = [allocLAContextInstance() init];
     446    NSError *error = nil;
     447    if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
     448        auto retainContext = adoptNS(context);
     449        LOG_ERROR("Couldn't evaluate authentication with biometrics policy: %@", error);
     450        // FIXME(182767)
     451        exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
     452        return;
     453    }
     454
     455    Vector<uint8_t> credentialId;
     456    NSData *nsCredentialId = selectedCredentialAttributes[(id)kSecAttrApplicationLabel];
     457    credentialId.append(reinterpret_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length);
     458    Vector<uint8_t> userhandle;
     459    NSData *nsUserhandle = selectedCredentialAttributes[(id)kSecAttrApplicationTag];
     460    userhandle.append(reinterpret_cast<const uint8_t*>(nsUserhandle.bytes), nsUserhandle.length);
     461    auto reply = BlockPtr<void(BOOL, NSError *)>::fromCallable([callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), rpId = options.rpId.isolatedCopy(), hash, credentialId = WTFMove(credentialId), userhandle = WTFMove(userhandle), context = adoptNS(context)] (BOOL success, NSError *error) mutable {
     462        ASSERT(!isMainThread());
     463        if (!success || error) {
     464            LOG_ERROR("Couldn't authenticate with biometrics: %@", error);
     465            exceptionCallback({ NotAllowedError, ASCIILiteral("Couldn't get user consent.") });
     466            return;
     467        }
     468
     469        // Step 9-10.
     470        // FIXME(183533): Due to the stated Keychain limitations, we can't save the counter value.
     471        // Therefore, it is always zero.
     472        uint32_t counter = 0;
     473        auto authData = buildAuthData(rpId, getAssertionFlags, counter, { });
     474
     475        // Step 11.
     476        Vector<uint8_t> signature;
     477        {
     478            NSDictionary *query = @{
     479                (id)kSecClass: (id)kSecClassKey,
     480                (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
     481                (id)kSecAttrApplicationLabel: [NSData dataWithBytes:credentialId.data() length:credentialId.size()],
     482                (id)kSecUseAuthenticationContext: context.get(),
     483                (id)kSecReturnRef: @YES,
     484            };
     485            CFTypeRef privateKeyRef = nullptr;
     486            OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &privateKeyRef);
     487            if (status) {
     488                LOG_ERROR("Couldn't get the private key reference: %d", status);
     489                exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
     490                return;
     491            }
     492            auto privateKey = adoptCF(privateKeyRef);
     493
     494            NSMutableData *dataToSign = [NSMutableData dataWithBytes:authData.data() length:authData.size()];
     495            [dataToSign appendBytes:hash.data() length:hash.size()];
     496
     497            CFErrorRef errorRef = nullptr;
     498            // FIXME: Converting CFTypeRef to SecKeyRef is quite subtle here.
     499            CFDataRef signatureRef = SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef);
     500            auto retainError = adoptCF(errorRef);
     501            if (errorRef) {
     502                LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef);
     503                exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
     504                return;
     505            }
     506            auto retainSignature = adoptCF(signatureRef);
     507            NSData *nsSignature = (NSData *)signatureRef;
     508            signature.append(reinterpret_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length);
     509        }
     510
     511        // Step 13.
     512        callback(credentialId, authData, signature, userhandle);
     513    });
     514
     515    // FIXME(183533): Use userhandle instead of username due to the stated Keychain limitations.
     516    NSString *reason = [NSString stringWithFormat:@"Log into %@ with %@.", (id)options.rpId, selectedCredentialAttributes[(id)kSecAttrApplicationTag]];
     517    [context evaluateAccessControl:(__bridge SecAccessControlRef)selectedCredentialAttributes[(id)kSecAttrAccessControl] operation:LAAccessControlOperationUseKeySign localizedReason:reason reply:reply.get()];
    339518#endif // !PLATFORM(IOS)
    340519}
     
    372551    SecAccessControlRef accessControlRef;
    373552    {
    374         CFErrorRef errorRef = NULL;
     553        CFErrorRef errorRef = nullptr;
    375554        accessControlRef = SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, &errorRef);
    376555        auto retainError = adoptCF(errorRef);
  • trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj

    r229967 r230012  
    17101710                57303BF42009904600355965 /* JSPublicKeyCredentialDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303BF22009904500355965 /* JSPublicKeyCredentialDescriptor.h */; };
    17111711                57303C002009951C00355965 /* JSPublicKeyCredentialType.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303BF92009913400355965 /* JSPublicKeyCredentialType.h */; };
    1712                 57303C0A20099BAD00355965 /* PublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C06200998F800355965 /* PublicKeyCredentialRequestOptions.h */; };
     1712                57303C0A20099BAD00355965 /* PublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C06200998F800355965 /* PublicKeyCredentialRequestOptions.h */; settings = {ATTRIBUTES = (Private, ); }; };
    17131713                57303C1120099CB100355965 /* JSPublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C0C20099C7500355965 /* JSPublicKeyCredentialRequestOptions.h */; };
    17141714                57303C192009A2F300355965 /* JSPublicKeyCredentialCreationOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C132009A25700355965 /* JSPublicKeyCredentialCreationOptions.h */; };
  • trunk/Source/WebKit/ChangeLog

    r230009 r230012  
     12018-03-27  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthN] Implement authenticatorGetAssertion
     4        https://bugs.webkit.org/show_bug.cgi?id=183881
     5        <rdar://problem/37258628>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        * Shared/WebPreferences.yaml:
     10        * UIProcess/CredentialManagement/WebCredentialsMessengerProxy.cpp:
     11        (WebKit::WebCredentialsMessengerProxy::makeCredential):
     12        (WebKit::WebCredentialsMessengerProxy::getAssertion):
     13        (WebKit::WebCredentialsMessengerProxy::getAssertionReply):
     14        * UIProcess/CredentialManagement/WebCredentialsMessengerProxy.h:
     15        * UIProcess/CredentialManagement/WebCredentialsMessengerProxy.messages.in:
     16        * WebProcess/CredentialManagement/WebCredentialsMessenger.cpp:
     17        (WebKit::WebCredentialsMessenger::getAssertion):
     18        (WebKit::WebCredentialsMessenger::getAssertionReply):
     19        * WebProcess/CredentialManagement/WebCredentialsMessenger.messages.in:
     20
    1212018-03-27  Chris Dumez  <cdumez@apple.com>
    222
  • trunk/Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.cpp

    r229699 r230012  
    5858    }
    5959    // FIXME(183534): Weak pointers doesn't work in another thread because of race condition.
     60    // FIXME(183534): Unify callbacks.
    6061    auto weakThis = m_weakFactory.createWeakPtr(*this);
    6162    auto callback = [weakThis, messageId] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& attestationObject) {
     
    7273}
    7374
    74 void WebCredentialsMessengerProxy::getAssertion(uint64_t)
     75void WebCredentialsMessengerProxy::getAssertion(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions& options)
    7576{
     77    // FIXME(182767)
     78    if (!m_authenticator)
     79        exceptionReply(messageId, { WebCore::NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
     80    // FIXME(183534): Weak pointers doesn't work in another thread because of race condition.
     81    // FIXME(183534): Unify callbacks.
     82    auto weakThis = m_weakFactory.createWeakPtr(*this);
     83    auto callback = [weakThis, messageId] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle) {
     84        if (weakThis)
     85            weakThis->getAssertionReply(messageId, credentialId, authenticatorData, signature, userHandle);
     86    };
     87    auto exceptionCallback = [weakThis, messageId] (const WebCore::ExceptionData& exception) {
     88        if (weakThis)
     89            weakThis->exceptionReply(messageId, exception);
     90    };
     91    m_authenticator->getAssertion(hash, options, WTFMove(callback), WTFMove(exceptionCallback));
    7692}
    7793
     
    95111}
    96112
     113void WebCredentialsMessengerProxy::getAssertionReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle)
     114{
     115    m_webPageProxy.send(Messages::WebCredentialsMessenger::GetAssertionReply(messageId, credentialId, authenticatorData, signature, userHandle));
     116}
     117
    97118void WebCredentialsMessengerProxy::isUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool result)
    98119{
  • trunk/Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.h

    r229699 r230012  
    3838struct ExceptionData;
    3939struct PublicKeyCredentialCreationOptions;
     40struct PublicKeyCredentialRequestOptions;
    4041}
    4142
     
    5657    // Receivers.
    5758    void makeCredential(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialCreationOptions&);
    58     void getAssertion(uint64_t messageId);
     59    void getAssertion(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions&);
    5960    void isUserVerifyingPlatformAuthenticatorAvailable(uint64_t messageId);
    6061
     
    6263    void exceptionReply(uint64_t messageId, const WebCore::ExceptionData&);
    6364    void makeCredentialReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& attestationObject);
     65    void getAssertionReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle);
    6466    void isUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool);
    6567
  • trunk/Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.messages.in

    r229699 r230012  
    2828
    2929    MakeCredential(uint64_t messageId, Vector<uint8_t> hash, struct WebCore::PublicKeyCredentialCreationOptions options);
    30     GetAssertion(uint64_t messageId);
     30    GetAssertion(uint64_t messageId, Vector<uint8_t> hash, struct WebCore::PublicKeyCredentialRequestOptions options);
    3131    IsUserVerifyingPlatformAuthenticatorAvailable(uint64_t messageId);
    3232}
  • trunk/Source/WebKit/WebProcess/CredentialManagement/WebCredentialsMessenger.cpp

    r229699 r230012  
    3434#include "WebProcess.h"
    3535#include <WebCore/PublicKeyCredentialCreationOptions.h>
     36#include <WebCore/PublicKeyCredentialRequestOptions.h>
    3637
    3738namespace WebKit {
     
    5455}
    5556
    56 void WebCredentialsMessenger::getAssertion(const Vector<uint8_t>&, const WebCore::PublicKeyCredentialRequestOptions&, WebCore::RequestCompletionHandler&&)
     57void WebCredentialsMessenger::getAssertion(const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions& options, WebCore::RequestCompletionHandler&& handler)
    5758{
     59    auto messageId = addRequestCompletionHandler(WTFMove(handler));
     60    m_webPage.send(Messages::WebCredentialsMessengerProxy::GetAssertion(messageId, hash, options));
    5861}
    5962
     
    7275void WebCredentialsMessenger::getAssertionReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle)
    7376{
     77    auto handler = takeRequestCompletionHandler(messageId);
     78    handler(WebCore::AssertionReturnBundle(ArrayBuffer::create(credentialId.data(), credentialId.size()), ArrayBuffer::create(authenticatorData.data(), authenticatorData.size()), ArrayBuffer::create(signature.data(), signature.size()), ArrayBuffer::create(userHandle.data(), userHandle.size())));
    7479}
    7580
  • trunk/Source/WebKit/WebProcess/CredentialManagement/WebCredentialsMessenger.messages.in

    r229699 r230012  
    3131    GetAssertionReply(uint64_t messageId, Vector<uint8_t> credentialId, Vector<uint8_t> authenticatorData, Vector<uint8_t> signature, Vector<uint8_t> userHandle);
    3232    IsUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool result);
    33 exceptionReply(uint64_t messageId, struct WebCore::ExceptionData exception);
    3433}
    3534
  • trunk/Tools/ChangeLog

    r230011 r230012  
     12018-03-27  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthN] Implement authenticatorGetAssertion
     4        https://bugs.webkit.org/show_bug.cgi?id=183881
     5        <rdar://problem/37258628>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        * TestWebKitAPI/Tests/ios/LocalAuthenticator.mm:
     10        (TestWebKitAPI::getTestKey):
     11        (TestWebKitAPI::addTestKeyToKeychain):
     12        (TestWebKitAPI::LAEvaluatePolicyFailedSwizzler::evaluatePolicyFailed):
     13        (TestWebKitAPI::LAEvaluatePolicyPassedSwizzler::evaluatePolicyPassed):
     14        (TestWebKitAPI::LAEvaluateAccessControlFailedSwizzler::LAEvaluateAccessControlFailedSwizzler):
     15        (TestWebKitAPI::LAEvaluateAccessControlFailedSwizzler::evaluateAccessControlFailed):
     16        (TestWebKitAPI::LAEvaluateAccessControlPassedSwizzler::LAEvaluateAccessControlPassedSwizzler):
     17        (TestWebKitAPI::LAEvaluateAccessControlPassedSwizzler::evaluateAccessControlPassed):
     18        (TestWebKitAPI::TEST):
     19
    1202018-03-27  Brian Burg  <bburg@apple.com>
    221
  • trunk/Tools/TestWebKitAPI/Tests/ios/LocalAuthenticator.mm

    r229727 r230012  
    3939#import <WebCore/LocalAuthenticator.h>
    4040#import <WebCore/PublicKeyCredentialCreationOptions.h>
     41#import <WebCore/PublicKeyCredentialRequestOptions.h>
    4142#import <wtf/BlockPtr.h>
    4243#import <wtf/text/Base64.h>
     
    8687        (id)kSecAttrKeySizeInBits: @256,
    8788    };
    88     CFErrorRef errorRef = NULL;
     89    CFErrorRef errorRef = nullptr;
    8990    auto key = adoptCF(SecKeyCreateWithData(
    9091        (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:testES256PrivateKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
     
    9798}
    9899
     100void addTestKeyToKeychain()
     101{
     102    auto key = getTestKey();
     103    NSDictionary* addQuery = @{
     104        (id)kSecValueRef: (id)key.get(),
     105        (id)kSecClass: (id)kSecClassKey,
     106        (id)kSecAttrLabel: testRpId,
     107        (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)],
     108    };
     109    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
     110    EXPECT_FALSE(status);
     111}
     112
    99113void cleanUpKeychain()
    100114{
     
    142156    }
    143157private:
    144     static void evaluatePolicyFailed(id self, SEL _cmd, LAPolicy policy, NSString * reason, void (^reply)(BOOL success, NSError *error))
     158    static void evaluatePolicyFailed(id, SEL, LAPolicy, NSString *, void (^reply)(BOOL, NSError *))
    145159    {
    146160        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     
    159173    }
    160174private:
    161     static void evaluatePolicyPassed(id self, SEL _cmd, LAPolicy policy, NSString * reason, void (^reply)(BOOL success, NSError *error))
     175    static void evaluatePolicyPassed(id, SEL, LAPolicy, NSString *, void (^reply)(BOOL, NSError *))
     176    {
     177        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     178            Util::sleep(1); // mimic user interaction delay
     179            reply(YES, nil);
     180        });
     181    }
     182    InstanceMethodSwizzler m_swizzler;
     183};
     184
     185class LAEvaluateAccessControlFailedSwizzler {
     186public:
     187    LAEvaluateAccessControlFailedSwizzler()
     188        : m_swizzler([LAContext class], @selector(evaluateAccessControl:operation:localizedReason:reply:), reinterpret_cast<IMP>(evaluateAccessControlFailed))
     189    {
     190    }
     191private:
     192    static void evaluateAccessControlFailed(id, SEL, SecAccessControlRef, LAAccessControlOperation, NSString *, void (^reply)(BOOL, NSError *))
     193    {
     194        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     195            Util::sleep(1); // mimic user interaction delay
     196            reply(NO, nil);
     197        });
     198    }
     199    InstanceMethodSwizzler m_swizzler;
     200};
     201
     202class LAEvaluateAccessControlPassedSwizzler {
     203public:
     204    LAEvaluateAccessControlPassedSwizzler()
     205        : m_swizzler([LAContext class], @selector(evaluateAccessControl:operation:localizedReason:reply:), reinterpret_cast<IMP>(evaluateAccessControlPassed))
     206    {
     207    }
     208private:
     209    static void evaluateAccessControlPassed(id, SEL, SecAccessControlRef, LAAccessControlOperation, NSString *, void (^reply)(BOOL, NSError *))
    162210    {
    163211        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     
    235283TEST(LocalAuthenticator, MakeCredentialExcludeCredentialsMatch)
    236284{
    237     // Insert the test key into Keychain
    238     auto key = getTestKey();
    239     NSDictionary* addQuery = @{
    240         (id)kSecValueRef: (id)key.get(),
    241         (id)kSecClass: (id)kSecClassKey,
    242         (id)kSecAttrLabel: testRpId,
    243         (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)],
    244     };
    245     OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
    246     EXPECT_FALSE(status);
    247 
    248     // Invoke the LocalAuthenticator.
     285    addTestKeyToKeychain();
     286
    249287    WebCore::PublicKeyCredentialDescriptor descriptor;
    250288    descriptor.type = WebCore::PublicKeyCredentialType::PublicKey;
     
    351389
    352390    // Insert the older credential
    353     auto key = getTestKey();
    354     NSDictionary* addQuery = @{
    355         (id)kSecValueRef: (id)key.get(),
    356         (id)kSecClass: (id)kSecClassKey,
    357         (id)kSecAttrLabel: testRpId,
    358         (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)],
    359     };
    360     OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
    361     EXPECT_FALSE(status);
     391    addTestKeyToKeychain();
    362392
    363393    WebCore::PublicKeyCredentialCreationOptions creationOptions;
     
    476506        auto& attestationCertificateData = x5c[0].getByteString();
    477507        auto attestationCertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)[NSData dataWithBytes:attestationCertificateData.data() length:attestationCertificateData.size()]));
    478         CFStringRef commonName = NULL;
     508        CFStringRef commonName = nullptr;
    479509        status = SecCertificateCopyCommonName(attestationCertificate.get(), &commonName);
    480510        auto retainCommonName = adoptCF(commonName);
     
    484514        auto& attestationIssuingCACertificateData = x5c[1].getByteString();
    485515        auto attestationIssuingCACertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)[NSData dataWithBytes:attestationIssuingCACertificateData.data() length:attestationIssuingCACertificateData.size()]));
    486         commonName = NULL;
     516        commonName = nullptr;
    487517        status = SecCertificateCopyCommonName(attestationIssuingCACertificate.get(), &commonName);
    488518        retainCommonName = adoptCF(commonName);
     
    504534}
    505535
     536TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch1)
     537{
     538    // Transports mismatched
     539    WebCore::PublicKeyCredentialDescriptor descriptor;
     540    descriptor.type = WebCore::PublicKeyCredentialType::PublicKey;
     541    descriptor.transports.append(WebCore::PublicKeyCredentialDescriptor::AuthenticatorTransport::Usb);
     542    WebCore::PublicKeyCredentialRequestOptions requestOptions;
     543    requestOptions.allowCredentials.append(WTFMove(descriptor));
     544
     545    bool done = false;
     546    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
     547    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
     548        EXPECT_FALSE(true);
     549        done = true;
     550    };
     551    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
     552        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
     553        EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data());
     554        done = true;
     555    };
     556    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
     557
     558    TestWebKitAPI::Util::run(&done);
     559}
     560
     561TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch2)
     562{
     563    // No existing credential
     564    WebCore::PublicKeyCredentialRequestOptions requestOptions;
     565    requestOptions.rpId = testRpId;
     566
     567    bool done = false;
     568    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
     569    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
     570        EXPECT_FALSE(true);
     571        done = true;
     572    };
     573    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
     574        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
     575        EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data());
     576        done = true;
     577    };
     578    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
     579
     580    TestWebKitAPI::Util::run(&done);
     581}
     582
     583TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch3)
     584{
     585    // Credential ID mismatched
     586    addTestKeyToKeychain();
     587
     588    WebCore::PublicKeyCredentialDescriptor descriptor;
     589    descriptor.type = WebCore::PublicKeyCredentialType::PublicKey;
     590    WTF::base64Decode(testCredentialIdBase64, descriptor.idVector);
     591    descriptor.idVector[19] = 0; // nuke the last byte.
     592    WebCore::PublicKeyCredentialRequestOptions requestOptions;
     593    requestOptions.rpId = testRpId;
     594    requestOptions.allowCredentials.append(descriptor);
     595
     596    bool done = false;
     597    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
     598    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
     599        EXPECT_FALSE(true);
     600        cleanUpKeychain();
     601        done = true;
     602    };
     603    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
     604        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
     605        EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data());
     606        cleanUpKeychain();
     607        done = true;
     608    };
     609    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
     610
     611    TestWebKitAPI::Util::run(&done);
     612}
     613
     614TEST(LocalAuthenticator, GetAssertionBiometricsNotEnrolled)
     615{
     616    LACantEvaluatePolicySwizzler swizzler;
     617
     618    addTestKeyToKeychain();
     619
     620    WebCore::PublicKeyCredentialRequestOptions requestOptions;
     621    requestOptions.rpId = testRpId;
     622
     623    bool done = false;
     624    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
     625    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
     626        EXPECT_FALSE(true);
     627        cleanUpKeychain();
     628        done = true;
     629    };
     630    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
     631        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
     632        EXPECT_STREQ("No avaliable authenticators.", exception.message.ascii().data());
     633        cleanUpKeychain();
     634        done = true;
     635    };
     636    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
     637
     638    TestWebKitAPI::Util::run(&done);
     639}
     640
     641TEST(LocalAuthenticator, GetAssertionBiometricsNotAuthenticated)
     642{
     643    LACanEvaluatePolicySwizzler canEvaluatePolicySwizzler;
     644    LAEvaluateAccessControlFailedSwizzler evaluateAccessControlFailedSwizzler;
     645
     646    addTestKeyToKeychain();
     647
     648    WebCore::PublicKeyCredentialRequestOptions requestOptions;
     649    requestOptions.rpId = testRpId;
     650
     651    bool done = false;
     652    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
     653    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
     654        EXPECT_FALSE(true);
     655        cleanUpKeychain();
     656        done = true;
     657    };
     658    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
     659        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
     660        EXPECT_STREQ("Couldn't get user consent.", exception.message.ascii().data());
     661        cleanUpKeychain();
     662        done = true;
     663    };
     664    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
     665
     666    TestWebKitAPI::Util::run(&done);
     667}
     668
     669TEST(LocalAuthenticator, GetAssertionPassed)
     670{
     671    LACanEvaluatePolicySwizzler canEvaluatePolicySwizzler;
     672    LAEvaluateAccessControlPassedSwizzler evaluateAccessControlPassedSwizzler;
     673
     674    addTestKeyToKeychain();
     675
     676    WebCore::PublicKeyCredentialRequestOptions requestOptions;
     677    requestOptions.rpId = testRpId;
     678
     679    Vector<uint8_t> hash(32);
     680
     681    bool done = false;
     682    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
     683    auto callback = [&done, hash] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userhandle) {
     684        // Check Credential ID
     685        EXPECT_TRUE(WTF::base64Encode(credentialId.data(), credentialId.size()) == testCredentialIdBase64);
     686
     687        // Check Authenticator Data.
     688        size_t pos = 0;
     689        uint8_t expectedRpIdHash[] = {
     690            0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, 0x8c, 0x68,
     691            0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, 0x60, 0x5b,
     692            0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7,
     693            0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, 0x97, 0x63
     694        };
     695        EXPECT_FALSE(memcmp(authData.data() + pos, expectedRpIdHash, sizeof(expectedRpIdHash)));
     696        pos += sizeof(expectedRpIdHash);
     697
     698        // FLAGS
     699        EXPECT_EQ(5, authData[pos]);
     700        pos++;
     701
     702        uint32_t counter = -1;
     703        memcpy(&counter, authData.data() + pos, sizeof(uint32_t));
     704        EXPECT_EQ(0u, counter);
     705
     706        // Check signature
     707        auto privateKey = getTestKey();
     708        Vector<uint8_t> dataToSign(authData);
     709        dataToSign.appendVector(hash);
     710        EXPECT_TRUE(SecKeyVerifySignature(SecKeyCopyPublicKey(privateKey.get()), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:dataToSign.data() length:dataToSign.size()], (__bridge CFDataRef)[NSData dataWithBytes:signature.data() length:signature.size()], NULL));
     711
     712        // Check User Handle
     713        EXPECT_EQ(userhandle.size(), sizeof(testUserhandle));
     714        EXPECT_FALSE(memcmp(userhandle.data(), testUserhandle, sizeof(testUserhandle)));
     715
     716        cleanUpKeychain();
     717        done = true;
     718    };
     719    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
     720        EXPECT_FALSE(true);
     721        cleanUpKeychain();
     722        done = true;
     723    };
     724    authenticator->getAssertion(hash, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
     725
     726    TestWebKitAPI::Util::run(&done);
     727}
     728
    506729} // namespace TestWebKitAPI
    507730
Note: See TracChangeset for help on using the changeset viewer.