зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0294a21add
Коммит
1712f2c336
486
dom/u2f/U2F.cpp
486
dom/u2f/U2F.cpp
|
@ -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',
|
||||
]
|
||||
|
|
Загрузка…
Ссылка в новой задаче