Title: [288816] trunk
Revision
288816
Author
[email protected]
Date
2022-01-31 08:55:49 -0800 (Mon, 31 Jan 2022)

Log Message

[WebAuthn] Provide SPI to export/import local credentials
https://bugs.webkit.org/show_bug.cgi?id=234112
rdar://84822000

Reviewed by Brent Fulgham.

Source/WebCore:

This change adds SPI to _WKWebAuthenticationPanel to provide the ability
to import / export local credentials. Constants are used during serialization
as keys.

* Modules/webauthn/WebAuthenticationConstants.h: constants for credential serialization

Source/WebKit:

Covered by new API tests.

This patch adds new SPI to _WKWebAuthenticationPanel.h to import and export local
webauthn credentials. CBOR is used for serialization. WKErrors are used to differentiate
between malformed vs duplicate keys during import.

* UIProcess/API/Cocoa/WKError.h:
* UIProcess/API/Cocoa/WKError.mm:
(localizedDescriptionForErrorCode):
* UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
* UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
(+[_WKWebAuthenticationPanel importLocalAuthenticatorCredential:error:]):
(+[_WKWebAuthenticationPanel exportLocalAuthenticatorCredentialWithID:error:]):

Tools:

Add tests for SPI to import / export local webauthn credentials.

* TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
(TestWebKitAPI::TEST): New tests for import/export

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (288815 => 288816)


--- trunk/Source/WebCore/ChangeLog	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebCore/ChangeLog	2022-01-31 16:55:49 UTC (rev 288816)
@@ -1,3 +1,17 @@
+2022-01-31  J Pascoe  <[email protected]>
+
+        [WebAuthn] Provide SPI to export/import local credentials
+        https://bugs.webkit.org/show_bug.cgi?id=234112
+        rdar://84822000
+
+        Reviewed by Brent Fulgham.
+
+        This change adds SPI to _WKWebAuthenticationPanel to provide the ability
+        to import / export local credentials. Constants are used during serialization
+        as keys.
+
+        * Modules/webauthn/WebAuthenticationConstants.h: constants for credential serialization
+
 2022-01-31  Antti Koivisto  <[email protected]>
 
         [CSS Container Queries] Check for query containers when matching rules

Modified: trunk/Source/WebCore/Modules/webauthn/WebAuthenticationConstants.h (288815 => 288816)


--- trunk/Source/WebCore/Modules/webauthn/WebAuthenticationConstants.h	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebCore/Modules/webauthn/WebAuthenticationConstants.h	2022-01-31 16:55:49 UTC (rev 288816)
@@ -77,8 +77,18 @@
     Get
 };
 
+// rdar://88104045 - Remove once staged change completed
 const char LocalAuthenticatiorAccessGroup[] = "com.apple.webkit.webauthn";
 
+constexpr const char LocalAuthenticatorAccessGroup[] = "com.apple.webkit.webauthn";
+
+// Credential serialization
+constexpr const char privateKeyKey[] = "priv";
+constexpr const char keyTypeKey[] = "key_type";
+constexpr const char keySizeKey[] = "key_size";
+constexpr const char relyingPartyKey[] = "rp";
+constexpr const char applicationTagKey[] = "tag";
+
 } // namespace WebCore
 
 namespace WebAuthn {

Modified: trunk/Source/WebKit/ChangeLog (288815 => 288816)


--- trunk/Source/WebKit/ChangeLog	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebKit/ChangeLog	2022-01-31 16:55:49 UTC (rev 288816)
@@ -1,3 +1,25 @@
+2022-01-31  J Pascoe  <[email protected]>
+
+        [WebAuthn] Provide SPI to export/import local credentials
+        https://bugs.webkit.org/show_bug.cgi?id=234112
+        rdar://84822000
+
+        Reviewed by Brent Fulgham.
+
+        Covered by new API tests.
+
+        This patch adds new SPI to _WKWebAuthenticationPanel.h to import and export local
+        webauthn credentials. CBOR is used for serialization. WKErrors are used to differentiate
+        between malformed vs duplicate keys during import.
+
+        * UIProcess/API/Cocoa/WKError.h:
+        * UIProcess/API/Cocoa/WKError.mm:
+        (localizedDescriptionForErrorCode):
+        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
+        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
+        (+[_WKWebAuthenticationPanel importLocalAuthenticatorCredential:error:]):
+        (+[_WKWebAuthenticationPanel exportLocalAuthenticatorCredentialWithID:error:]):
+
 2022-01-31  Alexander Mikhaylenko  <[email protected]>
 
         REGRESSION(r288644): [GTK4] Criticals when using pinch zoom

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKError.h (288815 => 288816)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKError.h	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKError.h	2022-01-31 16:55:49 UTC (rev 288816)
@@ -63,6 +63,9 @@
     WKErrorJavaScriptInvalidFrameTarget WK_API_AVAILABLE(macos(11.0), ios(14.0)),
     WKErrorNavigationAppBoundDomain WK_API_AVAILABLE(macos(11.0), ios(14.0)),
     WKErrorJavaScriptAppBoundDomain WK_API_AVAILABLE(macos(11.0), ios(14.0)),
+    WKErrorDuplicateCredential WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)),
+    WKErrorMalformedCredential WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)),
+    WKErrorCredentialNotFound WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)),
 } WK_API_AVAILABLE(macos(10.10), ios(8.0));
 
 NS_ASSUME_NONNULL_END

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKError.mm (288815 => 288816)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKError.mm	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKError.mm	2022-01-31 16:55:49 UTC (rev 288816)
@@ -82,6 +82,15 @@
 
     case WKErrorJavaScriptAppBoundDomain:
         return WEB_UI_STRING("_javascript_ execution targeted a frame that is not in an app-bound domain", "WKErrorJavaScriptAppBoundDomain description");
+
+    case WKErrorDuplicateCredential:
+        return WEB_UI_STRING("This credential is already present", "WKErrorDuplicateCredential description");
+
+    case WKErrorMalformedCredential:
+        return WEB_UI_STRING("This credential is malformed", "WKErrorMalformedCredential description");
+            
+    case WKErrorCredentialNotFound:
+        return WEB_UI_STRING("Credential could not be found", "WKErrorCredentialNotFound description");
     }
 }
 

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h (288815 => 288816)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h	2022-01-31 16:55:49 UTC (rev 288816)
@@ -117,6 +117,9 @@
 + (void)clearAllLocalAuthenticatorCredentials WK_API_AVAILABLE(macos(12.0), ios(15.0));
 + (void)setUsernameForLocalCredentialWithID:(NSData *)credentialID username: (NSString *)username WK_API_AVAILABLE(macos(12.0), ios(15.0));
 
++ (NSData *)exportLocalAuthenticatorCredentialWithID:(NSData *)credentialID error:(NSError **)error WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
++ (NSData *)importLocalAuthenticatorCredential:(NSData *)credentialBlob error:(NSError **)error WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 + (BOOL)isUserVerifyingPlatformAuthenticatorAvailable WK_API_AVAILABLE(macos(12.0), ios(15.0));
 
 + (NSData *)getClientDataJSONForAuthenticationType:(_WKWebAuthenticationType)type challenge:(NSData *)challenge origin:(NSString *)origin WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm (288815 => 288816)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm	2022-01-31 16:55:49 UTC (rev 288816)
@@ -289,7 +289,7 @@
 + (NSArray<NSDictionary *> *)getAllLocalAuthenticatorCredentials
 {
 #if ENABLE(WEB_AUTHN)
-    return getAllLocalAuthenticatorCredentialsImpl(String(WebCore::LocalAuthenticatiorAccessGroup)).autorelease();
+    return getAllLocalAuthenticatorCredentialsImpl(String(WebCore::LocalAuthenticatorAccessGroup)).autorelease();
 #else
     return nullptr;
 #endif
@@ -388,6 +388,192 @@
 #endif
 }
 
+#if ENABLE(WEB_AUTHN)
+static void createNSErrorFromWKErrorIfNecessary(NSError **error, WKErrorCode errorCode)
+{
+    if (error)
+        *error = [NSError errorWithDomain:WKErrorDomain code: errorCode userInfo:nil];
+}
+#endif // ENABLE(WEB_AUTHN)
+
++ (NSData *)importLocalAuthenticatorCredential:(NSData *)credentialBlob error:(NSError **)error
+{
+#if ENABLE(WEB_AUTHN)
+    auto credential = cbor::CBORReader::read(vectorFromNSData(credentialBlob));
+    if (!credential || !credential->isMap()) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+
+    auto& credentialMap = credential->getMap();
+    auto it = credentialMap.find(cbor::CBORValue(WebCore::privateKeyKey));
+    if (it == credentialMap.end() || !it->second.isByteString()) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    auto privateKey = adoptNS([[NSData alloc] initWithBytes:it->second.getByteString().data() length:it->second.getByteString().size()]);
+
+    it = credentialMap.find(cbor::CBORValue(WebCore::keyTypeKey));
+    if (it == credentialMap.end() || !it->second.isInteger()) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    auto keyType = it->second.getInteger();
+
+    it = credentialMap.find(cbor::CBORValue(WebCore::keySizeKey));
+    if (it == credentialMap.end() || !it->second.isInteger()) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    auto keySize = it->second.getInteger();
+
+    it = credentialMap.find(cbor::CBORValue(WebCore::relyingPartyKey));
+    if (it == credentialMap.end() || !it->second.isString()) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    auto rp = it->second.getString();
+
+    it = credentialMap.find(cbor::CBORValue(WebCore::applicationTagKey));
+    if (it == credentialMap.end() || !it->second.isMap()) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    auto keyTag = cbor::CBORWriter::write(cbor::CBORValue(it->second.getMap()));
+
+    NSDictionary *options = @{
+        // Key type values are string values of numbers, stored as kCFNumberSInt64Type in attributes, but must be passed as string here
+        (id)kSecAttrKeyType: (id)[NSString stringWithFormat:@"%i", (int)keyType],
+        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
+        (id)kSecAttrKeySizeInBits: @(keySize),
+    };
+    CFErrorRef errorRef = nullptr;
+    auto key = adoptCF(SecKeyCreateWithData(
+        bridge_cast(privateKey.get()),
+        bridge_cast(options),
+        &errorRef
+    ));
+    if (errorRef) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+
+    auto publicKey = adoptCF(SecKeyCopyPublicKey(key.get()));
+    auto publicKeyDataRep = adoptCF(SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef));
+    if (errorRef) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+
+    NSData *nsPublicKeyData = (NSData *)publicKeyDataRep.get();
+    auto digest = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_1);
+    digest->addBytes(nsPublicKeyData.bytes, nsPublicKeyData.length);
+    auto credentialId = digest->computeHash();
+    auto nsCredentialId = adoptNS([[NSData alloc] initWithBytes:credentialId.data() length:credentialId.size()]);
+
+    auto query = adoptNS([[NSMutableDictionary alloc] initWithObjectsAndKeys:
+        (id)kSecClassKey, (id)kSecClass,
+        (id)kSecAttrKeyClassPrivate, (id)kSecAttrKeyClass,
+        (id)rp, (id)kSecAttrLabel,
+        nsCredentialId.get(), (id)kSecAttrApplicationLabel,
+        @YES, (id)kSecUseDataProtectionKeychain,
+        nil
+    ]);
+    updateQueryIfNecessary(query.get());
+
+    OSStatus status = SecItemCopyMatching(bridge_cast(query.get()), nullptr);
+    if (!status) {
+        // Credential with same id already exists, duplicate key.
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorDuplicateCredential);
+        return nullptr;
+    }
+
+    auto secAttrApplicationTag = adoptNS([[NSData alloc] initWithBytes:keyTag->data() length:keyTag->size()]);
+    NSDictionary *addQuery = @{
+        (id)kSecValueRef: (id)key.get(),
+        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
+        (id)kSecAttrLabel: rp,
+        (id)kSecAttrApplicationTag: secAttrApplicationTag.get(),
+        (id)kSecUseDataProtectionKeychain: @YES,
+        (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock
+    };
+    status = SecItemAdd(bridge_cast(addQuery), NULL);
+    if (status) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorUnknown);
+        return nullptr;
+    }
+
+    return nsCredentialId.autorelease();
+#else
+    return nullptr;
+#endif // ENABLE(WEB_AUTHN)
+}
+
++ (NSData *)exportLocalAuthenticatorCredentialWithID:(NSData *)credentialID error:(NSError **)error
+{
+#if ENABLE(WEB_AUTHN)
+    auto query = adoptNS([[NSMutableDictionary alloc] initWithObjectsAndKeys:
+        (id)kSecClassKey, (id)kSecClass,
+        credentialID, (id)kSecAttrApplicationLabel,
+        (id)kSecAttrKeyClassPrivate, (id)kSecAttrKeyClass,
+        @YES, (id)kSecReturnRef,
+        @YES, (id)kSecUseDataProtectionKeychain,
+        nil
+    ]);
+    updateQueryIfNecessary(query.get());
+    CFTypeRef privateKeyRef = nullptr;
+    OSStatus status = SecItemCopyMatching(bridge_cast(query.get()), &privateKeyRef);
+    if (status && status != errSecItemNotFound) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorCredentialNotFound);
+        return nullptr;
+    }
+    auto privateKey = adoptCF(privateKeyRef);
+    CFErrorRef errorRef = nullptr;
+    auto privateKeyRep = adoptCF(SecKeyCopyExternalRepresentation((__bridge SecKeyRef)((id)privateKeyRef), &errorRef));
+    auto retainError = adoptCF(errorRef);
+    if (errorRef) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorCredentialNotFound);
+        return nullptr;
+    }
+        
+    [query removeObjectForKey:(id)kSecReturnRef];
+    [query setObject: @YES forKey:(id)kSecReturnAttributes];
+    CFTypeRef attributesArrayRef = nullptr;
+    status = SecItemCopyMatching(bridge_cast(query.get()), &attributesArrayRef);
+    if (status && status != errSecItemNotFound) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorCredentialNotFound);
+        return nullptr;
+    }
+    NSDictionary *attributes = (__bridge NSDictionary *)attributesArrayRef;
+
+    int64_t keyType, keySize;
+    if (!CFNumberGetValue((__bridge CFNumberRef)attributes[bridge_id_cast(kSecAttrKeyType)], kCFNumberSInt64Type, &keyType)) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    if (!CFNumberGetValue((__bridge CFNumberRef)attributes[bridge_id_cast(kSecAttrKeySizeInBits)], kCFNumberSInt64Type, &keySize)) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    
+    cbor::CBORValue::MapValue credentialMap;
+    credentialMap[cbor::CBORValue(WebCore::privateKeyKey)] = cbor::CBORValue(WebCore::toBufferSource(bridge_id_cast(privateKeyRep.get())));
+    credentialMap[cbor::CBORValue(WebCore::keyTypeKey)] = cbor::CBORValue(keyType);
+    credentialMap[cbor::CBORValue(WebCore::keySizeKey)] = cbor::CBORValue(keySize);
+    credentialMap[cbor::CBORValue(WebCore::relyingPartyKey)] = cbor::CBORValue(String(attributes[bridge_id_cast(kSecAttrLabel)]));
+    auto decodedResponse = cbor::CBORReader::read(vectorFromNSData(attributes[bridge_id_cast(kSecAttrApplicationTag)]));
+    if (!decodedResponse || !decodedResponse->isMap()) {
+        createNSErrorFromWKErrorIfNecessary(error, WKErrorMalformedCredential);
+        return nullptr;
+    }
+    credentialMap[cbor::CBORValue(WebCore::applicationTagKey)] = cbor::CBORValue(WTFMove(*decodedResponse));
+    auto serializedCredential = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(credentialMap)));
+    return adoptNS([[NSData alloc] initWithBytes:serializedCredential.value().data() length:serializedCredential.value().size()]).autorelease();
+#else
+    return nullptr;
+#endif // ENABLE(WEB_AUTHN)
+}
+
 - (void)cancel
 {
 #if ENABLE(WEB_AUTHN)

Modified: trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm (288815 => 288816)


--- trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm	2022-01-31 16:55:49 UTC (rev 288816)
@@ -177,7 +177,7 @@
     auto query = adoptNS([[NSMutableDictionary alloc] init]);
     [query setDictionary:@{
         (id)kSecClass: (id)kSecClassKey,
-        (id)kSecAttrAccessGroup: (id)String(LocalAuthenticatiorAccessGroup),
+        (id)kSecAttrAccessGroup: (id)String(LocalAuthenticatorAccessGroup),
         (id)kSecUseDataProtectionKeychain: @YES
     }];
     updateQueryIfNecessary(query.get());

Modified: trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.mm (288815 => 288816)


--- trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.mm	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.mm	2022-01-31 16:55:49 UTC (rev 288816)
@@ -170,7 +170,7 @@
     RetainPtr privateKeyAttributes = @{
         (id)kSecAttrAccessControl: (id)accessControlRef,
         (id)kSecAttrIsPermanent: @YES,
-        (id)kSecAttrAccessGroup: (id)String(LocalAuthenticatiorAccessGroup),
+        (id)kSecAttrAccessGroup: (id)String(LocalAuthenticatorAccessGroup),
         (id)kSecAttrLabel: secAttrLabel,
         (id)kSecAttrApplicationTag: secAttrApplicationTag,
     };

Modified: trunk/Tools/ChangeLog (288815 => 288816)


--- trunk/Tools/ChangeLog	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Tools/ChangeLog	2022-01-31 16:55:49 UTC (rev 288816)
@@ -1,3 +1,16 @@
+2022-01-31  J Pascoe  <[email protected]>
+
+        [WebAuthn] Provide SPI to export/import local credentials
+        https://bugs.webkit.org/show_bug.cgi?id=234112
+        rdar://84822000
+
+        Reviewed by Brent Fulgham.
+
+        Add tests for SPI to import / export local webauthn credentials.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
+        (TestWebKitAPI::TEST): New tests for import/export
+
 2022-01-31  Don Olmstead  <[email protected]>
 
         Support additional WPEToolingBackend types

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm (288815 => 288816)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm	2022-01-31 16:05:31 UTC (rev 288815)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm	2022-01-31 16:55:49 UTC (rev 288816)
@@ -2228,6 +2228,61 @@
     cleanUpKeychain("example.com");
 }
 
+TEST(WebAuthenticationPanel, ExportImportCredential)
+{
+    reset();
+
+    addKeyToKeychain(testES256PrivateKeyBase64, "example.com", testUserEntityBundleBase64);
+
+    auto *credentials = [_WKWebAuthenticationPanel getAllLocalAuthenticatorCredentialsWithAccessGroup:@"com.apple.TestWebKitAPI"];
+    EXPECT_NOT_NULL(credentials);
+    EXPECT_EQ([credentials count], 1lu);
+
+    EXPECT_NOT_NULL([credentials firstObject]);
+    NSError *error = nil;
+    auto exportedKey = [_WKWebAuthenticationPanel exportLocalAuthenticatorCredentialWithID:[credentials firstObject][_WKLocalAuthenticatorCredentialIDKey] error:&error];
+    
+    cleanUpKeychain("example.com");
+
+    auto credentialId = [_WKWebAuthenticationPanel importLocalAuthenticatorCredential:exportedKey error:&error];
+    EXPECT_WK_STREQ([[credentials firstObject][_WKLocalAuthenticatorCredentialIDKey] base64EncodedStringWithOptions:0], [credentialId base64EncodedStringWithOptions:0]);
+
+    cleanUpKeychain("example.com");
+}
+
+TEST(WebAuthenticationPanel, ExportImportDuplicateCredential)
+{
+    reset();
+    cleanUpKeychain("");
+
+    addKeyToKeychain(testES256PrivateKeyBase64, "example.com", testUserEntityBundleBase64);
+
+    auto *credentials = [_WKWebAuthenticationPanel getAllLocalAuthenticatorCredentialsWithAccessGroup:@"com.apple.TestWebKitAPI"];
+    EXPECT_NOT_NULL(credentials);
+    EXPECT_EQ([credentials count], 1lu);
+
+    EXPECT_NOT_NULL([credentials firstObject]);
+    NSError *error = nil;
+    auto exportedKey = [_WKWebAuthenticationPanel exportLocalAuthenticatorCredentialWithID:[credentials firstObject][_WKLocalAuthenticatorCredentialIDKey] error:&error];
+
+    auto credentialId = [_WKWebAuthenticationPanel importLocalAuthenticatorCredential:exportedKey error:&error];
+    EXPECT_EQ(credentialId, nil);
+    EXPECT_EQ(error.code, WKErrorDuplicateCredential);
+
+    cleanUpKeychain("example.com");
+}
+
+TEST(WebAuthenticationPanel, ImportMalformedCredential)
+{
+    reset();
+
+    NSError *error = nil;
+    auto credentialId = [_WKWebAuthenticationPanel importLocalAuthenticatorCredential:adoptNS([[NSData alloc] initWithBase64EncodedString:testUserEntityBundleBase64 options:0]).get() error:&error];
+
+    EXPECT_EQ(error.code, WKErrorMalformedCredential);
+    EXPECT_EQ(credentialId, nil);
+}
+
 TEST(WebAuthenticationPanel, DeleteOneCredential)
 {
     reset();
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to