Skip to content
Permalink
Browse files
[WebAuthn] Add support for authenticators over CCID
https://bugs.webkit.org/show_bug.cgi?id=242365
rdar://82529212

Reviewed by Brent Fulgham.

This patch adds support for authenticators over CCID. This allows use of credentials
on smart cards. The transport used is determined by how the card responds to the
"Get Data Command," which returns a uid with contactless cards.

Added layout tests and tested manually with a smart card (contact and contactless), as well
as a Yubikey 5c over NFC.

* LayoutTests/http/wpt/webauthn/public-key-credential-create-success-ccid.https-expected.txt: Added.
* LayoutTests/http/wpt/webauthn/public-key-credential-create-success-ccid.https.html: Added.
* LayoutTests/http/wpt/webauthn/public-key-credential-get-success-ccid.https-expected.txt: Added.
* LayoutTests/http/wpt/webauthn/public-key-credential-get-success-ccid.https.html: Added.
* LayoutTests/http/wpt/webauthn/resources/util.js:
* Source/WebCore/Modules/webauthn/AuthenticatorTransport.h:
* Source/WebCore/Modules/webauthn/AuthenticatorTransport.idl:
* Source/WebCore/Modules/webauthn/WebAuthenticationConstants.h:
* Source/WebCore/Modules/webauthn/fido/AuthenticatorGetInfoResponse.cpp:
(fido::toString):
* Source/WebCore/Modules/webauthn/fido/DeviceResponseConverter.cpp:
(fido::convertStringToAuthenticatorTransport):
* Source/WebCore/testing/MockWebAuthenticationConfiguration.h:
(WebCore::MockWebAuthenticationConfiguration::CcidConfiguration::encode const):
(WebCore::MockWebAuthenticationConfiguration::CcidConfiguration::decode):
(WebCore::MockWebAuthenticationConfiguration::encode const):
(WebCore::MockWebAuthenticationConfiguration::decode):
* Source/WebCore/testing/MockWebAuthenticationConfiguration.idl:
* Source/WebKit/SourcesCocoa.txt:
* Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp:
(WebKit::WebCore::collectTransports):
* Source/WebKit/UIProcess/WebAuthentication/AuthenticatorTransportService.cpp:
(WebKit::AuthenticatorTransportService::create):
(WebKit::AuthenticatorTransportService::createMock):
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/CcidConnection.h: Copied from Source/WebCore/Modules/webauthn/AuthenticatorTransport.h.
(WebKit::CcidConnection::contactless const):
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/CcidConnection.mm: Added.
(WebKit::fido::compareVersion):
(WebKit::CcidConnection::create):
(WebKit::CcidConnection::CcidConnection):
(WebKit::CcidConnection::~CcidConnection):
(WebKit::CcidConnection::detectContactless):
(WebKit::CcidConnection::trySelectFidoApplet):
(WebKit::CcidConnection::transact const):
(WebKit::CcidConnection::stop const):
(WebKit::CcidConnection::restartPolling):
(WebKit::CcidConnection::startPolling):
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/CcidService.h: Copied from Source/WebCore/Modules/webauthn/AuthenticatorTransport.h.
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/CcidService.mm: Added.
(WebKit::CcidService::CcidService):
(WebKit::CcidService::~CcidService):
(WebKit::CcidService::didConnectTag):
(WebKit::CcidService::startDiscoveryInternal):
(WebKit::CcidService::restartDiscoveryInternal):
(WebKit::CcidService::platformStartDiscovery):
(WebKit::CcidService::onValidCard):
(WebKit::CcidService::updateSlots):
(-[_WKSmartCardSlotObserver initWithService:]):
(-[_WKSmartCardSlotObserver observeValueForKeyPath:ofObject:change:context:]):
(-[_WKSmartCardSlotStateObserver initWithService:slot:]):
(-[_WKSmartCardSlotStateObserver observeValueForKeyPath:ofObject:change:context:]):
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticatorCoordinatorProxy.mm:
(WebKit::toASCDescriptor):
* Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp:
(WebKit::MockAuthenticatorManager::filterTransports const):
* Source/WebKit/UIProcess/WebAuthentication/Mock/MockCcidService.h: Copied from Source/WebCore/Modules/webauthn/AuthenticatorTransport.idl.
* Source/WebKit/UIProcess/WebAuthentication/Mock/MockCcidService.mm: Added.
(-[_WKMockTKSmartCard initWithService:]):
(-[_WKMockTKSmartCard beginSessionWithReply:]):
(-[_WKMockTKSmartCard transmitRequest:reply:]):
(WebKit::MockCcidService::MockCcidService):
(WebKit::MockCcidService::platformStartDiscovery):
(WebKit::MockCcidService::nextReply):
* Source/WebKit/UIProcess/WebAuthentication/fido/CtapCcidDriver.cpp: Added.
(WebKit::CtapCcidDriver::CtapCcidDriver):
(WebKit::CtapCcidDriver::transact):
(WebKit::CtapCcidDriver::respondAsync const):
* Source/WebKit/UIProcess/WebAuthentication/fido/CtapCcidDriver.h: Copied from Source/WebCore/Modules/webauthn/AuthenticatorTransport.h.
* Source/WebKit/WebKit.xcodeproj/project.pbxproj:

Canonical link: https://commits.webkit.org/252425@main
  • Loading branch information
pascoej committed Jul 13, 2022
1 parent b4d9eee commit c0f5fc64aac387a18a1ec9f54e013847964b4067
Showing 27 changed files with 931 additions and 5 deletions.
@@ -0,0 +1,5 @@
CONSOLE MESSAGE: User gesture is not detected. To use the WebAuthn API, call 'navigator.credentials.create' or 'navigator.credentials.get' within user activated events.

PASS PublicKeyCredential's [[create]] with minimum options in a mock ccid authenticator.
PASS PublicKeyCredential's [[create]] with minimum options in a mock ccid authenticator with contactless.

@@ -0,0 +1,55 @@
<!DOCTYPE html><!-- webkit-test-runner -->
<title>Web Authentication API: PublicKeyCredential's [[create]] success cases with a mock ccid authenticator.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/util.js"></script>
<script src="./resources/cbor.js"></script>
<script>
// Default mock configuration. Tests need to override if they need different configuration.
if (window.internals)
internals.setMockWebAuthenticationConfiguration({ ccid: { payloadBase64: [testCcidNoUidBase64, testNfcCtapVersionBase64, testGetInfoResponseApduBase64, testCreationMessageApduBase64] } });

promise_test(t => {
const options = {
publicKey: {
rp: {
name: "localhost",
},
user: {
name: "John Appleseed",
id: Base64URL.parse(testUserhandleBase64),
displayName: "Appleseed",
},
challenge: Base64URL.parse("MTIzNDU2"),
pubKeyCredParams: [{ type: "public-key", alg: -7 }]
}
};

return navigator.credentials.create(options).then(credential => {
checkCtapMakeCredentialResult(credential, true /* isNoneAttestation */, ["smart-card"]);
});
}, "PublicKeyCredential's [[create]] with minimum options in a mock ccid authenticator.");

promise_test(t => {
if (window.internals)
internals.setMockWebAuthenticationConfiguration({ ccid: { payloadBase64: [testCcidValidUidBase64, testNfcCtapVersionBase64, testGetInfoResponseApduBase64, testCreationMessageApduBase64] } });
const options = {
publicKey: {
rp: {
name: "localhost",
},
user: {
name: "John Appleseed",
id: Base64URL.parse(testUserhandleBase64),
displayName: "Appleseed",
},
challenge: Base64URL.parse("MTIzNDU2"),
pubKeyCredParams: [{ type: "public-key", alg: -7 }]
}
};

return navigator.credentials.create(options).then(credential => {
checkCtapMakeCredentialResult(credential, true /* isNoneAttestation */, ["nfc"]);
});
}, "PublicKeyCredential's [[create]] with minimum options in a mock ccid authenticator with contactless.");
</script>
@@ -0,0 +1,3 @@

PASS PublicKeyCredential's [[get]] with minimum options in a mock ccid authenticator.

@@ -0,0 +1,23 @@
<!DOCTYPE html><!-- webkit-test-runner [ WebAuthenticationModernEnabled=false ] -->
<title>Web Authentication API: PublicKeyCredential's [[get]] success cases with a mock nfc authenticator.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/util.js"></script>
<script>
// Default mock configuration. Tests need to override if they need different configuration.
if (window.internals)
internals.setMockWebAuthenticationConfiguration({ ccid: { payloadBase64: [testCcidValidUidBase64, testNfcCtapVersionBase64, testGetInfoResponseApduBase64, testAssertionMessageApduBase64] } });

promise_test(t => {
const options = {
publicKey: {
challenge: Base64URL.parse("MTIzNDU2"),
timeout: 100
}
};

return navigator.credentials.get(options).then(credential => {
return checkCtapGetAssertionResult(credential);
});
}, "PublicKeyCredential's [[get]] with minimum options in a mock ccid authenticator.");
</script>
@@ -141,6 +141,8 @@ const testAssertionMessageApduBase64 =
"Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
"4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
"QoJ1L7Fe64G9uBeQAA==";
const testCcidNoUidBase64 = "aIE=";
const testCcidValidUidBase64 = "CH+d1ZAA";

const RESOURCES_DIR = "/WebKit/webauthn/resources/";

@@ -38,7 +38,8 @@ enum class AuthenticatorTransport {
Ble,
Internal,
Cable,
Hybrid
Hybrid,
SmartCard
};

WEBCORE_EXPORT std::optional<AuthenticatorTransport> toAuthenticatorTransport(const String& transport);
@@ -57,7 +58,8 @@ template<> struct EnumTraits<WebCore::AuthenticatorTransport> {
WebCore::AuthenticatorTransport::Ble,
WebCore::AuthenticatorTransport::Internal,
WebCore::AuthenticatorTransport::Cable,
WebCore::AuthenticatorTransport::Hybrid
WebCore::AuthenticatorTransport::Hybrid,
WebCore::AuthenticatorTransport::SmartCard
>;
};

@@ -31,5 +31,6 @@
"ble",
"internal",
"cable",
"hybrid"
"hybrid",
"smart-card"
};
@@ -92,8 +92,10 @@ constexpr auto authenticatorTransportNfc = "nfc"_s;
constexpr auto authenticatorTransportBle = "ble"_s;
constexpr auto authenticatorTransportInternal = "internal"_s;
constexpr auto authenticatorTransportCable = "cable"_s;
constexpr auto authenticatorTransportSmartCard = "smart-card"_s;
constexpr auto authenticatorTransportHybrid = "hybrid"_s;


} // namespace WebCore

namespace WebAuthn {
@@ -102,6 +102,8 @@ static String toString(WebCore::AuthenticatorTransport transport)
return WebCore::authenticatorTransportCable;
case WebCore::AuthenticatorTransport::Hybrid:
return WebCore::authenticatorTransportHybrid;
case WebCore::AuthenticatorTransport::SmartCard:
return WebCore::authenticatorTransportSmartCard;
default:
break;
}
@@ -68,6 +68,8 @@ static std::optional<AuthenticatorTransport> convertStringToAuthenticatorTranspo
return AuthenticatorTransport::Cable;
if (transport == authenticatorTransportHybrid)
return AuthenticatorTransport::Hybrid;
if (transport == authenticatorTransportSmartCard)
return AuthenticatorTransport::SmartCard;
return std::nullopt;
}

@@ -108,10 +108,18 @@ struct MockWebAuthenticationConfiguration {
template<class Decoder> static std::optional<NfcConfiguration> decode(Decoder&);
};

struct CcidConfiguration {
Vector<String> payloadBase64;

template<class Encoder> void encode(Encoder&) const;
template<class Decoder> static std::optional<CcidConfiguration> decode(Decoder&);
};

bool silentFailure { false };
std::optional<LocalConfiguration> local;
std::optional<HidConfiguration> hid;
std::optional<NfcConfiguration> nfc;
std::optional<CcidConfiguration> ccid;

template<class Encoder> void encode(Encoder&) const;
template<class Decoder> static std::optional<MockWebAuthenticationConfiguration> decode(Decoder&);
@@ -204,6 +212,21 @@ std::optional<MockWebAuthenticationConfiguration::HidConfiguration> MockWebAuthe
return result;
}

template<class Encoder>
void MockWebAuthenticationConfiguration::CcidConfiguration::encode(Encoder& encoder) const
{
encoder << payloadBase64;
}

template<class Decoder>
std::optional<MockWebAuthenticationConfiguration::CcidConfiguration> MockWebAuthenticationConfiguration::CcidConfiguration::decode(Decoder& decoder)
{
MockWebAuthenticationConfiguration::CcidConfiguration result;
if (!decoder.decode(result.payloadBase64))
return std::nullopt;
return result;
}

template<class Encoder>
void MockWebAuthenticationConfiguration::NfcConfiguration::encode(Encoder& encoder) const
{
@@ -228,7 +251,7 @@ std::optional<MockWebAuthenticationConfiguration::NfcConfiguration> MockWebAuthe
template<class Encoder>
void MockWebAuthenticationConfiguration::encode(Encoder& encoder) const
{
encoder << silentFailure << local << hid << nfc;
encoder << silentFailure << local << hid << nfc << ccid;
}

template<class Decoder>
@@ -260,6 +283,12 @@ std::optional<MockWebAuthenticationConfiguration> MockWebAuthenticationConfigura
return std::nullopt;
result.nfc = WTFMove(*nfc);

std::optional<std::optional<CcidConfiguration>> ccid;
decoder >> ccid;
if (!ccid)
return std::nullopt;
result.ccid = WTFMove(*ccid);

return result;
}

@@ -75,6 +75,7 @@
MockLocalConfiguration local;
MockHidConfiguration hid;
MockNfcConfiguration nfc;
MockCcidConfiguration ccid;
};

[
@@ -113,3 +114,9 @@
boolean multipleTags = false;
boolean multiplePhysicalTags = false;
};

[
Conditional=WEB_AUTHN,
] dictionary MockCcidConfiguration {
sequence<DOMString> payloadBase64;
};
@@ -587,6 +587,8 @@ UIProcess/RemoteLayerTree/RemoteScrollingTree.cpp
UIProcess/WebAuthentication/Cocoa/AppAttestInternalSoftLink.mm @no-unify
UIProcess/WebAuthentication/Cocoa/AuthenticationServicesCoreSoftLink.mm @no-unify
UIProcess/WebAuthentication/Cocoa/AuthenticatorPresenterCoordinator.mm
UIProcess/WebAuthentication/Cocoa/CcidConnection.mm
UIProcess/WebAuthentication/Cocoa/CcidService.mm
UIProcess/WebAuthentication/Cocoa/HidConnection.mm
UIProcess/WebAuthentication/Cocoa/HidService.mm
UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.mm @no-unify
@@ -604,6 +606,7 @@ UIProcess/WebAuthentication/Cocoa/WebAuthenticatorCoordinatorProxy.mm
UIProcess/WebAuthentication/Mock/MockLocalConnection.mm
UIProcess/WebAuthentication/Mock/MockLocalService.mm
UIProcess/WebAuthentication/Mock/MockNfcService.mm
UIProcess/WebAuthentication/Mock/MockCcidService.mm

UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm

@@ -65,6 +65,8 @@ static AuthenticatorManager::TransportSet collectTransports(const std::optional<
ASSERT_UNUSED(addResult, addResult.isNewEntry);
addResult = result.add(AuthenticatorTransport::Ble);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
addResult = result.add(AuthenticatorTransport::SmartCard);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
return result;
}

@@ -80,6 +82,8 @@ static AuthenticatorManager::TransportSet collectTransports(const std::optional<
ASSERT_UNUSED(addResult, addResult.isNewEntry);
addResult = result.add(AuthenticatorTransport::Ble);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
addResult = result.add(AuthenticatorTransport::SmartCard);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
return result;
}

@@ -104,6 +108,8 @@ static AuthenticatorManager::TransportSet collectTransports(const Vector<PublicK
ASSERT_UNUSED(addResult, addResult.isNewEntry);
addResult = result.add(AuthenticatorTransport::Ble);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
addResult = result.add(AuthenticatorTransport::SmartCard);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
}

for (auto& allowCredential : allowCredentials) {
@@ -112,6 +118,7 @@ static AuthenticatorManager::TransportSet collectTransports(const Vector<PublicK
result.add(AuthenticatorTransport::Usb);
result.add(AuthenticatorTransport::Nfc);
result.add(AuthenticatorTransport::Ble);
result.add(AuthenticatorTransport::SmartCard);

break;
}
@@ -133,6 +140,7 @@ static AuthenticatorManager::TransportSet collectTransports(const Vector<PublicK
result.remove(AuthenticatorTransport::Usb);
result.remove(AuthenticatorTransport::Nfc);
result.remove(AuthenticatorTransport::Ble);
result.remove(AuthenticatorTransport::SmartCard);
}

if (authenticatorAttachment == AuthenticatorAttachment::CrossPlatform)
@@ -175,7 +183,7 @@ static String getUserName(const std::variant<PublicKeyCredentialCreationOptions,

} // namespace

const size_t AuthenticatorManager::maxTransportNumber = 4;
const size_t AuthenticatorManager::maxTransportNumber = 5;

AuthenticatorManager::AuthenticatorManager()
: m_requestTimeOutTimer(RunLoop::main(), this, &AuthenticatorManager::timeOutTimerFired)
@@ -28,8 +28,10 @@

#if ENABLE(WEB_AUTHN)

#include "CcidService.h"
#include "HidService.h"
#include "LocalService.h"
#include "MockCcidService.h"
#include "MockHidService.h"
#include "MockLocalService.h"
#include "MockNfcService.h"
@@ -47,6 +49,8 @@ UniqueRef<AuthenticatorTransportService> AuthenticatorTransportService::create(W
return makeUniqueRef<HidService>(observer);
case WebCore::AuthenticatorTransport::Nfc:
return makeUniqueRef<NfcService>(observer);
case WebCore::AuthenticatorTransport::SmartCard:
return makeUniqueRef<CcidService>(observer);
default:
ASSERT_NOT_REACHED();
return makeUniqueRef<LocalService>(observer);
@@ -62,6 +66,8 @@ UniqueRef<AuthenticatorTransportService> AuthenticatorTransportService::createMo
return makeUniqueRef<MockHidService>(observer, configuration);
case WebCore::AuthenticatorTransport::Nfc:
return makeUniqueRef<MockNfcService>(observer, configuration);
case WebCore::AuthenticatorTransport::SmartCard:
return makeUniqueRef<MockCcidService>(observer, configuration);
default:
ASSERT_NOT_REACHED();
return makeUniqueRef<MockLocalService>(observer, configuration);

0 comments on commit c0f5fc6

Please sign in to comment.