Bug 1430150 - Implement WebAuthentication permission prompts r=jcj,johannh
Reviewers: jcj, johannh Reviewed By: jcj, johannh Bug #: 1430150 Differential Revision: https://phabricator.services.mozilla.com/D638
This commit is contained in:
@@ -1367,6 +1367,10 @@ toolbarpaletteitem[place="palette"][hidden] {
|
||||
box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
|
||||
}
|
||||
|
||||
.popup-notification-description[popupid=webauthn-prompt-register-direct] {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.dragfeedback-tab {
|
||||
-moz-appearance: none;
|
||||
opacity: 0.65;
|
||||
|
||||
@@ -1467,6 +1467,7 @@ var gBrowserInit = {
|
||||
BrowserOffline.init();
|
||||
IndexedDBPromptHelper.init();
|
||||
CanvasPermissionPromptHelper.init();
|
||||
WebAuthnPromptHelper.init();
|
||||
|
||||
// Initialize the full zoom setting.
|
||||
// We do this before the session restore service gets initialized so we can
|
||||
@@ -1937,6 +1938,7 @@ var gBrowserInit = {
|
||||
BrowserOffline.uninit();
|
||||
IndexedDBPromptHelper.uninit();
|
||||
CanvasPermissionPromptHelper.uninit();
|
||||
WebAuthnPromptHelper.uninit();
|
||||
PanelUI.uninit();
|
||||
AutoShowBookmarksToolbar.uninit();
|
||||
}
|
||||
@@ -6769,6 +6771,133 @@ var CanvasPermissionPromptHelper = {
|
||||
}
|
||||
};
|
||||
|
||||
var WebAuthnPromptHelper = {
|
||||
_icon: "default-notification-icon",
|
||||
_topic: "webauthn-prompt",
|
||||
|
||||
// The current notification, if any. The U2F manager is a singleton, we will
|
||||
// never allow more than one active request. And thus we'll never have more
|
||||
// than one notification either.
|
||||
_current: null,
|
||||
|
||||
// The current transaction ID. Will be checked when we're notified of the
|
||||
// cancellation of an ongoing WebAuthhn request.
|
||||
_tid: 0,
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, this._topic);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.obs.removeObserver(this, this._topic);
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic, aData) {
|
||||
let mgr = aSubject.QueryInterface(Ci.nsIU2FTokenManager);
|
||||
let data = JSON.parse(aData);
|
||||
|
||||
if (data.action == "register") {
|
||||
this.register(mgr, data);
|
||||
} else if (data.action == "register-direct") {
|
||||
this.registerDirect(mgr, data);
|
||||
} else if (data.action == "sign") {
|
||||
this.sign(mgr, data);
|
||||
} else if (data.action == "cancel") {
|
||||
this.cancel(data);
|
||||
}
|
||||
},
|
||||
|
||||
register(mgr, {origin, tid}) {
|
||||
let mainAction = this.buildCancelAction(mgr, tid);
|
||||
this.show(tid, "register", "webauthn.registerPrompt", origin, mainAction);
|
||||
},
|
||||
|
||||
registerDirect(mgr, {origin, tid}) {
|
||||
let mainAction = this.buildProceedAction(mgr, tid);
|
||||
let secondaryActions = [this.buildCancelAction(mgr, tid)];
|
||||
|
||||
let learnMoreURL =
|
||||
Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
||||
"webauthn-direct-attestation";
|
||||
|
||||
let options = {
|
||||
learnMoreURL,
|
||||
checkbox: {
|
||||
label: gNavigatorBundle.getString("webauthn.anonymize")
|
||||
}
|
||||
};
|
||||
|
||||
this.show(tid, "register-direct", "webauthn.registerDirectPrompt",
|
||||
origin, mainAction, secondaryActions, options);
|
||||
},
|
||||
|
||||
sign(mgr, {origin, tid}) {
|
||||
let mainAction = this.buildCancelAction(mgr, tid);
|
||||
this.show(tid, "sign", "webauthn.signPrompt", origin, mainAction);
|
||||
},
|
||||
|
||||
show(tid, id, stringId, origin, mainAction, secondaryActions = [], options = {}) {
|
||||
this.reset();
|
||||
|
||||
try {
|
||||
origin = Services.io.newURI(origin).asciiHost;
|
||||
} catch (e) {
|
||||
/* Might fail for arbitrary U2F RP IDs. */
|
||||
}
|
||||
|
||||
let brandShortName =
|
||||
document.getElementById("bundle_brand").getString("brandShortName");
|
||||
let message =
|
||||
gNavigatorBundle.getFormattedString(stringId, ["<>", brandShortName], 1);
|
||||
|
||||
options.name = origin;
|
||||
options.hideClose = true;
|
||||
options.eventCallback = event => {
|
||||
if (event == "removed") {
|
||||
this._current = null;
|
||||
this._tid = 0;
|
||||
}
|
||||
};
|
||||
|
||||
this._tid = tid;
|
||||
this._current = PopupNotifications.show(
|
||||
gBrowser.selectedBrowser, `webauthn-prompt-${id}`, message,
|
||||
this._icon, mainAction, secondaryActions, options);
|
||||
},
|
||||
|
||||
cancel({tid}) {
|
||||
if (this._tid == tid) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
if (this._current) {
|
||||
this._current.remove();
|
||||
}
|
||||
},
|
||||
|
||||
buildProceedAction(mgr, tid) {
|
||||
return {
|
||||
label: gNavigatorBundle.getString("webauthn.proceed"),
|
||||
accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
|
||||
callback(state) {
|
||||
mgr.resumeRegister(tid, !state.checkboxChecked);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
buildCancelAction(mgr, tid) {
|
||||
return {
|
||||
label: gNavigatorBundle.getString("webauthn.cancel"),
|
||||
accessKey: gNavigatorBundle.getString("webauthn.cancel.accesskey"),
|
||||
callback() {
|
||||
mgr.cancel(tid);
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function CanCloseWindow() {
|
||||
// Avoid redundant calls to canClose from showing multiple
|
||||
// PermitUnload dialogs.
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
@RESPATH@/components/dom_security.xpt
|
||||
@RESPATH@/components/dom_sidebar.xpt
|
||||
@RESPATH@/components/dom_storage.xpt
|
||||
@RESPATH@/components/dom_webauthn.xpt
|
||||
#ifdef MOZ_WEBSPEECH
|
||||
@RESPATH@/components/dom_webspeechrecognition.xpt
|
||||
#endif
|
||||
|
||||
@@ -504,6 +504,28 @@ canvas.allow=Allow Data Access
|
||||
canvas.allow.accesskey=A
|
||||
canvas.remember=Always remember my decision
|
||||
|
||||
# WebAuthn prompts
|
||||
# LOCALIZATION NOTE (webauthn.registerPrompt): %S is hostname
|
||||
webauthn.registerPrompt=%S wants to register an account with one of your security tokens. You can connect and authorize one now, or cancel.
|
||||
# LOCALIZATION NOTE (webauthn.registerDirectPrompt):
|
||||
# %1$S is hostname. %2$S is brandShortName.
|
||||
# The website is asking for extended information about your
|
||||
# hardware authenticator that shouldn't be generally necessary. Permitting
|
||||
# this is safe if you only use one account at this website. If you have
|
||||
# multiple accounts at this website, and you use the same hardware
|
||||
# authenticator, then the website could link those accounts together.
|
||||
# And this is true even if you use a different profile / browser (or even Tor
|
||||
# Browser). To avoid this, you should use different hardware authenticators
|
||||
# for different accounts on this website.
|
||||
webauthn.registerDirectPrompt=%1$S is requesting extended information about your authenticator, which may affect your privacy.\n\n%2$S can anonymize this for you, but the website might decline this authenticator. If declined, you can try again.
|
||||
# LOCALIZATION NOTE (webauthn.signPrompt): %S is hostname
|
||||
webauthn.signPrompt=%S wants to authenticate you using a registered security token. You can connect and authorize one now, or cancel.
|
||||
webauthn.cancel=Cancel
|
||||
webauthn.cancel.accesskey=c
|
||||
webauthn.proceed=Proceed
|
||||
webauthn.proceed.accesskey=p
|
||||
webauthn.anonymize=Anonymize anyway
|
||||
|
||||
# Spoof Accept-Language prompt
|
||||
privacy.spoof_english=Changing your language setting to English will make you more difficult to identify and enhance your privacy. Do you want to request English language versions of web pages?
|
||||
|
||||
|
||||
@@ -342,12 +342,14 @@ U2F::Register(const nsAString& aAppId,
|
||||
|
||||
uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
|
||||
|
||||
WebAuthnMakeCredentialInfo info(rpIdHash,
|
||||
WebAuthnMakeCredentialInfo info(mOrigin,
|
||||
rpIdHash,
|
||||
clientDataHash,
|
||||
adjustedTimeoutMillis,
|
||||
excludeList,
|
||||
extensions,
|
||||
authSelection);
|
||||
authSelection,
|
||||
false /* RequestDirectAttestation */);
|
||||
|
||||
MOZ_ASSERT(mTransaction.isNothing());
|
||||
mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback))));
|
||||
@@ -483,7 +485,8 @@ U2F::Sign(const nsAString& aAppId,
|
||||
|
||||
uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
|
||||
|
||||
WebAuthnGetAssertionInfo info(rpIdHash,
|
||||
WebAuthnGetAssertionInfo info(mOrigin,
|
||||
rpIdHash,
|
||||
clientDataHash,
|
||||
adjustedTimeoutMillis,
|
||||
permittedList,
|
||||
|
||||
@@ -47,19 +47,23 @@ union WebAuthnExtensionResult {
|
||||
};
|
||||
|
||||
struct WebAuthnMakeCredentialInfo {
|
||||
nsString Origin;
|
||||
uint8_t[] RpIdHash;
|
||||
uint8_t[] ClientDataHash;
|
||||
uint32_t TimeoutMS;
|
||||
WebAuthnScopedCredential[] ExcludeList;
|
||||
WebAuthnExtension[] Extensions;
|
||||
WebAuthnAuthenticatorSelection AuthenticatorSelection;
|
||||
bool RequestDirectAttestation;
|
||||
};
|
||||
|
||||
struct WebAuthnMakeCredentialResult {
|
||||
uint8_t[] RegBuffer;
|
||||
bool DirectAttestationPermitted;
|
||||
};
|
||||
|
||||
struct WebAuthnGetAssertionInfo {
|
||||
nsString Origin;
|
||||
uint8_t[] RpIdHash;
|
||||
uint8_t[] ClientDataHash;
|
||||
uint32_t TimeoutMS;
|
||||
|
||||
@@ -224,7 +224,9 @@ U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult)
|
||||
return;
|
||||
}
|
||||
|
||||
WebAuthnMakeCredentialResult result(registration);
|
||||
// Will be set by the U2FTokenManager.
|
||||
bool directAttestationPermitted = false;
|
||||
WebAuthnMakeCredentialResult result(registration, directAttestationPermitted);
|
||||
mRegisterPromise.Resolve(Move(result), __func__);
|
||||
}
|
||||
|
||||
|
||||
@@ -688,7 +688,10 @@ U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
|
||||
registrationBuf.AppendSECItem(attestCert.get()->derCert);
|
||||
registrationBuf.AppendSECItem(signatureItem);
|
||||
|
||||
WebAuthnMakeCredentialResult result((nsTArray<uint8_t>(registrationBuf)));
|
||||
// Will be set by the U2FTokenManager.
|
||||
bool directAttestationPermitted = false;
|
||||
WebAuthnMakeCredentialResult result((nsTArray<uint8_t>(registrationBuf)),
|
||||
directAttestationPermitted);
|
||||
return U2FRegisterPromise::CreateAndResolve(Move(result), __func__);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
#include "mozilla/dom/PWebAuthnTransactionParent.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/dom/WebAuthnUtil.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "hasht.h"
|
||||
#include "nsICryptoHash.h"
|
||||
#include "nsTextFormatter.h"
|
||||
#include "pkix/Input.h"
|
||||
#include "pkixutil.h"
|
||||
|
||||
@@ -24,6 +26,7 @@
|
||||
#define PREF_U2F_NSSTOKEN_COUNTER "security.webauth.softtoken_counter"
|
||||
#define PREF_WEBAUTHN_SOFTTOKEN_ENABLED "security.webauth.webauthn_enable_softtoken"
|
||||
#define PREF_WEBAUTHN_USBTOKEN_ENABLED "security.webauth.webauthn_enable_usbtoken"
|
||||
#define PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION "security.webauth.webauthn_testing_allow_direct_attestation"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@@ -38,8 +41,19 @@ namespace {
|
||||
static mozilla::LazyLogModule gU2FTokenManagerLog("u2fkeymanager");
|
||||
StaticRefPtr<U2FTokenManager> gU2FTokenManager;
|
||||
StaticRefPtr<U2FPrefManager> gPrefManager;
|
||||
static nsIThread* gBackgroundThread;
|
||||
}
|
||||
|
||||
// Data for WebAuthn UI prompt notifications.
|
||||
static const char16_t kRegisterPromptNotifcation[] =
|
||||
u"{\"action\":\"register\",\"tid\":%llu,\"origin\":\"%s\"}";
|
||||
static const char16_t kRegisterDirectPromptNotifcation[] =
|
||||
u"{\"action\":\"register-direct\",\"tid\":%llu,\"origin\":\"%s\"}";
|
||||
static const char16_t kSignPromptNotifcation[] =
|
||||
u"{\"action\":\"sign\",\"tid\":%llu,\"origin\":\"%s\"}";
|
||||
static const char16_t kCancelPromptNotifcation[] =
|
||||
u"{\"action\":\"cancel\",\"tid\":%llu}";
|
||||
|
||||
class U2FPrefManager final : public nsIObserver
|
||||
{
|
||||
private:
|
||||
@@ -61,6 +75,7 @@ public:
|
||||
Preferences::AddStrongObserver(gPrefManager, PREF_WEBAUTHN_SOFTTOKEN_ENABLED);
|
||||
Preferences::AddStrongObserver(gPrefManager, PREF_U2F_NSSTOKEN_COUNTER);
|
||||
Preferences::AddStrongObserver(gPrefManager, PREF_WEBAUTHN_USBTOKEN_ENABLED);
|
||||
Preferences::AddStrongObserver(gPrefManager, PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
|
||||
ClearOnShutdown(&gPrefManager, ShutdownPhase::ShutdownThreads);
|
||||
}
|
||||
return gPrefManager;
|
||||
@@ -89,6 +104,12 @@ public:
|
||||
return mUsbTokenEnabled;
|
||||
}
|
||||
|
||||
bool GetAllowDirectAttestationForTesting()
|
||||
{
|
||||
MutexAutoLock lock(mPrefMutex);
|
||||
return mAllowDirectAttestation;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Observe(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
@@ -104,12 +125,14 @@ private:
|
||||
mSoftTokenEnabled = Preferences::GetBool(PREF_WEBAUTHN_SOFTTOKEN_ENABLED);
|
||||
mSoftTokenCounter = Preferences::GetUint(PREF_U2F_NSSTOKEN_COUNTER);
|
||||
mUsbTokenEnabled = Preferences::GetBool(PREF_WEBAUTHN_USBTOKEN_ENABLED);
|
||||
mAllowDirectAttestation = Preferences::GetBool(PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
|
||||
}
|
||||
|
||||
Mutex mPrefMutex;
|
||||
bool mSoftTokenEnabled;
|
||||
int mSoftTokenCounter;
|
||||
bool mUsbTokenEnabled;
|
||||
bool mAllowDirectAttestation;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(U2FPrefManager, nsIObserver);
|
||||
@@ -118,6 +141,8 @@ NS_IMPL_ISUPPORTS(U2FPrefManager, nsIObserver);
|
||||
* U2FManager Implementation
|
||||
**********************************************************************/
|
||||
|
||||
NS_IMPL_ISUPPORTS(U2FTokenManager, nsIU2FTokenManager);
|
||||
|
||||
U2FTokenManager::U2FTokenManager()
|
||||
: mTransactionParent(nullptr)
|
||||
, mLastTransactionId(0)
|
||||
@@ -129,11 +154,6 @@ U2FTokenManager::U2FTokenManager()
|
||||
U2FPrefManager::GetOrCreate();
|
||||
}
|
||||
|
||||
U2FTokenManager::~U2FTokenManager()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
//static
|
||||
void
|
||||
U2FTokenManager::Initialize()
|
||||
@@ -178,28 +198,75 @@ U2FTokenManager::MaybeClearTransaction(PWebAuthnTransactionParent* aParent)
|
||||
void
|
||||
U2FTokenManager::ClearTransaction()
|
||||
{
|
||||
if (mLastTransactionId > 0) {
|
||||
// Remove any prompts we might be showing for the current transaction.
|
||||
SendPromptNotification(kCancelPromptNotifcation, mLastTransactionId);
|
||||
}
|
||||
|
||||
mTransactionParent = nullptr;
|
||||
|
||||
// Drop managers at the end of all transactions
|
||||
if (mTokenManagerImpl) {
|
||||
mTokenManagerImpl->Drop();
|
||||
mTokenManagerImpl = nullptr;
|
||||
}
|
||||
|
||||
// Forget promises, if necessary.
|
||||
mRegisterPromise.DisconnectIfExists();
|
||||
mSignPromise.DisconnectIfExists();
|
||||
|
||||
// Clear transaction id.
|
||||
mLastTransactionId = 0;
|
||||
|
||||
// Forget any pending registration.
|
||||
mPendingRegisterInfo.reset();
|
||||
}
|
||||
|
||||
template<typename ...T> void
|
||||
U2FTokenManager::SendPromptNotification(const char16_t* aFormat, T... aArgs)
|
||||
{
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
nsAutoString json;
|
||||
nsTextFormatter::ssprintf(json, aFormat, aArgs...);
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<nsString>(
|
||||
"U2FTokenManager::RunSendPromptNotification", this,
|
||||
&U2FTokenManager::RunSendPromptNotification, json));
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
GetMainThreadEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::RunSendPromptNotification(nsString aJSON)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (NS_WARN_IF(!os)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIU2FTokenManager> self = do_QueryInterface(this);
|
||||
MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(self, "webauthn-prompt", aJSON.get()));
|
||||
}
|
||||
|
||||
RefPtr<U2FTokenTransport>
|
||||
U2FTokenManager::GetTokenManagerImpl()
|
||||
{
|
||||
MOZ_ASSERT(U2FPrefManager::Get());
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (mTokenManagerImpl) {
|
||||
return mTokenManagerImpl;
|
||||
}
|
||||
|
||||
if (!gBackgroundThread) {
|
||||
gBackgroundThread = NS_GetCurrentThread();
|
||||
MOZ_ASSERT(gBackgroundThread, "This should never be null!");
|
||||
}
|
||||
|
||||
auto pm = U2FPrefManager::Get();
|
||||
|
||||
// Prefer the HW token, even if the softtoken is enabled too.
|
||||
@@ -246,13 +313,50 @@ U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t tid = mLastTransactionId = aTransactionId;
|
||||
mLastTransactionId = aTransactionId;
|
||||
|
||||
// If the RP request direct attestation, ask the user for permission and
|
||||
// store the transaction info until the user proceeds or cancels.
|
||||
// Might be overriden by a pref for testing purposes.
|
||||
if (aTransactionInfo.RequestDirectAttestation() &&
|
||||
!U2FPrefManager::Get()->GetAllowDirectAttestationForTesting()) {
|
||||
NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
|
||||
SendPromptNotification(kRegisterDirectPromptNotifcation,
|
||||
aTransactionId,
|
||||
origin.get());
|
||||
|
||||
MOZ_ASSERT(mPendingRegisterInfo.isNothing());
|
||||
mPendingRegisterInfo = Some(aTransactionInfo);
|
||||
} else {
|
||||
DoRegister(aTransactionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo)
|
||||
{
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(mLastTransactionId > 0);
|
||||
|
||||
// Show a prompt that lets the user cancel the ongoing transaction.
|
||||
NS_ConvertUTF16toUTF8 origin(aInfo.Origin());
|
||||
SendPromptNotification(kRegisterPromptNotifcation,
|
||||
mLastTransactionId,
|
||||
origin.get());
|
||||
|
||||
uint64_t tid = mLastTransactionId;
|
||||
mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
|
||||
bool requestDirectAttestation = aInfo.RequestDirectAttestation();
|
||||
|
||||
mTokenManagerImpl
|
||||
->Register(aTransactionInfo)
|
||||
->Register(aInfo)
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
[tid, startTime](WebAuthnMakeCredentialResult&& aResult) {
|
||||
[tid, startTime, requestDirectAttestation](WebAuthnMakeCredentialResult&& aResult) {
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
// The token manager implementations set DirectAttestationPermitted
|
||||
// to false by default. Override this here with information from
|
||||
// the JS prompt.
|
||||
aResult.DirectAttestationPermitted() = requestDirectAttestation;
|
||||
mgr->MaybeConfirmRegister(tid, aResult);
|
||||
Telemetry::ScalarAdd(
|
||||
Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
|
||||
@@ -314,8 +418,15 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
return;
|
||||
}
|
||||
|
||||
// Show a prompt that lets the user cancel the ongoing transaction.
|
||||
NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
|
||||
SendPromptNotification(kSignPromptNotifcation,
|
||||
aTransactionId,
|
||||
origin.get());
|
||||
|
||||
uint64_t tid = mLastTransactionId = aTransactionId;
|
||||
mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
|
||||
|
||||
mTokenManagerImpl
|
||||
->Sign(aTransactionInfo)
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
@@ -372,5 +483,82 @@ U2FTokenManager::Cancel(PWebAuthnTransactionParent* aParent,
|
||||
ClearTransaction();
|
||||
}
|
||||
|
||||
// nsIU2FTokenManager
|
||||
|
||||
NS_IMETHODIMP
|
||||
U2FTokenManager::ResumeRegister(uint64_t aTransactionId,
|
||||
bool aPermitDirectAttestation)
|
||||
{
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!gBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, bool>(
|
||||
"U2FTokenManager::RunResumeRegister", this,
|
||||
&U2FTokenManager::RunResumeRegister, aTransactionId,
|
||||
aPermitDirectAttestation));
|
||||
|
||||
return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::RunResumeRegister(uint64_t aTransactionId,
|
||||
bool aPermitDirectAttestation)
|
||||
{
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (NS_WARN_IF(mPendingRegisterInfo.isNothing())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLastTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Forward whether the user opted into direct attestation.
|
||||
mPendingRegisterInfo.ref().RequestDirectAttestation() =
|
||||
aPermitDirectAttestation;
|
||||
|
||||
// Resume registration and cleanup.
|
||||
DoRegister(mPendingRegisterInfo.ref());
|
||||
mPendingRegisterInfo.reset();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
U2FTokenManager::Cancel(uint64_t aTransactionId)
|
||||
{
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!gBackgroundThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t>(
|
||||
"U2FTokenManager::RunCancel", this,
|
||||
&U2FTokenManager::RunCancel, aTransactionId));
|
||||
|
||||
return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::RunCancel(uint64_t aTransactionId)
|
||||
{
|
||||
mozilla::ipc::AssertIsOnBackgroundThread();
|
||||
|
||||
if (mLastTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel the request.
|
||||
mTokenManagerImpl->Cancel();
|
||||
|
||||
// Reject the promise.
|
||||
AbortTransaction(aTransactionId, NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#ifndef mozilla_dom_U2FTokenManager_h
|
||||
#define mozilla_dom_U2FTokenManager_h
|
||||
|
||||
#include "nsIU2FTokenManager.h"
|
||||
#include "mozilla/dom/U2FTokenTransport.h"
|
||||
#include "mozilla/dom/PWebAuthnTransaction.h"
|
||||
|
||||
@@ -26,10 +27,12 @@ namespace dom {
|
||||
class U2FSoftTokenManager;
|
||||
class WebAuthnTransactionParent;
|
||||
|
||||
class U2FTokenManager final
|
||||
class U2FTokenManager final : public nsIU2FTokenManager
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(U2FTokenManager)
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIU2FTOKENMANAGER
|
||||
|
||||
static U2FTokenManager* Get();
|
||||
void Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
@@ -43,16 +46,27 @@ public:
|
||||
static void Initialize();
|
||||
private:
|
||||
U2FTokenManager();
|
||||
~U2FTokenManager();
|
||||
~U2FTokenManager() { }
|
||||
RefPtr<U2FTokenTransport> GetTokenManagerImpl();
|
||||
void AbortTransaction(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
void ClearTransaction();
|
||||
// Step two of "Register", kicking off the actual transaction.
|
||||
void DoRegister(const WebAuthnMakeCredentialInfo& aInfo);
|
||||
void MaybeConfirmRegister(const uint64_t& aTransactionId,
|
||||
const WebAuthnMakeCredentialResult& aResult);
|
||||
void MaybeAbortRegister(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
void MaybeConfirmSign(const uint64_t& aTransactionId,
|
||||
const WebAuthnGetAssertionResult& aResult);
|
||||
void MaybeAbortSign(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
// The main thread runnable function for "nsIU2FTokenManager.ResumeRegister".
|
||||
void RunResumeRegister(uint64_t aTransactionId, bool aPermitDirectAttestation);
|
||||
// The main thread runnable function for "nsIU2FTokenManager.Cancel".
|
||||
void RunCancel(uint64_t aTransactionId);
|
||||
// Sends a "webauthn-prompt" observer notification with the given data.
|
||||
template<typename ...T>
|
||||
void SendPromptNotification(const char16_t* aFormat, T... aArgs);
|
||||
// The main thread runnable function for "SendPromptNotification".
|
||||
void RunSendPromptNotification(nsString aJSON);
|
||||
// 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.
|
||||
@@ -64,6 +78,8 @@ private:
|
||||
// guards any cancel messages to ensure we don't cancel newer transactions
|
||||
// due to a stale message.
|
||||
uint64_t mLastTransactionId;
|
||||
// Pending registration info while we wait for user input.
|
||||
Maybe<WebAuthnMakeCredentialInfo> mPendingRegisterInfo;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
||||
@@ -408,36 +408,19 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
|
||||
bool requestDirectAttestation =
|
||||
attestation == AttestationConveyancePreference::Direct;
|
||||
|
||||
// XXX Bug 1430150. Need something that allows direct attestation
|
||||
// for tests until we implement a permission dialog we can click.
|
||||
if (requestDirectAttestation) {
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIPrefBranch> branch;
|
||||
rv = prefService->GetBranch("security.webauth.", getter_AddRefs(branch));
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = branch->GetBoolPref("webauthn_testing_allow_direct_attestation",
|
||||
&requestDirectAttestation);
|
||||
}
|
||||
}
|
||||
|
||||
requestDirectAttestation &= NS_SUCCEEDED(rv);
|
||||
}
|
||||
|
||||
// Create and forward authenticator selection criteria.
|
||||
WebAuthnAuthenticatorSelection authSelection(selection.mRequireResidentKey,
|
||||
requireUserVerification,
|
||||
requirePlatformAttachment);
|
||||
|
||||
WebAuthnMakeCredentialInfo info(rpIdHash,
|
||||
WebAuthnMakeCredentialInfo info(origin,
|
||||
rpIdHash,
|
||||
clientDataHash,
|
||||
adjustedTimeout,
|
||||
excludeList,
|
||||
extensions,
|
||||
authSelection);
|
||||
authSelection,
|
||||
requestDirectAttestation);
|
||||
|
||||
ListenForVisibilityEvents();
|
||||
|
||||
@@ -455,6 +438,7 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
|
||||
signal));
|
||||
|
||||
mChild->SendRequestRegister(mTransaction.ref().mId, info);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
@@ -638,7 +622,8 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
|
||||
extensions.AppendElement(WebAuthnExtensionAppId(appIdHash));
|
||||
}
|
||||
|
||||
WebAuthnGetAssertionInfo info(rpIdHash,
|
||||
WebAuthnGetAssertionInfo info(origin,
|
||||
rpIdHash,
|
||||
clientDataHash,
|
||||
adjustedTimeout,
|
||||
allowList,
|
||||
@@ -785,10 +770,10 @@ WebAuthnManager::FinishMakeCredential(const uint64_t& aTransactionId,
|
||||
return;
|
||||
}
|
||||
|
||||
// Direct attestation might have been requested by the RP. mDirectAttestation
|
||||
// will be true only if the user consented via the permission UI.
|
||||
// Direct attestation might have been requested by the RP. This will
|
||||
// be true only if the user consented via the permission UI.
|
||||
CryptoBuffer attObj;
|
||||
if (mTransaction.ref().mDirectAttestation) {
|
||||
if (aResult.DirectAttestationPermitted()) {
|
||||
rv = CBOREncodeFidoU2FAttestationObj(authDataBuf, attestationCertBuf,
|
||||
signatureBuf, attObj);
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,12 @@ IPDL_SOURCES += [
|
||||
'PWebAuthnTransaction.ipdl'
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIU2FTokenManager.idl'
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'dom_webauthn'
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'AuthenticatorAssertionResponse.h',
|
||||
'AuthenticatorAttestationResponse.h',
|
||||
|
||||
38
dom/webauthn/nsIU2FTokenManager.idl
Normal file
38
dom/webauthn/nsIU2FTokenManager.idl
Normal file
@@ -0,0 +1,38 @@
|
||||
/* -*- 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"
|
||||
|
||||
/**
|
||||
* nsIU2FTokenManager
|
||||
*
|
||||
* An interface to the U2FTokenManager singleton.
|
||||
*
|
||||
* This should be used only by the WebAuthn browser UI prompts.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(745e1eac-e449-4342-bca1-ee0e6ead09fc)]
|
||||
interface nsIU2FTokenManager : nsISupports
|
||||
{
|
||||
/**
|
||||
* Resumes the current WebAuthn/U2F transaction if that matches the given
|
||||
* transaction ID. This is used only when direct attestation was requested
|
||||
* and we have to wait for user input to proceed.
|
||||
*
|
||||
* @param aTransactionID : The ID of the transaction to resume.
|
||||
* @param aPermitDirectAttestation : Whether direct attestation was
|
||||
* permitted by the user.
|
||||
*/
|
||||
void resumeRegister(in uint64_t aTransactionID,
|
||||
in bool aPermitDirectAttestation);
|
||||
|
||||
/**
|
||||
* Cancels the current WebAuthn/U2F transaction if that matches the given
|
||||
* transaction ID.
|
||||
*
|
||||
* @param aTransactionID : The ID of the transaction to cancel.
|
||||
*/
|
||||
void cancel(in uint64_t aTransactionID);
|
||||
};
|
||||
@@ -3,11 +3,12 @@ support-files =
|
||||
head.js
|
||||
tab_webauthn_result.html
|
||||
tab_webauthn_success.html
|
||||
../cbor/*
|
||||
../pkijs/*
|
||||
../cbor.js
|
||||
../u2futil.js
|
||||
skip-if = !e10s
|
||||
|
||||
[browser_abort_visibility.js]
|
||||
[browser_fido_appid_extension.js]
|
||||
[browser_webauthn_prompts.js]
|
||||
[browser_webauthn_telemetry.js]
|
||||
|
||||
215
dom/webauthn/tests/browser/browser_webauthn_prompts.js
Normal file
215
dom/webauthn/tests/browser/browser_webauthn_prompts.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "https://example.com/";
|
||||
|
||||
function promiseNotification(id) {
|
||||
return new Promise(resolve => {
|
||||
PopupNotifications.panel.addEventListener("popupshown", function shown() {
|
||||
let notification = PopupNotifications.getNotification(id);
|
||||
if (notification) {
|
||||
ok(true, `${id} prompt visible`);
|
||||
PopupNotifications.panel.removeEventListener("popupshown", shown);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function arrivingHereIsBad(aResult) {
|
||||
ok(false, "Bad result! Received a: " + aResult);
|
||||
}
|
||||
|
||||
function expectAbortError(aResult) {
|
||||
let expected = "AbortError";
|
||||
is(aResult.slice(0, expected.length), expected, `Expecting a ${expected}`);
|
||||
}
|
||||
|
||||
function verifyAnonymizedCertificate(attestationObject) {
|
||||
return webAuthnDecodeCBORAttestation(attestationObject)
|
||||
.then(({fmt, attStmt}) => {
|
||||
is("none", fmt, "Is a None Attestation");
|
||||
is("object", typeof(attStmt), "attStmt is a map");
|
||||
is(0, Object.keys(attStmt).length, "attStmt is empty");
|
||||
});
|
||||
}
|
||||
|
||||
function verifyDirectCertificate(attestationObject) {
|
||||
return webAuthnDecodeCBORAttestation(attestationObject)
|
||||
.then(({fmt, attStmt}) => {
|
||||
is("fido-u2f", fmt, "Is a FIDO U2F Attestation");
|
||||
is("object", typeof(attStmt), "attStmt is a map");
|
||||
ok(attStmt.hasOwnProperty("x5c"), "attStmt.x5c exists");
|
||||
ok(attStmt.hasOwnProperty("sig"), "attStmt.sig exists");
|
||||
});
|
||||
}
|
||||
|
||||
function promiseWebAuthnRegister(tab, attestation = "indirect") {
|
||||
return ContentTask.spawn(tab.linkedBrowser, [attestation],
|
||||
([attestation]) => {
|
||||
const cose_alg_ECDSA_w_SHA256 = -7;
|
||||
|
||||
let challenge = content.crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
let pubKeyCredParams = [{
|
||||
type: "public-key",
|
||||
alg: cose_alg_ECDSA_w_SHA256
|
||||
}];
|
||||
|
||||
let publicKey = {
|
||||
rp: {id: content.document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
pubKeyCredParams,
|
||||
attestation,
|
||||
challenge
|
||||
};
|
||||
|
||||
return content.navigator.credentials.create({publicKey})
|
||||
.then(cred => cred.response.attestationObject);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseWebAuthnSign(tab) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, [], () => {
|
||||
let challenge = content.crypto.getRandomValues(new Uint8Array(16));
|
||||
let key_handle = content.crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
let credential = {
|
||||
id: key_handle,
|
||||
type: "public-key",
|
||||
transports: ["usb"]
|
||||
};
|
||||
|
||||
let publicKey = {
|
||||
challenge,
|
||||
rpId: content.document.domain,
|
||||
allowCredentials: [credential],
|
||||
};
|
||||
|
||||
return content.navigator.credentials.get({publicKey});
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_setup_usbtoken() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["security.webauth.u2f", false],
|
||||
["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", false],
|
||||
["security.webauth.webauthn_enable_usbtoken", true]
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_register() {
|
||||
// Open a new tab.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
||||
// Request a new credential and wait for the prompt.
|
||||
let active = true;
|
||||
let request = promiseWebAuthnRegister(tab)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectAbortError)
|
||||
.then(() => active = false);
|
||||
await promiseNotification("webauthn-prompt-register");
|
||||
|
||||
// Cancel the request.
|
||||
ok(active, "request should still be active");
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
await request;
|
||||
|
||||
// Close tab.
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_sign() {
|
||||
// Open a new tab.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
||||
// Request a new assertion and wait for the prompt.
|
||||
let active = true;
|
||||
let request = promiseWebAuthnSign(tab)
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectAbortError)
|
||||
.then(() => active = false);
|
||||
await promiseNotification("webauthn-prompt-sign");
|
||||
|
||||
// Cancel the request.
|
||||
ok(active, "request should still be active");
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
await request;
|
||||
|
||||
// Close tab.
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_register_direct_cancel() {
|
||||
// Open a new tab.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
||||
// Request a new credential with direct attestation and wait for the prompt.
|
||||
let active = true;
|
||||
let promise = promiseWebAuthnRegister(tab, "direct")
|
||||
.then(arrivingHereIsBad).catch(expectAbortError)
|
||||
.then(() => active = false);
|
||||
await promiseNotification("webauthn-prompt-register-direct");
|
||||
|
||||
// Cancel the request.
|
||||
ok(active, "request should still be active");
|
||||
PopupNotifications.panel.firstChild.secondaryButton.click();
|
||||
await promise;
|
||||
|
||||
// Close tab.
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_setup_softtoken() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["security.webauth.u2f", false],
|
||||
["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", true],
|
||||
["security.webauth.webauthn_enable_usbtoken", false]
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
add_task(async function test_register_direct_proceed() {
|
||||
// Open a new tab.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
||||
// Request a new credential with direct attestation and wait for the prompt.
|
||||
let request = promiseWebAuthnRegister(tab, "direct");
|
||||
await promiseNotification("webauthn-prompt-register-direct");
|
||||
|
||||
// Proceed.
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
|
||||
// Ensure we got "direct" attestation.
|
||||
await request.then(verifyDirectCertificate);
|
||||
|
||||
// Close tab.
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_register_direct_proceed_anon() {
|
||||
// Open a new tab.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
||||
// Request a new credential with direct attestation and wait for the prompt.
|
||||
let request = promiseWebAuthnRegister(tab, "direct");
|
||||
await promiseNotification("webauthn-prompt-register-direct");
|
||||
|
||||
// Check "anonymize anyway" and proceed.
|
||||
PopupNotifications.panel.firstChild.checkbox.checked = true;
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
|
||||
// Ensure we got "none" attestation.
|
||||
await request.then(verifyAnonymizedCertificate);
|
||||
|
||||
// Close tab.
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
@@ -57,6 +57,17 @@ async function executeTestPage(aUri) {
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function test_setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", true],
|
||||
["security.webauth.webauthn_enable_usbtoken", false],
|
||||
["security.webauth.webauthn_testing_allow_direct_attestation", true]
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_loopback() {
|
||||
// These tests can't run simultaneously as the preference changes will race.
|
||||
// So let's run them sequentially here, but in an async function so we can
|
||||
@@ -64,12 +75,6 @@ add_task(async function test_loopback() {
|
||||
const testPage = "https://example.com/browser/dom/webauthn/tests/browser/tab_webauthn_success.html";
|
||||
{
|
||||
cleanupTelemetry();
|
||||
// Enable the soft token, and execute a simple end-to-end test
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn", true);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", true);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", false);
|
||||
Services.prefs.setBoolPref("security.webauth.webauthn_testing_allow_direct_attestation", true);
|
||||
|
||||
await executeTestPage(testPage);
|
||||
|
||||
let webauthn_used = getTelemetryForScalar("security.webauthn_used");
|
||||
|
||||
@@ -4,48 +4,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
function bytesToBase64(u8a){
|
||||
let CHUNK_SZ = 0x8000;
|
||||
let c = [];
|
||||
for (let i = 0; i < u8a.length; i += CHUNK_SZ) {
|
||||
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
|
||||
}
|
||||
return window.btoa(c.join(""));
|
||||
}
|
||||
|
||||
function bytesToBase64UrlSafe(buf) {
|
||||
return bytesToBase64(buf)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
}
|
||||
|
||||
function base64ToBytes(b64encoded) {
|
||||
return new Uint8Array(window.atob(b64encoded).split("").map(function(c) {
|
||||
return c.charCodeAt(0);
|
||||
}));
|
||||
}
|
||||
|
||||
function base64ToBytesUrlSafe(str) {
|
||||
if (!str || str.length % 4 == 1) {
|
||||
throw "Improper b64 string";
|
||||
}
|
||||
|
||||
var b64 = str.replace(/\-/g, "+").replace(/\_/g, "/");
|
||||
while (b64.length % 4 != 0) {
|
||||
b64 += "=";
|
||||
}
|
||||
return base64ToBytes(b64);
|
||||
}
|
||||
|
||||
function buffer2string(buf) {
|
||||
let str = "";
|
||||
if (!(buf.constructor === Uint8Array)) {
|
||||
buf = new Uint8Array(buf);
|
||||
}
|
||||
buf.map(function(x){ return str += String.fromCharCode(x) });
|
||||
return str;
|
||||
}
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/dom/webauthn/tests/browser/cbor.js",
|
||||
this);
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/dom/webauthn/tests/browser/u2futil.js",
|
||||
this);
|
||||
|
||||
function memcmp(x, y) {
|
||||
let xb = new Uint8Array(x);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<script type="text/javascript" src="../pkijs/asn1.js"></script>
|
||||
<script type="text/javascript" src="../pkijs/x509_schema.js"></script>
|
||||
<script type="text/javascript" src="../pkijs/x509_simpl.js"></script>
|
||||
<script type="text/javascript" src="../cbor/cbor.js"></script>
|
||||
<script type="text/javascript" src="cbor.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
cbor/*
|
||||
pkijs/*
|
||||
cbor.js
|
||||
u2futil.js
|
||||
pkijs/*
|
||||
skip-if = !e10s
|
||||
scheme = https
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<script type="text/javascript" src="pkijs/asn1.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
|
||||
<script type="text/javascript" src="cbor/cbor.js"></script>
|
||||
<script type="text/javascript" src="cbor.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<script type="text/javascript" src="pkijs/asn1.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
|
||||
<script type="text/javascript" src="cbor/cbor.js"></script>
|
||||
<script type="text/javascript" src="cbor.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -202,7 +202,7 @@ function webAuthnDecodeAuthDataArray(aAuthData) {
|
||||
console.log(":: Authenticator Data ::");
|
||||
console.log("AAGUID: " + hexEncode(attData.aaguid));
|
||||
|
||||
cborPubKey = aAuthData.slice(55 + attData.credIdLen);
|
||||
let cborPubKey = aAuthData.slice(55 + attData.credIdLen);
|
||||
var pubkeyObj = CBOR.decode(cborPubKey.buffer);
|
||||
if (!(cose_kty in pubkeyObj && cose_alg in pubkeyObj && cose_crv in pubkeyObj
|
||||
&& cose_crv_x in pubkeyObj && cose_crv_y in pubkeyObj)) {
|
||||
|
||||
@@ -512,11 +512,12 @@
|
||||
<xul:label class="popup-notification-origin header"
|
||||
xbl:inherits="value=origin,tooltiptext=origin"
|
||||
crop="center"/>
|
||||
<xul:description class="popup-notification-description"
|
||||
xbl:inherits="popupid">
|
||||
<!-- These need to be on the same line to avoid creating whitespace between them (whitespace is added in the localization file, if necessary). -->
|
||||
<html:span xbl:inherits="xbl:text=label,popupid"/><html:b xbl:inherits="xbl:text=name,popupid"/><html:span xbl:inherits="xbl:text=endlabel,popupid"/>
|
||||
</xul:description>
|
||||
<!-- These need to be on the same line to avoid creating
|
||||
whitespace between them (whitespace is added in the
|
||||
localization file, if necessary). -->
|
||||
<xul:description class="popup-notification-description" xbl:inherits="popupid"><html:span
|
||||
xbl:inherits="xbl:text=label,popupid"/><html:b xbl:inherits="xbl:text=name,popupid"/><html:span
|
||||
xbl:inherits="xbl:text=endlabel,popupid"/></xul:description>
|
||||
</xul:vbox>
|
||||
<xul:toolbarbutton anonid="closebutton"
|
||||
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
|
||||
|
||||
Reference in New Issue
Block a user