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:
John Schanck 2023-03-16 21:42:21 +00:00
parent c53db6818d
commit f40aea4a99
38 changed files with 3782 additions and 1532 deletions

16
Cargo.lock generated
View File

@ -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",

View File

@ -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);
}

View File

@ -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();

View File

@ -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;

View 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

View 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_

View File

@ -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

View File

@ -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
View 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
View 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

View 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

View 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

View File

@ -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;

View File

@ -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;

View File

@ -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__);
}

View File

@ -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;

File diff suppressed because it is too large Load Diff

View 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

View File

@ -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();

View File

@ -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;

View File

@ -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;

View 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

View 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

View File

@ -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;

View File

@ -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

View 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

View File

@ -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;
}

View 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" }

View 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, &notification_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, &notification_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, &notification_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, &notification_str);
continue;
}
PinError::PinAuthBlocked => {
let notification_str =
make_pin_error_prompt("pin-auth-blocked", tid, origin, browsing_context_id);
controller.send_prompt(tid, &notification_str);
}
PinError::PinBlocked => {
let notification_str =
make_pin_error_prompt("device-blocked", tid, origin, browsing_context_id);
controller.send_prompt(tid, &notification_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
}

View File

@ -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",

View File

@ -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.

View 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();
};

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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" }

View File

@ -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")]