Bug 1410346 - Merge U2F.cpp and U2FManager.cpp r=jcj

Reviewers: jcj

Reviewed By: jcj

Bug #: 1410346

Differential Revision: https://phabricator.services.mozilla.com/D288

--HG--
extra : amend_source : 5d078e8d9dc1bd6da11f2d84e349b6d77638ed6b
This commit is contained in:
Tim Taubert 2017-11-28 10:21:07 +01:00
Родитель 0294a21add
Коммит 1712f2c336
7 изменённых файлов: 488 добавлений и 756 удалений

Просмотреть файл

@ -5,13 +5,21 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/WebCryptoCommon.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "nsContentUtils.h"
#include "nsICryptoHash.h"
#include "nsIEffectiveTLDService.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsURLParsers.h"
#include "U2FManager.h"
#include "U2FTransactionChild.h"
#include "U2FUtil.h"
#include "hasht.h"
using namespace mozilla::ipc;
// Forward decl because of nsHTMLDocument.h's complex dependency on /layout/style
class nsHTMLDocument {
@ -25,12 +33,14 @@ namespace dom {
static mozilla::LazyLogModule gU2FLog("u2fmanager");
NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange");
NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
@ -38,6 +48,23 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
/***********************************************************************
* Utility Functions
**********************************************************************/
static ErrorCode
ConvertNSResultToErrorCode(const nsresult& aError)
{
if (aError == NS_ERROR_DOM_TIMEOUT_ERR) {
return ErrorCode::TIMEOUT;
}
/* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */
if (aError == NS_ERROR_DOM_NOT_ALLOWED_ERR) {
return ErrorCode::DEVICE_INELIGIBLE;
}
return ErrorCode::OTHER_ERROR;
}
static uint32_t
AdjustedTimeoutMillis(const Optional<Nullable<int32_t>>& opt_aSeconds)
{
@ -173,6 +200,59 @@ EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
return ErrorCode::BAD_REQUEST;
}
static nsresult
BuildTransactionHashes(const nsCString& aRpId,
const nsCString& aClientDataJSON,
/* out */ CryptoBuffer& aRpIdHash,
/* out */ CryptoBuffer& aClientDataHash)
{
nsresult srv;
nsCOMPtr<nsICryptoHash> hashService =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
if (NS_FAILED(srv)) {
return srv;
}
if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aRpId, aRpIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
if (MOZ_LOG_TEST(gU2FLog, LogLevel::Debug)) {
nsString base64;
Unused << NS_WARN_IF(NS_FAILED(aRpIdHash.ToJwkBase64(base64)));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::RpID: %s", aRpId.get()));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Rp ID Hash (base64): %s",
NS_ConvertUTF16toUTF8(base64).get()));
Unused << NS_WARN_IF(NS_FAILED(aClientDataHash.ToJwkBase64(base64)));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Client Data JSON: %s", aClientDataJSON.get()));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Client Data Hash (base64): %s",
NS_ConvertUTF16toUTF8(base64).get()));
}
return NS_OK;
}
template<typename T, typename C>
static void
ExecuteCallback(T& aResp, Maybe<nsMainThreadPtrHandle<C>>& aCb)
@ -194,14 +274,30 @@ ExecuteCallback(T& aResp, Maybe<nsMainThreadPtrHandle<C>>& aCb)
error.SuppressException(); // Useful exceptions already emitted
}
/***********************************************************************
* U2F JavaScript API Implementation
**********************************************************************/
U2F::U2F(nsPIDOMWindowInner* aParent)
: mParent(aParent)
{
MOZ_ASSERT(NS_IsMainThread());
}
U2F::~U2F()
{
mPromiseHolder.DisconnectIfExists();
MOZ_ASSERT(NS_IsMainThread());
if (mTransaction.isSome()) {
RejectTransaction(NS_ERROR_ABORT);
}
if (mChild) {
RefPtr<U2FTransactionChild> c;
mChild.swap(c);
c->Send__delete__(c);
}
mRegisterCallback.reset();
mSignCallback.reset();
}
@ -210,7 +306,6 @@ void
U2F::Init(ErrorResult& aRv)
{
MOZ_ASSERT(mParent);
MOZ_ASSERT(!mEventTarget);
nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
MOZ_ASSERT(doc);
@ -229,9 +324,6 @@ U2F::Init(ErrorResult& aRv)
aRv.Throw(NS_ERROR_FAILURE);
return;
}
mEventTarget = doc->EventTargetFor(TaskCategory::Other);
MOZ_ASSERT(mEventTarget);
}
/* virtual */ JSObject*
@ -250,7 +342,9 @@ U2F::Register(const nsAString& aAppId,
{
MOZ_ASSERT(NS_IsMainThread());
Cancel();
if (mTransaction.isSome()) {
CancelTransaction(NS_ERROR_ABORT);
}
MOZ_ASSERT(mRegisterCallback.isNothing());
mRegisterCallback = Some(nsMainThreadPtrHandle<U2FRegisterCallback>(
@ -302,38 +396,83 @@ U2F::Register(const nsAString& aAppId,
RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
excludeList);
auto& localReqHolder = mPromiseHolder;
auto& localCb = mRegisterCallback;
RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
RefPtr<U2FPromise> p = mgr->Register(mParent, cAppId,
NS_ConvertUTF16toUTF8(clientDataJSON),
adjustedTimeoutMillis, excludeList);
p->Then(mEventTarget, "dom::U2F::Register::Promise::Resolve",
[&localCb, &localReqHolder](nsString aResponse) {
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2F::Register::Promise::Resolve, response was %s",
NS_ConvertUTF16toUTF8(aResponse).get()));
RegisterResponse response;
response.Init(aResponse);
auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
// U2F could be reentered from microtask-checkpoint while calling
// ExecuteCallback(), so we should mark Complete() earlier.
localReqHolder.Complete();
ExecuteCallback(response, localCb);
},
[&localCb, &localReqHolder](ErrorCode aErrorCode) {
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2F::Register::Promise::Reject, response was %d",
static_cast<uint32_t>(aErrorCode)));
RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
CryptoBuffer rpIdHash, clientDataHash;
if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
rpIdHash, clientDataHash))) {
RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
ExecuteCallback(response, mRegisterCallback);
return;
}
// U2F could be reentered from microtask-checkpoint while calling
// ExecuteCallback(), so we should mark Complete() earlier.
localReqHolder.Complete();
ExecuteCallback(response, localCb);
})
->Track(mPromiseHolder);
if (!MaybeCreateBackgroundActor()) {
RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
ExecuteCallback(response, mRegisterCallback);
return;
}
ListenForVisibilityEvents();
// Always blank for U2F
nsTArray<WebAuthnExtension> extensions;
WebAuthnTransactionInfo info(rpIdHash,
clientDataHash,
adjustedTimeoutMillis,
excludeList,
extensions);
MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(U2FTransaction(Move(info), clientData));
mChild->SendRequestRegister(mTransaction.ref().mId, mTransaction.ref().mInfo);
}
void
U2F::FinishRegister(const uint64_t& aTransactionId,
nsTArray<uint8_t>& aRegBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
// Check for a valid transaction.
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
return;
}
CryptoBuffer clientDataBuf;
if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
CryptoBuffer regBuf;
if (NS_WARN_IF(!regBuf.Assign(aRegBuffer))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
nsString clientDataBase64;
nsString registrationDataBase64;
nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64);
if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
// Assemble a response object to return
RegisterResponse response;
response.mVersion.Construct(kRequiredU2FVersion);
response.mClientData.Construct(clientDataBase64);
response.mRegistrationData.Construct(registrationDataBase64);
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
ExecuteCallback(response, mRegisterCallback);
ClearTransaction();
}
void
@ -346,7 +485,9 @@ U2F::Sign(const nsAString& aAppId,
{
MOZ_ASSERT(NS_IsMainThread());
Cancel();
if (mTransaction.isSome()) {
CancelTransaction(NS_ERROR_ABORT);
}
MOZ_ASSERT(mSignCallback.isNothing());
mSignCallback = Some(nsMainThreadPtrHandle<U2FSignCallback>(
@ -383,65 +524,244 @@ U2F::Sign(const nsAString& aAppId,
nsTArray<WebAuthnScopedCredentialDescriptor> permittedList;
RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
permittedList);
auto& localReqHolder = mPromiseHolder;
auto& localCb = mSignCallback;
RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
RefPtr<U2FPromise> p = mgr->Sign(mParent, cAppId,
NS_ConvertUTF16toUTF8(clientDataJSON),
adjustedTimeoutMillis, permittedList);
p->Then(mEventTarget, "dom::U2F::Sign::Promise::Resolve",
[&localCb, &localReqHolder](nsString aResponse) {
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2F::Sign::Promise::Resolve, response was %s",
NS_ConvertUTF16toUTF8(aResponse).get()));
SignResponse response;
response.Init(aResponse);
// U2F could be reentered from microtask-checkpoint while calling
// ExecuteCallback(), so we should mark Complete() earlier.
localReqHolder.Complete();
ExecuteCallback(response, localCb);
},
[&localCb, &localReqHolder](ErrorCode aErrorCode) {
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2F::Sign::Promise::Reject, response was %d",
static_cast<uint32_t>(aErrorCode)));
SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
// U2F could be reentered from microtask-checkpoint while calling
// ExecuteCallback(), so we should mark Complete() earlier.
localReqHolder.Complete();
ExecuteCallback(response, localCb);
})
->Track(mPromiseHolder);
CryptoBuffer rpIdHash, clientDataHash;
if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
rpIdHash, clientDataHash))) {
SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
ExecuteCallback(response, mSignCallback);
return;
}
if (!MaybeCreateBackgroundActor()) {
SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
ExecuteCallback(response, mSignCallback);
return;
}
ListenForVisibilityEvents();
// Always blank for U2F
nsTArray<WebAuthnExtension> extensions;
WebAuthnTransactionInfo info(rpIdHash,
clientDataHash,
adjustedTimeoutMillis,
permittedList,
extensions);
MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(U2FTransaction(Move(info), clientData));
mChild->SendRequestSign(mTransaction.ref().mId, mTransaction.ref().mInfo);
}
void
U2F::Cancel()
U2F::FinishSign(const uint64_t& aTransactionId,
nsTArray<uint8_t>& aCredentialId,
nsTArray<uint8_t>& aSigBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
const ErrorCode errorCode = ErrorCode::OTHER_ERROR;
if (mRegisterCallback.isSome()) {
RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(errorCode));
ExecuteCallback(response, mRegisterCallback);
// Check for a valid transaction.
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
return;
}
if (mSignCallback.isSome()) {
SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(errorCode));
ExecuteCallback(response, mSignCallback);
CryptoBuffer clientDataBuf;
if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
RefPtr<U2FManager> mgr = U2FManager::Get();
if (mgr) {
mgr->MaybeCancelTransaction(NS_ERROR_DOM_OPERATION_ERR);
CryptoBuffer credBuf;
if (NS_WARN_IF(!credBuf.Assign(aCredentialId))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
mPromiseHolder.DisconnectIfExists();
CryptoBuffer sigBuf;
if (NS_WARN_IF(!sigBuf.Assign(aSigBuffer))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
// Assemble a response object to return
nsString clientDataBase64;
nsString signatureDataBase64;
nsString keyHandleBase64;
nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64);
nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64);
if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
NS_WARN_IF(NS_FAILED(rvSignatureData) ||
NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
SignResponse response;
response.mKeyHandle.Construct(keyHandleBase64);
response.mClientData.Construct(clientDataBase64);
response.mSignatureData.Construct(signatureDataBase64);
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
ExecuteCallback(response, mSignCallback);
ClearTransaction();
}
void
U2F::ClearTransaction()
{
if (!NS_WARN_IF(mTransaction.isNothing())) {
StopListeningForVisibilityEvents();
}
mTransaction.reset();
}
void
U2F::RejectTransaction(const nsresult& aError)
{
if (!NS_WARN_IF(mTransaction.isNothing())) {
ErrorCode code = ConvertNSResultToErrorCode(aError);
if (mRegisterCallback.isSome()) {
RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(code));
ExecuteCallback(response, mRegisterCallback);
}
if (mSignCallback.isSome()) {
SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(code));
ExecuteCallback(response, mSignCallback);
}
}
ClearTransaction();
}
void
U2F::CancelTransaction(const nsresult& aError)
{
if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
mChild->SendRequestCancel(mTransaction.ref().mId);
}
RejectTransaction(aError);
}
void
U2F::RequestAborted(const uint64_t& aTransactionId, const nsresult& aError)
{
MOZ_ASSERT(NS_IsMainThread());
if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
RejectTransaction(aError);
}
}
/***********************************************************************
* Event Handling
**********************************************************************/
void
U2F::ListenForVisibilityEvents()
{
nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
return;
}
nsresult rv = doc->AddSystemEventListener(kVisibilityChange, this,
/* use capture */ true,
/* wants untrusted */ false);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
void
U2F::StopListeningForVisibilityEvents()
{
nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
return;
}
nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, this,
/* use capture */ true);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
NS_IMETHODIMP
U2F::HandleEvent(nsIDOMEvent* aEvent)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aEvent);
nsAutoString type;
aEvent->GetType(type);
if (!type.Equals(kVisibilityChange)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDocument> doc =
do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
if (NS_WARN_IF(!doc)) {
return NS_ERROR_FAILURE;
}
if (doc->Hidden()) {
MOZ_LOG(gU2FLog, LogLevel::Debug,
("Visibility change: U2F window is hidden, cancelling job."));
CancelTransaction(NS_ERROR_ABORT);
}
return NS_OK;
}
/***********************************************************************
* IPC Protocol Implementation
**********************************************************************/
bool
U2F::MaybeCreateBackgroundActor()
{
MOZ_ASSERT(NS_IsMainThread());
if (mChild) {
return true;
}
PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!actorChild)) {
return false;
}
RefPtr<U2FTransactionChild> mgr(new U2FTransactionChild(this));
PWebAuthnTransactionChild* constructedMgr =
actorChild->SendPWebAuthnTransactionConstructor(mgr);
if (NS_WARN_IF(!constructedMgr)) {
return false;
}
MOZ_ASSERT(constructedMgr == mgr);
mChild = mgr.forget();
return true;
}
void
U2F::ActorDestroyed()
{
MOZ_ASSERT(NS_IsMainThread());
mChild = nullptr;
}
} // namespace dom

Просмотреть файл

@ -9,6 +9,7 @@
#include "js/TypeDecls.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/PWebAuthnTransaction.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/U2FBinding.h"
@ -17,12 +18,14 @@
#include "nsProxyRelease.h"
#include "nsWrapperCache.h"
#include "U2FAuthenticator.h"
#include "nsIDOMEventListener.h"
class nsISerialEventTarget;
namespace mozilla {
namespace dom {
class U2FTransactionChild;
class U2FRegisterCallback;
class U2FSignCallback;
@ -30,11 +33,44 @@ class U2FSignCallback;
struct RegisterRequest;
struct RegisteredKey;
// The U2F Class is used by the JS engine to initiate U2F operations.
class U2F final : public nsISupports
class U2FTransaction
{
public:
U2FTransaction(const WebAuthnTransactionInfo&& aInfo,
const nsCString& aClientData)
: mInfo(aInfo)
, mClientData(aClientData)
, mId(NextId())
{
MOZ_ASSERT(mId > 0);
}
// Holds the parameters of the current transaction, as we need them both
// before the transaction request is sent, and on successful return.
WebAuthnTransactionInfo mInfo;
// Client data used to assemble reply objects.
nsCString mClientData;
// Unique transaction id.
uint64_t mId;
private:
// Generates a unique id for new transactions. This doesn't have to be unique
// forever, it's sufficient to differentiate between temporally close
// transactions, where messages can intersect. Can overflow.
static uint64_t NextId() {
static uint64_t id = 0;
return ++id;
}
};
class U2F final : public nsIDOMEventListener
, public nsWrapperCache
{
public:
NS_DECL_NSIDOMEVENTLISTENER
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
@ -68,18 +104,48 @@ public:
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
ErrorResult& aRv);
private:
void
Cancel();
FinishRegister(const uint64_t& aTransactionId, nsTArray<uint8_t>& aRegBuffer);
void
FinishSign(const uint64_t& aTransactionId,
nsTArray<uint8_t>& aCredentialId,
nsTArray<uint8_t>& aSigBuffer);
void
RequestAborted(const uint64_t& aTransactionId, const nsresult& aError);
void ActorDestroyed();
private:
~U2F();
// Visibility event handling.
void ListenForVisibilityEvents();
void StopListeningForVisibilityEvents();
// Clears all information we have about the current transaction.
void ClearTransaction();
// Rejects the current transaction and calls ClearTransaction().
void RejectTransaction(const nsresult& aError);
// Cancels the current transaction (by sending a Cancel message to the
// parent) and rejects it by calling RejectTransaction().
void CancelTransaction(const nsresult& aError);
bool MaybeCreateBackgroundActor();
nsString mOrigin;
nsCOMPtr<nsPIDOMWindowInner> mParent;
nsCOMPtr<nsISerialEventTarget> mEventTarget;
// U2F API callbacks.
Maybe<nsMainThreadPtrHandle<U2FRegisterCallback>> mRegisterCallback;
Maybe<nsMainThreadPtrHandle<U2FSignCallback>> mSignCallback;
MozPromiseRequestHolder<U2FPromise> mPromiseHolder;
~U2F();
// IPC Channel to the parent process.
RefPtr<U2FTransactionChild> mChild;
// The current transaction, if any.
Maybe<U2FTransaction> mTransaction;
};
} // namespace dom

Просмотреть файл

@ -1,490 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "hasht.h"
#include "nsICryptoHash.h"
#include "nsNetCID.h"
#include "U2FManager.h"
#include "U2FTransactionChild.h"
#include "U2FUtil.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PWebAuthnTransaction.h"
#include "mozilla/dom/WebCryptoCommon.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/BackgroundChild.h"
using namespace mozilla::ipc;
namespace mozilla {
namespace dom {
/***********************************************************************
* Statics
**********************************************************************/
namespace {
StaticRefPtr<U2FManager> gU2FManager;
static mozilla::LazyLogModule gU2FManagerLog("u2fmanager");
}
NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange");
NS_IMPL_ISUPPORTS(U2FManager, nsIDOMEventListener);
/***********************************************************************
* Utility Functions
**********************************************************************/
static void
ListenForVisibilityEvents(nsPIDOMWindowInner* aParent,
U2FManager* aListener)
{
MOZ_ASSERT(aParent);
MOZ_ASSERT(aListener);
nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
return;
}
nsresult rv = doc->AddSystemEventListener(kVisibilityChange, aListener,
/* use capture */ true,
/* wants untrusted */ false);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
static void
StopListeningForVisibilityEvents(nsPIDOMWindowInner* aParent,
U2FManager* aListener)
{
MOZ_ASSERT(aParent);
MOZ_ASSERT(aListener);
nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
return;
}
nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, aListener,
/* use capture */ true);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
static ErrorCode
ConvertNSResultToErrorCode(const nsresult& aError)
{
if (aError == NS_ERROR_DOM_TIMEOUT_ERR) {
return ErrorCode::TIMEOUT;
}
/* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */
if (aError == NS_ERROR_DOM_NOT_ALLOWED_ERR) {
return ErrorCode::DEVICE_INELIGIBLE;
}
return ErrorCode::OTHER_ERROR;
}
/***********************************************************************
* U2FManager Implementation
**********************************************************************/
U2FManager::U2FManager()
{
MOZ_ASSERT(NS_IsMainThread());
}
void
U2FManager::ClearTransaction()
{
if (!NS_WARN_IF(mTransaction.isNothing())) {
StopListeningForVisibilityEvents(mTransaction.ref().mParent, this);
}
mTransaction.reset();
}
void
U2FManager::RejectTransaction(const nsresult& aError)
{
if (!NS_WARN_IF(mTransaction.isNothing())) {
ErrorCode code = ConvertNSResultToErrorCode(aError);
mTransaction.ref().mPromise.Reject(code, __func__);
}
ClearTransaction();
}
void
U2FManager::CancelTransaction(const nsresult& aError)
{
if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
mChild->SendRequestCancel(mTransaction.ref().mId);
}
RejectTransaction(aError);
}
U2FManager::~U2FManager()
{
MOZ_ASSERT(NS_IsMainThread());
if (mTransaction.isSome()) {
RejectTransaction(NS_ERROR_ABORT);
}
if (mChild) {
RefPtr<U2FTransactionChild> c;
mChild.swap(c);
c->Send__delete__(c);
}
}
bool
U2FManager::MaybeCreateBackgroundActor()
{
MOZ_ASSERT(NS_IsMainThread());
if (mChild) {
return true;
}
PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!actorChild)) {
return false;
}
RefPtr<U2FTransactionChild> mgr(new U2FTransactionChild());
PWebAuthnTransactionChild* constructedMgr =
actorChild->SendPWebAuthnTransactionConstructor(mgr);
if (NS_WARN_IF(!constructedMgr)) {
return false;
}
MOZ_ASSERT(constructedMgr == mgr);
mChild = mgr.forget();
return true;
}
//static
U2FManager*
U2FManager::GetOrCreate()
{
MOZ_ASSERT(NS_IsMainThread());
if (gU2FManager) {
return gU2FManager;
}
gU2FManager = new U2FManager();
ClearOnShutdown(&gU2FManager);
return gU2FManager;
}
//static
U2FManager*
U2FManager::Get()
{
MOZ_ASSERT(NS_IsMainThread());
return gU2FManager;
}
//static
nsresult
U2FManager::BuildTransactionHashes(const nsCString& aRpId,
const nsCString& aClientDataJSON,
/* out */ CryptoBuffer& aRpIdHash,
/* out */ CryptoBuffer& aClientDataHash)
{
nsresult srv;
nsCOMPtr<nsICryptoHash> hashService =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
if (NS_FAILED(srv)) {
return srv;
}
if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aRpId, aRpIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
if (MOZ_LOG_TEST(gU2FLog, LogLevel::Debug)) {
nsString base64;
Unused << NS_WARN_IF(NS_FAILED(aRpIdHash.ToJwkBase64(base64)));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::RpID: %s", aRpId.get()));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Rp ID Hash (base64): %s",
NS_ConvertUTF16toUTF8(base64).get()));
Unused << NS_WARN_IF(NS_FAILED(aClientDataHash.ToJwkBase64(base64)));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Client Data JSON: %s", aClientDataJSON.get()));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Client Data Hash (base64): %s",
NS_ConvertUTF16toUTF8(base64).get()));
}
return NS_OK;
}
already_AddRefed<U2FPromise>
U2FManager::Register(nsPIDOMWindowInner* aParent, const nsCString& aRpId,
const nsCString& aClientDataJSON,
const uint32_t& aTimeoutMillis,
const nsTArray<WebAuthnScopedCredentialDescriptor>& aExcludeList)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aParent);
if (mTransaction.isSome()) {
CancelTransaction(NS_ERROR_ABORT);
}
CryptoBuffer rpIdHash, clientDataHash;
if (NS_FAILED(BuildTransactionHashes(aRpId, aClientDataJSON,
rpIdHash, clientDataHash))) {
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
}
if (!MaybeCreateBackgroundActor()) {
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
}
ListenForVisibilityEvents(aParent, this);
// Always blank for U2F
nsTArray<WebAuthnExtension> extensions;
WebAuthnTransactionInfo info(rpIdHash,
clientDataHash,
aTimeoutMillis,
aExcludeList,
extensions);
MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(U2FTransaction(aParent, Move(info), aClientDataJSON));
mChild->SendRequestRegister(mTransaction.ref().mId, mTransaction.ref().mInfo);
return mTransaction.ref().mPromise.Ensure(__func__);
}
already_AddRefed<U2FPromise>
U2FManager::Sign(nsPIDOMWindowInner* aParent,
const nsCString& aRpId,
const nsCString& aClientDataJSON,
const uint32_t& aTimeoutMillis,
const nsTArray<WebAuthnScopedCredentialDescriptor>& aAllowList)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aParent);
if (mTransaction.isSome()) {
CancelTransaction(NS_ERROR_ABORT);
}
CryptoBuffer rpIdHash, clientDataHash;
if (NS_FAILED(BuildTransactionHashes(aRpId, aClientDataJSON,
rpIdHash, clientDataHash))) {
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
}
if (!MaybeCreateBackgroundActor()) {
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
}
ListenForVisibilityEvents(aParent, this);
// Always blank for U2F
nsTArray<WebAuthnExtension> extensions;
WebAuthnTransactionInfo info(rpIdHash,
clientDataHash,
aTimeoutMillis,
aAllowList,
extensions);
MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(U2FTransaction(aParent, Move(info), aClientDataJSON));
mChild->SendRequestSign(mTransaction.ref().mId, mTransaction.ref().mInfo);
return mTransaction.ref().mPromise.Ensure(__func__);
}
void
U2FManager::FinishRegister(const uint64_t& aTransactionId,
nsTArray<uint8_t>& aRegBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
// Check for a valid transaction.
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
return;
}
CryptoBuffer clientDataBuf;
if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
CryptoBuffer regBuf;
if (NS_WARN_IF(!regBuf.Assign(aRegBuffer))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
nsString clientDataBase64;
nsString registrationDataBase64;
nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64);
if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
// Assemble a response object to return
RegisterResponse response;
response.mVersion.Construct(kRequiredU2FVersion);
response.mClientData.Construct(clientDataBase64);
response.mRegistrationData.Construct(registrationDataBase64);
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
nsString responseStr;
if (NS_WARN_IF(!response.ToJSON(responseStr))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
mTransaction.ref().mPromise.Resolve(responseStr, __func__);
ClearTransaction();
}
void
U2FManager::FinishSign(const uint64_t& aTransactionId,
nsTArray<uint8_t>& aCredentialId,
nsTArray<uint8_t>& aSigBuffer)
{
MOZ_ASSERT(NS_IsMainThread());
// Check for a valid transaction.
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
return;
}
CryptoBuffer clientDataBuf;
if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
CryptoBuffer credBuf;
if (NS_WARN_IF(!credBuf.Assign(aCredentialId))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
CryptoBuffer sigBuf;
if (NS_WARN_IF(!sigBuf.Assign(aSigBuffer))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
// Assemble a response object to return
nsString clientDataBase64;
nsString signatureDataBase64;
nsString keyHandleBase64;
nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64);
nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64);
if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
NS_WARN_IF(NS_FAILED(rvSignatureData) ||
NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
SignResponse response;
response.mKeyHandle.Construct(keyHandleBase64);
response.mClientData.Construct(clientDataBase64);
response.mSignatureData.Construct(signatureDataBase64);
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
nsString responseStr;
if (NS_WARN_IF(!response.ToJSON(responseStr))) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
mTransaction.ref().mPromise.Resolve(responseStr, __func__);
ClearTransaction();
}
void
U2FManager::RequestAborted(const uint64_t& aTransactionId,
const nsresult& aError)
{
MOZ_ASSERT(NS_IsMainThread());
if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
RejectTransaction(aError);
}
}
NS_IMETHODIMP
U2FManager::HandleEvent(nsIDOMEvent* aEvent)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aEvent);
nsAutoString type;
aEvent->GetType(type);
if (!type.Equals(kVisibilityChange)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDocument> doc =
do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
if (NS_WARN_IF(!doc)) {
return NS_ERROR_FAILURE;
}
if (doc->Hidden()) {
MOZ_LOG(gU2FManagerLog, LogLevel::Debug,
("Visibility change: U2F window is hidden, cancelling job."));
CancelTransaction(NS_ERROR_ABORT);
}
return NS_OK;
}
void
U2FManager::ActorDestroyed()
{
MOZ_ASSERT(NS_IsMainThread());
mChild = nullptr;
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -1,161 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_U2FManager_h
#define mozilla_dom_U2FManager_h
#include "U2FAuthenticator.h"
#include "mozilla/MozPromise.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/PWebAuthnTransaction.h"
#include "nsIDOMEventListener.h"
/*
* Content process manager for the U2F protocol. Created on calls to the
* U2F DOM object, this manager handles establishing IPC channels
* for U2F transactions, as well as keeping track of MozPromise objects
* representing transactions in flight.
*
* The U2F spec (http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915.zip)
* allows for two different types of transactions: registration and signing.
* When either of these is requested via the DOM API, the following steps are
* executed in the U2FManager:
*
* - Validation of the request. Return a failed promise to the caller if request
* does not have correct parameters.
*
* - If request is valid, open a new IPC channel for running the transaction. If
* another transaction is already running in this content process, cancel it.
* Return a pending promise to the caller.
*
* - Send transaction information to parent process (by running the Start*
* functions of U2FManager). Assuming another transaction is currently in
* flight in another content process, parent will handle canceling it.
*
* - On return of successful transaction information from parent process, turn
* information into DOM object format required by spec, and resolve promise
* (by running the Finish* functions of U2FManager). On cancellation request
* from parent, reject promise with corresponding error code. Either
* outcome will also close the IPC channel.
*
*/
namespace mozilla {
namespace dom {
class ArrayBufferViewOrArrayBuffer;
class OwningArrayBufferViewOrArrayBuffer;
class Promise;
class U2FTransactionChild;
class U2FTransactionInfo;
class U2FTransaction
{
public:
U2FTransaction(nsPIDOMWindowInner* aParent,
const WebAuthnTransactionInfo&& aInfo,
const nsCString& aClientData)
: mParent(aParent)
, mInfo(aInfo)
, mClientData(aClientData)
, mId(NextId())
{
MOZ_ASSERT(mId > 0);
}
// Parent of the context we're running the transaction in.
nsCOMPtr<nsPIDOMWindowInner> mParent;
// JS Promise representing the transaction status.
MozPromiseHolder<U2FPromise> mPromise;
// Holds the parameters of the current transaction, as we need them both
// before the transaction request is sent, and on successful return.
WebAuthnTransactionInfo mInfo;
// Client data used to assemble reply objects.
nsCString mClientData;
// Unique transaction id.
uint64_t mId;
private:
// Generates a unique id for new transactions. This doesn't have to be unique
// forever, it's sufficient to differentiate between temporally close
// transactions, where messages can intersect. Can overflow.
static uint64_t NextId() {
static uint64_t id = 0;
return ++id;
}
};
class U2FManager final : public nsIDOMEventListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
static U2FManager* GetOrCreate();
static U2FManager* Get();
already_AddRefed<U2FPromise> Register(nsPIDOMWindowInner* aParent,
const nsCString& aRpId,
const nsCString& aClientDataJSON,
const uint32_t& aTimeoutMillis,
const nsTArray<WebAuthnScopedCredentialDescriptor>& aExcludeList);
already_AddRefed<U2FPromise> Sign(nsPIDOMWindowInner* aParent,
const nsCString& aRpId,
const nsCString& aClientDataJSON,
const uint32_t& aTimeoutMillis,
const nsTArray<WebAuthnScopedCredentialDescriptor>& aKeyList);
void FinishRegister(const uint64_t& aTransactionId,
nsTArray<uint8_t>& aRegBuffer);
void FinishSign(const uint64_t& aTransactionId,
nsTArray<uint8_t>& aCredentialId,
nsTArray<uint8_t>& aSigBuffer);
void RequestAborted(const uint64_t& aTransactionId, const nsresult& aError);
// XXX This is exposed only until we fix bug 1410346.
void MaybeCancelTransaction(const nsresult& aError) {
if (mTransaction.isSome()) {
CancelTransaction(NS_ERROR_ABORT);
}
}
void ActorDestroyed();
private:
U2FManager();
virtual ~U2FManager();
static nsresult
BuildTransactionHashes(const nsCString& aRpId,
const nsCString& aClientDataJSON,
/* out */ CryptoBuffer& aRpIdHash,
/* out */ CryptoBuffer& aClientDataHash);
// Clears all information we have about the current transaction.
void ClearTransaction();
// Rejects the current transaction and calls ClearTransaction().
void RejectTransaction(const nsresult& aError);
// Cancels the current transaction (by sending a Cancel message to the
// parent) and rejects it by calling RejectTransaction().
void CancelTransaction(const nsresult& aError);
bool MaybeCreateBackgroundActor();
// IPC Channel to the parent process.
RefPtr<U2FTransactionChild> mChild;
// The current transaction, if any.
Maybe<U2FTransaction> mTransaction;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_U2FManager_h

Просмотреть файл

@ -13,9 +13,7 @@ mozilla::ipc::IPCResult
U2FTransactionChild::RecvConfirmRegister(const uint64_t& aTransactionId,
nsTArray<uint8_t>&& aRegBuffer)
{
RefPtr<U2FManager> mgr = U2FManager::Get();
MOZ_ASSERT(mgr);
mgr->FinishRegister(aTransactionId, aRegBuffer);
mU2F->FinishRegister(aTransactionId, aRegBuffer);
return IPC_OK();
}
@ -24,9 +22,7 @@ U2FTransactionChild::RecvConfirmSign(const uint64_t& aTransactionId,
nsTArray<uint8_t>&& aCredentialId,
nsTArray<uint8_t>&& aBuffer)
{
RefPtr<U2FManager> mgr = U2FManager::Get();
MOZ_ASSERT(mgr);
mgr->FinishSign(aTransactionId, aCredentialId, aBuffer);
mU2F->FinishSign(aTransactionId, aCredentialId, aBuffer);
return IPC_OK();
}
@ -34,20 +30,14 @@ mozilla::ipc::IPCResult
U2FTransactionChild::RecvAbort(const uint64_t& aTransactionId,
const nsresult& aError)
{
RefPtr<U2FManager> mgr = U2FManager::Get();
MOZ_ASSERT(mgr);
mgr->RequestAborted(aTransactionId, aError);
mU2F->RequestAborted(aTransactionId, aError);
return IPC_OK();
}
void
U2FTransactionChild::ActorDestroy(ActorDestroyReason why)
{
RefPtr<U2FManager> mgr = U2FManager::Get();
// This could happen after the U2FManager has been shut down.
if (mgr) {
mgr->ActorDestroyed();
}
mU2F->ActorDestroyed();
}
} // namespace dom

Просмотреть файл

@ -21,6 +21,10 @@ namespace dom {
class U2FTransactionChild final : public WebAuthnTransactionChildBase
{
public:
U2FTransactionChild(U2F* aU2F) : mU2F(aU2F) {
MOZ_ASSERT(mU2F);
}
mozilla::ipc::IPCResult
RecvConfirmRegister(const uint64_t& aTransactionId,
nsTArray<uint8_t>&& aRegBuffer) override;
@ -34,6 +38,10 @@ public:
RecvAbort(const uint64_t& aTransactionId, const nsresult& aError) override;
void ActorDestroy(ActorDestroyReason why) override;
private:
// ~U2F() will destroy child actors.
U2F* mU2F;
};
}

Просмотреть файл

@ -15,7 +15,6 @@ EXPORTS.mozilla.dom += [
UNIFIED_SOURCES += [
'U2F.cpp',
'U2FManager.cpp',
'U2FTransactionChild.cpp',
'U2FTransactionParent.cpp',
]