Skip to content

Commit

Permalink
[WebAuthn] Support credProps extension and refactor extension handling
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=241199
rdar://90281799

Reviewed by Brent Fulgham.

This patch implements the credProps Web Authentication extension specified here:
https://www.w3.org/TR/webauthn-2/#sctn-authenticator-credential-properties-extension

This extension provides information about the created credential to the relying party,
at this time this is only the resident key credential property. This is useful information
for RPs to enable passwordless flows.

The patch also refactors how we ferry extension inputs/outputs between WebKit and Authentication
Services. We now passthrough inputs and outputs as a cbor serialized blob. This is well specified
as described here: https://www.w3.org/TR/webauthn-2/#sctn-extensions-inputs-outputs

This extension is covered by the web platform test webauthn/createcredential-resident-key.https.html.

* Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.cpp: Added.
(WebCore::AuthenticationExtensionsClientInputs::fromCBOR):
(WebCore::AuthenticationExtensionsClientInputs::toCBOR const):
* Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.h:
* Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.idl:
* Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.cpp: Added.
(WebCore::AuthenticationExtensionsClientOutputs::fromCBOR):
(WebCore::AuthenticationExtensionsClientOutputs::toCBOR const):
* Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.h:
(WebCore::AuthenticationExtensionsClientOutputs::encode const):
(WebCore::AuthenticationExtensionsClientOutputs::decode):
(WebCore::AuthenticationExtensionsClientOutputs::CredentialPropertiesOutput::encode const):
(WebCore::AuthenticationExtensionsClientOutputs::CredentialPropertiesOutput::decode):
* Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.idl:
* Source/WebCore/Modules/webauthn/AuthenticatorCoordinator.cpp:
(WebCore::AuthenticatorCoordinator::create const):
* Source/WebCore/Modules/webauthn/AuthenticatorResponse.cpp:
(WebCore::AuthenticatorResponse::tryCreate):
(WebCore::AuthenticatorResponse::data const):
* Source/WebCore/Modules/webauthn/AuthenticatorResponse.h:
* Source/WebCore/Modules/webauthn/AuthenticatorResponseData.h:
(WebCore::AuthenticatorResponseData::encode const):
(WebCore::AuthenticatorResponseData::decode):
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebKit/Platform/spi/Cocoa/AuthenticationServicesCoreSPI.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKAuthenticatorAssertionResponse.mm:
(-[_WKAuthenticatorAssertionResponse initWithClientDataJSON:rawId:extensionOutputsCBOR:authenticatorData:signature:userHandle:attachment:]):
* Source/WebKit/UIProcess/API/Cocoa/_WKAuthenticatorAssertionResponseInternal.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKAuthenticatorAttestationResponse.mm:
(-[_WKAuthenticatorAttestationResponse initWithClientDataJSON:rawId:extensionOutputsCBOR:attestationObject:attachment:transports:]):
* Source/WebKit/UIProcess/API/Cocoa/_WKAuthenticatorAttestationResponseInternal.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKAuthenticatorResponse.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKAuthenticatorResponse.mm:
(-[_WKAuthenticatorResponse initWithClientDataJSON:rawId:extensionOutputsCBOR:attachment:]):
* Source/WebKit/UIProcess/API/Cocoa/_WKAuthenticatorResponseInternal.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKPublicKeyCredentialCreationOptions.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKPublicKeyCredentialCreationOptions.mm:
(-[_WKPublicKeyCredentialCreationOptions dealloc]):
* Source/WebKit/UIProcess/API/Cocoa/_WKPublicKeyCredentialRequestOptions.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKPublicKeyCredentialRequestOptions.mm:
(-[_WKPublicKeyCredentialRequestOptions dealloc]):
* Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
(toNSData):
(+[_WKWebAuthenticationPanel convertToCoreCreationOptionsWithOptions:]):
(wkAuthenticatorAttestationResponse):
(wkAuthenticatorAssertionResponse):
(wkExtensionsClientOutputs): Deleted.
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h:
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:
(WebKit::LocalAuthenticator::processClientExtensions):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterUserVerification):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
(WebKit::LocalAuthenticator::continueGetAssertionAfterUserVerification):
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticatorCoordinatorProxy.mm:
(WebKit::configureRegistrationRequestContext):
(WebKit::configureAssertionOptions):
(WebKit::toExtensionOutputs):
(WebKit::continueAfterRequest):
* Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp:
(WebKit::CtapAuthenticator::continueMakeCredentialAfterResponseReceived):

Canonical link: https://commits.webkit.org/251413@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@295407 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
pascoej committed Jun 9, 2022
1 parent 5e46e23 commit df8b96d
Show file tree
Hide file tree
Showing 29 changed files with 380 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "config.h"
#include "AuthenticationExtensionsClientInputs.h"

#if ENABLE(WEB_AUTHN)

#include "CBORReader.h"
#include "CBORWriter.h"

namespace WebCore {

std::optional<AuthenticationExtensionsClientInputs> AuthenticationExtensionsClientInputs::fromCBOR(const Vector<uint8_t>& buffer)
{
std::optional<cbor::CBORValue> decodedValue = cbor::CBORReader::read(buffer);
if (!decodedValue || !decodedValue->isMap())
return std::nullopt;
AuthenticationExtensionsClientInputs clientInputs;

const auto& decodedMap = decodedValue->getMap();
auto it = decodedMap.find(cbor::CBORValue("appid"));
if (it != decodedMap.end() && it->second.isString())
clientInputs.appid = it->second.getString();
it = decodedMap.find(cbor::CBORValue("googleLegacyAppidSupport"));
if (it != decodedMap.end() && it->second.isBool())
clientInputs.googleLegacyAppidSupport = it->second.getBool();
it = decodedMap.find(cbor::CBORValue("credProps"));
if (it != decodedMap.end() && it->second.isBool())
clientInputs.credProps = it->second.getBool();
return clientInputs;
}

Vector<uint8_t> AuthenticationExtensionsClientInputs::toCBOR() const
{
cbor::CBORValue::MapValue clientInputsMap;
if (!appid.isEmpty())
clientInputsMap[cbor::CBORValue("appid")] = cbor::CBORValue(appid);
if (googleLegacyAppidSupport)
clientInputsMap[cbor::CBORValue("googleLegacyAppidSupport")] = cbor::CBORValue(googleLegacyAppidSupport);
if (credProps)
clientInputsMap[cbor::CBORValue("credProps")] = cbor::CBORValue(credProps);

auto clientInputs = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(clientInputsMap)));
ASSERT(clientInputs);

return *clientInputs;
}

} // namespace WebCore

#endif // ENABLE(WEB_AUTHN)
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ namespace WebCore {
struct AuthenticationExtensionsClientInputs {
String appid;
bool googleLegacyAppidSupport;
bool credProps;

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

WEBCORE_EXPORT Vector<uint8_t> toCBOR() const;
WEBCORE_EXPORT static std::optional<AuthenticationExtensionsClientInputs> fromCBOR(const Vector<uint8_t>&);
};

template<class Encoder>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
USVString appid;
// For Google to turn off the legacy AppID support.
boolean googleLegacyAppidSupport = true;
boolean credProps;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "config.h"
#include "AuthenticationExtensionsClientOutputs.h"

#if ENABLE(WEB_AUTHN)

#include "CBORReader.h"
#include "CBORWriter.h"

namespace WebCore {

std::optional<AuthenticationExtensionsClientOutputs> AuthenticationExtensionsClientOutputs::fromCBOR(const Vector<uint8_t>& buffer)
{
std::optional<cbor::CBORValue> decodedValue = cbor::CBORReader::read(buffer);
if (!decodedValue || !decodedValue->isMap())
return std::nullopt;
AuthenticationExtensionsClientOutputs clientOutputs;

const auto& decodedMap = decodedValue->getMap();
auto it = decodedMap.find(cbor::CBORValue("appid"));
if (it != decodedMap.end() && it->second.isBool())
clientOutputs.appid = it->second.getBool();
it = decodedMap.find(cbor::CBORValue("credProps"));
if (it != decodedMap.end() && it->second.isMap()) {
CredentialPropertiesOutput credProps;
it = it->second.getMap().find(cbor::CBORValue("rk"));
if (it != decodedMap.end() && it->second.isBool())
credProps.rk = it->second.getBool();
clientOutputs.credProps = credProps;
}

return clientOutputs;
}

Vector<uint8_t> AuthenticationExtensionsClientOutputs::toCBOR() const
{
cbor::CBORValue::MapValue clientOutputsMap;
if (appid)
clientOutputsMap[cbor::CBORValue("appid")] = cbor::CBORValue(*appid);
if (credProps) {
cbor::CBORValue::MapValue credPropsMap;
credPropsMap[cbor::CBORValue("rk")] = cbor::CBORValue(credProps->rk);
clientOutputsMap[cbor::CBORValue("credProps")] = cbor::CBORValue(credPropsMap);
}

auto clientOutputs = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(clientOutputsMap)));
ASSERT(clientOutputs);

return *clientOutputs;
}

} // namespace WebCore

#endif // ENABLE(WEB_AUTHN)
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,74 @@

#if ENABLE(WEB_AUTHN)

#include "CBORReader.h"
#include "CBORWriter.h"
#include <optional>

namespace WebCore {

struct AuthenticationExtensionsClientOutputs {
struct CredentialPropertiesOutput {
bool rk;
template<class Encoder> void encode(Encoder&) const;
template<class Decoder> static std::optional<CredentialPropertiesOutput> decode(Decoder&);
};
std::optional<bool> appid;
std::optional<CredentialPropertiesOutput> credProps;

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

WEBCORE_EXPORT Vector<uint8_t> toCBOR() const;
WEBCORE_EXPORT static std::optional<AuthenticationExtensionsClientOutputs> fromCBOR(const Vector<uint8_t>&);
};

template<class Encoder>
void AuthenticationExtensionsClientOutputs::encode(Encoder& encoder) const
{
encoder << appid << credProps;
}

template<class Decoder>
std::optional<AuthenticationExtensionsClientOutputs> AuthenticationExtensionsClientOutputs::decode(Decoder& decoder)
{
AuthenticationExtensionsClientOutputs result;

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

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

return result;
}

template<class Encoder>
void AuthenticationExtensionsClientOutputs::CredentialPropertiesOutput::encode(Encoder& encoder) const
{
encoder << rk;
}

template<class Decoder>
std::optional<AuthenticationExtensionsClientOutputs::CredentialPropertiesOutput> AuthenticationExtensionsClientOutputs::CredentialPropertiesOutput::decode(Decoder& decoder)
{
CredentialPropertiesOutput result;

std::optional<bool> rk;
decoder >> rk;
if (!rk)
return std::nullopt;
result.rk = *rk;

return result;
}

} // namespace WebCore

#endif // ENABLE(WEB_AUTHN)
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
[
Conditional=WEB_AUTHN,
JSGenerateToJSObject,
] dictionary CredentialPropertiesOutput {
boolean rk;
};

[
Conditional=WEB_AUTHN,
JSGenerateToJSObject,
] dictionary AuthenticationExtensionsClientOutputs {
boolean appid;
CredentialPropertiesOutput credProps;
};
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ void AuthenticatorCoordinator::create(const Document& document, const PublicKeyC

// Step 11-12.
// Only Google Legacy AppID Support Extension is supported.
options.extensions = AuthenticationExtensionsClientInputs { String(), processGoogleLegacyAppIdSupportExtension(options.extensions, options.rp.id) };
options.extensions = AuthenticationExtensionsClientInputs { String(), processGoogleLegacyAppIdSupportExtension(options.extensions, options.rp.id), options.extensions && options.extensions->credProps };

// Step 13-15.
auto clientDataJson = buildClientDataJson(ClientDataType::Create, options.challenge, callerOrigin, scope);
Expand Down
9 changes: 6 additions & 3 deletions Source/WebCore/Modules/webauthn/AuthenticatorResponse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,23 @@ RefPtr<AuthenticatorResponse> AuthenticatorResponse::tryCreate(AuthenticatorResp
if (!data.attestationObject)
return nullptr;

return AuthenticatorAttestationResponse::create(data.rawId.releaseNonNull(), data.attestationObject.releaseNonNull(), attachment, WTFMove(data.transports));
auto response = AuthenticatorAttestationResponse::create(data.rawId.releaseNonNull(), data.attestationObject.releaseNonNull(), attachment, WTFMove(data.transports));
if (data.extensionOutputs)
response->setExtensions(WTFMove(*data.extensionOutputs));
return WTFMove(response);
}

if (!data.authenticatorData || !data.signature)
return nullptr;

return AuthenticatorAssertionResponse::create(data.rawId.releaseNonNull(), data.authenticatorData.releaseNonNull(), data.signature.releaseNonNull(), WTFMove(data.userHandle), AuthenticationExtensionsClientOutputs { data.appid }, attachment);
return AuthenticatorAssertionResponse::create(data.rawId.releaseNonNull(), data.authenticatorData.releaseNonNull(), data.signature.releaseNonNull(), WTFMove(data.userHandle), WTFMove(data.extensionOutputs), attachment);
}

AuthenticatorResponseData AuthenticatorResponse::data() const
{
AuthenticatorResponseData data;
data.rawId = m_rawId.copyRef();
data.appid = m_extensions.appid;
data.extensionOutputs = m_extensions;
return data;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/Modules/webauthn/AuthenticatorResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AuthenticatorResponse : public RefCounted<AuthenticatorResponse> {

WEBCORE_EXPORT ArrayBuffer* rawId() const;
WEBCORE_EXPORT void setExtensions(AuthenticationExtensionsClientOutputs&&);
AuthenticationExtensionsClientOutputs extensions() const;
WEBCORE_EXPORT AuthenticationExtensionsClientOutputs extensions() const;
void setClientDataJSON(Ref<ArrayBuffer>&&);
ArrayBuffer* clientDataJSON() const;
WEBCORE_EXPORT AuthenticatorAttachment attachment() const;
Expand Down
19 changes: 9 additions & 10 deletions Source/WebCore/Modules/webauthn/AuthenticatorResponseData.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#if ENABLE(WEB_AUTHN)

#include "AuthenticationExtensionsClientOutputs.h"
#include "AuthenticatorTransport.h"
#include <JavaScriptCore/ArrayBuffer.h>
#include <wtf/Forward.h>
Expand All @@ -42,7 +43,7 @@ struct AuthenticatorResponseData {
RefPtr<ArrayBuffer> rawId;

// Extensions
std::optional<bool> appid;
std::optional<AuthenticationExtensionsClientOutputs> extensionOutputs;

// AuthenticatorAttestationResponse
RefPtr<ArrayBuffer> attestationObject;
Expand Down Expand Up @@ -92,6 +93,7 @@ void AuthenticatorResponseData::encode(Encoder& encoder) const
}
encoder << false;
encodeArrayBuffer(encoder, *rawId);
encoder << extensionOutputs;

encoder << isAuthenticatorAttestationResponse;

Expand All @@ -106,9 +108,6 @@ void AuthenticatorResponseData::encode(Encoder& encoder) const
encodeArrayBuffer(encoder, *authenticatorData);
encodeArrayBuffer(encoder, *signature);

// Encode AppID before user handle to avoid the userHandle flag.
encoder << appid;

if (!userHandle) {
encoder << false;
return;
Expand All @@ -132,6 +131,12 @@ std::optional<AuthenticatorResponseData> AuthenticatorResponseData::decode(Decod
result.rawId = decodeArrayBuffer(decoder);
if (!result.rawId)
return std::nullopt;

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

std::optional<bool> isAuthenticatorAttestationResponse;
decoder >> isAuthenticatorAttestationResponse;
Expand Down Expand Up @@ -160,12 +165,6 @@ std::optional<AuthenticatorResponseData> AuthenticatorResponseData::decode(Decod
if (!result.signature)
return std::nullopt;

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

std::optional<bool> hasUserHandle;
decoder >> hasUserHandle;
if (!hasUserHandle)
Expand Down

0 comments on commit df8b96d

Please sign in to comment.