mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-04 07:40:42 +00:00
Bug 1813982 - XPCOM interface to authenticator-rs. r=keeler,geckoview-reviewers,m_kato
Differential Revision: https://phabricator.services.mozilla.com/D171269
This commit is contained in:
parent
c53db6818d
commit
f40aea4a99
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -405,6 +405,20 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "authrs_bridge"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"authenticator",
|
||||
"log",
|
||||
"moz_task",
|
||||
"nserror",
|
||||
"nsstring",
|
||||
"serde_cbor",
|
||||
"thin-vec",
|
||||
"xpcom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@ -2195,7 +2209,7 @@ dependencies = [
|
||||
"audioipc-server",
|
||||
"audioipc2-client",
|
||||
"audioipc2-server",
|
||||
"authenticator",
|
||||
"authrs_bridge",
|
||||
"binary_http",
|
||||
"bitsdownload",
|
||||
"bookmark_sync",
|
||||
|
@ -7550,7 +7550,6 @@ var WebAuthnPromptHelper = {
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic, aData) {
|
||||
let mgr = aSubject.QueryInterface(Ci.nsIU2FTokenManager);
|
||||
let data = JSON.parse(aData);
|
||||
|
||||
// If we receive a cancel, it might be a WebAuthn prompt starting in another
|
||||
@ -7558,6 +7557,7 @@ var WebAuthnPromptHelper = {
|
||||
// cancellations, so any cancel action we get should prompt us to cancel.
|
||||
if (data.action == "cancel") {
|
||||
this.cancel(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
@ -7567,6 +7567,10 @@ var WebAuthnPromptHelper = {
|
||||
return;
|
||||
}
|
||||
|
||||
let mgr = aSubject.QueryInterface(
|
||||
data.is_ctap2 ? Ci.nsIWebAuthnController : Ci.nsIU2FTokenManager
|
||||
);
|
||||
|
||||
if (data.action == "register") {
|
||||
this.register(mgr, data);
|
||||
} else if (data.action == "register-direct") {
|
||||
@ -7631,7 +7635,7 @@ var WebAuthnPromptHelper = {
|
||||
label: unescape(decodeURIComponent(usernames[i])),
|
||||
accessKey: i.toString(),
|
||||
callback(aState) {
|
||||
mgr.resumeWithSelectedSignResult(tid, i);
|
||||
mgr.signatureSelectionCallback(tid, i);
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -7657,7 +7661,7 @@ var WebAuthnPromptHelper = {
|
||||
aPassword
|
||||
);
|
||||
if (res) {
|
||||
mgr.pinCallback(aPassword.value);
|
||||
mgr.pinCallback(tid, aPassword.value);
|
||||
} else {
|
||||
mgr.cancel(tid);
|
||||
}
|
||||
|
@ -88,8 +88,7 @@ void AndroidWebAuthnTokenManager::Drop() {
|
||||
}
|
||||
|
||||
RefPtr<U2FRegisterPromise> AndroidWebAuthnTokenManager::Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void _status_callback(rust_ctap2_status_update_res*)) {
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
ClearPromises();
|
||||
@ -276,8 +275,7 @@ void AndroidWebAuthnTokenManager::HandleRegisterResult(
|
||||
}
|
||||
|
||||
RefPtr<U2FSignPromise> AndroidWebAuthnTokenManager::Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void _status_callback(rust_ctap2_status_update_res*)) {
|
||||
const WebAuthnGetAssertionInfo& aInfo) {
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
ClearPromises();
|
||||
|
@ -109,12 +109,11 @@ class AndroidWebAuthnTokenManager final : public U2FTokenTransport {
|
||||
~AndroidWebAuthnTokenManager() {}
|
||||
|
||||
virtual RefPtr<U2FRegisterPromise> Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void status_callback(rust_ctap2_status_update_res*)) override;
|
||||
const WebAuthnMakeCredentialInfo& aInfo,
|
||||
bool aForceNoneAttestation) override;
|
||||
|
||||
virtual RefPtr<U2FSignPromise> Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void status_callback(rust_ctap2_status_update_res*)) override;
|
||||
const WebAuthnGetAssertionInfo& aInfo) override;
|
||||
|
||||
void Cancel() override;
|
||||
|
||||
|
29
dom/webauthn/AuthrsTransport.cpp
Normal file
29
dom/webauthn/AuthrsTransport.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
/* 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 "AuthrsTransport.h"
|
||||
#include "nsIWebAuthnController.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
namespace {
|
||||
extern "C" {
|
||||
|
||||
// Implemented in Rust
|
||||
nsresult authrs_transport_constructor(nsIWebAuthnTransport** result);
|
||||
|
||||
} // extern "C"
|
||||
} // namespace
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
already_AddRefed<nsIWebAuthnTransport> NewAuthrsTransport() {
|
||||
nsCOMPtr<nsIWebAuthnTransport> transport;
|
||||
nsresult rv = authrs_transport_constructor(getter_AddRefs(transport));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return nullptr;
|
||||
}
|
||||
return transport.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
17
dom/webauthn/AuthrsTransport.h
Normal file
17
dom/webauthn/AuthrsTransport.h
Normal file
@ -0,0 +1,17 @@
|
||||
/* 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 DOM_WEBAUTHN_AUTHRS_BRIDGE_H_
|
||||
#define DOM_WEBAUTHN_AUTHRS_BRIDGE_H_
|
||||
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "nsIWebAuthnController.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
already_AddRefed<nsIWebAuthnTransport> NewAuthrsTransport();
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_WEBAUTHN_AUTHRS_BRIDGE_H_
|
@ -1,662 +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 "WebAuthnCoseIdentifiers.h"
|
||||
#include "WebAuthnEnumStrings.h"
|
||||
#include "mozilla/dom/CTAPHIDTokenManager.h"
|
||||
#include "mozilla/dom/U2FHIDTokenManager.h"
|
||||
#include "mozilla/dom/WebAuthnUtil.h"
|
||||
#include "mozilla/dom/WebAuthnCBORUtil.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include <iostream>
|
||||
namespace mozilla::dom {
|
||||
|
||||
static StaticMutex gCTAPMutex;
|
||||
static CTAPHIDTokenManager* gCTAPInstance;
|
||||
static nsIThread* gPCTAPBackgroundThread;
|
||||
|
||||
static void ctap1_register_callback(uint64_t aTransactionId,
|
||||
rust_u2f_result* aResult) {
|
||||
UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult);
|
||||
|
||||
StaticMutexAutoLock lock(gCTAPMutex);
|
||||
if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>(
|
||||
"CTAPHIDTokenManager::HandleRegisterResult", gCTAPInstance,
|
||||
&CTAPHIDTokenManager::HandleRegisterResult, std::move(rv)));
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
|
||||
}
|
||||
|
||||
static void ctap2_register_callback(uint64_t aTransactionId,
|
||||
rust_ctap2_register_result* aResult) {
|
||||
UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult);
|
||||
|
||||
StaticMutexAutoLock lock(gCTAPMutex);
|
||||
if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>(
|
||||
"CTAPHIDTokenManager::HandleRegisterResult", gCTAPInstance,
|
||||
&CTAPHIDTokenManager::HandleRegisterResult, std::move(rv)));
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
|
||||
}
|
||||
|
||||
static void ctap1_sign_callback(uint64_t aTransactionId,
|
||||
rust_u2f_result* aResult) {
|
||||
UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult);
|
||||
|
||||
StaticMutexAutoLock lock(gCTAPMutex);
|
||||
if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>(
|
||||
"CTAPHIDTokenManager::HandleSignResult", gCTAPInstance,
|
||||
&CTAPHIDTokenManager::HandleSignResult, std::move(rv)));
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
|
||||
}
|
||||
|
||||
static void ctap2_sign_callback(uint64_t aTransactionId,
|
||||
rust_ctap2_sign_result* aResult) {
|
||||
UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult);
|
||||
|
||||
StaticMutexAutoLock lock(gCTAPMutex);
|
||||
if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>(
|
||||
"CTAPHIDTokenManager::HandleSignResult", gCTAPInstance,
|
||||
&CTAPHIDTokenManager::HandleSignResult, std::move(rv)));
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
|
||||
}
|
||||
|
||||
CTAPHIDTokenManager::CTAPHIDTokenManager() {
|
||||
StaticMutexAutoLock lock(gCTAPMutex);
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(!gCTAPInstance);
|
||||
|
||||
mCTAPManager = rust_ctap2_mgr_new();
|
||||
gPCTAPBackgroundThread = NS_GetCurrentThread();
|
||||
MOZ_ASSERT(gPCTAPBackgroundThread, "This should never be null!");
|
||||
gCTAPInstance = this;
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::Drop() {
|
||||
{
|
||||
StaticMutexAutoLock lock(gCTAPMutex);
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
|
||||
gCTAPInstance = nullptr;
|
||||
}
|
||||
|
||||
// Release gCTAPMutex before we call CTAPManager::drop(). It will wait
|
||||
// for the work queue thread to join, and that requires the
|
||||
// u2f_{register,sign}_callback to lock and return.
|
||||
rust_ctap2_mgr_free(mCTAPManager);
|
||||
mCTAPManager = nullptr;
|
||||
|
||||
// Reset transaction ID so that queued runnables exit early.
|
||||
mTransaction.reset();
|
||||
}
|
||||
|
||||
// A CTAP Register operation causes a new key pair to be generated by the token.
|
||||
// The token then returns the public key of the key pair, and a handle to the
|
||||
// private key, which is a fancy way of saying "key wrapped private key", as
|
||||
// well as the generated attestation certificate and a signature using that
|
||||
// certificate's private key.
|
||||
// Requests can be either CTAP1 or CTAP2, those will be packaged differently
|
||||
// and handed over to the Rust lib.
|
||||
RefPtr<U2FRegisterPromise> CTAPHIDTokenManager::Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void status_callback(rust_ctap2_status_update_res*)) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
uint64_t registerFlags = 0;
|
||||
bool is_ctap2_request = false;
|
||||
const uint8_t* user_id = nullptr;
|
||||
size_t user_id_len = 0;
|
||||
nsCString user_name;
|
||||
|
||||
if (aInfo.Extra().isSome()) {
|
||||
const auto& extra = aInfo.Extra().ref();
|
||||
const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection();
|
||||
|
||||
bool requireUserVerification =
|
||||
sel.userVerificationRequirement().EqualsLiteral(
|
||||
MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED);
|
||||
|
||||
bool requirePlatformAttachment = false;
|
||||
if (sel.authenticatorAttachment().isSome()) {
|
||||
const nsString& authenticatorAttachment =
|
||||
sel.authenticatorAttachment().value();
|
||||
if (authenticatorAttachment.EqualsLiteral(
|
||||
MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
|
||||
requirePlatformAttachment = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set flags for credential creation.
|
||||
if (sel.requireResidentKey()) {
|
||||
registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY;
|
||||
}
|
||||
if (requireUserVerification) {
|
||||
registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
|
||||
}
|
||||
if (requirePlatformAttachment) {
|
||||
registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT;
|
||||
}
|
||||
|
||||
nsTArray<CoseAlg> coseAlgos;
|
||||
for (const auto& coseAlg : extra.coseAlgs()) {
|
||||
switch (static_cast<CoseAlgorithmIdentifier>(coseAlg.alg())) {
|
||||
case CoseAlgorithmIdentifier::ES256:
|
||||
coseAlgos.AppendElement(coseAlg);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only if no algorithms were specified, default to the only CTAP 1 / U2F
|
||||
// protocol-supported algorithm. Ultimately this logic must move into
|
||||
// u2f-hid-rs in a fashion that doesn't break the tests.
|
||||
if (extra.coseAlgs().IsEmpty()) {
|
||||
coseAlgos.AppendElement(
|
||||
static_cast<int32_t>(CoseAlgorithmIdentifier::ES256));
|
||||
}
|
||||
|
||||
// If there are no acceptable/supported algorithms, reject the promise.
|
||||
if (coseAlgos.IsEmpty()) {
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
|
||||
__func__);
|
||||
}
|
||||
|
||||
user_id_len = extra.User().Id().Length();
|
||||
user_id = extra.User().Id().Elements();
|
||||
user_name = NS_ConvertUTF16toUTF8(extra.User().DisplayName());
|
||||
is_ctap2_request = true;
|
||||
}
|
||||
|
||||
CryptoBuffer rpIdHash, clientDataHash;
|
||||
NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
|
||||
nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
|
||||
clientDataHash);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
__func__);
|
||||
}
|
||||
|
||||
ClearPromises();
|
||||
mTransaction.reset();
|
||||
|
||||
const int32_t pub_cred_params = (int32_t)
|
||||
CoseAlgorithmIdentifier::ES256; // Currently the only supported one
|
||||
uint64_t tid;
|
||||
if (is_ctap2_request) {
|
||||
AuthenticatorArgsUser user = {user_id, user_id_len, user_name.get()};
|
||||
AuthenticatorArgsPubCred pub_cred = {&pub_cred_params, 1};
|
||||
AuthenticatorArgsChallenge challenge = {aInfo.Challenge().Elements(),
|
||||
aInfo.Challenge().Length()};
|
||||
AuthenticatorArgsOptions options = {
|
||||
static_cast<bool>(registerFlags & U2F_FLAG_REQUIRE_RESIDENT_KEY),
|
||||
static_cast<bool>(registerFlags & U2F_FLAG_REQUIRE_USER_VERIFICATION),
|
||||
true, // user presence
|
||||
aForceNoneAttestation};
|
||||
tid = rust_ctap2_mgr_register(
|
||||
mCTAPManager, (uint64_t)aInfo.TimeoutMS(), ctap2_register_callback,
|
||||
status_callback, challenge, rpId.get(),
|
||||
NS_ConvertUTF16toUTF8(aInfo.Origin()).get(), user, pub_cred,
|
||||
Ctap2PubKeyCredentialDescriptor(aInfo.ExcludeList()).Get(), options,
|
||||
nullptr);
|
||||
} else {
|
||||
tid = rust_u2f_mgr_register(
|
||||
mCTAPManager, registerFlags, (uint64_t)aInfo.TimeoutMS(),
|
||||
ctap1_register_callback, clientDataHash.Elements(),
|
||||
clientDataHash.Length(), rpIdHash.Elements(), rpIdHash.Length(),
|
||||
U2FKeyHandles(aInfo.ExcludeList()).Get());
|
||||
}
|
||||
if (tid == 0) {
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
__func__);
|
||||
}
|
||||
|
||||
mTransaction = Some(Transaction(
|
||||
tid, rpIdHash, Nothing(), aInfo.ClientDataJSON(), aForceNoneAttestation));
|
||||
|
||||
return mRegisterPromise.Ensure(__func__);
|
||||
}
|
||||
|
||||
// Signing into a webpage. Again, depending on if the request is CTAP1 or
|
||||
// CTAP2, it will be packaged differently and passed to the Rust lib.
|
||||
RefPtr<U2FSignPromise> CTAPHIDTokenManager::Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void status_callback(rust_ctap2_status_update_res*)) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
bool is_ctap2_request = false;
|
||||
CryptoBuffer rpIdHash, clientDataHash;
|
||||
NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
|
||||
nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
|
||||
clientDataHash);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
}
|
||||
|
||||
uint64_t signFlags = 0;
|
||||
nsTArray<nsTArray<uint8_t>> appIds;
|
||||
appIds.AppendElement(rpIdHash.InfallibleClone());
|
||||
|
||||
Maybe<nsTArray<uint8_t>> appIdHashExt = Nothing();
|
||||
nsCString appId;
|
||||
|
||||
if (aInfo.Extra().isSome()) {
|
||||
const auto& extra = aInfo.Extra().ref();
|
||||
|
||||
// Set flags for credential requests.
|
||||
if (extra.userVerificationRequirement().EqualsLiteral(
|
||||
MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
|
||||
signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
|
||||
}
|
||||
|
||||
// Process extensions.
|
||||
for (const WebAuthnExtension& ext : extra.Extensions()) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
|
||||
appId = NS_ConvertUTF16toUTF8(
|
||||
ext.get_WebAuthnExtensionAppId().appIdentifier());
|
||||
appIdHashExt = Some(ext.get_WebAuthnExtensionAppId().AppId().Clone());
|
||||
appIds.AppendElement(appIdHashExt->Clone());
|
||||
}
|
||||
}
|
||||
|
||||
is_ctap2_request = true;
|
||||
}
|
||||
|
||||
ClearPromises();
|
||||
mTransaction.reset();
|
||||
uint64_t tid;
|
||||
if (is_ctap2_request) {
|
||||
AuthenticatorArgsChallenge challenge = {aInfo.Challenge().Elements(),
|
||||
aInfo.Challenge().Length()};
|
||||
AuthenticatorArgsOptions options = {
|
||||
false, // resident key, not used when signing
|
||||
static_cast<bool>(signFlags & U2F_FLAG_REQUIRE_USER_VERIFICATION),
|
||||
true, // user presence
|
||||
};
|
||||
tid = rust_ctap2_mgr_sign(
|
||||
mCTAPManager, (uint64_t)aInfo.TimeoutMS(), ctap2_sign_callback,
|
||||
status_callback, challenge, rpId.get(), appId.get(),
|
||||
NS_ConvertUTF16toUTF8(aInfo.Origin()).get(),
|
||||
Ctap2PubKeyCredentialDescriptor(aInfo.AllowList()).Get(), options,
|
||||
nullptr);
|
||||
} else {
|
||||
tid = rust_u2f_mgr_sign(
|
||||
mCTAPManager, signFlags, (uint64_t)aInfo.TimeoutMS(),
|
||||
ctap1_sign_callback, clientDataHash.Elements(), clientDataHash.Length(),
|
||||
U2FAppIds(appIds).Get(), U2FKeyHandles(aInfo.AllowList()).Get());
|
||||
}
|
||||
if (tid == 0) {
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
}
|
||||
|
||||
mTransaction = Some(Transaction(tid, std::move(rpIdHash), appIdHashExt,
|
||||
aInfo.ClientDataJSON()));
|
||||
|
||||
return mSignPromise.Ensure(__func__);
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::Cancel() {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
ClearPromises();
|
||||
rust_u2f_mgr_cancel(mCTAPManager);
|
||||
mTransaction.reset();
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::HandleRegisterResult(
|
||||
UniquePtr<CTAPResult>&& aResult) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (mTransaction.isNothing() ||
|
||||
aResult->GetTransactionId() != mTransaction.ref().mId) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mRegisterPromise.IsEmpty());
|
||||
|
||||
if (aResult->IsError()) {
|
||||
mRegisterPromise.Reject(aResult->GetError(), __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aResult->IsCtap2()) {
|
||||
HandleRegisterResultCtap2(std::move(aResult));
|
||||
} else {
|
||||
HandleRegisterResultCtap1(std::move(aResult));
|
||||
}
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::HandleRegisterResultCtap1(
|
||||
UniquePtr<CTAPResult>&& aResult) {
|
||||
CryptoBuffer regData;
|
||||
CryptoBuffer pubKeyBuf;
|
||||
CryptoBuffer keyHandle;
|
||||
CryptoBuffer attestationCertBuf;
|
||||
CryptoBuffer signatureBuf;
|
||||
|
||||
nsTArray<uint8_t> registration;
|
||||
if (!aResult->CopyRegistration(registration)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
// Decompose the U2F registration packet
|
||||
|
||||
regData.Assign(registration);
|
||||
|
||||
// Only handles attestation cert chains of length=1.
|
||||
nsresult rv = U2FDecomposeRegistrationResponse(
|
||||
regData, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoBuffer rpIdHashBuf;
|
||||
if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoBuffer attObj;
|
||||
rv = AssembleAttestationObject(
|
||||
rpIdHashBuf, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf,
|
||||
mTransaction.ref().mForceNoneAttestation, attObj);
|
||||
if (NS_FAILED(rv)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON,
|
||||
attObj, keyHandle, regData, extensions);
|
||||
mRegisterPromise.Resolve(std::move(result), __func__);
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::HandleRegisterResultCtap2(
|
||||
UniquePtr<CTAPResult>&& aResult) {
|
||||
CryptoBuffer attObj;
|
||||
|
||||
nsTArray<uint8_t> attestation;
|
||||
if (!aResult->Ctap2CopyAttestation(attestation)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!attObj.Assign(attestation)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> credentialId;
|
||||
if (!aResult->Ctap2CopyCredentialId(credentialId)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoBuffer keyHandle;
|
||||
if (!keyHandle.Assign(credentialId)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// We would have a copy of the client data stored inside mTransaction,
|
||||
// but we need the one from authenticator-rs, as that data is part of
|
||||
// the signed payload. If we reorder the JSON-values (e.g. by sorting the
|
||||
// members alphabetically, as the codegen from IDL does, so we can't use
|
||||
// that), that would break the signature and lead to a failed authentication
|
||||
// on the server. So we make sure to take exactly the client data that
|
||||
// authenticator-rs sent to the token.
|
||||
nsCString clientData;
|
||||
if (!aResult->CopyClientDataStr(clientData)) {
|
||||
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dummy-values. Not used with CTAP2.
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
CryptoBuffer regData;
|
||||
WebAuthnMakeCredentialResult result(clientData, attObj, keyHandle, regData,
|
||||
extensions);
|
||||
mRegisterPromise.Resolve(std::move(result), __func__);
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::HandleSignResult(UniquePtr<CTAPResult>&& aResult) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (mTransaction.isNothing() ||
|
||||
aResult->GetTransactionId() != mTransaction.ref().mId) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mSignPromise.IsEmpty());
|
||||
|
||||
if (aResult->IsError()) {
|
||||
mSignPromise.Reject(aResult->GetError(), __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aResult->IsCtap2()) {
|
||||
HandleSignResultCtap2(std::move(aResult));
|
||||
} else {
|
||||
HandleSignResultCtap1(std::move(aResult));
|
||||
}
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::HandleSignResultCtap1(
|
||||
UniquePtr<CTAPResult>&& aResult) {
|
||||
nsTArray<uint8_t> hashChosenByAuthenticator;
|
||||
if (!aResult->CopyAppId(hashChosenByAuthenticator)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> keyHandle;
|
||||
if (!aResult->CopyKeyHandle(keyHandle)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> signature;
|
||||
if (!aResult->CopySignature(signature)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoBuffer rawSignatureBuf;
|
||||
if (!rawSignatureBuf.Assign(signature)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
|
||||
if (mTransaction.ref().mAppIdHash.isSome()) {
|
||||
bool usedAppId =
|
||||
(hashChosenByAuthenticator == mTransaction.ref().mAppIdHash.ref());
|
||||
extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
|
||||
}
|
||||
|
||||
CryptoBuffer signatureBuf;
|
||||
CryptoBuffer counterBuf;
|
||||
uint8_t flags = 0;
|
||||
nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf,
|
||||
signatureBuf);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoBuffer chosenAppIdBuf;
|
||||
if (!chosenAppIdBuf.Assign(hashChosenByAuthenticator)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve the two LSBs of the flags byte, UP and RFU1.
|
||||
// See <https://github.com/fido-alliance/fido-2-specs/pull/519>
|
||||
flags &= 0b11;
|
||||
|
||||
CryptoBuffer emptyAttestationData;
|
||||
CryptoBuffer authenticatorData;
|
||||
rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf,
|
||||
emptyAttestationData, authenticatorData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> userHandle;
|
||||
|
||||
WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON,
|
||||
keyHandle, signatureBuf, authenticatorData,
|
||||
extensions, rawSignatureBuf, userHandle);
|
||||
nsTArray<WebAuthnGetAssertionResultWrapper> results = {
|
||||
{result, mozilla::Nothing()}};
|
||||
mSignPromise.Resolve(std::move(results), __func__);
|
||||
}
|
||||
|
||||
void CTAPHIDTokenManager::HandleSignResultCtap2(
|
||||
UniquePtr<CTAPResult>&& aResult) {
|
||||
// Have choice here. For discoverable creds, the token
|
||||
// can return multiple assertions. The user has to choose
|
||||
// into which account we should sign in. We are getting
|
||||
// all of them from auth-rs, let the user select one and send
|
||||
// that back to the server
|
||||
size_t num_of_results;
|
||||
if (!aResult->Ctap2GetNumberOfSignAssertions(num_of_results)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
nsTArray<WebAuthnGetAssertionResultWrapper> results;
|
||||
for (size_t idx = 0; idx < num_of_results; ++idx) {
|
||||
auto assertion = HandleSelectedSignResultCtap2(
|
||||
std::forward<UniquePtr<CTAPResult>>(aResult), idx);
|
||||
if (assertion.isNothing()) {
|
||||
return;
|
||||
}
|
||||
results.AppendElement(assertion.extract());
|
||||
}
|
||||
if (results.IsEmpty()) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
} else {
|
||||
mSignPromise.Resolve(std::move(results), __func__);
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::Maybe<WebAuthnGetAssertionResultWrapper>
|
||||
CTAPHIDTokenManager::HandleSelectedSignResultCtap2(
|
||||
UniquePtr<CTAPResult>&& aResult, size_t index) {
|
||||
nsTArray<uint8_t> signature;
|
||||
if (!aResult->Ctap2CopySignature(signature, index)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
|
||||
CryptoBuffer signatureBuf;
|
||||
if (!signatureBuf.Assign(signature)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> cred;
|
||||
CryptoBuffer pubKeyCred;
|
||||
if (aResult->Ctap2HasPubKeyCredential(index)) {
|
||||
if (!aResult->Ctap2CopyPubKeyCredential(cred, index)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
if (!pubKeyCred.Assign(cred)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> auth;
|
||||
if (!aResult->Ctap2CopyAuthData(auth, index)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
|
||||
CryptoBuffer authData;
|
||||
if (!authData.Assign(auth)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> userID;
|
||||
if (aResult->HasUserId(index)) {
|
||||
if (!aResult->Ctap2CopyUserId(userID,
|
||||
index)) { // Misusing AppId for User-handle
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
}
|
||||
|
||||
// We would have a copy of the client data stored inside mTransaction,
|
||||
// but we need the one from authenticator-rs, as that data is part of
|
||||
// the signed payload. If we reorder the JSON-values (e.g. by sorting the
|
||||
// members alphabetically, as the codegen from IDL does, so we can't use
|
||||
// that), that would break the signature and lead to a failed authentication
|
||||
// on the server. So we make sure to take exactly the client data that
|
||||
// authenticator-rs sent to the token.
|
||||
nsCString clientData;
|
||||
if (!aResult->CopyClientDataStr(clientData)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> effectiveRpIdHash;
|
||||
if (!aResult->Ctap2CopyRpIdHash(effectiveRpIdHash, index)) {
|
||||
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
return mozilla::Nothing();
|
||||
}
|
||||
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
if (mTransaction.ref().mAppIdHash.isSome()) {
|
||||
bool usedAppId = (effectiveRpIdHash == mTransaction.ref().mAppIdHash.ref());
|
||||
extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionResult assertion(clientData, pubKeyCred, signatureBuf,
|
||||
authData, extensions, signature, userID);
|
||||
mozilla::Maybe<nsCString> username;
|
||||
nsCString name;
|
||||
if (aResult->CopyUserName(name, index)) {
|
||||
username = Some(name);
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionResultWrapper result = {assertion, username};
|
||||
return mozilla::Some(result);
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
@ -1,391 +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_CTAPHIDTokenManager_h
|
||||
#define mozilla_dom_CTAPHIDTokenManager_h
|
||||
|
||||
#include "mozilla/dom/U2FTokenTransport.h"
|
||||
#include "authenticator/src/u2fhid-capi.h"
|
||||
#include "authenticator/src/ctap2-capi.h"
|
||||
|
||||
/*
|
||||
* CTAPHIDTokenManager is a Rust implementation of a secure token manager
|
||||
* for the CTAP2, U2F and WebAuthn APIs, talking to HIDs.
|
||||
*/
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class Ctap2PubKeyCredentialDescriptor {
|
||||
public:
|
||||
explicit Ctap2PubKeyCredentialDescriptor(
|
||||
const nsTArray<WebAuthnScopedCredential>& aCredentials) {
|
||||
cred_descriptors = rust_ctap2_pkcd_new();
|
||||
|
||||
for (auto& cred : aCredentials) {
|
||||
rust_ctap2_pkcd_add(cred_descriptors, cred.id().Elements(),
|
||||
cred.id().Length(), cred.transports());
|
||||
}
|
||||
}
|
||||
|
||||
rust_ctap2_pub_key_cred_descriptors* Get() { return cred_descriptors; }
|
||||
|
||||
~Ctap2PubKeyCredentialDescriptor() { rust_ctap2_pkcd_free(cred_descriptors); }
|
||||
|
||||
private:
|
||||
rust_ctap2_pub_key_cred_descriptors* cred_descriptors;
|
||||
};
|
||||
|
||||
class CTAPResult {
|
||||
public:
|
||||
explicit CTAPResult(uint64_t aTransactionId, rust_u2f_result* aResult)
|
||||
: mTransactionId(aTransactionId), mU2FResult(aResult) {
|
||||
MOZ_ASSERT(mU2FResult);
|
||||
}
|
||||
|
||||
explicit CTAPResult(uint64_t aTransactionId,
|
||||
rust_ctap2_register_result* aResult)
|
||||
: mTransactionId(aTransactionId), mRegisterResult(aResult) {
|
||||
MOZ_ASSERT(mRegisterResult);
|
||||
}
|
||||
|
||||
explicit CTAPResult(uint64_t aTransactionId, rust_ctap2_sign_result* aResult)
|
||||
: mTransactionId(aTransactionId), mSignResult(aResult) {
|
||||
MOZ_ASSERT(mSignResult);
|
||||
}
|
||||
|
||||
~CTAPResult() {
|
||||
// Rust-API can handle possible NULL-pointers
|
||||
rust_u2f_res_free(mU2FResult);
|
||||
rust_ctap2_register_res_free(mRegisterResult);
|
||||
rust_ctap2_sign_res_free(mSignResult);
|
||||
}
|
||||
|
||||
uint64_t GetTransactionId() { return mTransactionId; }
|
||||
|
||||
bool IsError() { return NS_FAILED(GetError()); }
|
||||
|
||||
nsresult GetError() {
|
||||
uint8_t res;
|
||||
if (mU2FResult) {
|
||||
res = rust_u2f_result_error(mU2FResult);
|
||||
} else if (mRegisterResult) {
|
||||
res = rust_ctap2_register_result_error(mRegisterResult);
|
||||
} else if (mSignResult) {
|
||||
res = rust_ctap2_sign_result_error(mSignResult);
|
||||
} else {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
switch (res) {
|
||||
case U2F_OK:
|
||||
return NS_OK;
|
||||
case U2F_ERROR_UKNOWN:
|
||||
case U2F_ERROR_CONSTRAINT:
|
||||
return NS_ERROR_DOM_UNKNOWN_ERR;
|
||||
case U2F_ERROR_NOT_SUPPORTED:
|
||||
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
case U2F_ERROR_INVALID_STATE:
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
case U2F_ERROR_NOT_ALLOWED:
|
||||
return NS_ERROR_DOM_NOT_ALLOWED_ERR;
|
||||
case CTAP_ERROR_PIN_REQUIRED:
|
||||
case CTAP_ERROR_PIN_INVALID:
|
||||
case CTAP_ERROR_PIN_AUTH_BLOCKED:
|
||||
case CTAP_ERROR_PIN_BLOCKED:
|
||||
// This is not perfect, but we are reusing an existing error-code here.
|
||||
// We need to differentiate only PIN-errors from non-PIN errors
|
||||
// to know if the Popup-Dialog should be removed or stay
|
||||
// after the operation errors out. We don't want to define
|
||||
// new NS-errors at the moment, since it's all internal anyways.
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
default:
|
||||
// Generic error
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
bool CopyRegistration(nsTArray<uint8_t>& aBuffer) {
|
||||
return CopyBuffer(U2F_RESBUF_ID_REGISTRATION, aBuffer);
|
||||
}
|
||||
|
||||
bool CopyKeyHandle(nsTArray<uint8_t>& aBuffer) {
|
||||
return CopyBuffer(U2F_RESBUF_ID_KEYHANDLE, aBuffer);
|
||||
}
|
||||
|
||||
bool CopySignature(nsTArray<uint8_t>& aBuffer) {
|
||||
return CopyBuffer(U2F_RESBUF_ID_SIGNATURE, aBuffer);
|
||||
}
|
||||
|
||||
bool CopyAppId(nsTArray<uint8_t>& aBuffer) {
|
||||
return CopyBuffer(U2F_RESBUF_ID_APPID, aBuffer);
|
||||
}
|
||||
|
||||
bool CopyClientDataStr(nsCString& aBuffer) {
|
||||
if (mU2FResult) {
|
||||
return false;
|
||||
} else if (mRegisterResult) {
|
||||
size_t len;
|
||||
if (!rust_ctap2_register_result_client_data_len(mRegisterResult, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aBuffer.SetLength(len, fallible)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rust_ctap2_register_result_client_data_copy(mRegisterResult,
|
||||
aBuffer.Data());
|
||||
} else if (mSignResult) {
|
||||
size_t len;
|
||||
if (!rust_ctap2_sign_result_client_data_len(mSignResult, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aBuffer.SetLength(len, fallible)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rust_ctap2_sign_result_client_data_copy(mSignResult,
|
||||
aBuffer.Data());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCtap2() {
|
||||
// If it's not an U2F result, we already know its CTAP2
|
||||
return !mU2FResult;
|
||||
}
|
||||
|
||||
bool HasAppId() { return Contains(U2F_RESBUF_ID_APPID); }
|
||||
|
||||
bool HasKeyHandle() { return Contains(U2F_RESBUF_ID_KEYHANDLE); }
|
||||
|
||||
bool Ctap2GetNumberOfSignAssertions(size_t& len) {
|
||||
return rust_ctap2_sign_result_assertions_len(mSignResult, &len);
|
||||
}
|
||||
|
||||
bool Ctap2CopyAttestation(nsTArray<uint8_t>& aBuffer) {
|
||||
if (!mRegisterResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
if (!rust_ctap2_register_result_attestation_len(mRegisterResult, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aBuffer.SetLength(len, fallible)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rust_ctap2_register_result_attestation_copy(mRegisterResult,
|
||||
aBuffer.Elements());
|
||||
}
|
||||
|
||||
bool Ctap2CopyCredentialId(nsTArray<uint8_t>& aBuffer) {
|
||||
if (!mRegisterResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
if (!rust_ctap2_register_result_credential_id_len(mRegisterResult, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aBuffer.SetLength(len, fallible)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rust_ctap2_register_result_credential_id_copy(mRegisterResult,
|
||||
aBuffer.Elements());
|
||||
}
|
||||
|
||||
bool Ctap2CopyPubKeyCredential(nsTArray<uint8_t>& aBuffer, size_t index) {
|
||||
return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_PUBKEY_CRED_ID,
|
||||
aBuffer);
|
||||
}
|
||||
|
||||
bool Ctap2CopySignature(nsTArray<uint8_t>& aBuffer, size_t index) {
|
||||
return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_SIGNATURE, aBuffer);
|
||||
}
|
||||
|
||||
bool Ctap2CopyUserId(nsTArray<uint8_t>& aBuffer, size_t index) {
|
||||
return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_USER_ID, aBuffer);
|
||||
}
|
||||
|
||||
bool Ctap2CopyAuthData(nsTArray<uint8_t>& aBuffer, size_t index) {
|
||||
return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_AUTH_DATA, aBuffer);
|
||||
}
|
||||
|
||||
bool Ctap2CopyRpIdHash(nsTArray<uint8_t>& aBuffer, size_t index) {
|
||||
return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_RP_ID_HASH, aBuffer);
|
||||
}
|
||||
|
||||
bool Ctap2HasPubKeyCredential(size_t index) {
|
||||
return Ctap2SignResContains(index, CTAP2_SIGN_RESULT_PUBKEY_CRED_ID);
|
||||
}
|
||||
|
||||
bool HasUserId(size_t index) {
|
||||
return Ctap2SignResContains(index, CTAP2_SIGN_RESULT_USER_ID);
|
||||
}
|
||||
|
||||
bool HasUserName(size_t index) {
|
||||
return Ctap2SignResContains(index, CTAP2_SIGN_RESULT_USER_NAME);
|
||||
}
|
||||
|
||||
bool CopyUserName(nsCString& aBuffer, size_t index) {
|
||||
if (!mSignResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
if (!rust_ctap2_sign_result_username_len(mSignResult, index, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aBuffer.SetLength(len, fallible)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rust_ctap2_sign_result_username_copy(mSignResult, index,
|
||||
aBuffer.Data());
|
||||
}
|
||||
|
||||
private:
|
||||
bool Contains(uint8_t aResBufId) {
|
||||
if (mU2FResult) {
|
||||
return rust_u2f_resbuf_contains(mU2FResult, aResBufId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
|
||||
if (!mU2FResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
if (!rust_u2f_resbuf_length(mU2FResult, aResBufID, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aBuffer.SetLength(len, fallible)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rust_u2f_resbuf_copy(mU2FResult, aResBufID, aBuffer.Elements());
|
||||
}
|
||||
|
||||
bool Ctap2SignResContains(size_t assertion_idx, uint8_t item_idx) {
|
||||
if (mSignResult) {
|
||||
return rust_ctap2_sign_result_item_contains(mSignResult, assertion_idx,
|
||||
item_idx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Ctap2SignResCopyBuffer(size_t assertion_idx, uint8_t item_idx,
|
||||
nsTArray<uint8_t>& aBuffer) {
|
||||
if (!mSignResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
if (!rust_ctap2_sign_result_item_len(mSignResult, assertion_idx, item_idx,
|
||||
&len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aBuffer.SetLength(len, fallible)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rust_ctap2_sign_result_item_copy(mSignResult, assertion_idx,
|
||||
item_idx, aBuffer.Elements());
|
||||
}
|
||||
|
||||
uint64_t mTransactionId;
|
||||
rust_u2f_result* mU2FResult = nullptr;
|
||||
rust_ctap2_register_result* mRegisterResult = nullptr;
|
||||
rust_ctap2_sign_result* mSignResult = nullptr;
|
||||
};
|
||||
|
||||
class CTAPHIDTokenManager final : public U2FTokenTransport {
|
||||
public:
|
||||
explicit CTAPHIDTokenManager();
|
||||
|
||||
// TODO(MS): Once we completely switch over to CTAPHIDTokenManager and remove
|
||||
// the old U2F version, this needs to be renamed to CTAPRegisterPromise. Same
|
||||
// for Sign.
|
||||
virtual RefPtr<U2FRegisterPromise> Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void status_callback(rust_ctap2_status_update_res*)) override;
|
||||
|
||||
virtual RefPtr<U2FSignPromise> Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void status_callback(rust_ctap2_status_update_res*)) override;
|
||||
|
||||
void Cancel() override;
|
||||
void Drop() override;
|
||||
|
||||
void HandleRegisterResult(UniquePtr<CTAPResult>&& aResult);
|
||||
void HandleSignResult(UniquePtr<CTAPResult>&& aResult);
|
||||
|
||||
private:
|
||||
~CTAPHIDTokenManager() = default;
|
||||
|
||||
void HandleRegisterResultCtap1(UniquePtr<CTAPResult>&& aResult);
|
||||
void HandleRegisterResultCtap2(UniquePtr<CTAPResult>&& aResult);
|
||||
void HandleSignResultCtap1(UniquePtr<CTAPResult>&& aResult);
|
||||
void HandleSignResultCtap2(UniquePtr<CTAPResult>&& aResult);
|
||||
mozilla::Maybe<WebAuthnGetAssertionResultWrapper>
|
||||
HandleSelectedSignResultCtap2(UniquePtr<CTAPResult>&& aResult, size_t index);
|
||||
void ClearPromises() {
|
||||
mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
|
||||
}
|
||||
|
||||
class Transaction {
|
||||
public:
|
||||
Transaction(uint64_t aId, const nsTArray<uint8_t>& aRpIdHash,
|
||||
const Maybe<nsTArray<uint8_t>>& aAppIdHash,
|
||||
const nsCString& aClientDataJSON,
|
||||
bool aForceNoneAttestation = false)
|
||||
: mId(aId),
|
||||
mRpIdHash(aRpIdHash.Clone()),
|
||||
mClientDataJSON(aClientDataJSON),
|
||||
mForceNoneAttestation(aForceNoneAttestation) {
|
||||
if (aAppIdHash) {
|
||||
mAppIdHash = Some(aAppIdHash->Clone());
|
||||
} else {
|
||||
mAppIdHash = Nothing();
|
||||
}
|
||||
}
|
||||
|
||||
// The transaction ID.
|
||||
uint64_t mId;
|
||||
|
||||
// The RP ID hash.
|
||||
nsTArray<uint8_t> mRpIdHash;
|
||||
|
||||
// The App ID hash, if the AppID extension was set
|
||||
Maybe<nsTArray<uint8_t>> mAppIdHash;
|
||||
|
||||
// The clientData JSON.
|
||||
nsCString mClientDataJSON;
|
||||
|
||||
// Whether we'll force "none" attestation.
|
||||
bool mForceNoneAttestation;
|
||||
};
|
||||
|
||||
rust_ctap_manager* mCTAPManager;
|
||||
Maybe<Transaction> mTransaction;
|
||||
MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
|
||||
MozPromiseHolder<U2FSignPromise> mSignPromise;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // mozilla_dom_CTAPHIDTokenManager_h
|
302
dom/webauthn/CtapArgs.cpp
Normal file
302
dom/webauthn/CtapArgs.cpp
Normal file
@ -0,0 +1,302 @@
|
||||
/* -*- 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 "CtapArgs.h"
|
||||
#include "WebAuthnEnumStrings.h"
|
||||
#include "mozilla/dom/PWebAuthnTransactionParent.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_ISUPPORTS(CtapRegisterArgs, nsICtapRegisterArgs)
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetOrigin(nsAString& aOrigin) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aOrigin = mInfo.Origin();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetClientDataJSON(nsACString& aClientDataJSON) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aClientDataJSON = mInfo.ClientDataJSON();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetChallenge(nsTArray<uint8_t>& aChallenge) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aChallenge.Clear();
|
||||
aChallenge.AppendElements(mInfo.Challenge());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetRpId(nsAString& aRpId) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aRpId = mInfo.RpId();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetRpName(nsAString& aRpName) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aRpName = (*mInfo.Extra()).Rp().Name();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetUserId(nsTArray<uint8_t>& aUserId) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aUserId.Clear();
|
||||
aUserId.AppendElements((*mInfo.Extra()).User().Id());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetUserName(nsAString& aUserName) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aUserName = (*mInfo.Extra()).User().Name();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetUserDisplayName(nsAString& aUserDisplayName) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aUserDisplayName = (*mInfo.Extra()).User().DisplayName();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetCoseAlgs(nsTArray<int32_t>& aCoseAlgs) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aCoseAlgs.Clear();
|
||||
for (const CoseAlg& coseAlg : (*mInfo.Extra()).coseAlgs()) {
|
||||
aCoseAlgs.AppendElement(coseAlg.alg());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetExcludeList(nsTArray<nsTArray<uint8_t> >& aExcludeList) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aExcludeList.Clear();
|
||||
for (const WebAuthnScopedCredential& cred : mInfo.ExcludeList()) {
|
||||
aExcludeList.AppendElement(cred.id().Clone());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetHmacCreateSecret(bool* aHmacCreateSecret) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (const WebAuthnExtension& ext : (*mInfo.Extra()).Extensions()) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionHmacSecret) {
|
||||
*aHmacCreateSecret =
|
||||
ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret();
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetRequireResidentKey(bool* aRequireResidentKey) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
*aRequireResidentKey =
|
||||
(*mInfo.Extra()).AuthenticatorSelection().requireResidentKey();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetUserVerification(nsAString& aUserVerificationRequirement) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aUserVerificationRequirement =
|
||||
(*mInfo.Extra()).AuthenticatorSelection().userVerificationRequirement();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetAuthenticatorAttachment(
|
||||
nsAString& aAuthenticatorAttachment) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
if ((*mInfo.Extra())
|
||||
.AuthenticatorSelection()
|
||||
.authenticatorAttachment()
|
||||
.isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aAuthenticatorAttachment =
|
||||
*(*mInfo.Extra()).AuthenticatorSelection().authenticatorAttachment();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetTimeoutMS(uint32_t* aTimeoutMS) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
*aTimeoutMS = mInfo.TimeoutMS();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterArgs::GetAttestationConveyancePreference(
|
||||
nsAString& aAttestationConveyancePreference) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (mForceNoneAttestation) {
|
||||
aAttestationConveyancePreference = NS_ConvertUTF8toUTF16(
|
||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE);
|
||||
} else {
|
||||
aAttestationConveyancePreference =
|
||||
(*mInfo.Extra()).attestationConveyancePreference();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(CtapSignArgs, nsICtapSignArgs)
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetOrigin(nsAString& aOrigin) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aOrigin = mInfo.Origin();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetRpId(nsAString& aRpId) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aRpId = mInfo.RpId();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetClientDataJSON(nsACString& aClientDataJSON) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aClientDataJSON = mInfo.ClientDataJSON();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetChallenge(nsTArray<uint8_t>& aChallenge) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aChallenge.Clear();
|
||||
aChallenge.AppendElements(mInfo.Challenge());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetAllowList(nsTArray<nsTArray<uint8_t> >& aAllowList) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
aAllowList.Clear();
|
||||
for (const WebAuthnScopedCredential& cred : mInfo.AllowList()) {
|
||||
aAllowList.AppendElement(cred.id().Clone());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetHmacCreateSecret(bool* aHmacCreateSecret) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (const WebAuthnExtension& ext : (*mInfo.Extra()).Extensions()) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionHmacSecret) {
|
||||
*aHmacCreateSecret =
|
||||
ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret();
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetAppId(nsAString& aAppId) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (const WebAuthnExtension& ext : (*mInfo.Extra()).Extensions()) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
|
||||
aAppId = ext.get_WebAuthnExtensionAppId().appIdentifier();
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetAppIdHash(nsTArray<uint8_t>& aAppIdHash) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (const WebAuthnExtension& ext : (*mInfo.Extra()).Extensions()) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
|
||||
aAppIdHash.Clear();
|
||||
aAppIdHash.AppendElements(ext.get_WebAuthnExtensionAppId().AppId());
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetUserVerification(nsAString& aUserVerificationRequirement) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mInfo.Extra().isNothing()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
aUserVerificationRequirement = (*mInfo.Extra()).userVerificationRequirement();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignArgs::GetTimeoutMS(uint32_t* aTimeoutMS) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
*aTimeoutMS = mInfo.TimeoutMS();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
58
dom/webauthn/CtapArgs.h
Normal file
58
dom/webauthn/CtapArgs.h
Normal file
@ -0,0 +1,58 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 CtapArgs_h
|
||||
#define CtapArgs_h
|
||||
|
||||
#include "mozilla/dom/WebAuthnTransactionChild.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "nsIWebAuthnController.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// These classes provide an FFI between C++ and Rust for the getters of IPC
|
||||
// objects (WebAuthnMakeCredentialInfo and WebAuthnGetAssertionInfo). They hold
|
||||
// non-owning references to IPC objects, and must only be used within the
|
||||
// lifetime of the IPC transaction that created them. There are runtime
|
||||
// assertions to ensure that these types are created and used on the IPC
|
||||
// background thread, but that alone does not guarantee safety.
|
||||
|
||||
class CtapRegisterArgs final : public nsICtapRegisterArgs {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICTAPREGISTERARGS
|
||||
|
||||
explicit CtapRegisterArgs(const WebAuthnMakeCredentialInfo& aInfo,
|
||||
bool aForceNoneAttestation)
|
||||
: mInfo(aInfo), mForceNoneAttestation(aForceNoneAttestation) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
}
|
||||
|
||||
private:
|
||||
~CtapRegisterArgs() = default;
|
||||
|
||||
const WebAuthnMakeCredentialInfo& mInfo;
|
||||
const bool mForceNoneAttestation;
|
||||
};
|
||||
|
||||
class CtapSignArgs final : public nsICtapSignArgs {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICTAPSIGNARGS
|
||||
|
||||
explicit CtapSignArgs(const WebAuthnGetAssertionInfo& aInfo) : mInfo(aInfo) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
}
|
||||
|
||||
private:
|
||||
~CtapSignArgs() = default;
|
||||
|
||||
const WebAuthnGetAssertionInfo& mInfo;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // CtapArgs_h
|
88
dom/webauthn/CtapResults.cpp
Normal file
88
dom/webauthn/CtapResults.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
/* -*- 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 "CtapResults.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_ISUPPORTS(CtapRegisterResult, nsICtapRegisterResult)
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterResult::GetStatus(nsresult* aStatus) {
|
||||
*aStatus = mStatus;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterResult::GetClientDataJSON(nsACString& aClientDataJSON) {
|
||||
aClientDataJSON = mClientDataJSON;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterResult::GetAttestationObject(
|
||||
nsTArray<uint8_t>& aAttestationObject) {
|
||||
aAttestationObject.Clear();
|
||||
aAttestationObject.AppendElements(mAttestationObject);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapRegisterResult::GetCredentialId(nsTArray<uint8_t>& aCredentialId) {
|
||||
aCredentialId.Clear();
|
||||
aCredentialId.AppendElements(mCredentialId);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(CtapSignResult, nsICtapSignResult)
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignResult::GetStatus(nsresult* aStatus) {
|
||||
*aStatus = mStatus;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignResult::GetCredentialId(nsTArray<uint8_t>& aCredentialId) {
|
||||
aCredentialId.Clear();
|
||||
aCredentialId.AppendElements(mCredentialId);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignResult::GetAuthenticatorData(nsTArray<uint8_t>& aAuthenticatorData) {
|
||||
aAuthenticatorData.Clear();
|
||||
aAuthenticatorData.AppendElements(mAuthenticatorData);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignResult::GetSignature(nsTArray<uint8_t>& aSignature) {
|
||||
aSignature.Clear();
|
||||
aSignature.AppendElements(mSignature);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignResult::GetUserHandle(nsTArray<uint8_t>& aUserHandle) {
|
||||
aUserHandle.Clear();
|
||||
aUserHandle.AppendElements(mUserHandle);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignResult::GetUserName(nsACString& aUserName) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CtapSignResult::GetRpIdHash(nsTArray<uint8_t>& aRpIdHash) {
|
||||
aRpIdHash.Clear();
|
||||
aRpIdHash.AppendElements(mRpIdHash);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
67
dom/webauthn/CtapResults.h
Normal file
67
dom/webauthn/CtapResults.h
Normal file
@ -0,0 +1,67 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=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 CtapResults_h
|
||||
#define CtapResults_h
|
||||
|
||||
#include "mozilla/dom/WebAuthnTransactionChild.h"
|
||||
#include "nsIWebAuthnController.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class CtapRegisterResult final : public nsICtapRegisterResult {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICTAPREGISTERRESULT
|
||||
|
||||
explicit CtapRegisterResult(nsresult aStatus, nsCString&& aClientDataJSON,
|
||||
nsTArray<uint8_t>&& aAttestationObject,
|
||||
nsTArray<uint8_t>&& aCredentialId)
|
||||
: mStatus(aStatus),
|
||||
mClientDataJSON(std::move(aClientDataJSON)),
|
||||
mAttestationObject(std::move(aAttestationObject)),
|
||||
mCredentialId(std::move(aCredentialId)) {}
|
||||
|
||||
private:
|
||||
~CtapRegisterResult() = default;
|
||||
|
||||
nsresult mStatus;
|
||||
nsCString mClientDataJSON;
|
||||
nsTArray<uint8_t> mAttestationObject;
|
||||
nsTArray<uint8_t> mCredentialId;
|
||||
};
|
||||
|
||||
class CtapSignResult final : public nsICtapSignResult {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICTAPSIGNRESULT
|
||||
|
||||
explicit CtapSignResult(nsresult aStatus, nsTArray<uint8_t>&& aCredentialId,
|
||||
nsTArray<uint8_t>&& aSignature,
|
||||
nsTArray<uint8_t>&& aAuthenticatorData,
|
||||
nsTArray<uint8_t>&& aUserHandle,
|
||||
nsTArray<uint8_t>&& aRpIdHash)
|
||||
: mStatus(aStatus),
|
||||
mCredentialId(std::move(aCredentialId)),
|
||||
mSignature(std::move(aSignature)),
|
||||
mAuthenticatorData(std::move(aAuthenticatorData)),
|
||||
mUserHandle(std::move(aUserHandle)),
|
||||
mRpIdHash(std::move(aRpIdHash)) {}
|
||||
|
||||
private:
|
||||
~CtapSignResult() = default;
|
||||
|
||||
nsresult mStatus;
|
||||
nsTArray<uint8_t> mCredentialId;
|
||||
nsTArray<uint8_t> mSignature;
|
||||
nsTArray<uint8_t> mAuthenticatorData;
|
||||
nsTArray<uint8_t> mUserHandle;
|
||||
nsTArray<uint8_t> mRpIdHash;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // CtapResult_h
|
@ -104,8 +104,7 @@ void U2FHIDTokenManager::Drop() {
|
||||
// * attestation signature
|
||||
//
|
||||
RefPtr<U2FRegisterPromise> U2FHIDTokenManager::Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void _status_callback(rust_ctap2_status_update_res*)) {
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
uint64_t registerFlags = 0;
|
||||
@ -207,8 +206,7 @@ RefPtr<U2FRegisterPromise> U2FHIDTokenManager::Register(
|
||||
// * Signature
|
||||
//
|
||||
RefPtr<U2FSignPromise> U2FHIDTokenManager::Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void _status_callback(rust_ctap2_status_update_res*)) {
|
||||
const WebAuthnGetAssertionInfo& aInfo) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
CryptoBuffer rpIdHash, clientDataHash;
|
||||
|
@ -123,12 +123,11 @@ class U2FHIDTokenManager final : public U2FTokenTransport {
|
||||
explicit U2FHIDTokenManager();
|
||||
|
||||
virtual RefPtr<U2FRegisterPromise> Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void _status_callback(rust_ctap2_status_update_res*)) override;
|
||||
const WebAuthnMakeCredentialInfo& aInfo,
|
||||
bool aForceNoneAttestation) override;
|
||||
|
||||
virtual RefPtr<U2FSignPromise> Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void _status_callback(rust_ctap2_status_update_res*)) override;
|
||||
const WebAuthnGetAssertionInfo& aInfo) override;
|
||||
|
||||
void Cancel() override;
|
||||
void Drop() override;
|
||||
|
@ -29,7 +29,7 @@ using mozilla::dom::CreateECParamsForCurve;
|
||||
const nsCString U2FSoftTokenManager::mSecretNickname = "U2F_NSSTOKEN"_ns;
|
||||
|
||||
namespace {
|
||||
constexpr auto kAttestCertSubjectName = "CN=Firefox U2F Soft Token"_ns;
|
||||
constexpr auto kAttestCertSubjectNameOld = "CN=Firefox U2F Soft Token"_ns;
|
||||
|
||||
// This U2F-compatible soft token uses FIDO U2F-compatible ECDSA keypairs
|
||||
// on the SEC_OID_SECG_EC_SECP256R1 curve. When asked to Register, it will
|
||||
@ -42,25 +42,25 @@ constexpr auto kAttestCertSubjectName = "CN=Firefox U2F Soft Token"_ns;
|
||||
// ephemeral to counteract profiling. They have little use for a soft-token
|
||||
// at any rate, but are required by the specification.
|
||||
|
||||
const uint32_t kParamLen = 32;
|
||||
const uint32_t kPublicKeyLen = 65;
|
||||
const uint32_t kWrappedKeyBufLen = 256;
|
||||
const uint32_t kWrappingKeyByteLen = 128 / 8;
|
||||
const uint32_t kSaltByteLen = 64 / 8;
|
||||
const uint32_t kVersion1KeyHandleLen = 162;
|
||||
constexpr auto kEcAlgorithm =
|
||||
const uint32_t kParamLenOld = 32;
|
||||
const uint32_t kPublicKeyLenOld = 65;
|
||||
const uint32_t kWrappedKeyBufLenOld = 256;
|
||||
const uint32_t kWrappingKeyByteLenOld = 128 / 8;
|
||||
const uint32_t kSaltByteLenOld = 64 / 8;
|
||||
const uint32_t kVersion1KeyHandleLenOld = 162;
|
||||
constexpr auto kEcAlgorithmOld =
|
||||
NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P256);
|
||||
|
||||
const PRTime kOneDay = PRTime(PR_USEC_PER_SEC) * PRTime(60) // sec
|
||||
* PRTime(60) // min
|
||||
* PRTime(24); // hours
|
||||
const PRTime kExpirationSlack = kOneDay; // Pre-date for clock skew
|
||||
const PRTime kExpirationLife = kOneDay;
|
||||
const PRTime kOneDayOld = PRTime(PR_USEC_PER_SEC) * PRTime(60) // sec
|
||||
* PRTime(60) // min
|
||||
* PRTime(24); // hours
|
||||
const PRTime kExpirationSlackOld = kOneDayOld; // Pre-date for clock skew
|
||||
const PRTime kExpirationLifeOld = kOneDayOld;
|
||||
|
||||
static mozilla::LazyLogModule gNSSTokenLog("webauth_u2f");
|
||||
static mozilla::LazyLogModule gNSSTokenLogOld("webauth_u2f");
|
||||
|
||||
enum SoftTokenHandle {
|
||||
Version1 = 0,
|
||||
enum SoftTokenHandleOld {
|
||||
Version1Old = 0,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@ -77,21 +77,21 @@ U2FSoftTokenManager::U2FSoftTokenManager(uint32_t aCounter)
|
||||
* @param aNickname Nickname the key should have.
|
||||
* @return The first key found. nullptr if no key could be found.
|
||||
*/
|
||||
static UniquePK11SymKey GetSymKeyByNickname(const UniquePK11SlotInfo& aSlot,
|
||||
const nsCString& aNickname) {
|
||||
static UniquePK11SymKey GetSymKeyByNicknameOld(const UniquePK11SlotInfo& aSlot,
|
||||
const nsCString& aNickname) {
|
||||
MOZ_ASSERT(aSlot);
|
||||
if (NS_WARN_IF(!aSlot)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug,
|
||||
("Searching for a symmetric key named %s", aNickname.get()));
|
||||
|
||||
UniquePK11SymKey keyListHead(
|
||||
PK11_ListFixedKeysInSlot(aSlot.get(), const_cast<char*>(aNickname.get()),
|
||||
/* wincx */ nullptr));
|
||||
if (NS_WARN_IF(!keyListHead)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key not found."));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug, ("Symmetric key not found."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ static UniquePK11SymKey GetSymKeyByNickname(const UniquePK11SlotInfo& aSlot,
|
||||
// nickname.
|
||||
MOZ_ASSERT(aNickname ==
|
||||
UniquePORTString(PK11_GetSymKeyNickname(keyListHead.get())).get());
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key found!"));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug, ("Symmetric key found!"));
|
||||
|
||||
// Free any remaining keys in the key list.
|
||||
UniquePK11SymKey freeKey(PK11_GetNextSymKey(keyListHead.get()));
|
||||
@ -110,9 +110,9 @@ static UniquePK11SymKey GetSymKeyByNickname(const UniquePK11SlotInfo& aSlot,
|
||||
return keyListHead;
|
||||
}
|
||||
|
||||
static nsresult GenEcKeypair(const UniquePK11SlotInfo& aSlot,
|
||||
/*out*/ UniqueSECKEYPrivateKey& aPrivKey,
|
||||
/*out*/ UniqueSECKEYPublicKey& aPubKey) {
|
||||
static nsresult GenEcKeypairOld(const UniquePK11SlotInfo& aSlot,
|
||||
/*out*/ UniqueSECKEYPrivateKey& aPrivKey,
|
||||
/*out*/ UniqueSECKEYPublicKey& aPubKey) {
|
||||
MOZ_ASSERT(aSlot);
|
||||
if (NS_WARN_IF(!aSlot)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
@ -124,7 +124,7 @@ static nsresult GenEcKeypair(const UniquePK11SlotInfo& aSlot,
|
||||
}
|
||||
|
||||
// Set the curve parameters; keyParams belongs to the arena memory space
|
||||
SECItem* keyParams = CreateECParamsForCurve(kEcAlgorithm, arena.get());
|
||||
SECItem* keyParams = CreateECParamsForCurve(kEcAlgorithmOld, arena.get());
|
||||
if (NS_WARN_IF(!keyParams)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
@ -144,7 +144,7 @@ static nsresult GenEcKeypair(const UniquePK11SlotInfo& aSlot,
|
||||
}
|
||||
|
||||
// Check that the public key has the correct length
|
||||
if (NS_WARN_IF(aPubKey->u.ec.publicValue.len != kPublicKeyLen)) {
|
||||
if (NS_WARN_IF(aPubKey->u.ec.publicValue.len != kPublicKeyLenOld)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
@ -160,28 +160,28 @@ nsresult U2FSoftTokenManager::GetOrCreateWrappingKey(
|
||||
|
||||
// Search for an existing wrapping key. If we find it,
|
||||
// store it for later and mark ourselves initialized.
|
||||
mWrappingKey = GetSymKeyByNickname(aSlot, mSecretNickname);
|
||||
mWrappingKey = GetSymKeyByNicknameOld(aSlot, mSecretNickname);
|
||||
if (mWrappingKey) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token Key found."));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug, ("U2F Soft Token Key found."));
|
||||
mInitialized = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Info,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Info,
|
||||
("No keys found. Generating new U2F Soft Token wrapping key."));
|
||||
|
||||
// We did not find an existing wrapping key, so we generate one in the
|
||||
// persistent database (e.g, Token).
|
||||
mWrappingKey = UniquePK11SymKey(PK11_TokenKeyGenWithFlags(
|
||||
aSlot.get(), CKM_AES_KEY_GEN,
|
||||
/* default params */ nullptr, kWrappingKeyByteLen,
|
||||
/* default params */ nullptr, kWrappingKeyByteLenOld,
|
||||
/* empty keyid */ nullptr,
|
||||
/* flags */ CKF_WRAP | CKF_UNWRAP,
|
||||
/* attributes */ PK11_ATTR_TOKEN | PK11_ATTR_PRIVATE,
|
||||
/* wincx */ nullptr));
|
||||
|
||||
if (NS_WARN_IF(!mWrappingKey)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to store wrapping key, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -189,12 +189,12 @@ nsresult U2FSoftTokenManager::GetOrCreateWrappingKey(
|
||||
SECStatus srv =
|
||||
PK11_SetSymKeyNickname(mWrappingKey.get(), mSecretNickname.get());
|
||||
if (NS_WARN_IF(srv != SECSuccess)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to set nickname, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug,
|
||||
("Key stored, nickname set to %s.", mSecretNickname.get()));
|
||||
|
||||
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
||||
@ -206,7 +206,7 @@ nsresult U2FSoftTokenManager::GetOrCreateWrappingKey(
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static nsresult GetAttestationCertificate(
|
||||
static nsresult GetAttestationCertificateOld(
|
||||
const UniquePK11SlotInfo& aSlot,
|
||||
/*out*/ UniqueSECKEYPrivateKey& aAttestPrivKey,
|
||||
/*out*/ UniqueCERTCertificate& aAttestCert) {
|
||||
@ -218,17 +218,17 @@ static nsresult GetAttestationCertificate(
|
||||
UniqueSECKEYPublicKey pubKey;
|
||||
|
||||
// Construct an ephemeral keypair for this Attestation Certificate
|
||||
nsresult rv = GenEcKeypair(aSlot, aAttestPrivKey, pubKey);
|
||||
nsresult rv = GenEcKeypairOld(aSlot, aAttestPrivKey, pubKey);
|
||||
if (NS_WARN_IF(NS_FAILED(rv) || !aAttestPrivKey || !pubKey)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to gen keypair, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Construct the Attestation Certificate itself
|
||||
UniqueCERTName subjectName(CERT_AsciiToName(kAttestCertSubjectName.get()));
|
||||
UniqueCERTName subjectName(CERT_AsciiToName(kAttestCertSubjectNameOld.get()));
|
||||
if (NS_WARN_IF(!subjectName)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to set subject name, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -236,7 +236,7 @@ static nsresult GetAttestationCertificate(
|
||||
UniqueCERTSubjectPublicKeyInfo spki(
|
||||
SECKEY_CreateSubjectPublicKeyInfo(pubKey.get()));
|
||||
if (NS_WARN_IF(!spki)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to set SPKI, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -244,18 +244,18 @@ static nsresult GetAttestationCertificate(
|
||||
UniqueCERTCertificateRequest certreq(
|
||||
CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr));
|
||||
if (NS_WARN_IF(!certreq)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to gen CSR, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
PRTime now = PR_Now();
|
||||
PRTime notBefore = now - kExpirationSlack;
|
||||
PRTime notAfter = now + kExpirationLife;
|
||||
PRTime notBefore = now - kExpirationSlackOld;
|
||||
PRTime notAfter = now + kExpirationLifeOld;
|
||||
|
||||
UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
|
||||
if (NS_WARN_IF(!validity)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to gen validity, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -266,7 +266,7 @@ static nsresult GetAttestationCertificate(
|
||||
SECStatus srv =
|
||||
PK11_GenerateRandomOnSlot(aSlot.get(), serialBytes, sizeof(serial));
|
||||
if (NS_WARN_IF(srv != SECSuccess)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to gen serial, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -282,7 +282,7 @@ static nsresult GetAttestationCertificate(
|
||||
aAttestCert = UniqueCERTCertificate(CERT_CreateCertificate(
|
||||
serial, subjectName.get(), validity.get(), certreq.get()));
|
||||
if (NS_WARN_IF(!aAttestCert)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to gen certificate, NSS error #%d", PORT_GetError()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -319,7 +319,7 @@ static nsresult GetAttestationCertificate(
|
||||
}
|
||||
aAttestCert->derCert = *signedCert;
|
||||
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug,
|
||||
("U2F Soft Token attestation certificate generated."));
|
||||
return NS_OK;
|
||||
}
|
||||
@ -342,14 +342,14 @@ nsresult U2FSoftTokenManager::Init() {
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token initialized."));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug, ("U2F Soft Token initialized."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Convert a Private Key object into an opaque key handle, using AES Key Wrap
|
||||
// with the long-lived aPersistentKey mixed with aAppParam to convert aPrivKey.
|
||||
// The key handle's format is version || saltLen || salt || wrappedPrivateKey
|
||||
static UniqueSECItem KeyHandleFromPrivateKey(
|
||||
static UniqueSECItem KeyHandleFromPrivateKeyOld(
|
||||
const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey,
|
||||
uint8_t* aAppParam, uint32_t aAppParamLen,
|
||||
const UniqueSECKEYPrivateKey& aPrivKey) {
|
||||
@ -362,11 +362,11 @@ static UniqueSECItem KeyHandleFromPrivateKey(
|
||||
}
|
||||
|
||||
// Generate a random salt
|
||||
uint8_t saltParam[kSaltByteLen];
|
||||
uint8_t saltParam[kSaltByteLenOld];
|
||||
SECStatus srv =
|
||||
PK11_GenerateRandomOnSlot(aSlot.get(), saltParam, sizeof(saltParam));
|
||||
if (NS_WARN_IF(srv != SECSuccess)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to generate a salt, NSS error #%d", PORT_GetError()));
|
||||
return nullptr;
|
||||
}
|
||||
@ -382,19 +382,19 @@ static UniqueSECItem KeyHandleFromPrivateKey(
|
||||
// derived symmetric key and don't matter because we ignore them anyway.
|
||||
UniquePK11SymKey wrapKey(
|
||||
PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams,
|
||||
CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen));
|
||||
CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLenOld));
|
||||
if (NS_WARN_IF(!wrapKey.get())) {
|
||||
MOZ_LOG(
|
||||
gNSSTokenLog, LogLevel::Warning,
|
||||
gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to derive a wrapping key, NSS error #%d", PORT_GetError()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniqueSECItem wrappedKey(::SECITEM_AllocItem(/* default arena */ nullptr,
|
||||
/* no buffer */ nullptr,
|
||||
kWrappedKeyBufLen));
|
||||
kWrappedKeyBufLenOld));
|
||||
if (NS_WARN_IF(!wrappedKey)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -406,7 +406,7 @@ static UniqueSECItem KeyHandleFromPrivateKey(
|
||||
CKM_NSS_AES_KEY_WRAP_PAD, param.get(), wrappedKey.get(),
|
||||
/* wincx */ nullptr);
|
||||
if (NS_WARN_IF(srv != SECSuccess)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to wrap U2F key, NSS error #%d", PORT_GetError()));
|
||||
return nullptr;
|
||||
}
|
||||
@ -415,13 +415,13 @@ static UniqueSECItem KeyHandleFromPrivateKey(
|
||||
mozilla::dom::CryptoBuffer keyHandleBuf;
|
||||
if (NS_WARN_IF(!keyHandleBuf.SetCapacity(
|
||||
wrappedKey.get()->len + sizeof(saltParam) + 2, mozilla::fallible))) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// It's OK to ignore the return values here because we're writing into
|
||||
// pre-allocated space
|
||||
(void)keyHandleBuf.AppendElement(SoftTokenHandle::Version1,
|
||||
(void)keyHandleBuf.AppendElement(SoftTokenHandleOld::Version1Old,
|
||||
mozilla::fallible);
|
||||
(void)keyHandleBuf.AppendElement(sizeof(saltParam), mozilla::fallible);
|
||||
(void)keyHandleBuf.AppendElements(saltParam, sizeof(saltParam),
|
||||
@ -430,13 +430,13 @@ static UniqueSECItem KeyHandleFromPrivateKey(
|
||||
|
||||
UniqueSECItem keyHandle(::SECITEM_AllocItem(nullptr, nullptr, 0));
|
||||
if (NS_WARN_IF(!keyHandle)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!keyHandleBuf.ToSECItem(/* default arena */ nullptr,
|
||||
keyHandle.get()))) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning, ("Failed to allocate memory"));
|
||||
return nullptr;
|
||||
}
|
||||
return keyHandle;
|
||||
@ -445,7 +445,7 @@ static UniqueSECItem KeyHandleFromPrivateKey(
|
||||
// Convert an opaque key handle aKeyHandle back into a Private Key object, using
|
||||
// the long-lived aPersistentKey mixed with aAppParam and the AES Key Wrap
|
||||
// algorithm.
|
||||
static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(
|
||||
static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandleOld(
|
||||
const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey,
|
||||
uint8_t* aKeyHandle, uint32_t aKeyHandleLen, uint8_t* aAppParam,
|
||||
uint32_t aAppParamLen) {
|
||||
@ -461,18 +461,18 @@ static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(
|
||||
|
||||
// As we only support one key format ourselves (right now), fail early if
|
||||
// we aren't that length
|
||||
if (NS_WARN_IF(aKeyHandleLen != kVersion1KeyHandleLen)) {
|
||||
if (NS_WARN_IF(aKeyHandleLen != kVersion1KeyHandleLenOld)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(aKeyHandle[0] != SoftTokenHandle::Version1)) {
|
||||
if (NS_WARN_IF(aKeyHandle[0] != SoftTokenHandleOld::Version1Old)) {
|
||||
// Unrecognized version
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint8_t saltLen = aKeyHandle[1];
|
||||
uint8_t* saltPtr = aKeyHandle + 2;
|
||||
if (NS_WARN_IF(saltLen != kSaltByteLen)) {
|
||||
if (NS_WARN_IF(saltLen != kSaltByteLenOld)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -487,10 +487,10 @@ static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(
|
||||
// derived symmetric key and don't matter because we ignore them anyway.
|
||||
UniquePK11SymKey wrapKey(
|
||||
PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams,
|
||||
CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen));
|
||||
CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLenOld));
|
||||
if (NS_WARN_IF(!wrapKey.get())) {
|
||||
MOZ_LOG(
|
||||
gNSSTokenLog, LogLevel::Warning,
|
||||
gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Failed to derive a wrapping key, NSS error #%d", PORT_GetError()));
|
||||
return nullptr;
|
||||
}
|
||||
@ -501,7 +501,7 @@ static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(
|
||||
ScopedAutoSECItem wrappedKeyItem(wrappedLen);
|
||||
memcpy(wrappedKeyItem.data, wrappedPtr, wrappedKeyItem.len);
|
||||
|
||||
ScopedAutoSECItem pubKey(kPublicKeyLen);
|
||||
ScopedAutoSECItem pubKey(kPublicKeyLenOld);
|
||||
|
||||
UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD,
|
||||
/* default IV */ nullptr));
|
||||
@ -519,7 +519,7 @@ static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(
|
||||
/* wincx */ nullptr));
|
||||
if (NS_WARN_IF(!unwrappedKey)) {
|
||||
// Not our key.
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug,
|
||||
("Could not unwrap key handle, NSS Error #%d", PORT_GetError()));
|
||||
return nullptr;
|
||||
}
|
||||
@ -542,7 +542,7 @@ nsresult U2FSoftTokenManager::IsRegistered(const nsTArray<uint8_t>& aKeyHandle,
|
||||
MOZ_ASSERT(slot.get());
|
||||
|
||||
// Decode the key handle
|
||||
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(
|
||||
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandleOld(
|
||||
slot, mWrappingKey, const_cast<uint8_t*>(aKeyHandle.Elements()),
|
||||
aKeyHandle.Length(), const_cast<uint8_t*>(aAppParam.Elements()),
|
||||
aAppParam.Length());
|
||||
@ -570,8 +570,7 @@ nsresult U2FSoftTokenManager::IsRegistered(const nsTArray<uint8_t>& aKeyHandle,
|
||||
// * attestation signature
|
||||
//
|
||||
RefPtr<U2FRegisterPromise> U2FSoftTokenManager::Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void _ctap2_status_callback(rust_ctap2_status_update_res*)) {
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
|
||||
if (!mInitialized) {
|
||||
nsresult rv = Init();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
@ -661,7 +660,7 @@ RefPtr<U2FRegisterPromise> U2FSoftTokenManager::Register(
|
||||
// Construct a one-time-use Attestation Certificate
|
||||
UniqueSECKEYPrivateKey attestPrivKey;
|
||||
UniqueCERTCertificate attestCert;
|
||||
rv = GetAttestationCertificate(slot, attestPrivKey, attestCert);
|
||||
rv = GetAttestationCertificateOld(slot, attestPrivKey, attestCert);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
@ -671,13 +670,13 @@ RefPtr<U2FRegisterPromise> U2FSoftTokenManager::Register(
|
||||
// Generate a new keypair; the private will be wrapped into a Key Handle
|
||||
UniqueSECKEYPrivateKey privKey;
|
||||
UniqueSECKEYPublicKey pubKey;
|
||||
rv = GenEcKeypair(slot, privKey, pubKey);
|
||||
rv = GenEcKeypairOld(slot, privKey, pubKey);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
// The key handle will be the result of keywrap(privKey, key=mWrappingKey)
|
||||
UniqueSECItem keyHandleItem = KeyHandleFromPrivateKey(
|
||||
UniqueSECItem keyHandleItem = KeyHandleFromPrivateKeyOld(
|
||||
slot, mWrappingKey, const_cast<uint8_t*>(rpIdHash.Elements()),
|
||||
rpIdHash.Length(), privKey);
|
||||
if (NS_WARN_IF(!keyHandleItem.get())) {
|
||||
@ -688,7 +687,7 @@ RefPtr<U2FRegisterPromise> U2FSoftTokenManager::Register(
|
||||
mozilla::dom::CryptoBuffer signedDataBuf;
|
||||
if (NS_WARN_IF(!signedDataBuf.SetCapacity(
|
||||
1 + rpIdHash.Length() + clientDataHash.Length() + keyHandleItem->len +
|
||||
kPublicKeyLen,
|
||||
kPublicKeyLenOld,
|
||||
mozilla::fallible))) {
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY,
|
||||
__func__);
|
||||
@ -707,7 +706,7 @@ RefPtr<U2FRegisterPromise> U2FSoftTokenManager::Register(
|
||||
signedDataBuf.Length(), attestPrivKey.get(),
|
||||
SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
|
||||
if (NS_WARN_IF(srv != SECSuccess)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Signature failure: %d", PORT_GetError()));
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
@ -715,7 +714,7 @@ RefPtr<U2FRegisterPromise> U2FSoftTokenManager::Register(
|
||||
// Serialize the registration data
|
||||
mozilla::dom::CryptoBuffer registrationBuf;
|
||||
if (NS_WARN_IF(!registrationBuf.SetCapacity(
|
||||
1 + kPublicKeyLen + 1 + keyHandleItem->len +
|
||||
1 + kPublicKeyLenOld + 1 + keyHandleItem->len +
|
||||
attestCert.get()->derCert.len + signatureItem.len,
|
||||
mozilla::fallible))) {
|
||||
return U2FRegisterPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY,
|
||||
@ -800,8 +799,7 @@ bool U2FSoftTokenManager::FindRegisteredKeyHandle(
|
||||
// * Signature
|
||||
//
|
||||
RefPtr<U2FSignPromise> U2FSoftTokenManager::Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void _ctap2_status_callback(rust_ctap2_status_update_res*)) {
|
||||
const WebAuthnGetAssertionInfo& aInfo) {
|
||||
if (!mInitialized) {
|
||||
nsresult rv = Init();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
@ -856,23 +854,23 @@ RefPtr<U2FSignPromise> U2FSoftTokenManager::Sign(
|
||||
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
MOZ_ASSERT(slot.get());
|
||||
|
||||
if (NS_WARN_IF((clientDataHash.Length() != kParamLen) ||
|
||||
(chosenAppId.Length() != kParamLen))) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
if (NS_WARN_IF((clientDataHash.Length() != kParamLenOld) ||
|
||||
(chosenAppId.Length() != kParamLenOld))) {
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
|
||||
(uint32_t)clientDataHash.Length(), (uint32_t)chosenAppId.Length(),
|
||||
kParamLen));
|
||||
kParamLenOld));
|
||||
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
|
||||
}
|
||||
|
||||
// Decode the key handle
|
||||
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(
|
||||
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandleOld(
|
||||
slot, mWrappingKey, const_cast<uint8_t*>(keyHandle.Elements()),
|
||||
keyHandle.Length(), const_cast<uint8_t*>(chosenAppId.Elements()),
|
||||
chosenAppId.Length());
|
||||
if (NS_WARN_IF(!privKey.get())) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning, ("Couldn't get the priv key!"));
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
@ -892,7 +890,7 @@ RefPtr<U2FSignPromise> U2FSoftTokenManager::Sign(
|
||||
|
||||
// Compute the signature
|
||||
mozilla::dom::CryptoBuffer signedDataBuf;
|
||||
if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + 4 + (2 * kParamLen),
|
||||
if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + 4 + (2 * kParamLenOld),
|
||||
mozilla::fallible))) {
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
|
||||
}
|
||||
@ -906,7 +904,7 @@ RefPtr<U2FSignPromise> U2FSoftTokenManager::Sign(
|
||||
(void)signedDataBuf.AppendElements(
|
||||
clientDataHash.Elements(), clientDataHash.Length(), mozilla::fallible);
|
||||
|
||||
if (MOZ_LOG_TEST(gNSSTokenLog, LogLevel::Debug)) {
|
||||
if (MOZ_LOG_TEST(gNSSTokenLogOld, LogLevel::Debug)) {
|
||||
nsAutoCString base64;
|
||||
nsresult rv =
|
||||
Base64URLEncode(signedDataBuf.Length(), signedDataBuf.Elements(),
|
||||
@ -915,7 +913,7 @@ RefPtr<U2FSignPromise> U2FSoftTokenManager::Sign(
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Debug,
|
||||
("U2F Token signing bytes (base64): %s", base64.get()));
|
||||
}
|
||||
|
||||
@ -924,7 +922,7 @@ RefPtr<U2FSignPromise> U2FSoftTokenManager::Sign(
|
||||
signedDataBuf.Length(), privKey.get(),
|
||||
SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
|
||||
if (NS_WARN_IF(srv != SECSuccess)) {
|
||||
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
|
||||
MOZ_LOG(gNSSTokenLogOld, LogLevel::Warning,
|
||||
("Signature failure: %d", PORT_GetError()));
|
||||
return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
@ -21,15 +21,10 @@ class U2FSoftTokenManager final : public U2FTokenTransport {
|
||||
public:
|
||||
explicit U2FSoftTokenManager(uint32_t aCounter);
|
||||
|
||||
RefPtr<U2FRegisterPromise> Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void _ctap2_status_callback(
|
||||
rust_ctap2_status_update_res* status)) override;
|
||||
RefPtr<U2FRegisterPromise> Register(const WebAuthnMakeCredentialInfo& aInfo,
|
||||
bool aForceNoneAttestation) override;
|
||||
|
||||
RefPtr<U2FSignPromise> Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void _ctap2_status_callback(
|
||||
rust_ctap2_status_update_res* status)) override;
|
||||
RefPtr<U2FSignPromise> Sign(const WebAuthnGetAssertionInfo& aInfo) override;
|
||||
|
||||
void Cancel() override;
|
||||
|
||||
|
1018
dom/webauthn/U2FSoftTokenTransport.cpp
Normal file
1018
dom/webauthn/U2FSoftTokenTransport.cpp
Normal file
File diff suppressed because it is too large
Load Diff
53
dom/webauthn/U2FSoftTokenTransport.h
Normal file
53
dom/webauthn/U2FSoftTokenTransport.h
Normal file
@ -0,0 +1,53 @@
|
||||
/* -*- 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_U2FSoftTokenTransport_h
|
||||
#define mozilla_dom_U2FSoftTokenTransport_h
|
||||
|
||||
#include "nsIWebAuthnController.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
|
||||
/*
|
||||
* U2FSoftTokenManager is a software implementation of a secure token manager
|
||||
* for the U2F and WebAuthn APIs.
|
||||
*/
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class U2FSoftTokenTransport final : public nsIWebAuthnTransport {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIWEBAUTHNTRANSPORT
|
||||
|
||||
explicit U2FSoftTokenTransport(uint32_t aCounter);
|
||||
|
||||
private:
|
||||
~U2FSoftTokenTransport() = default;
|
||||
nsresult Init();
|
||||
|
||||
nsresult IsRegistered(const nsTArray<uint8_t>& aKeyHandle,
|
||||
const nsTArray<uint8_t>& aAppParam, bool& aResult);
|
||||
|
||||
bool FindRegisteredKeyHandle(
|
||||
const nsTArray<nsTArray<uint8_t>>& aAppIds,
|
||||
const nsTArray<nsTArray<uint8_t>>& aCredentialIds,
|
||||
/*out*/ nsTArray<uint8_t>& aKeyHandle,
|
||||
/*out*/ nsTArray<uint8_t>& aAppId);
|
||||
|
||||
bool mInitialized;
|
||||
mozilla::UniquePK11SymKey mWrappingKey;
|
||||
|
||||
static const nsCString mSecretNickname;
|
||||
|
||||
nsresult GetOrCreateWrappingKey(const mozilla::UniquePK11SlotInfo& aSlot);
|
||||
uint32_t mCounter;
|
||||
|
||||
nsCOMPtr<nsIWebAuthnController> mController;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // mozilla_dom_U2FSoftTokenTransport_h
|
@ -6,7 +6,6 @@
|
||||
#include "json/json.h"
|
||||
#include "mozilla/dom/U2FTokenManager.h"
|
||||
#include "mozilla/dom/U2FTokenTransport.h"
|
||||
#include "mozilla/dom/CTAPHIDTokenManager.h"
|
||||
#include "mozilla/dom/U2FHIDTokenManager.h"
|
||||
#include "mozilla/dom/U2FSoftTokenManager.h"
|
||||
#include "mozilla/dom/PWebAuthnTransactionParent.h"
|
||||
@ -41,7 +40,6 @@
|
||||
"security.webauth.webauthn_testing_allow_direct_attestation"
|
||||
#define PREF_WEBAUTHN_ANDROID_FIDO2_ENABLED \
|
||||
"security.webauth.webauthn_enable_android_fido2"
|
||||
#define PREF_WEBAUTHN_CTAP2 "security.webauthn.ctap2"
|
||||
namespace mozilla::dom {
|
||||
|
||||
/***********************************************************************
|
||||
@ -58,29 +56,17 @@ static nsIThread* gBackgroundThread;
|
||||
} // namespace
|
||||
|
||||
// Data for WebAuthn UI prompt notifications.
|
||||
static const char16_t kRegisterPromptNotifcation[] =
|
||||
u"{\"action\":\"register\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu,\"is_ctap2\":%s, \"device_selected\":%s}";
|
||||
static const char16_t kRegisterDirectPromptNotifcation[] =
|
||||
u"{\"action\":\"register-direct\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu}";
|
||||
static const char16_t kSignPromptNotifcation[] =
|
||||
u"{\"action\":\"sign\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu,\"is_ctap2\":%s, \"device_selected\":%s}";
|
||||
static const char16_t kCancelPromptNotifcation[] =
|
||||
u"{\"action\":\"cancel\",\"tid\":%llu}";
|
||||
static const char16_t kPinRequiredNotifcation[] =
|
||||
u"{\"action\":\"pin-required\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu,\"wasInvalid\":%s,\"retriesLeft\":%i}";
|
||||
static const char16_t kSelectDeviceNotifcation[] =
|
||||
u"{\"action\":\"select-device\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu}";
|
||||
static const char16_t kSelectSignResultNotifcation[] =
|
||||
u"{\"action\":\"select-sign-result\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu,\"usernames\":[%s]}";
|
||||
static const char16_t kPinErrorNotifications[] =
|
||||
u"{\"action\":\"%s\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu}";
|
||||
static const char16_t kRegisterPromptNotificationU2F[] =
|
||||
u"{\"is_ctap2\":false,\"action\":\"register\",\"tid\":%llu,"
|
||||
u"\"origin\":\"%s\",\"browsingContextId\":%llu,\"device_selected\":%s}";
|
||||
static const char16_t kRegisterDirectPromptNotificationU2F[] =
|
||||
u"{\"is_ctap2\":false,\"action\":\"register-direct\",\"tid\":%llu,"
|
||||
u"\"origin\":\"%s\",\"browsingContextId\":%llu}";
|
||||
static const char16_t kSignPromptNotificationU2F[] =
|
||||
u"{\"is_ctap2\":false,\"action\":\"sign\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu,\"device_selected\":%s}";
|
||||
static const char16_t kCancelPromptNotificationU2F[] =
|
||||
u"{\"is_ctap2\":false,\"action\":\"cancel\",\"tid\":%llu}";
|
||||
|
||||
class U2FPrefManager final : public nsIObserver {
|
||||
private:
|
||||
@ -103,7 +89,6 @@ class U2FPrefManager final : public nsIObserver {
|
||||
PREF_WEBAUTHN_ANDROID_FIDO2_ENABLED);
|
||||
Preferences::AddStrongObserver(gPrefManager,
|
||||
PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
|
||||
Preferences::AddStrongObserver(gPrefManager, PREF_WEBAUTHN_CTAP2);
|
||||
ClearOnShutdown(&gPrefManager, ShutdownPhase::XPCOMShutdownThreads);
|
||||
}
|
||||
return gPrefManager;
|
||||
@ -116,7 +101,7 @@ class U2FPrefManager final : public nsIObserver {
|
||||
return mSoftTokenEnabled;
|
||||
}
|
||||
|
||||
int GetSoftTokenCounter() {
|
||||
uint32_t GetSoftTokenCounter() {
|
||||
MutexAutoLock lock(mPrefMutex);
|
||||
return mSoftTokenCounter;
|
||||
}
|
||||
@ -136,10 +121,7 @@ class U2FPrefManager final : public nsIObserver {
|
||||
return mAllowDirectAttestation;
|
||||
}
|
||||
|
||||
bool GetIsCtap2() {
|
||||
MutexAutoLock lock(mPrefMutex);
|
||||
return mCtap2;
|
||||
}
|
||||
bool GetIsCtap2() { return false; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
Observe(nsISupports* aSubject, const char* aTopic,
|
||||
@ -159,16 +141,14 @@ class U2FPrefManager final : public nsIObserver {
|
||||
Preferences::GetBool(PREF_WEBAUTHN_ANDROID_FIDO2_ENABLED);
|
||||
mAllowDirectAttestation =
|
||||
Preferences::GetBool(PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
|
||||
mCtap2 = Preferences::GetBool(PREF_WEBAUTHN_CTAP2);
|
||||
}
|
||||
|
||||
Mutex mPrefMutex MOZ_UNANNOTATED;
|
||||
bool mSoftTokenEnabled;
|
||||
int mSoftTokenCounter;
|
||||
uint32_t mSoftTokenCounter;
|
||||
bool mUsbTokenEnabled;
|
||||
bool mAndroidFido2Enabled;
|
||||
bool mAllowDirectAttestation;
|
||||
bool mCtap2;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(U2FPrefManager, nsIObserver);
|
||||
@ -235,14 +215,10 @@ void U2FTokenManager::MaybeClearTransaction(
|
||||
void U2FTokenManager::ClearTransaction(bool send_cancel) {
|
||||
if (mLastTransactionId && send_cancel) {
|
||||
// Remove any prompts we might be showing for the current transaction.
|
||||
SendPromptNotification(kCancelPromptNotifcation, mLastTransactionId);
|
||||
SendPromptNotification(kCancelPromptNotificationU2F, mLastTransactionId);
|
||||
}
|
||||
|
||||
mTransactionParent = nullptr;
|
||||
|
||||
// We have to "hang up" in case auth-rs is still waiting for us to send a PIN
|
||||
// so it can exit cleanly
|
||||
status_update_result = nullptr;
|
||||
// Drop managers at the end of all transactions
|
||||
if (mTokenManagerImpl) {
|
||||
mTokenManagerImpl->Drop();
|
||||
@ -291,111 +267,6 @@ void U2FTokenManager::RunSendPromptNotification(const nsString& aJSON) {
|
||||
os->NotifyObservers(self, "webauthn-prompt", aJSON.get()));
|
||||
}
|
||||
|
||||
void U2FTokenManager::StatusUpdateResFreePolicy::operator()(
|
||||
rust_ctap2_status_update_res* p) {
|
||||
rust_ctap2_destroy_status_update_res(p);
|
||||
}
|
||||
|
||||
static void status_callback(rust_ctap2_status_update_res* status) {
|
||||
if (!U2FPrefManager::Get()->GetIsCtap2() || (NS_WARN_IF(!status))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The result will be cleared automatically upon exiting this function,
|
||||
// unless we have a Pin error, then we need it for a callback from JS.
|
||||
// Then we move ownership of it to U2FTokenManager.
|
||||
UniquePtr<rust_ctap2_status_update_res,
|
||||
U2FTokenManager::StatusUpdateResFreePolicy>
|
||||
status_result_update(status);
|
||||
size_t len;
|
||||
if (NS_WARN_IF(!rust_ctap2_status_update_len(status, &len))) {
|
||||
return;
|
||||
}
|
||||
nsCString st;
|
||||
if (NS_WARN_IF(!st.SetLength(len, fallible))) {
|
||||
return;
|
||||
}
|
||||
if (NS_WARN_IF(!rust_ctap2_status_update_copy_json(status, st.Data()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* gInstance = U2FTokenManager::Get();
|
||||
|
||||
Json::Value jsonRoot;
|
||||
Json::Reader reader;
|
||||
if (NS_WARN_IF(!reader.parse(st.Data(), jsonRoot))) {
|
||||
return;
|
||||
}
|
||||
if (NS_WARN_IF(!jsonRoot.isObject())) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString notification_json;
|
||||
|
||||
uint64_t browsingCtxId = gInstance->GetCurrentBrowsingCtxId().value();
|
||||
NS_ConvertUTF16toUTF8 origin(gInstance->GetCurrentOrigin().value());
|
||||
if (jsonRoot.isMember("PinError")) {
|
||||
uint64_t tid = gInstance->GetCurrentTransactionId();
|
||||
bool pinRequired = (jsonRoot["PinError"].isString() &&
|
||||
jsonRoot["PinError"].asString() == "PinRequired");
|
||||
bool pinInvalid = (jsonRoot["PinError"].isObject() &&
|
||||
jsonRoot["PinError"].isMember("InvalidPin"));
|
||||
if (pinRequired || pinInvalid) {
|
||||
bool wasInvalid = false;
|
||||
int retries = -1;
|
||||
if (pinInvalid) {
|
||||
wasInvalid = true;
|
||||
if (jsonRoot["PinError"]["InvalidPin"].isInt()) {
|
||||
retries = jsonRoot["PinError"]["InvalidPin"].asInt();
|
||||
}
|
||||
}
|
||||
gInstance->status_update_result = std::move(status_result_update);
|
||||
nsTextFormatter::ssprintf(notification_json, kPinRequiredNotifcation, tid,
|
||||
origin.get(), browsingCtxId,
|
||||
wasInvalid ? "true" : "false", retries);
|
||||
} else if (jsonRoot["PinError"].isString()) {
|
||||
// Not saving the status_result, so the callback will error out and cancel
|
||||
// the transaction, because these errors are not recoverable by
|
||||
// user-input.
|
||||
if (jsonRoot["PinError"].asString() == "PinAuthBlocked") {
|
||||
// Pin authentication blocked. Device needs power cycle!
|
||||
nsTextFormatter::ssprintf(notification_json, kPinErrorNotifications,
|
||||
"pin-auth-blocked", tid, origin.get(),
|
||||
browsingCtxId);
|
||||
} else if (jsonRoot["PinError"].asString() == "PinBlocked") {
|
||||
// No retries left. Pin blocked. Device needs reset!
|
||||
nsTextFormatter::ssprintf(notification_json, kPinErrorNotifications,
|
||||
"device-blocked", tid, origin.get(),
|
||||
browsingCtxId);
|
||||
}
|
||||
}
|
||||
} else if (jsonRoot.isMember("SelectDeviceNotice")) {
|
||||
nsTextFormatter::ssprintf(notification_json, kSelectDeviceNotifcation,
|
||||
gInstance->GetCurrentTransactionId(),
|
||||
origin.get(), browsingCtxId);
|
||||
} else if (jsonRoot.isMember("DeviceSelected")) {
|
||||
if (gInstance->CurrentTransactionIsRegister()) {
|
||||
nsTextFormatter::ssprintf(notification_json, kRegisterPromptNotifcation,
|
||||
gInstance->GetCurrentTransactionId(),
|
||||
origin.get(), browsingCtxId, "true", "true");
|
||||
} else if (gInstance->CurrentTransactionIsSign()) {
|
||||
nsTextFormatter::ssprintf(notification_json, kSignPromptNotifcation,
|
||||
gInstance->GetCurrentTransactionId(),
|
||||
origin.get(), browsingCtxId, "true", "true");
|
||||
}
|
||||
} else {
|
||||
// No-op for now
|
||||
}
|
||||
|
||||
if (!notification_json.IsEmpty()) {
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<nsString>(
|
||||
"U2FTokenManager::RunSendPromptNotification", gInstance,
|
||||
&U2FTokenManager::RunSendPromptNotification, notification_json));
|
||||
MOZ_ALWAYS_SUCCEEDS(GetMainThreadSerialEventTarget()->Dispatch(
|
||||
r.forget(), NS_DISPATCH_NORMAL));
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<U2FTokenTransport> U2FTokenManager::GetTokenManagerImpl() {
|
||||
MOZ_ASSERT(U2FPrefManager::Get());
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
@ -423,13 +294,7 @@ RefPtr<U2FTokenTransport> U2FTokenManager::GetTokenManagerImpl() {
|
||||
// same time as the softtoken would always win the race to register.
|
||||
// We could support it for signing though...
|
||||
if (pm->GetUsbTokenEnabled()) {
|
||||
U2FTokenTransport* manager;
|
||||
if (U2FPrefManager::Get()->GetIsCtap2()) {
|
||||
manager = new CTAPHIDTokenManager();
|
||||
} else {
|
||||
manager = new U2FHIDTokenManager();
|
||||
}
|
||||
return manager;
|
||||
return new U2FHIDTokenManager();
|
||||
}
|
||||
|
||||
if (pm->GetSoftTokenEnabled()) {
|
||||
@ -498,7 +363,7 @@ void U2FTokenManager::Register(
|
||||
// If the RP request direct attestation, ask the user for permission and
|
||||
// store the transaction info until the user proceeds or cancels.
|
||||
NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
|
||||
SendPromptNotification(kRegisterDirectPromptNotifcation, aTransactionId,
|
||||
SendPromptNotification(kRegisterDirectPromptNotificationU2F, aTransactionId,
|
||||
origin.get(), aTransactionInfo.BrowsingContextId());
|
||||
|
||||
MOZ_ASSERT(mPendingRegisterInfo.isNothing());
|
||||
@ -512,25 +377,21 @@ void U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo,
|
||||
|
||||
// Show a prompt that lets the user cancel the ongoing transaction.
|
||||
NS_ConvertUTF16toUTF8 origin(aInfo.Origin());
|
||||
bool is_ctap2 = U2FPrefManager::Get()->GetIsCtap2();
|
||||
SendPromptNotification(kRegisterPromptNotifcation, mLastTransactionId,
|
||||
origin.get(), aInfo.BrowsingContextId(),
|
||||
is_ctap2 ? "true" : "false", "false");
|
||||
SendPromptNotification(kRegisterPromptNotificationU2F, mLastTransactionId,
|
||||
origin.get(), aInfo.BrowsingContextId(), "false");
|
||||
|
||||
uint64_t tid = mLastTransactionId;
|
||||
|
||||
mTokenManagerImpl->Register(aInfo, aForceNoneAttestation, status_callback)
|
||||
mTokenManagerImpl->Register(aInfo, aForceNoneAttestation)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[tid, is_ctap2](WebAuthnMakeCredentialResult&& aResult) {
|
||||
[tid](WebAuthnMakeCredentialResult&& aResult) {
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->MaybeConfirmRegister(tid, aResult);
|
||||
Telemetry::ScalarAdd(
|
||||
Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
is_ctap2 ? u"CTAPRegisterFinish"_ns : u"U2FRegisterFinish"_ns,
|
||||
1);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"U2FRegisterFinish"_ns, 1);
|
||||
},
|
||||
[tid, is_ctap2](nsresult rv) {
|
||||
[tid](nsresult rv) {
|
||||
MOZ_ASSERT(NS_FAILED(rv));
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
bool shouldCancelActiveDialog = true;
|
||||
@ -539,9 +400,8 @@ void U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo,
|
||||
shouldCancelActiveDialog = false;
|
||||
}
|
||||
mgr->MaybeAbortRegister(tid, rv, shouldCancelActiveDialog);
|
||||
Telemetry::ScalarAdd(
|
||||
Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
is_ctap2 ? u"CTAPRegisterAbort"_ns : u"U2FRegisterAbort"_ns, 1);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"U2FRegisterAbort"_ns, 1);
|
||||
})
|
||||
->Track(mRegisterPromise);
|
||||
}
|
||||
@ -592,41 +452,22 @@ void U2FTokenManager::DoSign(const WebAuthnGetAssertionInfo& aTransactionInfo) {
|
||||
uint64_t browserCtxId = aTransactionInfo.BrowsingContextId();
|
||||
|
||||
// Show a prompt that lets the user cancel the ongoing transaction.
|
||||
bool is_ctap2 = U2FPrefManager::Get()->GetIsCtap2();
|
||||
SendPromptNotification(kSignPromptNotifcation, tid, origin.get(),
|
||||
browserCtxId, is_ctap2 ? "true" : "false", "false");
|
||||
SendPromptNotification(kSignPromptNotificationU2F, tid, origin.get(),
|
||||
browserCtxId, "false");
|
||||
|
||||
mTokenManagerImpl->Sign(aTransactionInfo, status_callback)
|
||||
mTokenManagerImpl->Sign(aTransactionInfo)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[tid, origin, browserCtxId,
|
||||
is_ctap2](nsTArray<WebAuthnGetAssertionResultWrapper>&& aResult) {
|
||||
[tid, origin](nsTArray<WebAuthnGetAssertionResultWrapper>&& aResult) {
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
if (aResult.Length() == 1) {
|
||||
WebAuthnGetAssertionResult result = aResult[0].assertion;
|
||||
mgr->MaybeConfirmSign(tid, result);
|
||||
} else {
|
||||
nsCString res;
|
||||
StringJoinAppend(
|
||||
res, ","_ns, aResult,
|
||||
[](nsACString& dst,
|
||||
const WebAuthnGetAssertionResultWrapper& assertion) {
|
||||
nsCString username =
|
||||
assertion.username.valueOr("<Unknown username>"_ns);
|
||||
nsCString escaped_username;
|
||||
NS_Escape(username, escaped_username, url_XAlphas);
|
||||
dst.Append("\""_ns + escaped_username + "\""_ns);
|
||||
});
|
||||
mgr->mPendingSignResults.Assign(aResult);
|
||||
mgr->SendPromptNotification(kSelectSignResultNotifcation, tid,
|
||||
origin.get(), browserCtxId,
|
||||
res.get());
|
||||
}
|
||||
Telemetry::ScalarAdd(
|
||||
Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
is_ctap2 ? u"CTAPSignFinish"_ns : u"U2FSignFinish"_ns, 1);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"U2FSignFinish"_ns, 1);
|
||||
},
|
||||
[tid, is_ctap2](nsresult rv) {
|
||||
[tid](nsresult rv) {
|
||||
MOZ_ASSERT(NS_FAILED(rv));
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
bool shouldCancelActiveDialog = true;
|
||||
@ -635,9 +476,8 @@ void U2FTokenManager::DoSign(const WebAuthnGetAssertionInfo& aTransactionInfo) {
|
||||
shouldCancelActiveDialog = false;
|
||||
}
|
||||
mgr->MaybeAbortSign(tid, rv, shouldCancelActiveDialog);
|
||||
Telemetry::ScalarAdd(
|
||||
Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
is_ctap2 ? u"CTAPSignAbort"_ns : u"U2FSignAbort"_ns, 1);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"U2FSignAbort"_ns, 1);
|
||||
})
|
||||
->Track(mSignPromise);
|
||||
}
|
||||
@ -693,66 +533,6 @@ U2FTokenManager::ResumeRegister(uint64_t aTransactionId,
|
||||
return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
U2FTokenManager::ResumeWithSelectedSignResult(uint64_t aTransactionId,
|
||||
uint64_t idx) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!gBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, uint64_t>(
|
||||
"U2FTokenManager::RunResumeWithSelectedSignResult", this,
|
||||
&U2FTokenManager::RunResumeWithSelectedSignResult, aTransactionId, idx));
|
||||
|
||||
return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void U2FTokenManager::RunResumeWithSelectedSignResult(uint64_t aTransactionId,
|
||||
uint64_t idx) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (NS_WARN_IF(mPendingSignResults.IsEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(mPendingSignResults.Length() <= idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLastTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionResult result = mPendingSignResults[idx].assertion;
|
||||
MaybeConfirmSign(aTransactionId, result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
U2FTokenManager::PinCallback(const nsACString& aPin) {
|
||||
if (!U2FPrefManager::Get()->GetIsCtap2()) {
|
||||
// Not used in CTAP1
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!gBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
// Move the result here locally, so it will be freed either way
|
||||
UniquePtr<rust_ctap2_status_update_res,
|
||||
U2FTokenManager::StatusUpdateResFreePolicy>
|
||||
result = nullptr;
|
||||
std::swap(result, status_update_result);
|
||||
|
||||
if (NS_WARN_IF(
|
||||
!rust_ctap2_status_update_send_pin(result.get(), aPin.Data()))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void U2FTokenManager::RunResumeRegister(uint64_t aTransactionId,
|
||||
bool aForceNoneAttestation) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
@ -807,9 +587,6 @@ void U2FTokenManager::RunCancel(uint64_t aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to "hang up" in case auth-rs is still waiting for us to send a PIN
|
||||
// so it can exit cleanly
|
||||
status_update_result = nullptr;
|
||||
// Cancel the request.
|
||||
mTokenManagerImpl->Cancel();
|
||||
|
||||
|
@ -55,17 +55,6 @@ class U2FTokenManager final : public nsIU2FTokenManager {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
Maybe<uint64_t> GetCurrentBrowsingCtxId() {
|
||||
if (mPendingRegisterInfo.isSome()) {
|
||||
return Some(mPendingRegisterInfo.value().BrowsingContextId());
|
||||
}
|
||||
|
||||
if (mPendingSignInfo.isSome()) {
|
||||
return Some(mPendingSignInfo.value().BrowsingContextId());
|
||||
}
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
uint64_t GetCurrentTransactionId() { return mLastTransactionId; }
|
||||
|
||||
bool CurrentTransactionIsRegister() { return mPendingRegisterInfo.isSome(); }
|
||||
@ -78,13 +67,6 @@ class U2FTokenManager final : public nsIU2FTokenManager {
|
||||
// The main thread runnable function for "SendPromptNotification".
|
||||
void RunSendPromptNotification(const nsString& aJSON);
|
||||
|
||||
struct StatusUpdateResFreePolicy {
|
||||
void operator()(rust_ctap2_status_update_res* p);
|
||||
};
|
||||
UniquePtr<rust_ctap2_status_update_res,
|
||||
U2FTokenManager::StatusUpdateResFreePolicy>
|
||||
status_update_result = nullptr;
|
||||
|
||||
private:
|
||||
U2FTokenManager();
|
||||
~U2FTokenManager() = default;
|
||||
|
@ -16,8 +16,6 @@
|
||||
* transport types.
|
||||
*/
|
||||
|
||||
struct rust_ctap2_status_update_res;
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class WebAuthnGetAssertionResultWrapper {
|
||||
@ -37,12 +35,10 @@ class U2FTokenTransport {
|
||||
U2FTokenTransport() = default;
|
||||
|
||||
virtual RefPtr<U2FRegisterPromise> Register(
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
|
||||
void status_callback(rust_ctap2_status_update_res*)) = 0;
|
||||
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) = 0;
|
||||
|
||||
virtual RefPtr<U2FSignPromise> Sign(
|
||||
const WebAuthnGetAssertionInfo& aInfo,
|
||||
void status_callback(rust_ctap2_status_update_res*)) = 0;
|
||||
const WebAuthnGetAssertionInfo& aInfo) = 0;
|
||||
|
||||
virtual void Cancel() = 0;
|
||||
|
||||
|
745
dom/webauthn/WebAuthnController.cpp
Normal file
745
dom/webauthn/WebAuthnController.cpp
Normal file
@ -0,0 +1,745 @@
|
||||
/* -*- 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 "json/json.h"
|
||||
#include "mozilla/dom/WebAuthnController.h"
|
||||
#include "mozilla/dom/PWebAuthnTransactionParent.h"
|
||||
#include "mozilla/dom/WebAuthnUtil.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/StaticPrefs_security.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsEscape.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsTextFormatter.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
#include "AuthrsTransport.h"
|
||||
#include "CtapArgs.h"
|
||||
#include "CtapResults.h"
|
||||
#include "U2FSoftTokenTransport.h"
|
||||
#include "WebAuthnEnumStrings.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
# include "mozilla/dom/AndroidWebAuthnTokenManager.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
/***********************************************************************
|
||||
* Statics
|
||||
**********************************************************************/
|
||||
|
||||
namespace {
|
||||
static mozilla::LazyLogModule gWebAuthnControllerLog("webauthncontroller");
|
||||
StaticRefPtr<WebAuthnController> gWebAuthnController;
|
||||
static nsIThread* gWebAuthnBackgroundThread;
|
||||
} // namespace
|
||||
|
||||
// Data for WebAuthn UI prompt notifications.
|
||||
static const char16_t kRegisterPromptNotification[] =
|
||||
u"{\"is_ctap2\":true,\"action\":\"register\",\"tid\":%llu,"
|
||||
u"\"origin\":\"%s\",\"browsingContextId\":%llu,\"device_selected\":%s}";
|
||||
static const char16_t kRegisterDirectPromptNotification[] =
|
||||
u"{\"is_ctap2\":true,\"action\":\"register-direct\",\"tid\":%llu,"
|
||||
u"\"origin\":\"%s\",\"browsingContextId\":%llu}";
|
||||
static const char16_t kSignPromptNotification[] =
|
||||
u"{\"is_ctap2\":true,\"action\":\"sign\",\"tid\":%llu,\"origin\":\"%s\","
|
||||
u"\"browsingContextId\":%llu,\"device_selected\":%s}";
|
||||
static const char16_t kCancelPromptNotification[] =
|
||||
u"{\"is_ctap2\":true,\"action\":\"cancel\",\"tid\":%llu}";
|
||||
static const char16_t kSelectSignResultNotification[] =
|
||||
u"{\"is_ctap2\":true,\"action\":\"select-sign-result\",\"tid\":%llu,"
|
||||
u"\"origin\":\"%s\",\"browsingContextId\":%llu,\"usernames\":[%s]}";
|
||||
|
||||
/***********************************************************************
|
||||
* U2FManager Implementation
|
||||
**********************************************************************/
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebAuthnController, nsIWebAuthnController,
|
||||
nsIU2FTokenManager);
|
||||
|
||||
WebAuthnController::WebAuthnController() : mTransactionParent(nullptr) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
// Create on the main thread to make sure ClearOnShutdown() works.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
// static
|
||||
void WebAuthnController::Initialize() {
|
||||
if (!XRE_IsParentProcess()) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!gWebAuthnController);
|
||||
gWebAuthnController = new WebAuthnController();
|
||||
ClearOnShutdown(&gWebAuthnController);
|
||||
}
|
||||
|
||||
// static
|
||||
WebAuthnController* WebAuthnController::Get() {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
return gWebAuthnController;
|
||||
}
|
||||
|
||||
void WebAuthnController::AbortTransaction(const uint64_t& aTransactionId,
|
||||
const nsresult& aError,
|
||||
bool shouldCancelActiveDialog) {
|
||||
if (mTransactionParent && mTransaction.isSome() && aTransactionId > 0 &&
|
||||
aTransactionId == mTransaction.ref().mTransactionId) {
|
||||
Unused << mTransactionParent->SendAbort(aTransactionId, aError);
|
||||
ClearTransaction(shouldCancelActiveDialog);
|
||||
}
|
||||
}
|
||||
|
||||
void WebAuthnController::AbortOngoingTransaction() {
|
||||
if (mTransaction.isSome()) {
|
||||
AbortTransaction(mTransaction.ref().mTransactionId, NS_ERROR_DOM_ABORT_ERR,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void WebAuthnController::MaybeClearTransaction(
|
||||
PWebAuthnTransactionParent* aParent) {
|
||||
// Only clear if we've been requested to do so by our current transaction
|
||||
// parent.
|
||||
if (mTransactionParent == aParent) {
|
||||
ClearTransaction(true);
|
||||
}
|
||||
}
|
||||
|
||||
void WebAuthnController::ClearTransaction(bool cancel_prompt) {
|
||||
if (cancel_prompt && mTransaction.isSome() &&
|
||||
mTransaction.ref().mTransactionId > 0) {
|
||||
// Remove any prompts we might be showing for the current transaction.
|
||||
SendPromptNotification(kCancelPromptNotification,
|
||||
mTransaction.ref().mTransactionId);
|
||||
}
|
||||
mTransactionParent = nullptr;
|
||||
mTransportImpl = nullptr;
|
||||
|
||||
// Forget any pending registration.
|
||||
mPendingRegisterInfo.reset();
|
||||
mPendingSignInfo.reset();
|
||||
mPendingSignResults.Clear();
|
||||
mTransaction.reset();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void WebAuthnController::SendPromptNotification(const char16_t* aFormat,
|
||||
T... aArgs) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
nsAutoString json;
|
||||
nsTextFormatter::ssprintf(json, aFormat, aArgs...);
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<nsString>(
|
||||
"WebAuthnController::RunSendPromptNotification", this,
|
||||
&WebAuthnController::RunSendPromptNotification, json));
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(GetMainThreadSerialEventTarget()->Dispatch(
|
||||
r.forget(), NS_DISPATCH_NORMAL));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebAuthnController::SendPromptNotificationPreformatted(
|
||||
uint64_t aTransactionId, const nsACString& aJson) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<nsString>(
|
||||
"WebAuthnController::RunSendPromptNotification", this,
|
||||
&WebAuthnController::RunSendPromptNotification,
|
||||
NS_ConvertUTF8toUTF16(aJson)));
|
||||
MOZ_ALWAYS_SUCCEEDS(GetMainThreadSerialEventTarget()->Dispatch(
|
||||
r.forget(), NS_DISPATCH_NORMAL));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void WebAuthnController::RunSendPromptNotification(const nsString& aJSON) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (NS_WARN_IF(!os)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWebAuthnController> self = this;
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
os->NotifyObservers(self, "webauthn-prompt", aJSON.get()));
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWebAuthnTransport> WebAuthnController::GetTransportImpl() {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (mTransportImpl) {
|
||||
return mTransportImpl;
|
||||
}
|
||||
|
||||
/* Enable in Bug 1819414 */
|
||||
#if 0
|
||||
# ifdef MOZ_WIDGET_ANDROID
|
||||
// On Android, prefer the platform support if enabled.
|
||||
if (StaticPrefs::security_webauth_webauthn_enable_android_fido2()) {
|
||||
nsCOMPtr<nsIWebAuthnTransport> transport = AndroidWebAuthnTokenManager::GetInstance();
|
||||
transport->SetController(this);
|
||||
return transport;
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Prefer the HW token, even if the softtoken is enabled too.
|
||||
// We currently don't support soft and USB tokens enabled at the
|
||||
// same time as the softtoken would always win the race to register.
|
||||
// We could support it for signing though...
|
||||
if (StaticPrefs::security_webauth_webauthn_enable_usbtoken()) {
|
||||
nsCOMPtr<nsIWebAuthnTransport> transport = NewAuthrsTransport();
|
||||
transport->SetController(this);
|
||||
return transport;
|
||||
}
|
||||
|
||||
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
|
||||
nsCOMPtr<nsIWebAuthnTransport> transport = new U2FSoftTokenTransport(
|
||||
StaticPrefs::security_webauth_softtoken_counter());
|
||||
transport->SetController(this);
|
||||
return transport;
|
||||
}
|
||||
|
||||
// TODO Use WebAuthnRequest to aggregate results from all transports,
|
||||
// once we have multiple HW transport types.
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WebAuthnController::Cancel(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const Tainted<uint64_t>& aTransactionId) {
|
||||
// The last transaction ID also suffers from the issue described in Bug
|
||||
// 1696159. A content process could cancel another content processes
|
||||
// transaction by guessing the last transaction ID.
|
||||
if (mTransactionParent != aTransactionParent || mTransaction.isNothing() ||
|
||||
!MOZ_IS_VALID(aTransactionId,
|
||||
mTransaction.ref().mTransactionId == aTransactionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mTransportImpl) {
|
||||
mTransportImpl->Cancel();
|
||||
}
|
||||
|
||||
ClearTransaction(true);
|
||||
}
|
||||
|
||||
void WebAuthnController::Register(
|
||||
PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId, const WebAuthnMakeCredentialInfo& aInfo) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
MOZ_LOG(gWebAuthnControllerLog, LogLevel::Debug,
|
||||
("WebAuthnController::Register"));
|
||||
MOZ_ASSERT(aTransactionId > 0);
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
gWebAuthnBackgroundThread = NS_GetCurrentThread();
|
||||
MOZ_ASSERT(gWebAuthnBackgroundThread, "This should never be null!");
|
||||
}
|
||||
|
||||
AbortOngoingTransaction();
|
||||
mTransactionParent = aTransactionParent;
|
||||
|
||||
/* We could refactor to defer the hashing here */
|
||||
CryptoBuffer rpIdHash, clientDataHash;
|
||||
NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
|
||||
nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
|
||||
clientDataHash);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
// We haven't set mTransaction yet, so we can't use AbortTransaction
|
||||
Unused << mTransactionParent->SendAbort(aTransactionId,
|
||||
NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hold on to any state that we need to finish the transaction.
|
||||
mTransaction = Some(
|
||||
Transaction(aTransactionId, rpIdHash, Nothing(), aInfo.ClientDataJSON()));
|
||||
|
||||
MOZ_ASSERT(mPendingRegisterInfo.isNothing());
|
||||
mPendingRegisterInfo = Some(aInfo);
|
||||
|
||||
// Determine whether direct attestation was requested.
|
||||
bool noneAttestationRequested = true;
|
||||
|
||||
// On Android, let's always reject direct attestations until we have a
|
||||
// mechanism to solicit user consent, from Bug 1550164
|
||||
#ifndef MOZ_WIDGET_ANDROID
|
||||
const auto& extra = aInfo.Extra().ref();
|
||||
|
||||
// The default attestation type is "none", so set
|
||||
// noneAttestationRequested=false only if the RP's preference matches one of
|
||||
// the other known types. This needs to be reviewed if values are added to
|
||||
// the AttestationConveyancePreference enum.
|
||||
const nsString& attestation = extra.attestationConveyancePreference();
|
||||
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2);
|
||||
if (attestation.EqualsLiteral(
|
||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT) ||
|
||||
attestation.EqualsLiteral(
|
||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT) ||
|
||||
attestation.EqualsLiteral(
|
||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE)) {
|
||||
noneAttestationRequested = false;
|
||||
}
|
||||
#endif // not MOZ_WIDGET_ANDROID
|
||||
|
||||
// Start a register request immediately if direct attestation
|
||||
// wasn't requested or the test pref is set.
|
||||
if (noneAttestationRequested ||
|
||||
StaticPrefs::
|
||||
security_webauth_webauthn_testing_allow_direct_attestation()) {
|
||||
DoRegister(aInfo, noneAttestationRequested);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the RP request direct attestation, ask the user for permission and
|
||||
// store the transaction info until the user proceeds or cancels.
|
||||
NS_ConvertUTF16toUTF8 origin(aInfo.Origin());
|
||||
SendPromptNotification(kRegisterDirectPromptNotification, aTransactionId,
|
||||
origin.get(), aInfo.BrowsingContextId());
|
||||
}
|
||||
|
||||
void WebAuthnController::DoRegister(const WebAuthnMakeCredentialInfo& aInfo,
|
||||
bool aForceNoneAttestation) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(mTransaction.isSome());
|
||||
if (NS_WARN_IF(mTransaction.isNothing())) {
|
||||
// Clear prompt?
|
||||
return;
|
||||
}
|
||||
|
||||
// Show a prompt that lets the user cancel the ongoing transaction.
|
||||
NS_ConvertUTF16toUTF8 origin(aInfo.Origin());
|
||||
SendPromptNotification(kRegisterPromptNotification,
|
||||
mTransaction.ref().mTransactionId, origin.get(),
|
||||
aInfo.BrowsingContextId(), "false");
|
||||
|
||||
RefPtr<CtapRegisterArgs> args(
|
||||
new CtapRegisterArgs(aInfo, aForceNoneAttestation));
|
||||
|
||||
mTransportImpl = GetTransportImpl();
|
||||
if (!mTransportImpl) {
|
||||
AbortTransaction(mTransaction.ref().mTransactionId,
|
||||
NS_ERROR_DOM_NOT_ALLOWED_ERR, true);
|
||||
return;
|
||||
}
|
||||
nsresult rv = mTransportImpl->MakeCredential(
|
||||
mTransaction.ref().mTransactionId, aInfo.BrowsingContextId(), args);
|
||||
if (NS_FAILED(rv)) {
|
||||
AbortTransaction(mTransaction.ref().mTransactionId, rv, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebAuthnController::ResumeRegister(uint64_t aTransactionId,
|
||||
bool aForceNoneAttestation) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, bool>(
|
||||
"WebAuthnController::RunResumeRegister", this,
|
||||
&WebAuthnController::RunResumeRegister, aTransactionId,
|
||||
aForceNoneAttestation));
|
||||
|
||||
return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void WebAuthnController::RunResumeRegister(uint64_t aTransactionId,
|
||||
bool aForceNoneAttestation) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (NS_WARN_IF(mPendingRegisterInfo.isNothing())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mTransaction.isNothing() ||
|
||||
mTransaction.ref().mTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resume registration and cleanup.
|
||||
DoRegister(mPendingRegisterInfo.ref(), aForceNoneAttestation);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebAuthnController::FinishRegister(uint64_t aTransactionId,
|
||||
nsICtapRegisterResult* aResult) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
nsCOMPtr<nsIRunnable> r(
|
||||
NewRunnableMethod<uint64_t, RefPtr<nsICtapRegisterResult>>(
|
||||
"WebAuthnController::RunFinishRegister", this,
|
||||
&WebAuthnController::RunFinishRegister, aTransactionId, aResult));
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void WebAuthnController::RunFinishRegister(
|
||||
uint64_t aTransactionId, const RefPtr<nsICtapRegisterResult>& aResult) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mTransaction.isNothing() ||
|
||||
aTransactionId != mTransaction.ref().mTransactionId) {
|
||||
// The previous transaction was likely cancelled from the prompt.
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsresult status;
|
||||
rv = aResult->GetStatus(&status);
|
||||
if (NS_WARN_IF(NS_FAILED(rv)) || NS_FAILED(status)) {
|
||||
bool shouldCancelActiveDialog = true;
|
||||
if (status == NS_ERROR_DOM_OPERATION_ERR) {
|
||||
// PIN-related errors. Let the dialog show to inform the user
|
||||
shouldCancelActiveDialog = false;
|
||||
}
|
||||
AbortTransaction(aTransactionId, status, shouldCancelActiveDialog);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"CTAPRegisterAbort"_ns, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCString clientDataJson;
|
||||
rv = aResult->GetClientDataJSON(clientDataJson);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> attObj;
|
||||
rv = aResult->GetAttestationObject(attObj);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> credentialId;
|
||||
rv = aResult->GetCredentialId(credentialId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
nsTArray<uint8_t> regData; /* Only used in U2F */
|
||||
WebAuthnMakeCredentialResult result(clientDataJson, attObj, credentialId,
|
||||
regData, extensions);
|
||||
|
||||
Unused << mTransactionParent->SendConfirmRegister(aTransactionId, result);
|
||||
ClearTransaction(true);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"CTAPRegisterFinish"_ns, 1);
|
||||
}
|
||||
|
||||
void WebAuthnController::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnGetAssertionInfo& aInfo) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
MOZ_LOG(gWebAuthnControllerLog, LogLevel::Debug, ("WebAuthnSign"));
|
||||
MOZ_ASSERT(aTransactionId > 0);
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
gWebAuthnBackgroundThread = NS_GetCurrentThread();
|
||||
MOZ_ASSERT(gWebAuthnBackgroundThread, "This should never be null!");
|
||||
}
|
||||
|
||||
AbortOngoingTransaction();
|
||||
mTransactionParent = aTransactionParent;
|
||||
|
||||
/* We could refactor to defer the hashing here */
|
||||
CryptoBuffer rpIdHash, clientDataHash;
|
||||
NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
|
||||
nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
|
||||
clientDataHash);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
// We haven't set mTransaction yet, so we can't use AbortTransaction
|
||||
Unused << mTransactionParent->SendAbort(aTransactionId,
|
||||
NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
Maybe<nsTArray<uint8_t>> appIdHash = Nothing();
|
||||
if (aInfo.Extra().isSome()) {
|
||||
const auto& extra = aInfo.Extra().ref();
|
||||
for (const WebAuthnExtension& ext : extra.Extensions()) {
|
||||
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
|
||||
appIdHash = Some(ext.get_WebAuthnExtensionAppId().AppId().Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hold on to any state that we need to finish the transaction.
|
||||
mTransaction = Some(
|
||||
Transaction(aTransactionId, rpIdHash, appIdHash, aInfo.ClientDataJSON()));
|
||||
|
||||
mPendingSignInfo = Some(aInfo);
|
||||
|
||||
NS_ConvertUTF16toUTF8 origin(aInfo.Origin());
|
||||
|
||||
// Show a prompt that lets the user cancel the ongoing transaction.
|
||||
SendPromptNotification(kSignPromptNotification,
|
||||
mTransaction.ref().mTransactionId, origin.get(),
|
||||
aInfo.BrowsingContextId(), "false");
|
||||
|
||||
RefPtr<CtapSignArgs> args(new CtapSignArgs(aInfo));
|
||||
|
||||
mTransportImpl = GetTransportImpl();
|
||||
if (!mTransportImpl) {
|
||||
AbortTransaction(mTransaction.ref().mTransactionId,
|
||||
NS_ERROR_DOM_NOT_ALLOWED_ERR, true);
|
||||
return;
|
||||
}
|
||||
|
||||
rv = mTransportImpl->GetAssertion(mTransaction.ref().mTransactionId,
|
||||
aInfo.BrowsingContextId(), args.get());
|
||||
if (NS_FAILED(rv)) {
|
||||
AbortTransaction(mTransaction.ref().mTransactionId, rv, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebAuthnController::FinishSign(
|
||||
uint64_t aTransactionId, const nsACString& aClientDataJson,
|
||||
const nsTArray<RefPtr<nsICtapSignResult>>& aResult) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
nsTArray<RefPtr<nsICtapSignResult>> ownedResult = aResult.Clone();
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(
|
||||
NewRunnableMethod<uint64_t, nsCString,
|
||||
nsTArray<RefPtr<nsICtapSignResult>>>(
|
||||
"WebAuthnController::RunFinishSign", this,
|
||||
&WebAuthnController::RunFinishSign, aTransactionId, aClientDataJson,
|
||||
std::move(ownedResult)));
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void WebAuthnController::RunFinishSign(
|
||||
uint64_t aTransactionId, const nsACString& aClientDataJson,
|
||||
const nsTArray<RefPtr<nsICtapSignResult>>& aResult) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mTransaction.isNothing() ||
|
||||
aTransactionId != mTransaction.ref().mTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aResult.Length() == 0) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR, true);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"CTAPSignAbort"_ns, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aResult.Length() == 1) {
|
||||
nsresult status;
|
||||
aResult[0]->GetStatus(&status);
|
||||
if (NS_FAILED(status)) {
|
||||
bool shouldCancelActiveDialog = true;
|
||||
if (status == NS_ERROR_DOM_OPERATION_ERR) {
|
||||
// PIN-related errors, e.g. blocked token. Let the dialog show to inform
|
||||
// the user
|
||||
shouldCancelActiveDialog = false;
|
||||
}
|
||||
AbortTransaction(aTransactionId, status, shouldCancelActiveDialog);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"CTAPSignAbort"_ns, 1);
|
||||
return;
|
||||
}
|
||||
mPendingSignResults = aResult.Clone();
|
||||
mTransaction.ref().mClientDataJSON = aClientDataJson;
|
||||
RunResumeWithSelectedSignResult(aTransactionId, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we more than one assertion, all of them should have OK status.
|
||||
for (const auto& assertion : aResult) {
|
||||
nsresult status;
|
||||
assertion->GetStatus(&status);
|
||||
if (NS_WARN_IF(NS_FAILED(status))) {
|
||||
AbortTransaction(aTransactionId, status, true);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"CTAPSignAbort"_ns, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsCString usernames;
|
||||
StringJoinAppend(
|
||||
usernames, ","_ns, aResult,
|
||||
[](nsACString& dst, const RefPtr<nsICtapSignResult>& assertion) {
|
||||
nsCString username;
|
||||
nsresult rv = assertion->GetUserName(username);
|
||||
if (NS_FAILED(rv)) {
|
||||
username.Assign("<Unknown username>");
|
||||
}
|
||||
nsCString escaped_username;
|
||||
NS_Escape(username, escaped_username, url_XAlphas);
|
||||
dst.Append("\""_ns + escaped_username + "\""_ns);
|
||||
});
|
||||
|
||||
mPendingSignResults = aResult.Clone();
|
||||
mTransaction.ref().mClientDataJSON = aClientDataJson;
|
||||
NS_ConvertUTF16toUTF8 origin(mPendingSignInfo.ref().Origin());
|
||||
SendPromptNotification(kSelectSignResultNotification,
|
||||
mTransaction.ref().mTransactionId, origin.get(),
|
||||
mPendingSignInfo.ref().BrowsingContextId(),
|
||||
usernames.get());
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebAuthnController::SignatureSelectionCallback(uint64_t aTransactionId,
|
||||
uint64_t idx) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, uint64_t>(
|
||||
"WebAuthnController::RunResumeWithSelectedSignResult", this,
|
||||
&WebAuthnController::RunResumeWithSelectedSignResult, aTransactionId,
|
||||
idx));
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void WebAuthnController::RunResumeWithSelectedSignResult(
|
||||
uint64_t aTransactionId, uint64_t idx) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
if (mTransaction.isNothing() ||
|
||||
mTransaction.ref().mTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(mPendingSignResults.Length() <= idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<nsICtapSignResult>& selected = mPendingSignResults[idx];
|
||||
|
||||
nsTArray<uint8_t> credentialId;
|
||||
nsresult rv = selected->GetCredentialId(credentialId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> signature;
|
||||
rv = selected->GetSignature(signature);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> authenticatorData;
|
||||
rv = selected->GetAuthenticatorData(authenticatorData);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> rpIdHash;
|
||||
rv = selected->GetRpIdHash(rpIdHash);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> userHandle;
|
||||
Unused << selected->GetUserHandle(userHandle); // optional
|
||||
|
||||
nsTArray<WebAuthnExtensionResult> extensions;
|
||||
if (mTransaction.ref().mAppIdHash.isSome()) {
|
||||
bool usedAppId = (rpIdHash == mTransaction.ref().mAppIdHash.ref());
|
||||
extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> signatureData; // Only used in U2F
|
||||
WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON,
|
||||
credentialId, signature, authenticatorData,
|
||||
extensions, signatureData, userHandle);
|
||||
|
||||
Unused << mTransactionParent->SendConfirmSign(aTransactionId, result);
|
||||
ClearTransaction(true);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
u"CTAPSignFinish"_ns, 1);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebAuthnController::PinCallback(uint64_t aTransactionId,
|
||||
const nsACString& aPin) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, nsCString>(
|
||||
"WebAuthnController::RunPinCallback", this,
|
||||
&WebAuthnController::RunPinCallback, aTransactionId, aPin));
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void WebAuthnController::RunPinCallback(uint64_t aTransactionId,
|
||||
const nsCString& aPin) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
mTransportImpl->PinCallback(aTransactionId, aPin);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebAuthnController::Cancel(uint64_t aTransactionId) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t>(
|
||||
"WebAuthnController::RunCancel", this, &WebAuthnController::RunCancel,
|
||||
aTransactionId));
|
||||
|
||||
if (!gWebAuthnBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void WebAuthnController::RunCancel(uint64_t aTransactionId) {
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (mTransaction.isNothing() ||
|
||||
mTransaction.ref().mTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel the request.
|
||||
if (mTransportImpl) {
|
||||
mTransportImpl->Cancel();
|
||||
}
|
||||
|
||||
// Reject the promise.
|
||||
AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR, true);
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
130
dom/webauthn/WebAuthnController.h
Normal file
130
dom/webauthn/WebAuthnController.h
Normal file
@ -0,0 +1,130 @@
|
||||
/* -*- 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_WebAuthnController_h
|
||||
#define mozilla_dom_WebAuthnController_h
|
||||
|
||||
#include "nsIWebAuthnController.h"
|
||||
#include "mozilla/dom/PWebAuthnTransaction.h"
|
||||
#include "mozilla/Tainting.h"
|
||||
|
||||
/*
|
||||
* Parent process manager for WebAuthn API transactions. Handles process
|
||||
* transactions from all content processes, make sure only one transaction is
|
||||
* live at any time. Manages access to hardware and software based key systems.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class WebAuthnController final : public nsIWebAuthnController {
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIU2FTOKENMANAGER
|
||||
NS_DECL_NSIWEBAUTHNCONTROLLER
|
||||
|
||||
static void Initialize();
|
||||
static WebAuthnController* Get();
|
||||
void Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnMakeCredentialInfo& aInfo);
|
||||
|
||||
void Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnGetAssertionInfo& aInfo);
|
||||
|
||||
void Cancel(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const Tainted<uint64_t>& aTransactionId);
|
||||
|
||||
void MaybeClearTransaction(PWebAuthnTransactionParent* aParent);
|
||||
|
||||
uint64_t GetCurrentTransactionId() {
|
||||
return mTransaction.isNothing() ? 0 : mTransaction.ref().mTransactionId;
|
||||
}
|
||||
|
||||
bool CurrentTransactionIsRegister() { return mPendingRegisterInfo.isSome(); }
|
||||
|
||||
bool CurrentTransactionIsSign() { return mPendingSignInfo.isSome(); }
|
||||
|
||||
// Sends a "webauthn-prompt" observer notification with the given data.
|
||||
template <typename... T>
|
||||
void SendPromptNotification(const char16_t* aFormat, T... aArgs);
|
||||
|
||||
// Same as SendPromptNotification, but with the already formatted string
|
||||
// void SendPromptNotificationPreformatted(const nsACString& aJSON);
|
||||
// The main thread runnable function for "SendPromptNotification".
|
||||
void RunSendPromptNotification(const nsString& aJSON);
|
||||
|
||||
private:
|
||||
WebAuthnController();
|
||||
~WebAuthnController() = default;
|
||||
nsCOMPtr<nsIWebAuthnTransport> GetTransportImpl();
|
||||
|
||||
void AbortTransaction(const uint64_t& aTransactionId, const nsresult& aError,
|
||||
bool shouldCancelActiveDialog);
|
||||
void AbortOngoingTransaction();
|
||||
void ClearTransaction(bool cancel_prompt);
|
||||
|
||||
void DoRegister(const WebAuthnMakeCredentialInfo& aInfo,
|
||||
bool aForceNoneAttestation);
|
||||
void DoSign(const WebAuthnGetAssertionInfo& aTransactionInfo);
|
||||
|
||||
void RunFinishRegister(uint64_t aTransactionId,
|
||||
const RefPtr<nsICtapRegisterResult>& aResult);
|
||||
void RunFinishSign(uint64_t aTransactionId, const nsACString& aClientDataJson,
|
||||
const nsTArray<RefPtr<nsICtapSignResult>>& aResult);
|
||||
|
||||
// The main thread runnable function for "nsIU2FTokenManager.ResumeRegister".
|
||||
void RunResumeRegister(uint64_t aTransactionId, bool aForceNoneAttestation);
|
||||
void RunResumeSign(uint64_t aTransactionId);
|
||||
void RunResumeWithSelectedSignResult(uint64_t aTransactionId, uint64_t idx);
|
||||
void RunPinCallback(uint64_t aTransactionId, const nsCString& aPin);
|
||||
|
||||
// The main thread runnable function for "nsIU2FTokenManager.Cancel".
|
||||
void RunCancel(uint64_t aTransactionId);
|
||||
|
||||
// Using a raw pointer here, as the lifetime of the IPC object is managed by
|
||||
// the PBackground protocol code. This means we cannot be left holding an
|
||||
// invalid IPC protocol object after the transaction is finished.
|
||||
PWebAuthnTransactionParent* mTransactionParent;
|
||||
|
||||
nsCOMPtr<nsIWebAuthnTransport> mTransportImpl;
|
||||
|
||||
// Pending registration info while we wait for user input.
|
||||
Maybe<WebAuthnMakeCredentialInfo> mPendingRegisterInfo;
|
||||
|
||||
// Pending registration info while we wait for user input.
|
||||
Maybe<WebAuthnGetAssertionInfo> mPendingSignInfo;
|
||||
|
||||
nsTArray<RefPtr<nsICtapSignResult>> mPendingSignResults;
|
||||
|
||||
class Transaction {
|
||||
public:
|
||||
Transaction(uint64_t aTransactionId, const nsTArray<uint8_t>& aRpIdHash,
|
||||
const Maybe<nsTArray<uint8_t>>& aAppIdHash,
|
||||
const nsCString& aClientDataJSON,
|
||||
bool aForceNoneAttestation = false)
|
||||
: mTransactionId(aTransactionId),
|
||||
mRpIdHash(aRpIdHash.Clone()),
|
||||
mClientDataJSON(aClientDataJSON) {
|
||||
if (aAppIdHash.isSome()) {
|
||||
mAppIdHash = Some(aAppIdHash.ref().Clone());
|
||||
} else {
|
||||
mAppIdHash = Nothing();
|
||||
}
|
||||
}
|
||||
uint64_t mTransactionId;
|
||||
nsTArray<uint8_t> mRpIdHash;
|
||||
Maybe<nsTArray<uint8_t>> mAppIdHash;
|
||||
nsCString mClientDataJSON;
|
||||
};
|
||||
|
||||
Maybe<Transaction> mTransaction;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // mozilla_dom_U2FTokenManager_h
|
@ -10,6 +10,7 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "WebAuthnCoseIdentifiers.h"
|
||||
#include "WebAuthnEnumStrings.h"
|
||||
#include "WebAuthnTransportIdentifiers.h"
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/dom/AuthenticatorAssertionResponse.h"
|
||||
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
|
||||
@ -565,16 +566,16 @@ already_AddRefed<Promise> WebAuthnManager::GetAssertion(
|
||||
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2);
|
||||
for (const nsAString& str : s.mTransports.Value()) {
|
||||
if (str.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_USB)) {
|
||||
transports |= U2F_AUTHENTICATOR_TRANSPORT_USB;
|
||||
transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB;
|
||||
} else if (str.EqualsLiteral(
|
||||
MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_NFC)) {
|
||||
transports |= U2F_AUTHENTICATOR_TRANSPORT_NFC;
|
||||
transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC;
|
||||
} else if (str.EqualsLiteral(
|
||||
MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_BLE)) {
|
||||
transports |= U2F_AUTHENTICATOR_TRANSPORT_BLE;
|
||||
transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE;
|
||||
} else if (str.EqualsLiteral(
|
||||
MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_INTERNAL)) {
|
||||
transports |= CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL;
|
||||
transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL;
|
||||
}
|
||||
}
|
||||
c.transports() = transports;
|
||||
|
@ -5,9 +5,13 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/WebAuthnTransactionParent.h"
|
||||
#include "mozilla/dom/WebAuthnController.h"
|
||||
#include "mozilla/dom/U2FTokenManager.h"
|
||||
#include "mozilla/ipc/PBackgroundParent.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/StaticPrefs_security.h"
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#ifdef OS_WIN
|
||||
# include "WinWebAuthnManager.h"
|
||||
@ -15,6 +19,11 @@
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// Bug 1737205: we need to continue to support the legacy U2F interface until ~
|
||||
// FX 114, but we don't want to add an extra compatibility layer to our new
|
||||
// CTAP2-compatible controller. So the Register and Sign methods below use the
|
||||
// U2FTokenManager when the request is from the legacy U2F api.
|
||||
|
||||
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnMakeCredentialInfo& aTransactionInfo) {
|
||||
@ -24,14 +33,24 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
|
||||
if (WinWebAuthnManager::AreWebAuthNApisAvailable()) {
|
||||
WinWebAuthnManager* mgr = WinWebAuthnManager::Get();
|
||||
mgr->Register(this, aTransactionId, aTransactionInfo);
|
||||
return IPC_OK();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool allowCtap2 = StaticPrefs::security_webauthn_ctap2();
|
||||
bool androidFido2 =
|
||||
StaticPrefs::security_webauth_webauthn_enable_android_fido2();
|
||||
|
||||
// Remove as part of Bug 1737205.
|
||||
bool legacyReq = aTransactionInfo.Extra().isNothing();
|
||||
|
||||
if (allowCtap2 && !androidFido2 && !legacyReq) {
|
||||
WebAuthnController* ctrl = WebAuthnController::Get();
|
||||
ctrl->Register(this, aTransactionId, aTransactionInfo);
|
||||
} else {
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Register(this, aTransactionId, aTransactionInfo);
|
||||
}
|
||||
#else
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Register(this, aTransactionId, aTransactionInfo);
|
||||
#endif
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
@ -45,14 +64,24 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
|
||||
if (WinWebAuthnManager::AreWebAuthNApisAvailable()) {
|
||||
WinWebAuthnManager* mgr = WinWebAuthnManager::Get();
|
||||
mgr->Sign(this, aTransactionId, aTransactionInfo);
|
||||
return IPC_OK();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool allowCtap2 = StaticPrefs::security_webauthn_ctap2();
|
||||
bool androidFido2 =
|
||||
StaticPrefs::security_webauth_webauthn_enable_android_fido2();
|
||||
|
||||
// Remove as part of Bug 1737205.
|
||||
bool legacyReq = aTransactionInfo.Extra().isNothing();
|
||||
|
||||
if (allowCtap2 && !androidFido2 && !legacyReq) {
|
||||
WebAuthnController* ctrl = WebAuthnController::Get();
|
||||
ctrl->Sign(this, aTransactionId, aTransactionInfo);
|
||||
} else {
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Sign(this, aTransactionId, aTransactionInfo);
|
||||
}
|
||||
#else
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Sign(this, aTransactionId, aTransactionInfo);
|
||||
#endif
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
@ -65,14 +94,21 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestCancel(
|
||||
if (WinWebAuthnManager::AreWebAuthNApisAvailable()) {
|
||||
WinWebAuthnManager* mgr = WinWebAuthnManager::Get();
|
||||
mgr->Cancel(this, aTransactionId);
|
||||
} else {
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
return IPC_OK();
|
||||
}
|
||||
#endif
|
||||
|
||||
// We don't know whether WebAuthnController or U2FTokenManager was used, so
|
||||
// try cancelling both.
|
||||
WebAuthnController* ctrl = WebAuthnController::Get();
|
||||
if (ctrl) {
|
||||
ctrl->Cancel(this, aTransactionId);
|
||||
}
|
||||
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
if (mgr) {
|
||||
mgr->Cancel(this, aTransactionId);
|
||||
}
|
||||
#else
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Cancel(this, aTransactionId);
|
||||
#endif
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
@ -107,18 +143,19 @@ void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy) {
|
||||
if (mgr) {
|
||||
mgr->MaybeClearTransaction(this);
|
||||
}
|
||||
} else {
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
if (mgr) {
|
||||
mgr->MaybeClearTransaction(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
|
||||
WebAuthnController* ctrl = WebAuthnController::Get();
|
||||
if (ctrl) {
|
||||
ctrl->MaybeClearTransaction(this);
|
||||
}
|
||||
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
if (mgr) {
|
||||
mgr->MaybeClearTransaction(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
15
dom/webauthn/WebAuthnTransportIdentifiers.h
Normal file
15
dom/webauthn/WebAuthnTransportIdentifiers.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* -*- 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_WebAuthnTransportIdentifiers_h
|
||||
#define mozilla_dom_WebAuthnTransportIdentifiers_h
|
||||
|
||||
#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB 1
|
||||
#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC 2
|
||||
#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE 4
|
||||
#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL 8
|
||||
|
||||
#endif // mozilla_dom_WebAuthnTransportIdentifiers_h
|
@ -12,6 +12,7 @@
|
||||
#include "nsTextFormatter.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
#include "WebAuthnEnumStrings.h"
|
||||
#include "WebAuthnTransportIdentifiers.h"
|
||||
#include "winwebauthn/webauthn.h"
|
||||
#include "WinWebAuthnManager.h"
|
||||
|
||||
@ -333,16 +334,16 @@ void WinWebAuthnManager::Register(
|
||||
for (auto& cred : aInfo.ExcludeList()) {
|
||||
uint8_t transports = cred.transports();
|
||||
DWORD winTransports = 0;
|
||||
if (transports & U2F_AUTHENTICATOR_TRANSPORT_USB) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_USB;
|
||||
}
|
||||
if (transports & U2F_AUTHENTICATOR_TRANSPORT_NFC) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_NFC;
|
||||
}
|
||||
if (transports & U2F_AUTHENTICATOR_TRANSPORT_BLE) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_BLE;
|
||||
}
|
||||
if (transports & CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_INTERNAL;
|
||||
}
|
||||
|
||||
@ -611,16 +612,16 @@ void WinWebAuthnManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
for (auto& cred : aInfo.AllowList()) {
|
||||
uint8_t transports = cred.transports();
|
||||
DWORD winTransports = 0;
|
||||
if (transports & U2F_AUTHENTICATOR_TRANSPORT_USB) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_USB;
|
||||
}
|
||||
if (transports & U2F_AUTHENTICATOR_TRANSPORT_NFC) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_NFC;
|
||||
}
|
||||
if (transports & U2F_AUTHENTICATOR_TRANSPORT_BLE) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_BLE;
|
||||
}
|
||||
if (transports & CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL) {
|
||||
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL) {
|
||||
winTransports |= WEBAUTHN_CTAP_TRANSPORT_INTERNAL;
|
||||
}
|
||||
|
||||
|
15
dom/webauthn/authrs_bridge/Cargo.toml
Normal file
15
dom/webauthn/authrs_bridge/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "authrs_bridge"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Martin Sirringhaus", "John Schanck"]
|
||||
|
||||
[dependencies]
|
||||
authenticator = { version = "0.4.0-alpha.10", features = ["gecko"] }
|
||||
log = "0.4"
|
||||
moz_task = { path = "../../../xpcom/rust/moz_task" }
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../../xpcom/rust/nsstring" }
|
||||
serde_cbor = "0.11"
|
||||
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
|
||||
xpcom = { path = "../../../xpcom/rust/xpcom" }
|
749
dom/webauthn/authrs_bridge/src/lib.rs
Normal file
749
dom/webauthn/authrs_bridge/src/lib.rs
Normal file
@ -0,0 +1,749 @@
|
||||
/* 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/. */
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[macro_use]
|
||||
extern crate xpcom;
|
||||
|
||||
use authenticator::{
|
||||
authenticatorservice::{
|
||||
AuthenticatorService, CtapVersion, GetAssertionOptions, MakeCredentialsOptions,
|
||||
RegisterArgsCtap2, SignArgsCtap2,
|
||||
},
|
||||
ctap2::attestation::AttestationStatement,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User,
|
||||
},
|
||||
errors::{AuthenticatorError, PinError, U2FTokenError},
|
||||
statecallback::StateCallback,
|
||||
Assertion, Pin, RegisterResult, SignResult, StatusUpdate,
|
||||
};
|
||||
use moz_task::RunnableBuilder;
|
||||
use nserror::{
|
||||
nsresult, NS_ERROR_DOM_INVALID_STATE_ERR, NS_ERROR_DOM_NOT_ALLOWED_ERR,
|
||||
NS_ERROR_DOM_NOT_SUPPORTED_ERR, NS_ERROR_DOM_OPERATION_ERR, NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
NS_ERROR_FAILURE, NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER,
|
||||
NS_OK,
|
||||
};
|
||||
use nsstring::{nsACString, nsCString, nsString};
|
||||
use serde_cbor;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use thin_vec::ThinVec;
|
||||
use xpcom::interfaces::{
|
||||
nsICtapRegisterArgs, nsICtapRegisterResult, nsICtapSignArgs, nsICtapSignResult,
|
||||
nsIWebAuthnController, nsIWebAuthnTransport,
|
||||
};
|
||||
use xpcom::{xpcom_method, RefPtr};
|
||||
|
||||
fn make_register_prompt(tid: u64, origin: &str, browsing_context_id: u64) -> String {
|
||||
format!(
|
||||
r#"{{"is_ctap2":true,"action":"register","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id},"device_selected":true}}"#,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_sign_prompt(tid: u64, origin: &str, browsing_context_id: u64) -> String {
|
||||
format!(
|
||||
r#"{{"is_ctap2":true,"action":"sign","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id},"device_selected":true}}"#,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_select_device_prompt(tid: u64, origin: &str, browsing_context_id: u64) -> String {
|
||||
format!(
|
||||
r#"{{"is_ctap2":true,"action":"select-device","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id}}}"#,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_pin_error_prompt(action: &str, tid: u64, origin: &str, browsing_context_id: u64) -> String {
|
||||
format!(
|
||||
r#"{{"is_ctap2":true,"action":"{action}","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id}}}"#,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_pin_required_prompt(
|
||||
tid: u64,
|
||||
origin: &str,
|
||||
browsing_context_id: u64,
|
||||
was_invalid: bool,
|
||||
retries: i64,
|
||||
) -> String {
|
||||
format!(
|
||||
r#"{{"is_ctap2":true,"action":"pin-required","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id},"wasInvalid":{was_invalid},"retriesLeft":{retries}}}"#,
|
||||
)
|
||||
}
|
||||
|
||||
fn authrs_to_nserror(e: &AuthenticatorError) -> nsresult {
|
||||
match e {
|
||||
AuthenticatorError::U2FToken(U2FTokenError::Unknown) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::NotSupported) => NS_ERROR_DOM_NOT_SUPPORTED_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::InvalidState) => NS_ERROR_DOM_INVALID_STATE_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::ConstraintError) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::NotAllowed) => NS_ERROR_DOM_NOT_ALLOWED_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinRequired) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinIsTooShort) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinIsTooLong(_)) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::InvalidKeyLen) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::InvalidPin(_)) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinAuthBlocked) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinBlocked) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinNotSet) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::Backend(_)) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
_ => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
}
|
||||
}
|
||||
|
||||
#[xpcom(implement(nsICtapRegisterResult), atomic)]
|
||||
pub struct CtapRegisterResult {
|
||||
result: Result<RegisterResult, AuthenticatorError>,
|
||||
}
|
||||
|
||||
impl CtapRegisterResult {
|
||||
xpcom_method!(get_client_data_json => GetClientDataJSON() -> nsACString);
|
||||
fn get_client_data_json(&self) -> Result<nsCString, nsresult> {
|
||||
match &self.result {
|
||||
Ok(RegisterResult::CTAP2(_, client_data)) => {
|
||||
return Ok(nsCString::from(client_data.serialized_data.clone()))
|
||||
}
|
||||
_ => return Err(NS_ERROR_FAILURE),
|
||||
}
|
||||
}
|
||||
|
||||
xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
|
||||
fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||
let mut out = ThinVec::new();
|
||||
if let Ok(RegisterResult::CTAP2(attestation, _)) = &self.result {
|
||||
if let Ok(encoded_att_obj) = serde_cbor::to_vec(&attestation) {
|
||||
out.extend_from_slice(&encoded_att_obj);
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
|
||||
fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||
let mut out = ThinVec::new();
|
||||
if let Ok(RegisterResult::CTAP2(attestation, _)) = &self.result {
|
||||
if let Some(credential_data) = &attestation.auth_data.credential_data {
|
||||
out.extend(credential_data.credential_id.clone());
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_status => GetStatus() -> nsresult);
|
||||
fn get_status(&self) -> Result<nsresult, nsresult> {
|
||||
match &self.result {
|
||||
Ok(_) => Ok(NS_OK),
|
||||
Err(e) => Err(authrs_to_nserror(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[xpcom(implement(nsICtapSignResult), atomic)]
|
||||
pub struct CtapSignResult {
|
||||
result: Result<Assertion, AuthenticatorError>,
|
||||
}
|
||||
|
||||
impl CtapSignResult {
|
||||
xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
|
||||
fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||
let mut out = ThinVec::new();
|
||||
if let Ok(assertion) = &self.result {
|
||||
if let Some(cred) = &assertion.credentials {
|
||||
out.extend_from_slice(&cred.id);
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_signature => GetSignature() -> ThinVec<u8>);
|
||||
fn get_signature(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||
let mut out = ThinVec::new();
|
||||
if let Ok(assertion) = &self.result {
|
||||
out.extend_from_slice(&assertion.signature);
|
||||
return Ok(out);
|
||||
}
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
|
||||
fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||
let mut out = ThinVec::new();
|
||||
if let Ok(assertion) = &self.result {
|
||||
if let Ok(encoded_auth_data) = assertion.auth_data.to_vec() {
|
||||
out.extend_from_slice(&encoded_auth_data);
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_user_handle => GetUserHandle() -> ThinVec<u8>);
|
||||
fn get_user_handle(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||
let mut out = ThinVec::new();
|
||||
if let Ok(assertion) = &self.result {
|
||||
if let Some(user) = &assertion.user {
|
||||
out.extend_from_slice(&user.id);
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_user_name => GetUserName() -> nsACString);
|
||||
fn get_user_name(&self) -> Result<nsCString, nsresult> {
|
||||
if let Ok(assertion) = &self.result {
|
||||
if let Some(user) = &assertion.user {
|
||||
if let Some(name) = &user.name {
|
||||
return Ok(nsCString::from(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(NS_ERROR_NOT_AVAILABLE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_rp_id_hash => GetRpIdHash() -> ThinVec<u8>);
|
||||
fn get_rp_id_hash(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||
// assertion.auth_data.rp_id_hash
|
||||
let mut out = ThinVec::new();
|
||||
if let Ok(assertion) = &self.result {
|
||||
out.extend(assertion.auth_data.rp_id_hash.0.clone());
|
||||
return Ok(out);
|
||||
}
|
||||
Err(NS_ERROR_FAILURE)
|
||||
}
|
||||
|
||||
xpcom_method!(get_status => GetStatus() -> nsresult);
|
||||
fn get_status(&self) -> Result<nsresult, nsresult> {
|
||||
match &self.result {
|
||||
Ok(_) => Ok(NS_OK),
|
||||
Err(e) => Err(authrs_to_nserror(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Controller wraps a raw pointer to an nsIWebAuthnController. The AuthrsTransport struct holds a
|
||||
/// Controller which we need to initialize from the SetController XPCOM method. Since XPCOM
|
||||
/// methods take &self, Controller must have interior mutability.
|
||||
#[derive(Clone)]
|
||||
struct Controller(RefCell<*const nsIWebAuthnController>);
|
||||
|
||||
/// Our implementation of nsIWebAuthnController in WebAuthnController.cpp has the property that its
|
||||
/// XPCOM methods are safe to call from any thread, hence a raw pointer to an nsIWebAuthnController
|
||||
/// is Send.
|
||||
unsafe impl Send for Controller {}
|
||||
|
||||
impl Controller {
|
||||
fn init(&self, ptr: *const nsIWebAuthnController) -> Result<(), nsresult> {
|
||||
self.0.replace(ptr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_prompt(&self, tid: u64, msg: &str) {
|
||||
if (*self.0.borrow()).is_null() {
|
||||
warn!("Controller not initialized");
|
||||
return;
|
||||
}
|
||||
let notification_str = nsCString::from(msg);
|
||||
unsafe {
|
||||
(**(self.0.borrow())).SendPromptNotificationPreformatted(tid, &*notification_str);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_register(
|
||||
&self,
|
||||
tid: u64,
|
||||
result: Result<RegisterResult, AuthenticatorError>,
|
||||
) -> Result<(), nsresult> {
|
||||
if (*self.0.borrow()).is_null() {
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
let wrapped_result = CtapRegisterResult::allocate(InitCtapRegisterResult { result })
|
||||
.query_interface::<nsICtapRegisterResult>()
|
||||
.ok_or(NS_ERROR_FAILURE)?;
|
||||
unsafe {
|
||||
(**(self.0.borrow())).FinishRegister(tid, wrapped_result.coerce());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_sign(
|
||||
&self,
|
||||
tid: u64,
|
||||
result: Result<SignResult, AuthenticatorError>,
|
||||
) -> Result<(), nsresult> {
|
||||
if (*self.0.borrow()).is_null() {
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
// If result is an error, we return a single CtapSignResult that has its status field set
|
||||
// to an error. Otherwise we convert the entries of SignResult (= Vec<Assertion>) into
|
||||
// CtapSignResults with OK statuses.
|
||||
let mut assertions: ThinVec<Option<RefPtr<nsICtapSignResult>>> = ThinVec::new();
|
||||
let mut client_data = nsCString::new();
|
||||
match result {
|
||||
Err(e) => assertions.push(
|
||||
CtapSignResult::allocate(InitCtapSignResult { result: Err(e) })
|
||||
.query_interface::<nsICtapSignResult>(),
|
||||
),
|
||||
Ok(SignResult::CTAP2(assertion_vec, json)) => {
|
||||
client_data = nsCString::from(json.serialized_data);
|
||||
for assertion in assertion_vec.0 {
|
||||
assertions.push(
|
||||
CtapSignResult::allocate(InitCtapSignResult {
|
||||
result: Ok(assertion),
|
||||
})
|
||||
.query_interface::<nsICtapSignResult>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => return Err(NS_ERROR_NOT_IMPLEMENTED), // SignResult::CTAP1 shouldn't happen.
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(**(self.0.borrow())).FinishSign(tid, &mut *client_data, &mut assertions);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
Register,
|
||||
Sign,
|
||||
}
|
||||
|
||||
// The state machine creates a Sender<Pin>/Receiver<Pin> channel in ask_user_for_pin. It passes the
|
||||
// Sender through status_callback, which stores the Sender in the pin_receiver field of an
|
||||
// AuthrsTransport. The u64 in PinReceiver is a transaction ID, which the AuthrsTransport uses the
|
||||
// transaction ID as a consistency check.
|
||||
type PinReceiver = Option<(u64, Sender<Pin>)>;
|
||||
|
||||
fn status_callback(
|
||||
status_rx: Receiver<StatusUpdate>,
|
||||
tid: u64,
|
||||
origin: &String,
|
||||
browsing_context_id: u64,
|
||||
controller: Controller,
|
||||
event_type: EventType,
|
||||
pin_receiver: Arc<Mutex<PinReceiver>>, /* Shared with an AuthrsTransport */
|
||||
) {
|
||||
loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
debug!("STATUS: device available: {}", dev_info)
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
debug!("STATUS: device unavailable: {}", dev_info)
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
debug!("STATUS: success using device: {}", dev_info);
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
debug!("STATUS: Please select a device by touching one of them.");
|
||||
let notification_str = make_select_device_prompt(tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
debug!("STATUS: Continuing with device: {}", dev_info);
|
||||
let notification_str = match event_type {
|
||||
EventType::Register => make_register_prompt(tid, origin, browsing_context_id),
|
||||
EventType::Sign => make_sign_prompt(tid, origin, browsing_context_id),
|
||||
};
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinError(error, sender)) => match error {
|
||||
PinError::PinRequired => {
|
||||
let guard = pin_receiver.lock();
|
||||
if let Ok(mut entry) = guard {
|
||||
entry.replace((tid, sender));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let notification_str =
|
||||
make_pin_required_prompt(tid, origin, browsing_context_id, false, -1);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
continue;
|
||||
}
|
||||
PinError::InvalidPin(attempts) => {
|
||||
let guard = pin_receiver.lock();
|
||||
if let Ok(mut entry) = guard {
|
||||
entry.replace((tid, sender));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let notification_str = make_pin_required_prompt(
|
||||
tid,
|
||||
origin,
|
||||
browsing_context_id,
|
||||
true,
|
||||
attempts.map_or(-1, |x| x as i64),
|
||||
);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
continue;
|
||||
}
|
||||
PinError::PinAuthBlocked => {
|
||||
let notification_str =
|
||||
make_pin_error_prompt("pin-auth-blocked", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
PinError::PinBlocked => {
|
||||
let notification_str =
|
||||
make_pin_error_prompt("device-blocked", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
e => {
|
||||
warn!("Unexpected error: {:?}", e)
|
||||
}
|
||||
},
|
||||
Err(RecvError) => {
|
||||
debug!("STATUS: end");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AuthrsTransport provides an nsIWebAuthnTransport interface to an AuthenticatorService. This
|
||||
// allows an nsIWebAuthnController to dispatch MakeCredential and GetAssertion requests to USB HID
|
||||
// tokens. The AuthrsTransport struct also keeps 1) a pointer to the active nsIWebAuthnController,
|
||||
// which is used to prompt the user for input and to return results to the controller; and
|
||||
// 2) a channel through which to receive a pin callback.
|
||||
#[xpcom(implement(nsIWebAuthnTransport), atomic)]
|
||||
pub struct AuthrsTransport {
|
||||
auth_service: RefCell<AuthenticatorService>, // interior mutable for use in XPCOM methods
|
||||
controller: Controller,
|
||||
pin_receiver: Arc<Mutex<PinReceiver>>,
|
||||
}
|
||||
|
||||
impl AuthrsTransport {
|
||||
xpcom_method!(get_controller => GetController() -> *const nsIWebAuthnController);
|
||||
fn get_controller(&self) -> Result<RefPtr<nsIWebAuthnController>, nsresult> {
|
||||
Err(NS_ERROR_NOT_IMPLEMENTED)
|
||||
}
|
||||
|
||||
xpcom_method!(set_controller => SetController(aController: *const nsIWebAuthnController));
|
||||
fn set_controller(&self, controller: *const nsIWebAuthnController) -> Result<(), nsresult> {
|
||||
self.controller.init(controller)
|
||||
}
|
||||
|
||||
xpcom_method!(pin_callback => PinCallback(aTransactionId: u64, aPin: *const nsACString));
|
||||
fn pin_callback(&self, transaction_id: u64, pin: &nsACString) -> Result<(), nsresult> {
|
||||
let mut guard = self.pin_receiver.lock().or(Err(NS_ERROR_FAILURE))?;
|
||||
match guard.take() {
|
||||
// The pin_receiver is single-use.
|
||||
Some((tid, channel)) if tid == transaction_id => channel
|
||||
.send(Pin::new(&pin.to_string()))
|
||||
.or(Err(NS_ERROR_FAILURE)),
|
||||
// Either we weren't expecting a pin, or the controller is confused
|
||||
// about which transaction is active. Neither is recoverable, so it's
|
||||
// OK to drop the PinReceiver here.
|
||||
_ => Err(NS_ERROR_FAILURE),
|
||||
}
|
||||
}
|
||||
|
||||
xpcom_method!(make_credential => MakeCredential(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsICtapRegisterArgs));
|
||||
fn make_credential(
|
||||
&self,
|
||||
tid: u64,
|
||||
browsing_context_id: u64,
|
||||
args: *const nsICtapRegisterArgs,
|
||||
) -> Result<(), nsresult> {
|
||||
if args.is_null() {
|
||||
return Err(NS_ERROR_NULL_POINTER);
|
||||
}
|
||||
let args = unsafe { &*args };
|
||||
|
||||
let mut origin = nsString::new();
|
||||
unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
|
||||
|
||||
let mut relying_party_id = nsString::new();
|
||||
unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
|
||||
|
||||
let mut challenge = ThinVec::new();
|
||||
unsafe { args.GetChallenge(&mut challenge) }.to_result()?;
|
||||
|
||||
let mut client_data_json = nsCString::new();
|
||||
unsafe { args.GetClientDataJSON(&mut *client_data_json) }.to_result()?;
|
||||
|
||||
let mut timeout_ms = 0u32;
|
||||
unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
|
||||
|
||||
let mut exclude_list = ThinVec::new();
|
||||
unsafe { args.GetExcludeList(&mut exclude_list) }.to_result()?;
|
||||
let exclude_list = exclude_list
|
||||
.iter_mut()
|
||||
.map(|id| PublicKeyCredentialDescriptor {
|
||||
id: id.to_vec(),
|
||||
transports: vec![],
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut relying_party_name = nsString::new();
|
||||
unsafe { args.GetRpName(&mut *relying_party_name) }.to_result()?;
|
||||
|
||||
let mut user_id = ThinVec::new();
|
||||
unsafe { args.GetUserId(&mut user_id) }.to_result()?;
|
||||
|
||||
let mut user_name = nsString::new();
|
||||
unsafe { args.GetUserName(&mut *user_name) }.to_result()?;
|
||||
|
||||
let mut user_display_name = nsString::new();
|
||||
unsafe { args.GetUserDisplayName(&mut *user_display_name) }.to_result()?;
|
||||
|
||||
let mut cose_algs = ThinVec::new();
|
||||
unsafe { args.GetCoseAlgs(&mut cose_algs) }.to_result()?;
|
||||
let pub_cred_params = cose_algs
|
||||
.iter()
|
||||
.map(|alg| PublicKeyCredentialParameters::try_from(*alg).unwrap())
|
||||
.collect();
|
||||
|
||||
let mut require_resident_key = false;
|
||||
unsafe { args.GetRequireResidentKey(&mut require_resident_key) }.to_result()?;
|
||||
|
||||
let mut user_verification = nsString::new();
|
||||
unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
|
||||
let require_user_verification = user_verification.eq("required");
|
||||
|
||||
let mut attestation_conveyance_preference = nsString::new();
|
||||
unsafe { args.GetAttestationConveyancePreference(&mut *attestation_conveyance_preference) }
|
||||
.to_result()?;
|
||||
let none_attestation = attestation_conveyance_preference.eq("none");
|
||||
|
||||
// TODO(Bug 1593571) - Add this to the extensions
|
||||
// let mut hmac_create_secret = None;
|
||||
// let mut maybe_hmac_create_secret = false;
|
||||
// match unsafe { args.GetHmacCreateSecret(&mut maybe_hmac_create_secret) }.to_result() {
|
||||
// Ok(_) => hmac_create_secret = Some(maybe_hmac_create_secret),
|
||||
// _ => (),
|
||||
// }
|
||||
|
||||
let info = RegisterArgsCtap2 {
|
||||
challenge: challenge.to_vec(),
|
||||
relying_party: RelyingParty {
|
||||
id: relying_party_id.to_string(),
|
||||
name: None,
|
||||
icon: None,
|
||||
},
|
||||
origin: origin.to_string(),
|
||||
user: User {
|
||||
id: user_id.to_vec(),
|
||||
icon: None,
|
||||
name: Some(user_name.to_string()),
|
||||
display_name: None,
|
||||
},
|
||||
pub_cred_params,
|
||||
exclude_list,
|
||||
options: MakeCredentialsOptions {
|
||||
resident_key: require_resident_key.then_some(true),
|
||||
user_verification: require_user_verification.then_some(true),
|
||||
},
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
};
|
||||
|
||||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
let pin_receiver = self.pin_receiver.clone();
|
||||
let controller = self.controller.clone();
|
||||
let status_origin = origin.to_string();
|
||||
RunnableBuilder::new(
|
||||
"AuthrsTransport::MakeCredential::StatusReceiver",
|
||||
move || {
|
||||
status_callback(
|
||||
status_rx,
|
||||
tid,
|
||||
&status_origin,
|
||||
browsing_context_id,
|
||||
controller,
|
||||
EventType::Register,
|
||||
pin_receiver,
|
||||
)
|
||||
},
|
||||
)
|
||||
.may_block(true)
|
||||
.dispatch_background_task()?;
|
||||
|
||||
let controller = self.controller.clone();
|
||||
let state_callback = StateCallback::<Result<RegisterResult, AuthenticatorError>>::new(
|
||||
Box::new(move |result| {
|
||||
let result = match result {
|
||||
Ok(RegisterResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch(
|
||||
"AuthrsTransport::MakeCredential",
|
||||
2,
|
||||
)),
|
||||
Ok(RegisterResult::CTAP2(mut attestation_object, client_data)) => {
|
||||
// Tokens always provide attestation, but the user may have asked we not
|
||||
// include the attestation statement in the response.
|
||||
if none_attestation {
|
||||
attestation_object.att_statement = AttestationStatement::None;
|
||||
}
|
||||
Ok(RegisterResult::CTAP2(attestation_object, client_data))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
let _ = controller.finish_register(tid, result);
|
||||
}),
|
||||
);
|
||||
|
||||
self.auth_service
|
||||
.borrow_mut()
|
||||
.register(timeout_ms as u64, info.into(), status_tx, state_callback)
|
||||
.or(Err(NS_ERROR_FAILURE))
|
||||
}
|
||||
|
||||
xpcom_method!(get_assertion => GetAssertion(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsICtapSignArgs));
|
||||
fn get_assertion(
|
||||
&self,
|
||||
tid: u64,
|
||||
browsing_context_id: u64,
|
||||
args: *const nsICtapSignArgs,
|
||||
) -> Result<(), nsresult> {
|
||||
if args.is_null() {
|
||||
return Err(NS_ERROR_NULL_POINTER);
|
||||
}
|
||||
let args = unsafe { &*args };
|
||||
|
||||
let mut origin = nsString::new();
|
||||
unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
|
||||
|
||||
let mut relying_party_id = nsString::new();
|
||||
unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
|
||||
|
||||
let mut challenge = ThinVec::new();
|
||||
unsafe { args.GetChallenge(&mut challenge) }.to_result()?;
|
||||
|
||||
let mut client_data_json = nsCString::new();
|
||||
unsafe { args.GetClientDataJSON(&mut *client_data_json) }.to_result()?;
|
||||
|
||||
let mut timeout_ms = 0u32;
|
||||
unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
|
||||
|
||||
let mut allow_list = ThinVec::new();
|
||||
unsafe { args.GetAllowList(&mut allow_list) }.to_result()?;
|
||||
let allow_list: Vec<_> = allow_list
|
||||
.iter_mut()
|
||||
.map(|id| PublicKeyCredentialDescriptor {
|
||||
id: id.to_vec(),
|
||||
transports: vec![],
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut user_verification = nsString::new();
|
||||
unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
|
||||
let require_user_verification = user_verification.eq("required");
|
||||
|
||||
let mut alternate_rp_id = None;
|
||||
let mut maybe_alternate_rp_id = nsString::new();
|
||||
match unsafe { args.GetAppId(&mut *maybe_alternate_rp_id) }.to_result() {
|
||||
Ok(_) => alternate_rp_id = Some(maybe_alternate_rp_id.to_string()),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
let pin_receiver = self.pin_receiver.clone();
|
||||
let controller = self.controller.clone();
|
||||
let status_origin = origin.to_string();
|
||||
RunnableBuilder::new("AuthrsTransport::GetAssertion::StatusReceiver", move || {
|
||||
status_callback(
|
||||
status_rx,
|
||||
tid,
|
||||
&status_origin,
|
||||
browsing_context_id,
|
||||
controller,
|
||||
EventType::Sign,
|
||||
pin_receiver,
|
||||
)
|
||||
})
|
||||
.may_block(true)
|
||||
.dispatch_background_task()?;
|
||||
|
||||
let uniq_allowed_cred = if allow_list.len() == 1 {
|
||||
allow_list.first().cloned()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let controller = self.controller.clone();
|
||||
let state_callback =
|
||||
StateCallback::<Result<SignResult, AuthenticatorError>>::new(Box::new(move |result| {
|
||||
let result = match result {
|
||||
Ok(SignResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch(
|
||||
"AuthrsTransport::GetAssertion",
|
||||
2,
|
||||
)),
|
||||
Ok(SignResult::CTAP2(mut assertion_object, client_data)) => {
|
||||
// In CTAP 2.0, but not CTAP 2.1, the assertion object's credential field
|
||||
// "May be omitted if the allowList has exactly one Credential." If we had
|
||||
// a unique allowed credential, then copy its descriptor to the output.
|
||||
if uniq_allowed_cred.is_some() {
|
||||
if let Some(assertion) = assertion_object.0.first_mut() {
|
||||
if assertion.credentials.is_none() {
|
||||
assertion.credentials = uniq_allowed_cred.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(SignResult::CTAP2(assertion_object, client_data))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
let _ = controller.finish_sign(tid, result);
|
||||
}));
|
||||
|
||||
let info = SignArgsCtap2 {
|
||||
challenge: challenge.to_vec(),
|
||||
relying_party_id: relying_party_id.to_string(),
|
||||
origin: origin.to_string(),
|
||||
allow_list,
|
||||
options: GetAssertionOptions {
|
||||
user_presence: Some(true),
|
||||
user_verification: require_user_verification.then_some(true),
|
||||
},
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
alternate_rp_id,
|
||||
};
|
||||
|
||||
self.auth_service
|
||||
.borrow_mut()
|
||||
.sign(timeout_ms as u64, info.into(), status_tx, state_callback)
|
||||
.or(Err(NS_ERROR_FAILURE))
|
||||
}
|
||||
|
||||
xpcom_method!(cancel => Cancel());
|
||||
fn cancel(&self) -> Result<nsresult, nsresult> {
|
||||
// We may be waiting for a pin. Drop the channel to release the
|
||||
// state machine from `ask_user_for_pin`.
|
||||
drop(self.pin_receiver.lock().or(Err(NS_ERROR_FAILURE))?.take());
|
||||
|
||||
match &self.auth_service.borrow_mut().cancel() {
|
||||
Ok(_) => Ok(NS_OK),
|
||||
Err(e) => Err(authrs_to_nserror(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn authrs_transport_constructor(
|
||||
result: *mut *const nsIWebAuthnTransport,
|
||||
) -> nsresult {
|
||||
let mut auth_service = match AuthenticatorService::new(CtapVersion::CTAP2) {
|
||||
Ok(auth_service) => auth_service,
|
||||
_ => return NS_ERROR_FAILURE,
|
||||
};
|
||||
auth_service.add_detected_transports();
|
||||
let wrapper = AuthrsTransport::allocate(InitAuthrsTransport {
|
||||
auth_service: RefCell::new(auth_service),
|
||||
controller: Controller(RefCell::new(std::ptr::null())),
|
||||
pin_receiver: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
unsafe {
|
||||
RefPtr::new(wrapper.coerce::<nsIWebAuthnTransport>()).forget(&mut *result);
|
||||
}
|
||||
NS_OK
|
||||
}
|
@ -9,7 +9,7 @@ with Files("**"):
|
||||
|
||||
IPDL_SOURCES += ["PWebAuthnTransaction.ipdl"]
|
||||
|
||||
XPIDL_SOURCES += ["nsIU2FTokenManager.idl"]
|
||||
XPIDL_SOURCES += ["nsIU2FTokenManager.idl", "nsIWebAuthnController.idl"]
|
||||
|
||||
XPIDL_MODULE = "dom_webauthn"
|
||||
|
||||
@ -17,13 +17,13 @@ EXPORTS.mozilla.dom += [
|
||||
"AuthenticatorAssertionResponse.h",
|
||||
"AuthenticatorAttestationResponse.h",
|
||||
"AuthenticatorResponse.h",
|
||||
"CTAPHIDTokenManager.h",
|
||||
"PublicKeyCredential.h",
|
||||
"U2FHIDTokenManager.h",
|
||||
"U2FSoftTokenManager.h",
|
||||
"U2FTokenManager.h",
|
||||
"U2FTokenTransport.h",
|
||||
"WebAuthnCBORUtil.h",
|
||||
"WebAuthnController.h",
|
||||
"WebAuthnManager.h",
|
||||
"WebAuthnManagerBase.h",
|
||||
"WebAuthnTransactionChild.h",
|
||||
@ -36,14 +36,18 @@ UNIFIED_SOURCES += [
|
||||
"AuthenticatorAssertionResponse.cpp",
|
||||
"AuthenticatorAttestationResponse.cpp",
|
||||
"AuthenticatorResponse.cpp",
|
||||
"AuthrsTransport.cpp",
|
||||
"cbor-cpp/src/encoder.cpp",
|
||||
"cbor-cpp/src/output_dynamic.cpp",
|
||||
"CTAPHIDTokenManager.cpp",
|
||||
"CtapArgs.cpp",
|
||||
"CtapResults.cpp",
|
||||
"PublicKeyCredential.cpp",
|
||||
"U2FHIDTokenManager.cpp",
|
||||
"U2FSoftTokenManager.cpp",
|
||||
"U2FSoftTokenTransport.cpp",
|
||||
"U2FTokenManager.cpp",
|
||||
"WebAuthnCBORUtil.cpp",
|
||||
"WebAuthnController.cpp",
|
||||
"WebAuthnManager.cpp",
|
||||
"WebAuthnManagerBase.cpp",
|
||||
"WebAuthnTransactionChild.cpp",
|
||||
|
@ -6,9 +6,9 @@
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* TODO: U2FTokenManager needs to be renamed to CTAPTokenManager or similar,
|
||||
* because it now contains also CTAP2 functionality (e.g. pinCallback)
|
||||
* See bug 1801643
|
||||
* TODO(1737205,1819414) Fold this interface into nsIWebAuthnController when we
|
||||
* remove the legacy U2F DOM API.
|
||||
*
|
||||
* nsIU2FTokenManager
|
||||
*
|
||||
* An interface to the U2FTokenManager singleton.
|
||||
@ -30,29 +30,6 @@ interface nsIU2FTokenManager : nsISupports
|
||||
void resumeRegister(in uint64_t aTransactionID,
|
||||
in bool aForceNoneAttestation);
|
||||
|
||||
/**
|
||||
* Resumes the current WebAuthn transaction.
|
||||
* This is used only when the hardware token requires
|
||||
* user-verification and is thus protected by a PIN.
|
||||
*
|
||||
* @param aPin : PIN the user entered after being prompted.
|
||||
*/
|
||||
void pinCallback(in ACString aPin);
|
||||
|
||||
/**
|
||||
* Resumes the current WebAuthn transaction if that matches the given
|
||||
* transaction ID. This is used only when the hardware token returned
|
||||
* multiple results for signin in and the user needs to select with which
|
||||
* to log in.
|
||||
* TODO(MS): This is a CTAP2 operation, so U2FTokenManager is probably
|
||||
* not the ideal place for this function. It is a shortcut for now.
|
||||
*
|
||||
* @param aTransactionID : The ID of the transaction to resume.
|
||||
* @param idx : The index of the selected result
|
||||
*/
|
||||
void resumeWithSelectedSignResult(in uint64_t aTransactionID,
|
||||
in uint64_t idx);
|
||||
|
||||
/**
|
||||
* Cancels the current WebAuthn/U2F transaction if that matches the given
|
||||
* transaction ID.
|
||||
|
219
dom/webauthn/nsIWebAuthnController.idl
Normal file
219
dom/webauthn/nsIWebAuthnController.idl
Normal file
@ -0,0 +1,219 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "nsISupports.idl"
|
||||
#include "nsIU2FTokenManager.idl"
|
||||
|
||||
typedef long COSEAlgorithmIdentifier;
|
||||
|
||||
// The nsICtapRegisterArgs interface encapsulates the arguments to the CTAP
|
||||
// authenticatorMakeCredential command as defined in
|
||||
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential
|
||||
// It is essentially a shim that allows data to be copied from an IPDL-defined
|
||||
// WebAuthnMakeCredentialInfo C++ struct to an authenticator-rs defined
|
||||
// RegisterArgsCtap2 Rust struct.
|
||||
//
|
||||
[uuid(2fc8febe-a277-11ed-bda2-8f6495a5e75c)]
|
||||
interface nsICtapRegisterArgs : nsISupports {
|
||||
// TODO(Bug 1820035) The origin is only used for prompt callbacks. Refactor and remove.
|
||||
readonly attribute AString origin;
|
||||
|
||||
// TODO(Bug 1820037) The spec only has `clientDataHash` here. We could refactor
|
||||
// authenticator-rs to remove clientDataJSON
|
||||
// readonly attribute Array<octet> clientDataHash;
|
||||
readonly attribute ACString clientDataJSON;
|
||||
// The challenge field of clientDataJSON. Can be removed if we refactor
|
||||
// authenticators to only require clientDataHash.
|
||||
readonly attribute Array<octet> challenge;
|
||||
|
||||
// A PublicKeyCredentialRpEntity
|
||||
readonly attribute AString rpId;
|
||||
[must_use] readonly attribute AString rpName;
|
||||
|
||||
// A PublicKeyCredentialUserEntity
|
||||
[must_use] readonly attribute Array<octet> userId;
|
||||
[must_use] readonly attribute AString userName;
|
||||
[must_use] readonly attribute AString userDisplayName;
|
||||
|
||||
// The spec defines this as a sequence<PublicKeyCredentialParameters>.
|
||||
// We require type = "public-key" and only serialize the alg fields.
|
||||
[must_use] readonly attribute Array<COSEAlgorithmIdentifier> coseAlgs;
|
||||
|
||||
// The spec defines this as a sequence<PublicKeyCredentialDescriptor>.
|
||||
// We only include the ID field, as the transport field is optional and we
|
||||
// can assume that the type is "public-key".
|
||||
readonly attribute Array<Array<octet> > excludeList;
|
||||
|
||||
// CTAP2 passes extensions in a CBOR map of extension identifier ->
|
||||
// WebAuthn AuthenticationExtensionsClientInputs. That's not feasible here.
|
||||
// So we define a getter for each supported extension input and use the
|
||||
// return code to signal presence.
|
||||
[must_use] readonly attribute bool hmacCreateSecret;
|
||||
|
||||
// Options.
|
||||
[must_use] readonly attribute bool requireResidentKey;
|
||||
[must_use] readonly attribute AString userVerification;
|
||||
[must_use] readonly attribute AString authenticatorAttachment;
|
||||
|
||||
// This is the WebAuthn PublicKeyCredentialCreationOptions timeout.
|
||||
// Arguably we don't need to pass it through since WebAuthnController can
|
||||
// cancel transactions.
|
||||
readonly attribute uint32_t timeoutMS;
|
||||
|
||||
// This is the WebAuthn PublicKeyCredentialCreationOptions attestation.
|
||||
// We might overwrite the provided value with "none" if the user declines the
|
||||
// consent popup.
|
||||
[must_use] readonly attribute AString attestationConveyancePreference;
|
||||
};
|
||||
|
||||
// The nsICtapSignArgs interface encapsulates the arguments to the CTAP
|
||||
// authenticatorGetAssertion command as defined in
|
||||
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion
|
||||
// It is essentially a shim that allows data to be copied from an IPDL-defined
|
||||
// WebAuthnGetAssertionInfo C++ struct to an authenticator-rs defined
|
||||
// SignArgsCtap2 Rust struct.
|
||||
//
|
||||
[uuid(2e621cf4-a277-11ed-ae00-bf41a54ef553)]
|
||||
interface nsICtapSignArgs : nsISupports {
|
||||
// TODO(Bug 1820035) The origin is only used for prompt callbacks. Refactor and remove.
|
||||
readonly attribute AString origin;
|
||||
|
||||
// The spec only asks for the ID field of a PublicKeyCredentialRpEntity here
|
||||
readonly attribute AString rpId;
|
||||
|
||||
// TODO(Bug 1820037) The spec only has `clientDataHash` here. We could refactor
|
||||
// authenticator-rs to remove clientDataJSON
|
||||
// readonly attribute Array<octet> clientDataHash;
|
||||
readonly attribute ACString clientDataJSON;
|
||||
// The challenge field of clientDataJSON. Can be removed if we refactor
|
||||
// authenticators to only require clientDataHash.
|
||||
readonly attribute Array<octet> challenge;
|
||||
|
||||
// The spec defines this as a sequence<PublicKeyCredentialDescriptor>.
|
||||
// We only include the ID field, as the transport field is optional and we
|
||||
// can assume that the type is "public-key".
|
||||
readonly attribute Array<Array<octet> > allowList;
|
||||
|
||||
// CTAP2 passes extensions in a CBOR map of extension identifier ->
|
||||
// WebAuthn AuthenticationExtensionsClientInputs. That's not feasible here.
|
||||
// So we define a getter for each supported extension input and use the
|
||||
// return code to signal presence.
|
||||
[must_use] readonly attribute bool hmacCreateSecret;
|
||||
[must_use] readonly attribute AString appId;
|
||||
[must_use] readonly attribute Array<octet> appIdHash;
|
||||
|
||||
// Options
|
||||
[must_use] readonly attribute AString userVerification;
|
||||
|
||||
// This is the WebAuthn PublicKeyCredentialCreationOptions timeout.
|
||||
// Arguably we don't need to pass it through since WebAuthnController can
|
||||
// cancel transactions.
|
||||
readonly attribute unsigned long timeoutMS;
|
||||
};
|
||||
|
||||
// The nsICtapRegisterResult interface is used to construct IPDL-defined
|
||||
// WebAuthnMakeCredentialResult from either Rust or C++.
|
||||
//
|
||||
[uuid(0567c384-a728-11ed-85f7-030324a370f0)]
|
||||
interface nsICtapRegisterResult : nsISupports {
|
||||
readonly attribute nsresult status;
|
||||
|
||||
readonly attribute ACString clientDataJSON;
|
||||
|
||||
// The serialied attestation object as defined in
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-attestation
|
||||
// Includes the format, the attestation statement, and
|
||||
// the authenticator data.
|
||||
readonly attribute Array<octet> attestationObject;
|
||||
|
||||
// The Credential ID field of the Attestation Object's Attested
|
||||
// Credential Data. This is used to construct the rawID field of a
|
||||
// WebAuthn PublicKeyCredential without having to parse the
|
||||
// attestationObject.
|
||||
readonly attribute Array<octet> credentialId;
|
||||
|
||||
// Bug 1536155
|
||||
// readonly attribute Array<AString> transports;
|
||||
|
||||
// Bug 1816519
|
||||
// readonly attribute Array<octet> authenticatorData;
|
||||
|
||||
// Bug 1816520
|
||||
// readonly attribute Array<octet> publicKey
|
||||
// readonly attribute COSEAlgorithmIdentifier publicKeyAlgorithm;
|
||||
|
||||
// bug 1593571
|
||||
// readonly attribute bool hmacCreateSecret;
|
||||
};
|
||||
|
||||
|
||||
// The nsICtapSignResult interface is used to construct IPDL-defined
|
||||
// WebAuthnGetAssertionResult from either Rust or C++.
|
||||
//
|
||||
[uuid(05fff816-a728-11ed-b9ac-ff38cc2c8c28)]
|
||||
interface nsICtapSignResult : nsISupports {
|
||||
readonly attribute nsresult status;
|
||||
|
||||
// The ID field of the PublicKeyCredentialDescriptor returned
|
||||
// from authenticatorGetAssertion.
|
||||
readonly attribute Array<octet> credentialId;
|
||||
|
||||
// The authData field of the authenticatorGetAssertion response
|
||||
readonly attribute Array<octet> authenticatorData;
|
||||
|
||||
// The signature field of the authenticatorGetAssertion response
|
||||
readonly attribute Array<octet> signature;
|
||||
|
||||
// The ID field of the PublicKeyCredentialUserEntity returned from
|
||||
// authenticatorGetAssertion. (Optional)
|
||||
[must_use] readonly attribute Array<octet> userHandle;
|
||||
|
||||
// The displayName field of the PublicKeyCredentialUserEntity
|
||||
// returned from authenticatorGetAssertion. (Optional)
|
||||
[must_use] readonly attribute ACString userName;
|
||||
|
||||
// The SHA-256 hash of the RP ID of the requester. Used to
|
||||
// implement the FIDO AppID extension.
|
||||
readonly attribute Array<octet> rpIdHash;
|
||||
|
||||
// bug 1593571
|
||||
// readonly attribute bool hmacCreateSecret;
|
||||
};
|
||||
|
||||
// The nsIWebAuthnController interface coordinates interactions between the user
|
||||
// and the authenticator to drive a WebAuthn transaction forward.
|
||||
// It allows an nsIWebAuthnTransport to
|
||||
// 1) prompt the user for input,
|
||||
// 2) receive a callback from a prompt, and
|
||||
// 3) return results to the content process.
|
||||
//
|
||||
[scriptable, uuid(c0744f48-ad64-11ed-b515-cf5149f4d6a6)]
|
||||
interface nsIWebAuthnController : nsIU2FTokenManager
|
||||
{
|
||||
// Prompt callbacks
|
||||
void pinCallback(in uint64_t aTransactionId, in ACString aPin);
|
||||
void signatureSelectionCallback(in uint64_t aTransactionId, in uint64_t aIndex);
|
||||
|
||||
// Authenticator callbacks
|
||||
[noscript] void sendPromptNotificationPreformatted(in uint64_t aTransactionId, in ACString aJSON);
|
||||
[noscript] void finishRegister(in uint64_t aTransactionId, in nsICtapRegisterResult aResult);
|
||||
[noscript] void finishSign(in uint64_t aTransactionId, in ACString aClientDataJson, in Array<nsICtapSignResult> aResult);
|
||||
};
|
||||
|
||||
// The nsIWebAuthnTransport interface allows a C++ implemented nsIWebAuthnController to interact
|
||||
// with authenticators written in both Rust and C++
|
||||
[uuid(e236a9b4-a26f-11ed-b6cc-07a9834e19b1)]
|
||||
interface nsIWebAuthnTransport : nsISupports
|
||||
{
|
||||
attribute nsIWebAuthnController controller;
|
||||
|
||||
void makeCredential(in uint64_t aTransactionId, in uint64_t browsingContextId, in nsICtapRegisterArgs args);
|
||||
void getAssertion(in uint64_t aTransactionId, in uint64_t browsingContextId, in nsICtapSignArgs args);
|
||||
|
||||
// These are prompt callbacks but they're not intended to be called directly from
|
||||
// JavaScript---they are proxied through the nsIWebAuthnController first.
|
||||
[noscript] void pinCallback(in uint64_t aTransactionId, in ACString aPin);
|
||||
[noscript] void cancel();
|
||||
};
|
@ -107,6 +107,7 @@
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/WebIDLGlobalNameHash.h"
|
||||
#include "mozilla/dom/U2FTokenManager.h"
|
||||
#include "mozilla/dom/WebAuthnController.h"
|
||||
#ifdef OS_WIN
|
||||
# include "mozilla/dom/WinWebAuthnManager.h"
|
||||
#endif
|
||||
@ -263,6 +264,7 @@ nsresult nsLayoutStatics::Initialize() {
|
||||
mozilla::RemoteLazyInputStreamStorage::Initialize();
|
||||
|
||||
mozilla::dom::U2FTokenManager::Initialize();
|
||||
mozilla::dom::WebAuthnController::Initialize();
|
||||
|
||||
#ifdef OS_WIN
|
||||
mozilla::dom::WinWebAuthnManager::Initialize();
|
||||
|
@ -608,6 +608,3 @@ pref("extensions.systemAddon.update.enabled", true);
|
||||
// E10s stuff. We don't support 'privileged' process types.
|
||||
pref("browser.tabs.remote.separatePrivilegedContentProcess", false);
|
||||
pref("browser.tabs.remote.enforceRemoteTypeRestrictions", false);
|
||||
|
||||
// Allow Web Authentication
|
||||
pref("security.webauth.webauthn_enable_android_fido2", true);
|
||||
|
@ -13528,10 +13528,42 @@
|
||||
|
||||
# WebAuthn CTAP2 support
|
||||
- name: security.webauthn.ctap2
|
||||
type: bool
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Dispatch WebAuthn requests to the software CTAP1 token.
|
||||
# (mutually exclusive with webauthn_enable_android_fido2,
|
||||
# and webauthn_enable_usbtoken)
|
||||
- name: security.webauth.webauthn_enable_softtoken
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# Dispatch WebAuthn requests to the Android platform API
|
||||
- name: security.webauth.webauthn_enable_android_fido2
|
||||
type: RelaxedAtomicBool
|
||||
value: @IS_ANDROID@
|
||||
mirror: always
|
||||
|
||||
# Dispatch WebAuthn requests to authenticator-rs
|
||||
- name: security.webauth.webauthn_enable_usbtoken
|
||||
type: RelaxedAtomicBool
|
||||
value: @IS_NOT_ANDROID@
|
||||
mirror: always
|
||||
|
||||
# Skip direct attestation consent prompts (for tests).
|
||||
- name: security.webauth.webauthn_testing_allow_direct_attestation
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# Global counter for U2F soft token operations
|
||||
- name: security.webauth.softtoken_counter
|
||||
type: RelaxedAtomicUint32
|
||||
value: 0
|
||||
mirror: always
|
||||
|
||||
# Block Worker/SharedWorker scripts with wrong MIME type.
|
||||
- name: security.block_Worker_with_wrong_mime
|
||||
type: bool
|
||||
|
@ -37,19 +37,6 @@ pref("security.remember_cert_checkbox_default_setting", true);
|
||||
// x_11_x: COSE is required, PKCS#7 disabled (fail when present)
|
||||
pref("security.signed_app_signatures.policy", 2);
|
||||
|
||||
// Only one of ["enable_softtoken", "enable_usbtoken",
|
||||
// "webauthn_enable_android_fido2"] should be true at a time, as the
|
||||
// softtoken will override the other two. Note android's pref is set in
|
||||
// mobile.js / geckoview-prefs.js
|
||||
pref("security.webauth.webauthn_enable_softtoken", false);
|
||||
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
// the Rust usbtoken support does not function on Android
|
||||
pref("security.webauth.webauthn_enable_usbtoken", false);
|
||||
#else
|
||||
pref("security.webauth.webauthn_enable_usbtoken", true);
|
||||
#endif
|
||||
|
||||
pref("security.xfocsp.errorReporting.enabled", true);
|
||||
pref("security.xfocsp.errorReporting.automatic", false);
|
||||
|
||||
|
@ -39,7 +39,7 @@ tokio-reactor = { version = "=0.1.3", optional = true }
|
||||
# audioipc2-client and audioipc2-server.
|
||||
tokio-threadpool = { version = "=0.1.17", optional = true }
|
||||
encoding_glue = { path = "../../../../intl/encoding_glue" }
|
||||
authenticator = { version = "0.4.0-alpha.10", features = ["gecko"] }
|
||||
authrs_bridge = { path = "../../../../dom/webauthn/authrs_bridge" }
|
||||
gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
|
||||
gecko_logger = { path = "../../../../xpcom/rust/gecko_logger" }
|
||||
rsdparsa_capi = { path = "../../../../dom/media/webrtc/sdp/rsdparsa_capi" }
|
||||
|
@ -13,7 +13,7 @@ extern crate audioipc2_server;
|
||||
extern crate audioipc_client;
|
||||
#[cfg(feature = "cubeb-remoting")]
|
||||
extern crate audioipc_server;
|
||||
extern crate authenticator;
|
||||
extern crate authrs_bridge;
|
||||
#[cfg(feature = "bitsdownload")]
|
||||
extern crate bitsdownload;
|
||||
#[cfg(feature = "moz_places")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user