[WebAuthN] Implement authenticatorGetAssertion
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Mar 2018 22:42:45 +0000 (22:42 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Mar 2018 22:42:45 +0000 (22:42 +0000)
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):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@230012 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Modules/webauthn/AuthenticatorManager.cpp
Source/WebCore/Modules/webauthn/PublicKeyCredentialCreationOptions.h
Source/WebCore/Modules/webauthn/PublicKeyCredentialRequestOptions.h
Source/WebCore/Modules/webauthn/cocoa/LocalAuthenticator.h
Source/WebCore/Modules/webauthn/cocoa/LocalAuthenticator.mm
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.cpp
Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.h
Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.messages.in
Source/WebKit/WebProcess/CredentialManagement/WebCredentialsMessenger.cpp
Source/WebKit/WebProcess/CredentialManagement/WebCredentialsMessenger.messages.in
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/ios/LocalAuthenticator.mm

index 8e857f7..da73211 100644 (file)
@@ -1,3 +1,44 @@
+2018-03-27  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Implement authenticatorGetAssertion
+        https://bugs.webkit.org/show_bug.cgi?id=183881
+        <rdar://problem/37258628>
+
+        Reviewed by Brent Fulgham.
+
+        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:
+
 2018-03-27  Chris Dumez  <cdumez@apple.com>
 
         Avoid constructing SecurityOrigin objects from non-main threads
index e22c493..7ee2afc 100644 (file)
@@ -160,7 +160,7 @@ void AuthenticatorManager::create(const SecurityOrigin& callerOrigin, const Publ
     // Step 18-21.
     // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
     // Also, resident keys, user verifications and direct attestation are enforced at this tage.
-    // For better performance, no filtering is done here regarding to options.excludeCredentials.
+    // For better performance, transports of options.excludeCredentials are checked in LocalAuthenticator.
     if (!m_messenger)  {
         promise.reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") });
         return;
@@ -219,7 +219,7 @@ void AuthenticatorManager::discoverFromExternalSource(const SecurityOrigin& call
     // Step 14-15, 17-19.
     // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
     // Also, resident keys, user verifications and direct attestation are enforced at this tage.
-    // For better performance, no filtering is done here regarding to options.allowCredentials.
+    // For better performance, filtering of options.allowCredentials is done in LocalAuthenticator.
     if (!m_messenger)  {
         promise.reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") });
         return;
index 495d58b..760e8fd 100644 (file)
@@ -47,7 +47,7 @@ struct PublicKeyCredentialCreationOptions {
 
     struct UserEntity : public Entity {
         BufferSource id; // id becomes idVector once it is passed to UIProcess.
-        mutable Vector<uint8_t> idVector;
+        Vector<uint8_t> idVector;
         String displayName;
     };
 
@@ -140,7 +140,7 @@ template<> struct CrossThreadCopierBase<false, false, WebCore::PublicKeyCredenti
         result.user.name = source.user.name.isolatedCopy();
         result.user.icon = source.user.icon.isolatedCopy();
         result.user.displayName = source.user.displayName.isolatedCopy();
-        result.user.idVector = WTFMove(source.user.idVector);
+        result.user.idVector = source.user.idVector;
         return result;
     }
 };
index 1e58551..f013b2b 100644 (file)
@@ -38,8 +38,29 @@ struct PublicKeyCredentialRequestOptions {
     std::optional<unsigned long> timeout;
     mutable String rpId;
     Vector<PublicKeyCredentialDescriptor> allowCredentials;
+
+    template<class Encoder> void encode(Encoder&) const;
+    template<class Decoder> static std::optional<PublicKeyCredentialRequestOptions> decode(Decoder&);
 };
 
+// Not every member is encoded.
+template<class Encoder>
+void PublicKeyCredentialRequestOptions::encode(Encoder& encoder) const
+{
+    encoder << rpId << allowCredentials;
+}
+
+template<class Decoder>
+std::optional<PublicKeyCredentialRequestOptions> PublicKeyCredentialRequestOptions::decode(Decoder& decoder)
+{
+    PublicKeyCredentialRequestOptions result;
+    if (!decoder.decode(result.rpId))
+        return std::nullopt;
+    if (!decoder.decode(result.allowCredentials))
+        return std::nullopt;
+    return result;
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(WEB_AUTHN)
index 3e5668f..72d012a 100644 (file)
@@ -35,8 +35,10 @@ namespace WebCore {
 
 struct ExceptionData;
 struct PublicKeyCredentialCreationOptions;
+struct PublicKeyCredentialRequestOptions;
 
 using CreationCallback = Function<void(const Vector<uint8_t>&, const Vector<uint8_t>&)>;
+using RequestCallback = Function<void(const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&)>;
 using ExceptionCallback = Function<void(const WebCore::ExceptionData&)>;
 
 typedef void (^CompletionBlock)(SecKeyRef _Nullable referenceKey, NSArray * _Nullable certificates, NSError * _Nullable error);
@@ -49,6 +51,7 @@ public:
     virtual ~LocalAuthenticator() = default;
 
     void makeCredential(const Vector<uint8_t>& hash, const PublicKeyCredentialCreationOptions&, CreationCallback&&, ExceptionCallback&&);
+    void getAssertion(const Vector<uint8_t>& hash, const PublicKeyCredentialRequestOptions&, RequestCallback&&, ExceptionCallback&&);
     bool isAvailable() const;
 
 protected:
index 95df5e6..a1ecccc 100644 (file)
 #import "COSEConstants.h"
 #import "ExceptionData.h"
 #import "PublicKeyCredentialCreationOptions.h"
+#import "PublicKeyCredentialRequestOptions.h"
 #import <Security/SecItem.h>
 #import <pal/crypto/CryptoDigest.h>
 #import <pal/spi/cocoa/DeviceIdentitySPI.h>
 #import <wtf/BlockPtr.h>
+#import <wtf/HashSet.h>
 #import <wtf/MainThread.h>
 #import <wtf/RetainPtr.h>
 #import <wtf/Vector.h>
+#import <wtf/text/StringHash.h>
 
 #import "LocalAuthenticationSoftLink.h"
 
@@ -46,12 +49,63 @@ namespace WebCore {
 
 namespace LocalAuthenticatorInternal {
 
-// UP, UV and AT are set. See https://www.w3.org/TR/webauthn/#flags.
-const uint8_t authenticatorDataFlags = 69;
+// See https://www.w3.org/TR/webauthn/#flags.
+const uint8_t makeCredentialFlags = 0b01000101; // UP, UV and AT are set.
+const uint8_t getAssertionFlags = 0b00000101; // UP and UV are set.
 // FIXME(rdar://problem/38320512): Define Apple AAGUID.
 const uint8_t AAGUID[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 16 bytes
-const size_t maxCredentialIdLength = 0xffff; // 2 bytes
+// Credential ID is currently SHA-1 of the corresponding public key.
+// FIXME(183534): Assume little endian here.
+const union {
+    uint16_t integer;
+    uint8_t bytes[2];
+} credentialIdLength = {0x0014};
 const size_t ES256KeySizeInBytes = 32;
+const size_t authDataPrefixFixedSize = 37; // hash(32) + flags(1) + counter(4)
+
+#if PLATFORM(IOS)
+// https://www.w3.org/TR/webauthn/#sec-authenticator-data
+static Vector<uint8_t> buildAuthData(const String& rpId, const uint8_t flags, const uint32_t counter, const Vector<uint8_t>& optionalAttestedCredentialData)
+{
+    Vector<uint8_t> authData;
+    authData.reserveCapacity(authDataPrefixFixedSize + optionalAttestedCredentialData.size());
+
+    // RP ID hash
+    auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
+    // FIXME(183534): Test IDN.
+    ASSERT(rpId.isAllASCII());
+    auto asciiRpId = rpId.ascii();
+    crypto->addBytes(asciiRpId.data(), asciiRpId.length());
+    authData = crypto->computeHash();
+
+    // FLAGS
+    authData.append(flags);
+
+    // COUNTER
+    // FIXME(183534): Assume little endian here.
+    union {
+        uint32_t integer;
+        uint8_t bytes[4];
+    } counterUnion;
+    counterUnion.integer = counter;
+    authData.append(counterUnion.bytes, sizeof(counterUnion.bytes));
+
+    // ATTESTED CRED. DATA
+    authData.appendVector(optionalAttestedCredentialData);
+
+    return authData;
+}
+
+inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors)
+{
+    HashSet<String> result;
+    for (auto& credentialDescriptor : credentialDescriptors) {
+        if (credentialDescriptor.transports.isEmpty() && credentialDescriptor.type == PublicKeyCredentialType::PublicKey && credentialDescriptor.idVector.size() == credentialIdLength.integer)
+            result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size()));
+    }
+    return result;
+}
+#endif // !PLATFORM(IOS)
 
 } // LocalAuthenticatorInternal
 
@@ -62,7 +116,7 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
 #if !PLATFORM(IOS)
     // FIXME(182772)
     ASSERT_UNUSED(hash, hash == hash);
-    ASSERT_UNUSED(options, options.rp.name.isEmpty());
+    ASSERT_UNUSED(options, !options.rp.id.isEmpty());
     ASSERT_UNUSED(callback, callback);
     exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
 #else
@@ -84,25 +138,31 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
     }
 
     // Step 3.
-    for (auto& excludeCredential : options.excludeCredentials) {
-        if (excludeCredential.type == PublicKeyCredentialType::PublicKey && excludeCredential.transports.isEmpty()) {
-            // Search Keychain for the Credential ID and RP ID, which is stored in the kSecAttrApplicationLabel and kSecAttrLabel attribute respectively.
-            NSDictionary *query = @{
-                (id)kSecClass: (id)kSecClassKey,
-                (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
-                (id)kSecAttrApplicationLabel: [NSData dataWithBytes:excludeCredential.idVector.data() length:excludeCredential.idVector.size()],
-                (id)kSecAttrLabel: options.rp.id
-            };
-            OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
-            if (!status) {
+    HashSet<String> excludeCredentialIds = produceHashSet(options.excludeCredentials);
+    if (!excludeCredentialIds.isEmpty()) {
+        // Search Keychain for the RP ID.
+        NSDictionary *query = @{
+            (id)kSecClass: (id)kSecClassKey,
+            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
+            (id)kSecAttrLabel: options.rp.id,
+            (id)kSecReturnAttributes: @YES,
+            (id)kSecMatchLimit: (id)kSecMatchLimitAll,
+        };
+        CFTypeRef attributesArrayRef = nullptr;
+        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
+        if (status && status != errSecItemNotFound) {
+            LOG_ERROR("Couldn't query Keychain: %d", status);
+            exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
+            return;
+        }
+        auto retainAttributesArray = adoptCF(attributesArrayRef);
+
+        for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
+            NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
+            if (excludeCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) {
                 exceptionCallback({ NotAllowedError, ASCIILiteral("At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.") });
                 return;
             }
-            if (status != errSecItemNotFound) {
-                LOG_ERROR("Couldn't query Keychain: %d", status);
-                exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
-                return;
-            }
         }
     }
 
@@ -111,7 +171,6 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
     // Get user consent.
     auto context = adoptNS([allocLAContextInstance() init]);
     NSError *error = nil;
-    NSString *reason = [NSString stringWithFormat:@"Allow %@ to create a public key credential for %@", (id)options.rp.id, (id)options.user.name];
     if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
         LOG_ERROR("Couldn't evaluate authentication with biometrics policy: %@", error);
         // FIXME(182767)
@@ -119,6 +178,7 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
         return;
     }
 
+    NSString *reason = [NSString stringWithFormat:@"Allow %@ to create a public key credential for %@", (id)options.rp.id, (id)options.user.name];
     // FIXME(183534): Optimize the following nested callbacks and threading.
     [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 {
         ASSERT(!isMainThread());
@@ -157,7 +217,7 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
             // Attestation Certificate and Attestation Issuing CA
             ASSERT(certificates && ([certificates count] == 2));
 
-            // Step 7.2 - 7.4.
+            // Step 7.2-7.4.
             // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution
             // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle.
             // kSecAttrLabel: RP ID
@@ -180,7 +240,7 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
                     (id)kSecAttrLabel: label,
                     (id)kSecReturnAttributes: @YES
                 };
-                CFTypeRef attributesRef = NULL;
+                CFTypeRef attributesRef = nullptr;
                 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, &attributesRef);
                 if (status) {
                     LOG_ERROR("Couldn't get Credential ID: %d", status);
@@ -191,7 +251,7 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
 
                 NSDictionary *nsAttributes = (NSDictionary *)attributesRef;
                 NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
-                credentialId.append(static_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length);
+                credentialId.append(reinterpret_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length);
 
                 NSDictionary *updateQuery = @{
                     (id)kSecClass: (id)kSecClassKey,
@@ -210,55 +270,30 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
                 }
             }
 
-            // Step 12.
-            // Apple Attestation Cont'
-            // Assemble the attestation object:
-            // https://www.w3.org/TR/webauthn/#attestation-object
-            // FIXME(183534): authData could throttle.
-            Vector<uint8_t> authData;
+            // Step 10.
+            // FIXME(183533): store the counter.
+            uint32_t counter = 0;
+
+            // FIXME(183534): attestedCredentialData could throttle.
+            // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data
+            Vector<uint8_t> attestedCredentialData;
             {
-                // RP ID hash
-                auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
-                // FIXME(183534): Test IDN.
-                ASSERT(options.rp.id.isAllASCII());
-                auto asciiRpId = options.rp.id.ascii();
-                crypto->addBytes(asciiRpId.data(), asciiRpId.length());
-                authData = crypto->computeHash();
-
-                // FLAGS
-                authData.append(authenticatorDataFlags);
-
-                // Step. 10.
-                // COUNTER
-                // FIXME(183533): store the counter.
-                union {
-                    uint32_t integer;
-                    uint8_t bytes[4];
-                } counter = {0x00000000};
-                authData.append(counter.bytes, sizeof(counter.bytes));
-
-                // Step 11.
-                // AAGUID
-                authData.append(AAGUID, sizeof(AAGUID));
-
-                // L
-                ASSERT(credentialId.size() <= maxCredentialIdLength);
-                // Assume little endian here.
-                union {
-                    uint16_t integer;
-                    uint8_t bytes[2];
-                } credentialIdLength;
-                credentialIdLength.integer = static_cast<uint16_t>(credentialId.size());
-                authData.append(credentialIdLength.bytes, sizeof(uint16_t));
-
-                // CREDENTIAL ID
-                authData.appendVector(credentialId);
-
-                // CREDENTIAL PUBLIC KEY
-                CFDataRef publicKeyDataRef = NULL;
+                // aaguid
+                attestedCredentialData.append(AAGUID, sizeof(AAGUID));
+
+                // credentialIdLength
+                ASSERT(credentialId.size() == credentialIdLength.integer);
+                // FIXME(183534): Assume little endian here.
+                attestedCredentialData.append(credentialIdLength.bytes, sizeof(uint16_t));
+
+                // credentialId
+                attestedCredentialData.appendVector(credentialId);
+
+                // credentialPublicKey
+                CFDataRef publicKeyDataRef = nullptr;
                 {
                     auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey));
-                    CFErrorRef errorRef = NULL;
+                    CFErrorRef errorRef = nullptr;
                     publicKeyDataRef = SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef);
                     auto retainError = adoptCF(errorRef);
                     if (errorRef) {
@@ -288,25 +323,31 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
                     exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
                     return;
                 }
-                authData.appendVector(cosePublicKey.value());
+                attestedCredentialData.appendVector(cosePublicKey.value());
             }
 
+            // Step 12.
+            auto authData = buildAuthData(options.rp.id, makeCredentialFlags, counter, attestedCredentialData);
+
+            // Step 13. Apple Attestation Cont'
+            // Assemble the attestation object:
+            // https://www.w3.org/TR/webauthn/#attestation-object
             cbor::CBORValue::MapValue attestationStatementMap;
             {
                 Vector<uint8_t> signature;
                 {
-                    CFErrorRef errorRef = NULL;
+                    CFErrorRef errorRef = nullptr;
                     // FIXME(183652): Reduce prompt for biometrics
                     CFDataRef signatureRef = SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:authData.data() length:authData.size()], &errorRef);
                     auto retainError = adoptCF(errorRef);
                     if (errorRef) {
-                        LOG_ERROR("Couldn't export the public key: %@", (NSError*)errorRef);
+                        LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef);
                         exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
                         return;
                     }
                     auto retainSignature = adoptCF(signatureRef);
                     NSData *nsSignature = (NSData *)signatureRef;
-                    signature.append(static_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length);
+                    signature.append(reinterpret_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length);
                 }
                 attestationStatementMap[cbor::CBORValue("alg")] = cbor::CBORValue(COSE::ES256);
                 attestationStatementMap[cbor::CBORValue("sig")] = cbor::CBORValue(signature);
@@ -316,7 +357,7 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
                     auto retainData = adoptCF(dataRef);
                     NSData *nsData = (NSData *)dataRef;
                     Vector<uint8_t> data;
-                    data.append(static_cast<const uint8_t*>(nsData.bytes), nsData.length);
+                    data.append(reinterpret_cast<const uint8_t*>(nsData.bytes), nsData.length);
                     cborArray.append(cbor::CBORValue(WTFMove(data)));
                 }
                 attestationStatementMap[cbor::CBORValue("x5c")] = cbor::CBORValue(WTFMove(cborArray));
@@ -339,6 +380,144 @@ void LocalAuthenticator::makeCredential(const Vector<uint8_t>& hash, const Publi
 #endif // !PLATFORM(IOS)
 }
 
+void LocalAuthenticator::getAssertion(const Vector<uint8_t>& hash, const PublicKeyCredentialRequestOptions& options, RequestCallback&& callback, ExceptionCallback&& exceptionCallback)
+{
+    using namespace LocalAuthenticatorInternal;
+
+#if !PLATFORM(IOS)
+    // FIXME(182772)
+    ASSERT_UNUSED(hash, hash == hash);
+    ASSERT_UNUSED(options, !options.rpId.isEmpty());
+    ASSERT_UNUSED(callback, callback);
+    exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
+#else
+    // The following implements https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017.
+    // Skip Step 2 as requireUserVerification is enforced.
+    // Skip Step 8 as extensions are not supported yet.
+    // Step 12 is implicitly captured by all UnknownError exception callbacks.
+    // 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.
+    HashSet<String> allowCredentialIds = produceHashSet(options.allowCredentials);
+    if (!options.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) {
+        exceptionCallback({ NotAllowedError, ASCIILiteral("No matched credentials are found in the platform attached authenticator.") });
+        return;
+    }
+
+    // Search Keychain for the RP ID.
+    NSDictionary *query = @{
+        (id)kSecClass: (id)kSecClassKey,
+        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
+        (id)kSecAttrLabel: options.rpId,
+        (id)kSecReturnAttributes: @YES,
+        (id)kSecMatchLimit: (id)kSecMatchLimitAll,
+    };
+    CFTypeRef attributesArrayRef = nullptr;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
+    if (status && status != errSecItemNotFound) {
+        LOG_ERROR("Couldn't query Keychain: %d", status);
+        exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
+        return;
+    }
+    auto retainAttributesArray = adoptCF(attributesArrayRef);
+
+    NSArray *intersectedCredentialsAttributes = nil;
+    if (options.allowCredentials.isEmpty())
+        intersectedCredentialsAttributes = (NSArray *)attributesArrayRef;
+    else {
+        NSMutableArray *result = [NSMutableArray arrayWithCapacity:allowCredentialIds.size()];
+        for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
+            NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
+            if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length)))
+                [result addObject:nsAttributes];
+        }
+        intersectedCredentialsAttributes = result;
+    }
+    if (!intersectedCredentialsAttributes.count) {
+        exceptionCallback({ NotAllowedError, ASCIILiteral("No matched credentials are found in the platform attached authenticator.") });
+        return;
+    }
+
+    // Step 6.
+    // 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.
+    NSDictionary *selectedCredentialAttributes = intersectedCredentialsAttributes[0];
+
+    // Step 7. Get user consent.
+    // FIXME(183534): The lifetime of context is managed by reply and the early return, which is a bit subtle.
+    LAContext *context = [allocLAContextInstance() init];
+    NSError *error = nil;
+    if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
+        auto retainContext = adoptNS(context);
+        LOG_ERROR("Couldn't evaluate authentication with biometrics policy: %@", error);
+        // FIXME(182767)
+        exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
+        return;
+    }
+
+    Vector<uint8_t> credentialId;
+    NSData *nsCredentialId = selectedCredentialAttributes[(id)kSecAttrApplicationLabel];
+    credentialId.append(reinterpret_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length);
+    Vector<uint8_t> userhandle;
+    NSData *nsUserhandle = selectedCredentialAttributes[(id)kSecAttrApplicationTag];
+    userhandle.append(reinterpret_cast<const uint8_t*>(nsUserhandle.bytes), nsUserhandle.length);
+    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 {
+        ASSERT(!isMainThread());
+        if (!success || error) {
+            LOG_ERROR("Couldn't authenticate with biometrics: %@", error);
+            exceptionCallback({ NotAllowedError, ASCIILiteral("Couldn't get user consent.") });
+            return;
+        }
+
+        // Step 9-10.
+        // FIXME(183533): Due to the stated Keychain limitations, we can't save the counter value.
+        // Therefore, it is always zero.
+        uint32_t counter = 0;
+        auto authData = buildAuthData(rpId, getAssertionFlags, counter, { });
+
+        // Step 11.
+        Vector<uint8_t> signature;
+        {
+            NSDictionary *query = @{
+                (id)kSecClass: (id)kSecClassKey,
+                (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
+                (id)kSecAttrApplicationLabel: [NSData dataWithBytes:credentialId.data() length:credentialId.size()],
+                (id)kSecUseAuthenticationContext: context.get(),
+                (id)kSecReturnRef: @YES,
+            };
+            CFTypeRef privateKeyRef = nullptr;
+            OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &privateKeyRef);
+            if (status) {
+                LOG_ERROR("Couldn't get the private key reference: %d", status);
+                exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
+                return;
+            }
+            auto privateKey = adoptCF(privateKeyRef);
+
+            NSMutableData *dataToSign = [NSMutableData dataWithBytes:authData.data() length:authData.size()];
+            [dataToSign appendBytes:hash.data() length:hash.size()];
+
+            CFErrorRef errorRef = nullptr;
+            // FIXME: Converting CFTypeRef to SecKeyRef is quite subtle here.
+            CFDataRef signatureRef = SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef);
+            auto retainError = adoptCF(errorRef);
+            if (errorRef) {
+                LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef);
+                exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });
+                return;
+            }
+            auto retainSignature = adoptCF(signatureRef);
+            NSData *nsSignature = (NSData *)signatureRef;
+            signature.append(reinterpret_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length);
+        }
+
+        // Step 13.
+        callback(credentialId, authData, signature, userhandle);
+    });
+
+    // FIXME(183533): Use userhandle instead of username due to the stated Keychain limitations.
+    NSString *reason = [NSString stringWithFormat:@"Log into %@ with %@.", (id)options.rpId, selectedCredentialAttributes[(id)kSecAttrApplicationTag]];
+    [context evaluateAccessControl:(__bridge SecAccessControlRef)selectedCredentialAttributes[(id)kSecAttrAccessControl] operation:LAAccessControlOperationUseKeySign localizedReason:reason reply:reply.get()];
+#endif // !PLATFORM(IOS)
+}
+
 bool LocalAuthenticator::isAvailable() const
 {
 #if !PLATFORM(IOS)
@@ -371,7 +550,7 @@ void LocalAuthenticator::issueClientCertificate(const String& rpId, const String
 
     SecAccessControlRef accessControlRef;
     {
-        CFErrorRef errorRef = NULL;
+        CFErrorRef errorRef = nullptr;
         accessControlRef = SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, &errorRef);
         auto retainError = adoptCF(errorRef);
         if (errorRef) {
index 9bb56f0..36fb81d 100644 (file)
                57303BEF200980C600355965 /* PublicKeyCredentialDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303BEC200980BF00355965 /* PublicKeyCredentialDescriptor.h */; settings = {ATTRIBUTES = (Private, ); }; };
                57303BF42009904600355965 /* JSPublicKeyCredentialDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303BF22009904500355965 /* JSPublicKeyCredentialDescriptor.h */; };
                57303C002009951C00355965 /* JSPublicKeyCredentialType.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303BF92009913400355965 /* JSPublicKeyCredentialType.h */; };
-               57303C0A20099BAD00355965 /* PublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C06200998F800355965 /* PublicKeyCredentialRequestOptions.h */; };
+               57303C0A20099BAD00355965 /* PublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C06200998F800355965 /* PublicKeyCredentialRequestOptions.h */; settings = {ATTRIBUTES = (Private, ); }; };
                57303C1120099CB100355965 /* JSPublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C0C20099C7500355965 /* JSPublicKeyCredentialRequestOptions.h */; };
                57303C192009A2F300355965 /* JSPublicKeyCredentialCreationOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C132009A25700355965 /* JSPublicKeyCredentialCreationOptions.h */; };
                57303C1F2009AB4200355965 /* AuthenticatorAttestationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C1B2009A98600355965 /* AuthenticatorAttestationResponse.h */; };
index bd24095..c805743 100644 (file)
@@ -1,3 +1,23 @@
+2018-03-27  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Implement authenticatorGetAssertion
+        https://bugs.webkit.org/show_bug.cgi?id=183881
+        <rdar://problem/37258628>
+
+        Reviewed by Brent Fulgham.
+
+        * 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:
+
 2018-03-27  Chris Dumez  <cdumez@apple.com>
 
         Avoid constructing SecurityOrigin objects from non-main threads
index feabea8..3afdd13 100644 (file)
@@ -57,6 +57,7 @@ void WebCredentialsMessengerProxy::makeCredential(uint64_t messageId, const Vect
         return;
     }
     // FIXME(183534): Weak pointers doesn't work in another thread because of race condition.
+    // FIXME(183534): Unify callbacks.
     auto weakThis = m_weakFactory.createWeakPtr(*this);
     auto callback = [weakThis, messageId] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& attestationObject) {
         if (!weakThis)
@@ -71,8 +72,23 @@ void WebCredentialsMessengerProxy::makeCredential(uint64_t messageId, const Vect
     m_authenticator->makeCredential(hash, options, WTFMove(callback), WTFMove(exceptionCallback));
 }
 
-void WebCredentialsMessengerProxy::getAssertion(uint64_t)
+void WebCredentialsMessengerProxy::getAssertion(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions& options)
 {
+    // FIXME(182767)
+    if (!m_authenticator)
+        exceptionReply(messageId, { WebCore::NotAllowedError, ASCIILiteral("No avaliable authenticators.") });
+    // FIXME(183534): Weak pointers doesn't work in another thread because of race condition.
+    // FIXME(183534): Unify callbacks.
+    auto weakThis = m_weakFactory.createWeakPtr(*this);
+    auto callback = [weakThis, messageId] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle) {
+        if (weakThis)
+            weakThis->getAssertionReply(messageId, credentialId, authenticatorData, signature, userHandle);
+    };
+    auto exceptionCallback = [weakThis, messageId] (const WebCore::ExceptionData& exception) {
+        if (weakThis)
+            weakThis->exceptionReply(messageId, exception);
+    };
+    m_authenticator->getAssertion(hash, options, WTFMove(callback), WTFMove(exceptionCallback));
 }
 
 void WebCredentialsMessengerProxy::isUserVerifyingPlatformAuthenticatorAvailable(uint64_t messageId)
@@ -94,6 +110,11 @@ void WebCredentialsMessengerProxy::makeCredentialReply(uint64_t messageId, const
     m_webPageProxy.send(Messages::WebCredentialsMessenger::MakeCredentialReply(messageId, credentialId, attestationObject));
 }
 
+void 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)
+{
+    m_webPageProxy.send(Messages::WebCredentialsMessenger::GetAssertionReply(messageId, credentialId, authenticatorData, signature, userHandle));
+}
+
 void WebCredentialsMessengerProxy::isUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool result)
 {
     m_webPageProxy.send(Messages::WebCredentialsMessenger::IsUserVerifyingPlatformAuthenticatorAvailableReply(messageId, result));
index 69eb70e..0c2a4de 100644 (file)
@@ -37,6 +37,7 @@ class LocalAuthenticator;
 
 struct ExceptionData;
 struct PublicKeyCredentialCreationOptions;
+struct PublicKeyCredentialRequestOptions;
 }
 
 namespace WebKit {
@@ -55,12 +56,13 @@ private:
 
     // Receivers.
     void makeCredential(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialCreationOptions&);
-    void getAssertion(uint64_t messageId);
+    void getAssertion(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions&);
     void isUserVerifyingPlatformAuthenticatorAvailable(uint64_t messageId);
 
     // Senders.
     void exceptionReply(uint64_t messageId, const WebCore::ExceptionData&);
     void makeCredentialReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& attestationObject);
+    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);
     void isUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool);
 
     WebPageProxy& m_webPageProxy;
index 55120a2..a58bbae 100644 (file)
@@ -27,7 +27,7 @@
 messages -> WebCredentialsMessengerProxy {
 
     MakeCredential(uint64_t messageId, Vector<uint8_t> hash, struct WebCore::PublicKeyCredentialCreationOptions options);
-    GetAssertion(uint64_t messageId);
+    GetAssertion(uint64_t messageId, Vector<uint8_t> hash, struct WebCore::PublicKeyCredentialRequestOptions options);
     IsUserVerifyingPlatformAuthenticatorAvailable(uint64_t messageId);
 }
 
index 03bba68..2279d7e 100644 (file)
@@ -33,6 +33,7 @@
 #include "WebPage.h"
 #include "WebProcess.h"
 #include <WebCore/PublicKeyCredentialCreationOptions.h>
+#include <WebCore/PublicKeyCredentialRequestOptions.h>
 
 namespace WebKit {
 
@@ -53,8 +54,10 @@ void WebCredentialsMessenger::makeCredential(const Vector<uint8_t>& hash, const
     m_webPage.send(Messages::WebCredentialsMessengerProxy::MakeCredential(messageId, hash, options));
 }
 
-void WebCredentialsMessenger::getAssertion(const Vector<uint8_t>&, const WebCore::PublicKeyCredentialRequestOptions&, WebCore::RequestCompletionHandler&&)
+void WebCredentialsMessenger::getAssertion(const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions& options, WebCore::RequestCompletionHandler&& handler)
 {
+    auto messageId = addRequestCompletionHandler(WTFMove(handler));
+    m_webPage.send(Messages::WebCredentialsMessengerProxy::GetAssertion(messageId, hash, options));
 }
 
 void WebCredentialsMessenger::isUserVerifyingPlatformAuthenticatorAvailable(WebCore::QueryCompletionHandler&& handler)
@@ -71,6 +74,8 @@ void WebCredentialsMessenger::makeCredentialReply(uint64_t messageId, const Vect
 
 void 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)
 {
+    auto handler = takeRequestCompletionHandler(messageId);
+    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())));
 }
 
 void WebCredentialsMessenger::isUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool result)
index 9280e06..a6c8eec 100644 (file)
@@ -30,7 +30,6 @@ messages -> WebCredentialsMessenger {
     MakeCredentialReply(uint64_t messageId, Vector<uint8_t> credentialId, Vector<uint8_t> attestationObject);
     GetAssertionReply(uint64_t messageId, Vector<uint8_t> credentialId, Vector<uint8_t> authenticatorData, Vector<uint8_t> signature, Vector<uint8_t> userHandle);
     IsUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool result);
-exceptionReply(uint64_t messageId, struct WebCore::ExceptionData exception);
 }
 
 #endif
index a64fe2a..6441fb7 100644 (file)
@@ -1,3 +1,22 @@
+2018-03-27  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Implement authenticatorGetAssertion
+        https://bugs.webkit.org/show_bug.cgi?id=183881
+        <rdar://problem/37258628>
+
+        Reviewed by Brent Fulgham.
+
+        * 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):
+
 2018-03-27  Brian Burg  <bburg@apple.com>
 
         REGRESSION(r229937): WebDriver tests no longer run, test runner hangs when launching wpt web server
index 79ac6d2..855b14f 100644 (file)
@@ -38,6 +38,7 @@
 #import <WebCore/ExceptionData.h>
 #import <WebCore/LocalAuthenticator.h>
 #import <WebCore/PublicKeyCredentialCreationOptions.h>
+#import <WebCore/PublicKeyCredentialRequestOptions.h>
 #import <wtf/BlockPtr.h>
 #import <wtf/text/Base64.h>
 #import <wtf/text/WTFString.h>
@@ -85,7 +86,7 @@ RetainPtr<SecKeyRef> getTestKey()
         (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
         (id)kSecAttrKeySizeInBits: @256,
     };
-    CFErrorRef errorRef = NULL;
+    CFErrorRef errorRef = nullptr;
     auto key = adoptCF(SecKeyCreateWithData(
         (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:testES256PrivateKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
         (__bridge CFDictionaryRef)options,
@@ -96,6 +97,19 @@ RetainPtr<SecKeyRef> getTestKey()
     return key;
 }
 
+void addTestKeyToKeychain()
+{
+    auto key = getTestKey();
+    NSDictionary* addQuery = @{
+        (id)kSecValueRef: (id)key.get(),
+        (id)kSecClass: (id)kSecClassKey,
+        (id)kSecAttrLabel: testRpId,
+        (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)],
+    };
+    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
+    EXPECT_FALSE(status);
+}
+
 void cleanUpKeychain()
 {
     // Cleanup the keychain.
@@ -141,7 +155,7 @@ public:
     {
     }
 private:
-    static void evaluatePolicyFailed(id self, SEL _cmd, LAPolicy policy, NSString * reason, void (^reply)(BOOL success, NSError *error))
+    static void evaluatePolicyFailed(id, SEL, LAPolicy, NSString *, void (^reply)(BOOL, NSError *))
     {
         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
             Util::sleep(1); // mimic user interaction delay
@@ -158,7 +172,41 @@ public:
     {
     }
 private:
-    static void evaluatePolicyPassed(id self, SEL _cmd, LAPolicy policy, NSString * reason, void (^reply)(BOOL success, NSError *error))
+    static void evaluatePolicyPassed(id, SEL, LAPolicy, NSString *, void (^reply)(BOOL, NSError *))
+    {
+        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+            Util::sleep(1); // mimic user interaction delay
+            reply(YES, nil);
+        });
+    }
+    InstanceMethodSwizzler m_swizzler;
+};
+
+class LAEvaluateAccessControlFailedSwizzler {
+public:
+    LAEvaluateAccessControlFailedSwizzler()
+        : m_swizzler([LAContext class], @selector(evaluateAccessControl:operation:localizedReason:reply:), reinterpret_cast<IMP>(evaluateAccessControlFailed))
+    {
+    }
+private:
+    static void evaluateAccessControlFailed(id, SEL, SecAccessControlRef, LAAccessControlOperation, NSString *, void (^reply)(BOOL, NSError *))
+    {
+        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+            Util::sleep(1); // mimic user interaction delay
+            reply(NO, nil);
+        });
+    }
+    InstanceMethodSwizzler m_swizzler;
+};
+
+class LAEvaluateAccessControlPassedSwizzler {
+public:
+    LAEvaluateAccessControlPassedSwizzler()
+        : m_swizzler([LAContext class], @selector(evaluateAccessControl:operation:localizedReason:reply:), reinterpret_cast<IMP>(evaluateAccessControlPassed))
+    {
+    }
+private:
+    static void evaluateAccessControlPassed(id, SEL, SecAccessControlRef, LAAccessControlOperation, NSString *, void (^reply)(BOOL, NSError *))
     {
         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
             Util::sleep(1); // mimic user interaction delay
@@ -234,18 +282,8 @@ TEST(LocalAuthenticator, MakeCredentialNotSupportedPubKeyCredParams)
 
 TEST(LocalAuthenticator, MakeCredentialExcludeCredentialsMatch)
 {
-    // Insert the test key into Keychain
-    auto key = getTestKey();
-    NSDictionary* addQuery = @{
-        (id)kSecValueRef: (id)key.get(),
-        (id)kSecClass: (id)kSecClassKey,
-        (id)kSecAttrLabel: testRpId,
-        (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)],
-    };
-    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
-    EXPECT_FALSE(status);
+    addTestKeyToKeychain();
 
-    // Invoke the LocalAuthenticator.
     WebCore::PublicKeyCredentialDescriptor descriptor;
     descriptor.type = WebCore::PublicKeyCredentialType::PublicKey;
     WTF::base64Decode(testCredentialIdBase64, descriptor.idVector);
@@ -350,15 +388,7 @@ TEST(LocalAuthenticator, MakeCredentialDeleteOlderCredenital)
     LAEvaluatePolicyPassedSwizzler evaluatePolicyPassedSwizzler;
 
     // Insert the older credential
-    auto key = getTestKey();
-    NSDictionary* addQuery = @{
-        (id)kSecValueRef: (id)key.get(),
-        (id)kSecClass: (id)kSecClassKey,
-        (id)kSecAttrLabel: testRpId,
-        (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)],
-    };
-    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
-    EXPECT_FALSE(status);
+    addTestKeyToKeychain();
 
     WebCore::PublicKeyCredentialCreationOptions creationOptions;
     creationOptions.rp.id = testRpId;
@@ -475,7 +505,7 @@ TEST(LocalAuthenticator, MakeCredentialPassedWithSelfAttestation)
         auto& x5c = attStmt.find(cbor::CBORValue("x5c"))->second.getArray();
         auto& attestationCertificateData = x5c[0].getByteString();
         auto attestationCertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)[NSData dataWithBytes:attestationCertificateData.data() length:attestationCertificateData.size()]));
-        CFStringRef commonName = NULL;
+        CFStringRef commonName = nullptr;
         status = SecCertificateCopyCommonName(attestationCertificate.get(), &commonName);
         auto retainCommonName = adoptCF(commonName);
         ASSERT(!status);
@@ -483,7 +513,7 @@ TEST(LocalAuthenticator, MakeCredentialPassedWithSelfAttestation)
 
         auto& attestationIssuingCACertificateData = x5c[1].getByteString();
         auto attestationIssuingCACertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)[NSData dataWithBytes:attestationIssuingCACertificateData.data() length:attestationIssuingCACertificateData.size()]));
-        commonName = NULL;
+        commonName = nullptr;
         status = SecCertificateCopyCommonName(attestationIssuingCACertificate.get(), &commonName);
         retainCommonName = adoptCF(commonName);
         ASSERT(!status);
@@ -503,6 +533,199 @@ TEST(LocalAuthenticator, MakeCredentialPassedWithSelfAttestation)
     TestWebKitAPI::Util::run(&done);
 }
 
+TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch1)
+{
+    // Transports mismatched
+    WebCore::PublicKeyCredentialDescriptor descriptor;
+    descriptor.type = WebCore::PublicKeyCredentialType::PublicKey;
+    descriptor.transports.append(WebCore::PublicKeyCredentialDescriptor::AuthenticatorTransport::Usb);
+    WebCore::PublicKeyCredentialRequestOptions requestOptions;
+    requestOptions.allowCredentials.append(WTFMove(descriptor));
+
+    bool done = false;
+    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
+    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
+        EXPECT_FALSE(true);
+        done = true;
+    };
+    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
+        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
+        EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data());
+        done = true;
+    };
+    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
+
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch2)
+{
+    // No existing credential
+    WebCore::PublicKeyCredentialRequestOptions requestOptions;
+    requestOptions.rpId = testRpId;
+
+    bool done = false;
+    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
+    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
+        EXPECT_FALSE(true);
+        done = true;
+    };
+    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
+        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
+        EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data());
+        done = true;
+    };
+    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
+
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch3)
+{
+    // Credential ID mismatched
+    addTestKeyToKeychain();
+
+    WebCore::PublicKeyCredentialDescriptor descriptor;
+    descriptor.type = WebCore::PublicKeyCredentialType::PublicKey;
+    WTF::base64Decode(testCredentialIdBase64, descriptor.idVector);
+    descriptor.idVector[19] = 0; // nuke the last byte.
+    WebCore::PublicKeyCredentialRequestOptions requestOptions;
+    requestOptions.rpId = testRpId;
+    requestOptions.allowCredentials.append(descriptor);
+
+    bool done = false;
+    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
+    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
+        EXPECT_FALSE(true);
+        cleanUpKeychain();
+        done = true;
+    };
+    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
+        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
+        EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data());
+        cleanUpKeychain();
+        done = true;
+    };
+    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
+
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(LocalAuthenticator, GetAssertionBiometricsNotEnrolled)
+{
+    LACantEvaluatePolicySwizzler swizzler;
+
+    addTestKeyToKeychain();
+
+    WebCore::PublicKeyCredentialRequestOptions requestOptions;
+    requestOptions.rpId = testRpId;
+
+    bool done = false;
+    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
+    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
+        EXPECT_FALSE(true);
+        cleanUpKeychain();
+        done = true;
+    };
+    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
+        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
+        EXPECT_STREQ("No avaliable authenticators.", exception.message.ascii().data());
+        cleanUpKeychain();
+        done = true;
+    };
+    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
+
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(LocalAuthenticator, GetAssertionBiometricsNotAuthenticated)
+{
+    LACanEvaluatePolicySwizzler canEvaluatePolicySwizzler;
+    LAEvaluateAccessControlFailedSwizzler evaluateAccessControlFailedSwizzler;
+
+    addTestKeyToKeychain();
+
+    WebCore::PublicKeyCredentialRequestOptions requestOptions;
+    requestOptions.rpId = testRpId;
+
+    bool done = false;
+    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
+    auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) {
+        EXPECT_FALSE(true);
+        cleanUpKeychain();
+        done = true;
+    };
+    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
+        EXPECT_EQ(WebCore::NotAllowedError, exception.code);
+        EXPECT_STREQ("Couldn't get user consent.", exception.message.ascii().data());
+        cleanUpKeychain();
+        done = true;
+    };
+    authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
+
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(LocalAuthenticator, GetAssertionPassed)
+{
+    LACanEvaluatePolicySwizzler canEvaluatePolicySwizzler;
+    LAEvaluateAccessControlPassedSwizzler evaluateAccessControlPassedSwizzler;
+
+    addTestKeyToKeychain();
+
+    WebCore::PublicKeyCredentialRequestOptions requestOptions;
+    requestOptions.rpId = testRpId;
+
+    Vector<uint8_t> hash(32);
+
+    bool done = false;
+    std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>();
+    auto callback = [&done, hash] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userhandle) {
+        // Check Credential ID
+        EXPECT_TRUE(WTF::base64Encode(credentialId.data(), credentialId.size()) == testCredentialIdBase64);
+
+        // Check Authenticator Data.
+        size_t pos = 0;
+        uint8_t expectedRpIdHash[] = {
+            0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, 0x8c, 0x68,
+            0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, 0x60, 0x5b,
+            0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7,
+            0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, 0x97, 0x63
+        };
+        EXPECT_FALSE(memcmp(authData.data() + pos, expectedRpIdHash, sizeof(expectedRpIdHash)));
+        pos += sizeof(expectedRpIdHash);
+
+        // FLAGS
+        EXPECT_EQ(5, authData[pos]);
+        pos++;
+
+        uint32_t counter = -1;
+        memcpy(&counter, authData.data() + pos, sizeof(uint32_t));
+        EXPECT_EQ(0u, counter);
+
+        // Check signature
+        auto privateKey = getTestKey();
+        Vector<uint8_t> dataToSign(authData);
+        dataToSign.appendVector(hash);
+        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));
+
+        // Check User Handle
+        EXPECT_EQ(userhandle.size(), sizeof(testUserhandle));
+        EXPECT_FALSE(memcmp(userhandle.data(), testUserhandle, sizeof(testUserhandle)));
+
+        cleanUpKeychain();
+        done = true;
+    };
+    auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable {
+        EXPECT_FALSE(true);
+        cleanUpKeychain();
+        done = true;
+    };
+    authenticator->getAssertion(hash, requestOptions, WTFMove(callback), WTFMove(exceptionCallback));
+
+    TestWebKitAPI::Util::run(&done);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // PLATFORM(IOS)