AuthenticatorCoordinator.cpp [plain text]
#include "config.h"
#include "AuthenticatorCoordinator.h"
#if ENABLE(WEB_AUTHN)
#include "AbortSignal.h"
#include "AuthenticatorAssertionResponse.h"
#include "AuthenticatorAttestationResponse.h"
#include "AuthenticatorCoordinatorClient.h"
#include "JSBasicCredential.h"
#include "PublicKeyCredential.h"
#include "PublicKeyCredentialCreationOptions.h"
#include "PublicKeyCredentialData.h"
#include "PublicKeyCredentialRequestOptions.h"
#include "SecurityOrigin.h"
#include <pal/crypto/CryptoDigest.h>
#include <wtf/JSONValues.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/text/Base64.h>
namespace WebCore {
namespace AuthenticatorCoordinatorInternal {
enum class ClientDataType {
Create,
Get
};
static Ref<ArrayBuffer> produceClientDataJson(ClientDataType type, const BufferSource& challenge, const SecurityOrigin& origin)
{
auto object = JSON::Object::create();
switch (type) {
case ClientDataType::Create:
object->setString("type"_s, "webauthn.create"_s);
break;
case ClientDataType::Get:
object->setString("type"_s, "webauthn.get"_s);
break;
}
object->setString("challenge"_s, WTF::base64URLEncode(challenge.data(), challenge.length()));
object->setString("origin"_s, origin.toRawString());
auto utf8JSONString = object->toJSONString().utf8();
return ArrayBuffer::create(utf8JSONString.data(), utf8JSONString.length());
}
static Vector<uint8_t> produceClientDataJsonHash(const ArrayBuffer& clientDataJson)
{
auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
crypto->addBytes(clientDataJson.data(), clientDataJson.byteLength());
return crypto->computeHash();
}
}
AuthenticatorCoordinator::AuthenticatorCoordinator(std::unique_ptr<AuthenticatorCoordinatorClient>&& client)
: m_client(WTFMove(client))
{
}
void AuthenticatorCoordinator::setClient(std::unique_ptr<AuthenticatorCoordinatorClient>&& client)
{
m_client = WTFMove(client);
}
void AuthenticatorCoordinator::create(const SecurityOrigin& callerOrigin, const PublicKeyCredentialCreationOptions& options, bool sameOriginWithAncestors, RefPtr<AbortSignal>&& abortSignal, CredentialPromise&& promise) const
{
using namespace AuthenticatorCoordinatorInternal;
if (!sameOriginWithAncestors) {
promise.reject(Exception { NotAllowedError, "The origin of the document is not the same as its ancestors."_s });
return;
}
if (!options.rp.id.isEmpty() && callerOrigin.host() != options.rp.id) {
promise.reject(Exception { SecurityError, "The origin of the document is not a registrable domain suffix of the provided RP ID."_s });
return;
}
if (options.rp.id.isEmpty())
options.rp.id = callerOrigin.host();
if (options.pubKeyCredParams.isEmpty()) {
promise.reject(Exception { NotSupportedError, "No desired properties of the to be created credential are provided."_s });
return;
}
auto clientDataJson = produceClientDataJson(ClientDataType::Create, options.challenge, callerOrigin);
auto clientDataJsonHash = produceClientDataJsonHash(clientDataJson);
if (!m_client) {
promise.reject(Exception { UnknownError, "Unknown internal error."_s });
return;
}
auto completionHandler = [clientDataJson = WTFMove(clientDataJson), promise = WTFMove(promise), abortSignal = WTFMove(abortSignal)] (const WebCore::PublicKeyCredentialData& data, const WebCore::ExceptionData& exception) mutable {
if (abortSignal && abortSignal->aborted()) {
promise.reject(Exception { AbortError, "Aborted by AbortSignal."_s });
return;
}
data.clientDataJSON = WTFMove(clientDataJson);
if (auto publicKeyCredential = PublicKeyCredential::tryCreate(data)) {
promise.resolve(publicKeyCredential.get());
return;
}
ASSERT(!exception.message.isNull());
promise.reject(exception.toException());
};
m_client->makeCredential(clientDataJsonHash, options, WTFMove(completionHandler));
}
void AuthenticatorCoordinator::discoverFromExternalSource(const SecurityOrigin& callerOrigin, const PublicKeyCredentialRequestOptions& options, bool sameOriginWithAncestors, RefPtr<AbortSignal>&& abortSignal, CredentialPromise&& promise) const
{
using namespace AuthenticatorCoordinatorInternal;
if (!sameOriginWithAncestors) {
promise.reject(Exception { NotAllowedError, "The origin of the document is not the same as its ancestors."_s });
return;
}
if (!options.rpId.isEmpty() && callerOrigin.host() != options.rpId) {
promise.reject(Exception { SecurityError, "The origin of the document is not a registrable domain suffix of the provided RP ID."_s });
return;
}
if (options.rpId.isEmpty())
options.rpId = callerOrigin.host();
auto clientDataJson = produceClientDataJson(ClientDataType::Get, options.challenge, callerOrigin);
auto clientDataJsonHash = produceClientDataJsonHash(clientDataJson);
if (!m_client) {
promise.reject(Exception { UnknownError, "Unknown internal error."_s });
return;
}
auto completionHandler = [clientDataJson = WTFMove(clientDataJson), promise = WTFMove(promise), abortSignal = WTFMove(abortSignal)] (const WebCore::PublicKeyCredentialData& data, const WebCore::ExceptionData& exception) mutable {
if (abortSignal && abortSignal->aborted()) {
promise.reject(Exception { AbortError, "Aborted by AbortSignal."_s });
return;
}
data.clientDataJSON = WTFMove(clientDataJson);
if (auto publicKeyCredential = PublicKeyCredential::tryCreate(data)) {
promise.resolve(publicKeyCredential.get());
return;
}
ASSERT(!exception.message.isNull());
promise.reject(exception.toException());
};
m_client->getAssertion(clientDataJsonHash, options, WTFMove(completionHandler));
}
void AuthenticatorCoordinator::isUserVerifyingPlatformAuthenticatorAvailable(DOMPromiseDeferred<IDLBoolean>&& promise) const
{
if (!m_client) {
promise.reject(Exception { UnknownError, "Unknown internal error."_s });
return;
}
auto completionHandler = [promise = WTFMove(promise)] (bool result) mutable {
promise.resolve(result);
};
m_client->isUserVerifyingPlatformAuthenticatorAvailable(WTFMove(completionHandler));
}
}
#endif // ENABLE(WEB_AUTHN)