AuthenticatorManager.cpp [plain text]
#include "config.h"
#include "AuthenticatorManager.h"
#if ENABLE(WEB_AUTHN)
#include "AbortSignal.h"
#include "AuthenticatorAssertionResponse.h"
#include "AuthenticatorAttestationResponse.h"
#include "CredentialsMessenger.h"
#include "JSBasicCredential.h"
#include "PublicKeyCredential.h"
#include "PublicKeyCredentialCreationOptions.h"
#include "PublicKeyCredentialRequestOptions.h"
#include "SecurityOrigin.h"
#include "Timer.h"
#include <pal/crypto/CryptoDigest.h>
#include <wtf/JSONValues.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/text/Base64.h>
namespace WebCore {
namespace AuthenticatorManagerInternal {
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());
object->setString("hashAlgorithm"_s, "SHA-256"_s);
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();
}
static std::unique_ptr<Timer> initTimeoutTimer(std::optional<unsigned long> timeOutInMs, const CredentialPromise& promise)
{
if (!timeOutInMs)
return nullptr;
auto timer = std::make_unique<Timer>([promise = promise] () mutable {
promise.reject(Exception { NotAllowedError, "Operation timed out."_s });
});
timer->startOneShot(Seconds::fromMilliseconds(*timeOutInMs));
return timer;
}
static bool didTimeoutTimerFire(Timer* timer)
{
if (!timer)
return false;
if (!timer->isActive())
return true;
timer->stop();
return false;
}
}
AuthenticatorManager& AuthenticatorManager::singleton()
{
ASSERT(isMainThread());
static NeverDestroyed<AuthenticatorManager> authenticator;
return authenticator;
}
void AuthenticatorManager::setMessenger(CredentialsMessenger& messenger)
{
m_messenger = makeWeakPtr(messenger);
}
void AuthenticatorManager::create(const SecurityOrigin& callerOrigin, const PublicKeyCredentialCreationOptions& options, bool sameOriginWithAncestors, RefPtr<AbortSignal>&& abortSignal, CredentialPromise&& promise) const
{
using namespace AuthenticatorManagerInternal;
if (!sameOriginWithAncestors) {
promise.reject(Exception { NotAllowedError, "The origin of the document is not the same as its ancestors."_s });
return;
}
std::unique_ptr<Timer> timeoutTimer = initTimeoutTimer(options.timeout, promise);
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_messenger) {
promise.reject(Exception { UnknownError, "Unknown internal error."_s });
return;
}
auto completionHandler = [clientDataJson = WTFMove(clientDataJson), promise = WTFMove(promise), timeoutTimer = WTFMove(timeoutTimer), abortSignal = WTFMove(abortSignal)] (ExceptionOr<CreationReturnBundle>&& result) mutable {
if (didTimeoutTimerFire(timeoutTimer.get()))
return;
if (abortSignal && abortSignal->aborted()) {
promise.reject(Exception { AbortError, "Aborted by AbortSignal."_s });
return;
}
if (result.hasException()) {
promise.reject(result.exception());
return;
}
auto bundle = result.releaseReturnValue();
promise.resolve(PublicKeyCredential::create(WTFMove(bundle.credentialId), AuthenticatorAttestationResponse::create(WTFMove(clientDataJson), ArrayBuffer::create(WTFMove(bundle.attestationObject)))).ptr());
};
m_messenger->makeCredential(clientDataJsonHash, options, WTFMove(completionHandler));
}
void AuthenticatorManager::discoverFromExternalSource(const SecurityOrigin& callerOrigin, const PublicKeyCredentialRequestOptions& options, bool sameOriginWithAncestors, RefPtr<AbortSignal>&& abortSignal, CredentialPromise&& promise) const
{
using namespace AuthenticatorManagerInternal;
if (!sameOriginWithAncestors) {
promise.reject(Exception { NotAllowedError, "The origin of the document is not the same as its ancestors."_s });
return;
}
std::unique_ptr<Timer> timeoutTimer = initTimeoutTimer(options.timeout, promise);
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_messenger) {
promise.reject(Exception { UnknownError, "Unknown internal error."_s });
return;
}
auto completionHandler = [clientDataJson = WTFMove(clientDataJson), promise = WTFMove(promise), timeoutTimer = WTFMove(timeoutTimer), abortSignal = WTFMove(abortSignal)] (ExceptionOr<AssertionReturnBundle>&& result) mutable {
if (didTimeoutTimerFire(timeoutTimer.get()))
return;
if (abortSignal && abortSignal->aborted()) {
promise.reject(Exception { AbortError, "Aborted by AbortSignal."_s });
return;
}
if (result.hasException()) {
promise.reject(result.exception());
return;
}
auto bundle = result.releaseReturnValue();
promise.resolve(PublicKeyCredential::create(WTFMove(bundle.credentialId), AuthenticatorAssertionResponse::create(WTFMove(clientDataJson), WTFMove(bundle.authenticatorData), WTFMove(bundle.signature), WTFMove(bundle.userHandle))).ptr());
};
m_messenger->getAssertion(clientDataJsonHash, options, WTFMove(completionHandler));
}
void AuthenticatorManager::isUserVerifyingPlatformAuthenticatorAvailable(DOMPromiseDeferred<IDLBoolean>&& promise) const
{
if (!m_messenger) {
promise.reject(Exception { UnknownError, "Unknown internal error."_s });
return;
}
auto completionHandler = [promise = WTFMove(promise)] (bool result) mutable {
promise.resolve(result);
};
m_messenger->isUserVerifyingPlatformAuthenticatorAvailable(WTFMove(completionHandler));
}
}
#endif // ENABLE(WEB_AUTHN)