Backed out changeset 9ef1475ad0a2 (bug 1890206) for causing build bustages in nsIWebAuthnAttObj.idl CLOSED TREE
This commit is contained in:
@@ -7134,8 +7134,8 @@ var WebAuthnPromptHelper = {
|
|||||||
|
|
||||||
if (data.prompt.type == "presence") {
|
if (data.prompt.type == "presence") {
|
||||||
this.presence_required(mgr, data);
|
this.presence_required(mgr, data);
|
||||||
} else if (data.prompt.type == "attestation-consent") {
|
} else if (data.prompt.type == "register-direct") {
|
||||||
this.attestation_consent(mgr, data);
|
this.registerDirect(mgr, data);
|
||||||
} else if (data.prompt.type == "pin-required") {
|
} else if (data.prompt.type == "pin-required") {
|
||||||
this.pin_required(mgr, false, data);
|
this.pin_required(mgr, false, data);
|
||||||
} else if (data.prompt.type == "pin-invalid") {
|
} else if (data.prompt.type == "pin-invalid") {
|
||||||
@@ -7298,23 +7298,9 @@ var WebAuthnPromptHelper = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
attestation_consent(mgr, { origin, tid }) {
|
registerDirect(mgr, { origin, tid }) {
|
||||||
let mainAction = {
|
let mainAction = this.buildProceedAction(mgr, tid);
|
||||||
label: gNavigatorBundle.getString("webauthn.allow"),
|
let secondaryActions = [this.buildCancelAction(mgr, tid)];
|
||||||
accessKey: gNavigatorBundle.getString("webauthn.allow.accesskey"),
|
|
||||||
callback(_state) {
|
|
||||||
mgr.setHasAttestationConsent(tid, true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let secondaryActions = [
|
|
||||||
{
|
|
||||||
label: gNavigatorBundle.getString("webauthn.block"),
|
|
||||||
accessKey: gNavigatorBundle.getString("webauthn.block.accesskey"),
|
|
||||||
callback(_state) {
|
|
||||||
mgr.setHasAttestationConsent(tid, false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let learnMoreURL =
|
let learnMoreURL =
|
||||||
Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
||||||
@@ -7322,6 +7308,9 @@ var WebAuthnPromptHelper = {
|
|||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
learnMoreURL,
|
learnMoreURL,
|
||||||
|
checkbox: {
|
||||||
|
label: gNavigatorBundle.getString("webauthn.anonymize"),
|
||||||
|
},
|
||||||
hintText: "webauthn.registerDirectPromptHint",
|
hintText: "webauthn.registerDirectPromptHint",
|
||||||
};
|
};
|
||||||
this.show(
|
this.show(
|
||||||
@@ -7444,6 +7433,16 @@ var WebAuthnPromptHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
buildProceedAction(mgr, tid) {
|
||||||
|
return {
|
||||||
|
label: gNavigatorBundle.getString("webauthn.proceed"),
|
||||||
|
accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
|
||||||
|
callback(state) {
|
||||||
|
mgr.resumeMakeCredential(tid, state.checkboxChecked);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
buildCancelAction(mgr, tid) {
|
buildCancelAction(mgr, tid) {
|
||||||
return {
|
return {
|
||||||
label: gNavigatorBundle.getString("webauthn.cancel"),
|
label: gNavigatorBundle.getString("webauthn.cancel"),
|
||||||
|
|||||||
@@ -150,10 +150,9 @@ webauthn.uvBlockedPrompt=User verification failed on %S. There were too many fai
|
|||||||
webauthn.alreadyRegisteredPrompt=This device is already registered. Try a different device.
|
webauthn.alreadyRegisteredPrompt=This device is already registered. Try a different device.
|
||||||
webauthn.cancel=Cancel
|
webauthn.cancel=Cancel
|
||||||
webauthn.cancel.accesskey=c
|
webauthn.cancel.accesskey=c
|
||||||
webauthn.allow=Allow
|
webauthn.proceed=Proceed
|
||||||
webauthn.allow.accesskey=A
|
webauthn.proceed.accesskey=p
|
||||||
webauthn.block=Block
|
webauthn.anonymize=Anonymize anyway
|
||||||
webauthn.block.accesskey=B
|
|
||||||
|
|
||||||
# LOCALIZATION NOTE (identity.identified.verifier, identity.identified.state_and_country, identity.ev.contentOwner2):
|
# LOCALIZATION NOTE (identity.identified.verifier, identity.identified.state_and_country, identity.ev.contentOwner2):
|
||||||
# %S is the hostname of the site that is being displayed.
|
# %S is the hostname of the site that is being displayed.
|
||||||
|
|||||||
@@ -186,6 +186,12 @@ AndroidWebAuthnService::MakeCredential(uint64_t aTransactionId,
|
|||||||
GetCurrentSerialEventTarget(), __func__,
|
GetCurrentSerialEventTarget(), __func__,
|
||||||
[aPromise, credPropsResponse = std::move(credPropsResponse)](
|
[aPromise, credPropsResponse = std::move(credPropsResponse)](
|
||||||
RefPtr<WebAuthnRegisterResult>&& aValue) {
|
RefPtr<WebAuthnRegisterResult>&& aValue) {
|
||||||
|
// We don't have a way for the user to consent to attestation
|
||||||
|
// on Android, so always anonymize the result.
|
||||||
|
nsresult rv = aValue->Anonymize();
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
aPromise->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||||
|
}
|
||||||
if (credPropsResponse.isSome()) {
|
if (credPropsResponse.isSome()) {
|
||||||
Unused << aValue->SetCredPropsRk(credPropsResponse.ref());
|
Unused << aValue->SetCredPropsRk(credPropsResponse.ref());
|
||||||
}
|
}
|
||||||
@@ -351,8 +357,8 @@ AndroidWebAuthnService::PinCallback(uint64_t aTransactionId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
AndroidWebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
|
AndroidWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
|
||||||
bool aHasConsent) {
|
bool aForceNoneAttestation) {
|
||||||
return NS_ERROR_NOT_IMPLEMENTED;
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1121,8 +1121,8 @@ MacOSWebAuthnService::PinCallback(uint64_t aTransactionId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
MacOSWebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
|
MacOSWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
|
||||||
bool aHasConsent) {
|
bool aForceNoneAttestation) {
|
||||||
return NS_ERROR_NOT_IMPLEMENTED;
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,18 +155,7 @@ WebAuthnRegisterArgs::GetTimeoutMS(uint32_t* aTimeoutMS) {
|
|||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
WebAuthnRegisterArgs::GetAttestationConveyancePreference(
|
WebAuthnRegisterArgs::GetAttestationConveyancePreference(
|
||||||
nsAString& aAttestationConveyancePreference) {
|
nsAString& aAttestationConveyancePreference) {
|
||||||
const nsString& attPref = mInfo.attestationConveyancePreference();
|
aAttestationConveyancePreference = mInfo.attestationConveyancePreference();
|
||||||
if (attPref.EqualsLiteral(
|
|
||||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT) ||
|
|
||||||
attPref.EqualsLiteral(
|
|
||||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT) ||
|
|
||||||
attPref.EqualsLiteral(
|
|
||||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE)) {
|
|
||||||
aAttestationConveyancePreference.Assign(attPref);
|
|
||||||
} else {
|
|
||||||
aAttestationConveyancePreference.AssignLiteral(
|
|
||||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE);
|
|
||||||
}
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,27 +102,7 @@ WebAuthnRegisterResult::GetAuthenticatorAttachment(
|
|||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
nsresult WebAuthnRegisterResult::Anonymize() {
|
||||||
WebAuthnRegisterResult::HasIdentifyingAttestation(
|
|
||||||
bool* aHasIdentifyingAttestation) {
|
|
||||||
// Assume the attestation statement is identifying in case the constructor or
|
|
||||||
// the getter below fail.
|
|
||||||
bool isIdentifying = true;
|
|
||||||
|
|
||||||
nsCOMPtr<nsIWebAuthnAttObj> attObj;
|
|
||||||
nsresult rv = authrs_webauthn_att_obj_constructor(mAttestationObject,
|
|
||||||
/* anonymize */ false,
|
|
||||||
getter_AddRefs(attObj));
|
|
||||||
if (NS_SUCCEEDED(rv)) {
|
|
||||||
Unused << attObj->IsIdentifying(&isIdentifying);
|
|
||||||
}
|
|
||||||
|
|
||||||
*aHasIdentifyingAttestation = isIdentifying;
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
WebAuthnRegisterResult::Anonymize() {
|
|
||||||
// The anonymize flag in the nsIWebAuthnAttObj constructor causes the
|
// The anonymize flag in the nsIWebAuthnAttObj constructor causes the
|
||||||
// attestation statement to be removed during deserialization. It also
|
// attestation statement to be removed during deserialization. It also
|
||||||
// causes the AAGUID to be zeroed out. If we can't deserialize the
|
// causes the AAGUID to be zeroed out. If we can't deserialize the
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ class WebAuthnRegisterResult final : public nsIWebAuthnRegisterResult {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
nsresult Anonymize();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~WebAuthnRegisterResult() = default;
|
~WebAuthnRegisterResult() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
#include "mozilla/Services.h"
|
#include "mozilla/Services.h"
|
||||||
#include "mozilla/StaticPrefs_security.h"
|
#include "mozilla/StaticPrefs_security.h"
|
||||||
#include "nsIObserverService.h"
|
#include "nsIObserverService.h"
|
||||||
#include "nsTextFormatter.h"
|
|
||||||
#include "nsThreadUtils.h"
|
#include "nsThreadUtils.h"
|
||||||
#include "WebAuthnEnumStrings.h"
|
|
||||||
#include "WebAuthnService.h"
|
#include "WebAuthnService.h"
|
||||||
#include "WebAuthnTransportIdentifiers.h"
|
#include "WebAuthnTransportIdentifiers.h"
|
||||||
|
|
||||||
@@ -20,139 +18,32 @@ already_AddRefed<nsIWebAuthnService> NewWebAuthnService() {
|
|||||||
|
|
||||||
NS_IMPL_ISUPPORTS(WebAuthnService, nsIWebAuthnService)
|
NS_IMPL_ISUPPORTS(WebAuthnService, nsIWebAuthnService)
|
||||||
|
|
||||||
void WebAuthnService::ShowAttestationConsentPrompt(
|
|
||||||
const nsString& aOrigin, uint64_t aTransactionId,
|
|
||||||
uint64_t aBrowsingContextId) {
|
|
||||||
RefPtr<WebAuthnService> self = this;
|
|
||||||
#ifdef MOZ_WIDGET_ANDROID
|
|
||||||
// We don't have a way to prompt the user for consent on Android, so just
|
|
||||||
// assume consent not granted.
|
|
||||||
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
|
|
||||||
__func__, [self, aOrigin, aTransactionId, aBrowsingContextId]() {
|
|
||||||
self->SetHasAttestationConsent(
|
|
||||||
aTransactionId,
|
|
||||||
StaticPrefs::
|
|
||||||
security_webauth_webauthn_testing_allow_direct_attestation());
|
|
||||||
}));
|
|
||||||
#else
|
|
||||||
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
|
|
||||||
__func__, [self, aOrigin, aTransactionId, aBrowsingContextId]() {
|
|
||||||
if (StaticPrefs::
|
|
||||||
security_webauth_webauthn_testing_allow_direct_attestation()) {
|
|
||||||
self->SetHasAttestationConsent(aTransactionId, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
|
||||||
if (!os) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nsLiteralString jsonFmt =
|
|
||||||
u"{\"prompt\": {\"type\":\"attestation-consent\"},"_ns
|
|
||||||
u"\"origin\": \"%S\","_ns
|
|
||||||
u"\"tid\": %llu, \"browsingContextId\": %llu}"_ns;
|
|
||||||
nsString json;
|
|
||||||
nsTextFormatter::ssprintf(json, jsonFmt.get(), aOrigin.get(),
|
|
||||||
aTransactionId, aBrowsingContextId);
|
|
||||||
MOZ_ALWAYS_SUCCEEDS(
|
|
||||||
os->NotifyObservers(nullptr, "webauthn-prompt", json.get()));
|
|
||||||
}));
|
|
||||||
#endif
|
|
||||||
NS_DispatchToMainThread(runnable.forget());
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
WebAuthnService::MakeCredential(uint64_t aTransactionId,
|
WebAuthnService::MakeCredential(uint64_t aTransactionId,
|
||||||
uint64_t aBrowsingContextId,
|
uint64_t browsingContextId,
|
||||||
nsIWebAuthnRegisterArgs* aArgs,
|
nsIWebAuthnRegisterArgs* aArgs,
|
||||||
nsIWebAuthnRegisterPromise* aPromise) {
|
nsIWebAuthnRegisterPromise* aPromise) {
|
||||||
MOZ_ASSERT(aArgs);
|
|
||||||
MOZ_ASSERT(aPromise);
|
|
||||||
|
|
||||||
auto guard = mTransactionState.Lock();
|
auto guard = mTransactionState.Lock();
|
||||||
ResetLocked(guard);
|
if (guard->isSome()) {
|
||||||
*guard = Some(TransactionState{.service = DefaultService(),
|
guard->ref().service->Reset();
|
||||||
.transactionId = aTransactionId,
|
*guard = Nothing();
|
||||||
.parentRegisterPromise = Some(aPromise)});
|
|
||||||
|
|
||||||
// We may need to show an attestation consent prompt before we return a
|
|
||||||
// credential to WebAuthnTransactionParent, so we insert a new promise that
|
|
||||||
// chains to `aPromise` here.
|
|
||||||
|
|
||||||
nsString attestation;
|
|
||||||
Unused << aArgs->GetAttestationConveyancePreference(attestation);
|
|
||||||
bool attestationRequested = !attestation.EqualsLiteral(
|
|
||||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE);
|
|
||||||
|
|
||||||
nsString origin;
|
|
||||||
Unused << aArgs->GetOrigin(origin);
|
|
||||||
|
|
||||||
RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder =
|
|
||||||
new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
|
|
||||||
|
|
||||||
RefPtr<WebAuthnService> self = this;
|
|
||||||
RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure();
|
|
||||||
promise
|
|
||||||
->Then(
|
|
||||||
GetCurrentSerialEventTarget(), __func__,
|
|
||||||
[self, origin, aTransactionId, aBrowsingContextId,
|
|
||||||
attestationRequested](
|
|
||||||
const WebAuthnRegisterPromise::ResolveOrRejectValue& aValue) {
|
|
||||||
auto guard = self->mTransactionState.Lock();
|
|
||||||
if (guard->isNothing()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
MOZ_ASSERT(guard->ref().parentRegisterPromise.isSome());
|
*guard = Some(TransactionState{DefaultService()});
|
||||||
MOZ_ASSERT(guard->ref().registerResult.isNothing());
|
return guard->ref().service->MakeCredential(aTransactionId, browsingContextId,
|
||||||
MOZ_ASSERT(guard->ref().childRegisterRequest.Exists());
|
aArgs, aPromise);
|
||||||
|
|
||||||
guard->ref().childRegisterRequest.Complete();
|
|
||||||
|
|
||||||
if (aValue.IsReject()) {
|
|
||||||
guard->ref().parentRegisterPromise.ref()->Reject(
|
|
||||||
aValue.RejectValue());
|
|
||||||
guard->reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsIWebAuthnRegisterResult* result = aValue.ResolveValue();
|
|
||||||
// If the RP requested attestation, we need to show a consent prompt
|
|
||||||
// before returning any identifying information. The platform may
|
|
||||||
// have already done this for us, so we need to inspect the
|
|
||||||
// attestation object at this point.
|
|
||||||
bool resultIsIdentifying = true;
|
|
||||||
Unused << result->HasIdentifyingAttestation(&resultIsIdentifying);
|
|
||||||
if (attestationRequested && resultIsIdentifying) {
|
|
||||||
guard->ref().registerResult = Some(result);
|
|
||||||
self->ShowAttestationConsentPrompt(origin, aTransactionId,
|
|
||||||
aBrowsingContextId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
result->Anonymize();
|
|
||||||
guard->ref().parentRegisterPromise.ref()->Resolve(result);
|
|
||||||
guard->reset();
|
|
||||||
})
|
|
||||||
->Track(guard->ref().childRegisterRequest);
|
|
||||||
|
|
||||||
nsresult rv = guard->ref().service->MakeCredential(
|
|
||||||
aTransactionId, aBrowsingContextId, aArgs, promiseHolder);
|
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
|
||||||
}
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
WebAuthnService::GetAssertion(uint64_t aTransactionId,
|
WebAuthnService::GetAssertion(uint64_t aTransactionId,
|
||||||
uint64_t aBrowsingContextId,
|
uint64_t browsingContextId,
|
||||||
nsIWebAuthnSignArgs* aArgs,
|
nsIWebAuthnSignArgs* aArgs,
|
||||||
nsIWebAuthnSignPromise* aPromise) {
|
nsIWebAuthnSignPromise* aPromise) {
|
||||||
MOZ_ASSERT(aArgs);
|
|
||||||
MOZ_ASSERT(aPromise);
|
|
||||||
|
|
||||||
auto guard = mTransactionState.Lock();
|
auto guard = mTransactionState.Lock();
|
||||||
ResetLocked(guard);
|
if (guard->isSome()) {
|
||||||
*guard = Some(TransactionState{.service = DefaultService(),
|
guard->ref().service->Reset();
|
||||||
.transactionId = aTransactionId});
|
*guard = Nothing();
|
||||||
|
}
|
||||||
|
*guard = Some(TransactionState{DefaultService()});
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
|
|
||||||
#if defined(XP_MACOSX)
|
#if defined(XP_MACOSX)
|
||||||
@@ -180,7 +71,7 @@ WebAuthnService::GetAssertion(uint64_t aTransactionId,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rv = guard->ref().service->GetAssertion(aTransactionId, aBrowsingContextId,
|
rv = guard->ref().service->GetAssertion(aTransactionId, browsingContextId,
|
||||||
aArgs, aPromise);
|
aArgs, aPromise);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
@@ -234,22 +125,13 @@ WebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
|
|||||||
return SelectedService()->ResumeConditionalGet(aTransactionId);
|
return SelectedService()->ResumeConditionalGet(aTransactionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebAuthnService::ResetLocked(
|
|
||||||
const TransactionStateMutex::AutoLock& aGuard) {
|
|
||||||
if (aGuard->isSome()) {
|
|
||||||
aGuard->ref().childRegisterRequest.DisconnectIfExists();
|
|
||||||
if (aGuard->ref().parentRegisterPromise.isSome()) {
|
|
||||||
aGuard->ref().parentRegisterPromise.ref()->Reject(NS_ERROR_DOM_ABORT_ERR);
|
|
||||||
}
|
|
||||||
aGuard->ref().service->Reset();
|
|
||||||
}
|
|
||||||
aGuard->reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
WebAuthnService::Reset() {
|
WebAuthnService::Reset() {
|
||||||
auto guard = mTransactionState.Lock();
|
auto guard = mTransactionState.Lock();
|
||||||
ResetLocked(guard);
|
if (guard->isSome()) {
|
||||||
|
guard->ref().service->Reset();
|
||||||
|
}
|
||||||
|
*guard = Nothing();
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,27 +146,10 @@ WebAuthnService::PinCallback(uint64_t aTransactionId, const nsACString& aPin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
WebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
|
WebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
|
||||||
bool aHasConsent) {
|
bool aForceNoneAttestation) {
|
||||||
auto guard = this->mTransactionState.Lock();
|
return SelectedService()->ResumeMakeCredential(aTransactionId,
|
||||||
if (guard->isNothing() || guard->ref().transactionId != aTransactionId) {
|
aForceNoneAttestation);
|
||||||
// This could happen if the transaction was reset just when the prompt was
|
|
||||||
// receiving user input.
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
MOZ_ASSERT(guard->ref().parentRegisterPromise.isSome());
|
|
||||||
MOZ_ASSERT(guard->ref().registerResult.isSome());
|
|
||||||
MOZ_ASSERT(!guard->ref().childRegisterRequest.Exists());
|
|
||||||
|
|
||||||
if (!aHasConsent) {
|
|
||||||
guard->ref().registerResult.ref()->Anonymize();
|
|
||||||
}
|
|
||||||
guard->ref().parentRegisterPromise.ref()->Resolve(
|
|
||||||
guard->ref().registerResult.ref());
|
|
||||||
|
|
||||||
guard->reset();
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "nsIWebAuthnService.h"
|
#include "nsIWebAuthnService.h"
|
||||||
#include "AuthrsBridge_ffi.h"
|
#include "AuthrsBridge_ffi.h"
|
||||||
#include "mozilla/dom/WebAuthnPromiseHolder.h"
|
|
||||||
|
|
||||||
#ifdef MOZ_WIDGET_ANDROID
|
#ifdef MOZ_WIDGET_ANDROID
|
||||||
# include "AndroidWebAuthnService.h"
|
# include "AndroidWebAuthnService.h"
|
||||||
@@ -56,21 +55,6 @@ class WebAuthnService final : public nsIWebAuthnService {
|
|||||||
private:
|
private:
|
||||||
~WebAuthnService() = default;
|
~WebAuthnService() = default;
|
||||||
|
|
||||||
struct TransactionState {
|
|
||||||
nsCOMPtr<nsIWebAuthnService> service;
|
|
||||||
uint64_t transactionId;
|
|
||||||
Maybe<nsCOMPtr<nsIWebAuthnRegisterPromise>> parentRegisterPromise;
|
|
||||||
Maybe<nsCOMPtr<nsIWebAuthnRegisterResult>> registerResult;
|
|
||||||
MozPromiseRequestHolder<WebAuthnRegisterPromise> childRegisterRequest;
|
|
||||||
};
|
|
||||||
using TransactionStateMutex = DataMutex<Maybe<TransactionState>>;
|
|
||||||
TransactionStateMutex mTransactionState;
|
|
||||||
|
|
||||||
void ShowAttestationConsentPrompt(const nsString& aOrigin,
|
|
||||||
uint64_t aTransactionId,
|
|
||||||
uint64_t aBrowsingContextId);
|
|
||||||
void ResetLocked(const TransactionStateMutex::AutoLock& aGuard);
|
|
||||||
|
|
||||||
nsIWebAuthnService* DefaultService() {
|
nsIWebAuthnService* DefaultService() {
|
||||||
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
|
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
|
||||||
return mAuthrsService;
|
return mAuthrsService;
|
||||||
@@ -88,6 +72,12 @@ class WebAuthnService final : public nsIWebAuthnService {
|
|||||||
return DefaultService();
|
return DefaultService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TransactionState {
|
||||||
|
nsCOMPtr<nsIWebAuthnService> service;
|
||||||
|
};
|
||||||
|
using TransactionStateMutex = DataMutex<Maybe<TransactionState>>;
|
||||||
|
TransactionStateMutex mTransactionState;
|
||||||
|
|
||||||
nsCOMPtr<nsIWebAuthnService> mAuthrsService;
|
nsCOMPtr<nsIWebAuthnService> mAuthrsService;
|
||||||
nsCOMPtr<nsIWebAuthnService> mPlatformService;
|
nsCOMPtr<nsIWebAuthnService> mPlatformService;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -411,12 +411,14 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
|
|||||||
// AttestationConveyance
|
// AttestationConveyance
|
||||||
nsString attestation;
|
nsString attestation;
|
||||||
Unused << aArgs->GetAttestationConveyancePreference(attestation);
|
Unused << aArgs->GetAttestationConveyancePreference(attestation);
|
||||||
|
bool anonymize = false;
|
||||||
// This mapping needs to be reviewed if values are added to the
|
// This mapping needs to be reviewed if values are added to the
|
||||||
// AttestationConveyancePreference enum.
|
// AttestationConveyancePreference enum.
|
||||||
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
|
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
|
||||||
if (attestation.EqualsLiteral(
|
if (attestation.EqualsLiteral(
|
||||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE)) {
|
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE)) {
|
||||||
winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
|
winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
|
||||||
|
anonymize = true;
|
||||||
} else if (
|
} else if (
|
||||||
attestation.EqualsLiteral(
|
attestation.EqualsLiteral(
|
||||||
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT)) {
|
MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT)) {
|
||||||
@@ -577,6 +579,13 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
|
|||||||
}
|
}
|
||||||
gWinWebauthnFreeCredentialAttestation(pWebAuthNCredentialAttestation);
|
gWinWebauthnFreeCredentialAttestation(pWebAuthNCredentialAttestation);
|
||||||
|
|
||||||
|
if (anonymize) {
|
||||||
|
nsresult rv = result->Anonymize();
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
aPromise->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
aPromise->Resolve(result);
|
aPromise->Resolve(result);
|
||||||
} else {
|
} else {
|
||||||
PCWSTR errorName = gWinWebauthnGetErrorName(hr);
|
PCWSTR errorName = gWinWebauthnGetErrorName(hr);
|
||||||
@@ -968,8 +977,8 @@ WinWebAuthnService::PinCallback(uint64_t aTransactionId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
WinWebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
|
WinWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
|
||||||
bool aHasConsent) {
|
bool aForceNoneAttestation) {
|
||||||
return NS_ERROR_NOT_IMPLEMENTED;
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ extern crate xpcom;
|
|||||||
|
|
||||||
use authenticator::{
|
use authenticator::{
|
||||||
authenticatorservice::{RegisterArgs, SignArgs},
|
authenticatorservice::{RegisterArgs, SignArgs},
|
||||||
ctap2::attestation::{AAGuid, AttestationObject, AttestationStatement},
|
ctap2::attestation::AttestationObject,
|
||||||
ctap2::commands::{get_info::AuthenticatorVersion, PinUvAuthResult},
|
ctap2::commands::{get_info::AuthenticatorVersion, PinUvAuthResult},
|
||||||
ctap2::server::{
|
ctap2::server::{
|
||||||
AuthenticationExtensionsClientInputs, AuthenticatorAttachment,
|
AuthenticationExtensionsClientInputs, AuthenticatorAttachment,
|
||||||
@@ -34,7 +34,6 @@ use nserror::{
|
|||||||
use nsstring::{nsACString, nsAString, nsCString, nsString};
|
use nsstring::{nsACString, nsAString, nsCString, nsString};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
|
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
@@ -87,6 +86,7 @@ enum BrowserPromptType<'a> {
|
|||||||
},
|
},
|
||||||
PinIsTooLong,
|
PinIsTooLong,
|
||||||
PinIsTooShort,
|
PinIsTooShort,
|
||||||
|
RegisterDirect,
|
||||||
UvInvalid {
|
UvInvalid {
|
||||||
retries: Option<u8>,
|
retries: Option<u8>,
|
||||||
},
|
},
|
||||||
@@ -166,8 +166,7 @@ fn cancel_prompts(tid: u64) -> Result<(), nsresult> {
|
|||||||
|
|
||||||
#[xpcom(implement(nsIWebAuthnRegisterResult), atomic)]
|
#[xpcom(implement(nsIWebAuthnRegisterResult), atomic)]
|
||||||
pub struct WebAuthnRegisterResult {
|
pub struct WebAuthnRegisterResult {
|
||||||
// result is only borrowed mutably in `Anonymize`.
|
result: RegisterResult,
|
||||||
result: RefCell<RegisterResult>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebAuthnRegisterResult {
|
impl WebAuthnRegisterResult {
|
||||||
@@ -179,13 +178,13 @@ impl WebAuthnRegisterResult {
|
|||||||
xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
|
xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
|
||||||
fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
|
fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||||
let mut out = ThinVec::new();
|
let mut out = ThinVec::new();
|
||||||
serde_cbor::to_writer(&mut out, &self.result.borrow().att_obj).or(Err(NS_ERROR_FAILURE))?;
|
serde_cbor::to_writer(&mut out, &self.result.att_obj).or(Err(NS_ERROR_FAILURE))?;
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
|
xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
|
||||||
fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
|
fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
|
||||||
let Some(credential_data) = &self.result.borrow().att_obj.auth_data.credential_data else {
|
let Some(credential_data) = &self.result.att_obj.auth_data.credential_data else {
|
||||||
return Err(NS_ERROR_FAILURE);
|
return Err(NS_ERROR_FAILURE);
|
||||||
};
|
};
|
||||||
Ok(credential_data.credential_id.as_slice().into())
|
Ok(credential_data.credential_id.as_slice().into())
|
||||||
@@ -198,7 +197,7 @@ impl WebAuthnRegisterResult {
|
|||||||
// In tests, the result is not very important, but we can at least return "internal" if
|
// In tests, the result is not very important, but we can at least return "internal" if
|
||||||
// we're simulating platform attachment.
|
// we're simulating platform attachment.
|
||||||
if static_prefs::pref!("security.webauth.webauthn_enable_softtoken")
|
if static_prefs::pref!("security.webauth.webauthn_enable_softtoken")
|
||||||
&& self.result.borrow().attachment == AuthenticatorAttachment::Platform
|
&& self.result.attachment == AuthenticatorAttachment::Platform
|
||||||
{
|
{
|
||||||
Ok(thin_vec![nsString::from("internal")])
|
Ok(thin_vec![nsString::from("internal")])
|
||||||
} else {
|
} else {
|
||||||
@@ -208,7 +207,7 @@ impl WebAuthnRegisterResult {
|
|||||||
|
|
||||||
xpcom_method!(get_hmac_create_secret => GetHmacCreateSecret() -> bool);
|
xpcom_method!(get_hmac_create_secret => GetHmacCreateSecret() -> bool);
|
||||||
fn get_hmac_create_secret(&self) -> Result<bool, nsresult> {
|
fn get_hmac_create_secret(&self) -> Result<bool, nsresult> {
|
||||||
let Some(hmac_create_secret) = self.result.borrow().extensions.hmac_create_secret else {
|
let Some(hmac_create_secret) = self.result.extensions.hmac_create_secret else {
|
||||||
return Err(NS_ERROR_NOT_AVAILABLE);
|
return Err(NS_ERROR_NOT_AVAILABLE);
|
||||||
};
|
};
|
||||||
Ok(hmac_create_secret)
|
Ok(hmac_create_secret)
|
||||||
@@ -216,7 +215,7 @@ impl WebAuthnRegisterResult {
|
|||||||
|
|
||||||
xpcom_method!(get_cred_props_rk => GetCredPropsRk() -> bool);
|
xpcom_method!(get_cred_props_rk => GetCredPropsRk() -> bool);
|
||||||
fn get_cred_props_rk(&self) -> Result<bool, nsresult> {
|
fn get_cred_props_rk(&self) -> Result<bool, nsresult> {
|
||||||
let Some(cred_props) = &self.result.borrow().extensions.cred_props else {
|
let Some(cred_props) = &self.result.extensions.cred_props else {
|
||||||
return Err(NS_ERROR_NOT_AVAILABLE);
|
return Err(NS_ERROR_NOT_AVAILABLE);
|
||||||
};
|
};
|
||||||
Ok(cred_props.rk)
|
Ok(cred_props.rk)
|
||||||
@@ -229,29 +228,12 @@ impl WebAuthnRegisterResult {
|
|||||||
|
|
||||||
xpcom_method!(get_authenticator_attachment => GetAuthenticatorAttachment() -> nsAString);
|
xpcom_method!(get_authenticator_attachment => GetAuthenticatorAttachment() -> nsAString);
|
||||||
fn get_authenticator_attachment(&self) -> Result<nsString, nsresult> {
|
fn get_authenticator_attachment(&self) -> Result<nsString, nsresult> {
|
||||||
match self.result.borrow().attachment {
|
match self.result.attachment {
|
||||||
AuthenticatorAttachment::CrossPlatform => Ok(nsString::from("cross-platform")),
|
AuthenticatorAttachment::CrossPlatform => Ok(nsString::from("cross-platform")),
|
||||||
AuthenticatorAttachment::Platform => Ok(nsString::from("platform")),
|
AuthenticatorAttachment::Platform => Ok(nsString::from("platform")),
|
||||||
AuthenticatorAttachment::Unknown => Err(NS_ERROR_NOT_AVAILABLE),
|
AuthenticatorAttachment::Unknown => Err(NS_ERROR_NOT_AVAILABLE),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xpcom_method!(has_identifying_attestation => HasIdentifyingAttestation() -> bool);
|
|
||||||
fn has_identifying_attestation(&self) -> Result<bool, nsresult> {
|
|
||||||
if self.result.borrow().att_obj.att_stmt != AttestationStatement::None {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
if let Some(data) = &self.result.borrow().att_obj.auth_data.credential_data {
|
|
||||||
return Ok(data.aaguid != AAGuid::default());
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
xpcom_method!(anonymize => Anonymize());
|
|
||||||
fn anonymize(&self) -> Result<nsresult, nsresult> {
|
|
||||||
self.result.borrow_mut().att_obj.anonymize();
|
|
||||||
Ok(NS_OK)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[xpcom(implement(nsIWebAuthnAttObj), atomic)]
|
#[xpcom(implement(nsIWebAuthnAttObj), atomic)]
|
||||||
@@ -293,17 +275,6 @@ impl WebAuthnAttObj {
|
|||||||
// safe to cast to i32 by inspection of defined values
|
// safe to cast to i32 by inspection of defined values
|
||||||
Ok(credential_data.credential_public_key.alg as i32)
|
Ok(credential_data.credential_public_key.alg as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
xpcom_method!(is_identifying => IsIdentifying() -> bool);
|
|
||||||
fn is_identifying(&self) -> Result<bool, nsresult> {
|
|
||||||
if self.att_obj.att_stmt != AttestationStatement::None {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
if let Some(data) = &self.att_obj.auth_data.credential_data {
|
|
||||||
return Ok(data.aaguid != AAGuid::default());
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[xpcom(implement(nsIWebAuthnSignResult), atomic)]
|
#[xpcom(implement(nsIWebAuthnSignResult), atomic)]
|
||||||
@@ -519,9 +490,8 @@ impl RegisterPromise {
|
|||||||
fn resolve_or_reject(&self, result: Result<RegisterResult, nsresult>) -> Result<(), nsresult> {
|
fn resolve_or_reject(&self, result: Result<RegisterResult, nsresult>) -> Result<(), nsresult> {
|
||||||
match result {
|
match result {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let wrapped_result = WebAuthnRegisterResult::allocate(InitWebAuthnRegisterResult {
|
let wrapped_result =
|
||||||
result: RefCell::new(result),
|
WebAuthnRegisterResult::allocate(InitWebAuthnRegisterResult { result })
|
||||||
})
|
|
||||||
.query_interface::<nsIWebAuthnRegisterResult>()
|
.query_interface::<nsIWebAuthnRegisterResult>()
|
||||||
.ok_or(NS_ERROR_FAILURE)?;
|
.ok_or(NS_ERROR_FAILURE)?;
|
||||||
unsafe { self.0.Resolve(wrapped_result.coerce()) };
|
unsafe { self.0.Resolve(wrapped_result.coerce()) };
|
||||||
@@ -573,6 +543,7 @@ impl TransactionPromise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum TransactionArgs {
|
enum TransactionArgs {
|
||||||
|
Register(/* timeout */ u64, RegisterArgs),
|
||||||
Sign(/* timeout */ u64, SignArgs),
|
Sign(/* timeout */ u64, SignArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,6 +713,13 @@ impl AuthrsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut attestation_conveyance_preference = nsString::new();
|
||||||
|
unsafe { args.GetAttestationConveyancePreference(&mut *attestation_conveyance_preference) }
|
||||||
|
.to_result()?;
|
||||||
|
let none_attestation = !(attestation_conveyance_preference.eq("indirect")
|
||||||
|
|| attestation_conveyance_preference.eq("direct")
|
||||||
|
|| attestation_conveyance_preference.eq("enterprise"));
|
||||||
|
|
||||||
let mut cred_props = false;
|
let mut cred_props = false;
|
||||||
unsafe { args.GetCredProps(&mut cred_props) }.to_result()?;
|
unsafe { args.GetCredProps(&mut cred_props) }.to_result()?;
|
||||||
|
|
||||||
@@ -782,19 +760,51 @@ impl AuthrsService {
|
|||||||
use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
|
use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut guard = self.transaction.lock().unwrap();
|
*self.transaction.lock().unwrap() = Some(TransactionState {
|
||||||
*guard = Some(TransactionState {
|
|
||||||
tid,
|
tid,
|
||||||
browsing_context_id,
|
browsing_context_id,
|
||||||
pending_args: None,
|
pending_args: Some(TransactionArgs::Register(timeout_ms as u64, info)),
|
||||||
promise: TransactionPromise::Register(promise),
|
promise: TransactionPromise::Register(promise),
|
||||||
pin_receiver: None,
|
pin_receiver: None,
|
||||||
selection_receiver: None,
|
selection_receiver: None,
|
||||||
interactive_receiver: None,
|
interactive_receiver: None,
|
||||||
puat_cache: None,
|
puat_cache: None,
|
||||||
});
|
});
|
||||||
// drop the guard here to ensure we don't deadlock if the call to `register()` below
|
|
||||||
// hairpins the state callback.
|
if none_attestation
|
||||||
|
|| static_prefs::pref!("security.webauth.webauthn_testing_allow_direct_attestation")
|
||||||
|
{
|
||||||
|
self.resume_make_credential(tid, none_attestation)
|
||||||
|
} else {
|
||||||
|
send_prompt(
|
||||||
|
BrowserPromptType::RegisterDirect,
|
||||||
|
tid,
|
||||||
|
Some(&origin),
|
||||||
|
Some(browsing_context_id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xpcom_method!(resume_make_credential => ResumeMakeCredential(aTid: u64, aForceNoneAttestation: bool));
|
||||||
|
fn resume_make_credential(
|
||||||
|
&self,
|
||||||
|
tid: u64,
|
||||||
|
force_none_attestation: bool,
|
||||||
|
) -> Result<(), nsresult> {
|
||||||
|
let mut guard = self.transaction.lock().unwrap();
|
||||||
|
let Some(state) = guard.as_mut() else {
|
||||||
|
return Err(NS_ERROR_FAILURE);
|
||||||
|
};
|
||||||
|
if state.tid != tid {
|
||||||
|
return Err(NS_ERROR_FAILURE);
|
||||||
|
};
|
||||||
|
let browsing_context_id = state.browsing_context_id;
|
||||||
|
let Some(TransactionArgs::Register(timeout_ms, info)) = state.pending_args.take() else {
|
||||||
|
return Err(NS_ERROR_FAILURE);
|
||||||
|
};
|
||||||
|
// We have to drop the guard here, as there _may_ still be another operation
|
||||||
|
// ongoing and `register()` below will try to cancel it. This will call the state
|
||||||
|
// callback of that operation, which in turn may try to access `transaction`, deadlocking.
|
||||||
drop(guard);
|
drop(guard);
|
||||||
|
|
||||||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||||
@@ -815,7 +825,7 @@ impl AuthrsService {
|
|||||||
let callback_transaction = self.transaction.clone();
|
let callback_transaction = self.transaction.clone();
|
||||||
let callback_origin = info.origin.clone();
|
let callback_origin = info.origin.clone();
|
||||||
let state_callback = StateCallback::<Result<RegisterResult, AuthenticatorError>>::new(
|
let state_callback = StateCallback::<Result<RegisterResult, AuthenticatorError>>::new(
|
||||||
Box::new(move |result| {
|
Box::new(move |mut result| {
|
||||||
let mut guard = callback_transaction.lock().unwrap();
|
let mut guard = callback_transaction.lock().unwrap();
|
||||||
let Some(state) = guard.as_mut() else {
|
let Some(state) = guard.as_mut() else {
|
||||||
return;
|
return;
|
||||||
@@ -826,6 +836,13 @@ impl AuthrsService {
|
|||||||
let TransactionPromise::Register(ref promise) = state.promise else {
|
let TransactionPromise::Register(ref promise) = state.promise else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
if let Ok(inner) = result.as_mut() {
|
||||||
|
// Tokens always provide attestation, but the user may have asked we not
|
||||||
|
// include the attestation statement in the response.
|
||||||
|
if force_none_attestation {
|
||||||
|
inner.att_obj.anonymize();
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Err(AuthenticatorError::CredentialExcluded) = result {
|
if let Err(AuthenticatorError::CredentialExcluded) = result {
|
||||||
let _ = send_prompt(
|
let _ = send_prompt(
|
||||||
BrowserPromptType::AlreadyRegistered,
|
BrowserPromptType::AlreadyRegistered,
|
||||||
@@ -857,14 +874,14 @@ impl AuthrsService {
|
|||||||
Some(browsing_context_id),
|
Some(browsing_context_id),
|
||||||
)?;
|
)?;
|
||||||
self.usb_token_manager.lock().unwrap().register(
|
self.usb_token_manager.lock().unwrap().register(
|
||||||
timeout_ms.into(),
|
timeout_ms,
|
||||||
info,
|
info,
|
||||||
status_tx,
|
status_tx,
|
||||||
state_callback,
|
state_callback,
|
||||||
);
|
);
|
||||||
} else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
|
} else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
|
||||||
self.test_token_manager
|
self.test_token_manager
|
||||||
.register(timeout_ms.into(), info, status_tx, state_callback);
|
.register(timeout_ms, info, status_tx, state_callback);
|
||||||
} else {
|
} else {
|
||||||
return Err(NS_ERROR_FAILURE);
|
return Err(NS_ERROR_FAILURE);
|
||||||
}
|
}
|
||||||
@@ -872,11 +889,6 @@ impl AuthrsService {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
xpcom_method!(set_has_attestation_consent => SetHasAttestationConsent(aTid: u64, aHasConsent: bool));
|
|
||||||
fn set_has_attestation_consent(&self, _tid: u64, _has_consent: bool) -> Result<(), nsresult> {
|
|
||||||
Err(NS_ERROR_NOT_IMPLEMENTED)
|
|
||||||
}
|
|
||||||
|
|
||||||
xpcom_method!(get_assertion => GetAssertion(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsIWebAuthnSignArgs, aPromise: *const nsIWebAuthnSignPromise));
|
xpcom_method!(get_assertion => GetAssertion(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsIWebAuthnSignArgs, aPromise: *const nsIWebAuthnSignPromise));
|
||||||
fn get_assertion(
|
fn get_assertion(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ interface nsIWebAuthnAttObj : nsISupports {
|
|||||||
readonly attribute Array<octet> publicKey;
|
readonly attribute Array<octet> publicKey;
|
||||||
|
|
||||||
readonly attribute COSEAlgorithmIdentifier publicKeyAlgorithm;
|
readonly attribute COSEAlgorithmIdentifier publicKeyAlgorithm;
|
||||||
|
|
||||||
bool isIdentifying();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ interface nsIWebAuthnRegisterResult : nsISupports {
|
|||||||
[must_use] attribute boolean credPropsRk;
|
[must_use] attribute boolean credPropsRk;
|
||||||
|
|
||||||
[must_use] readonly attribute AString authenticatorAttachment;
|
[must_use] readonly attribute AString authenticatorAttachment;
|
||||||
|
|
||||||
bool hasIdentifyingAttestation();
|
|
||||||
void anonymize();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The nsIWebAuthnSignResult interface is used to construct IPDL-defined
|
// The nsIWebAuthnSignResult interface is used to construct IPDL-defined
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ interface nsIWebAuthnService : nsISupports
|
|||||||
void resumeConditionalGet(in uint64_t aTransactionId);
|
void resumeConditionalGet(in uint64_t aTransactionId);
|
||||||
|
|
||||||
void pinCallback(in uint64_t aTransactionId, in ACString aPin);
|
void pinCallback(in uint64_t aTransactionId, in ACString aPin);
|
||||||
void setHasAttestationConsent(in uint64_t aTransactionId, in boolean aHasConsent);
|
void resumeMakeCredential(in uint64_t aTransactionId, in boolean aForceNoneAttestation);
|
||||||
void selectionCallback(in uint64_t aTransactionId, in uint64_t aIndex);
|
void selectionCallback(in uint64_t aTransactionId, in uint64_t aIndex);
|
||||||
|
|
||||||
// Adds a virtual (software) authenticator for use in tests (particularly
|
// Adds a virtual (software) authenticator for use in tests (particularly
|
||||||
|
|||||||
@@ -46,13 +46,15 @@ add_task(async function test_webauthn_modal_request_cancels_conditional_get() {
|
|||||||
ok(active, "conditional request should still be active");
|
ok(active, "conditional request should still be active");
|
||||||
|
|
||||||
let promptPromise = promiseNotification("webauthn-prompt-register-direct");
|
let promptPromise = promiseNotification("webauthn-prompt-register-direct");
|
||||||
let modalPromise = promiseWebAuthnMakeCredential(tab, "direct");
|
let modalPromise = promiseWebAuthnMakeCredential(tab, "direct")
|
||||||
|
.then(arrivingHereIsBad)
|
||||||
|
.catch(gExpectNotAllowedError);
|
||||||
|
|
||||||
await condPromise;
|
await condPromise;
|
||||||
|
|
||||||
ok(!active, "conditional request should not be active");
|
ok(!active, "conditional request should not be active");
|
||||||
|
|
||||||
// Proceed through the consent prompt
|
// Cancel the modal request with the button.
|
||||||
await promptPromise;
|
await promptPromise;
|
||||||
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
||||||
await modalPromise;
|
await modalPromise;
|
||||||
|
|||||||
@@ -43,26 +43,34 @@ add_task(async function test_setup_usbtoken() {
|
|||||||
});
|
});
|
||||||
add_task(test_register);
|
add_task(test_register);
|
||||||
add_task(test_register_escape);
|
add_task(test_register_escape);
|
||||||
|
add_task(test_register_direct_cancel);
|
||||||
|
add_task(test_register_direct_presence);
|
||||||
add_task(test_sign);
|
add_task(test_sign);
|
||||||
add_task(test_sign_escape);
|
add_task(test_sign_escape);
|
||||||
add_task(test_tab_switching);
|
add_task(test_tab_switching);
|
||||||
add_task(test_window_switching);
|
add_task(test_window_switching);
|
||||||
add_task(async function test_setup_softtoken() {
|
add_task(async function test_setup_fullscreen() {
|
||||||
gAuthenticatorId = add_virtual_authenticator();
|
|
||||||
return SpecialPowers.pushPrefEnv({
|
return SpecialPowers.pushPrefEnv({
|
||||||
set: [
|
set: [
|
||||||
["browser.fullscreen.autohide", true],
|
["browser.fullscreen.autohide", true],
|
||||||
["full-screen-api.enabled", true],
|
["full-screen-api.enabled", true],
|
||||||
["full-screen-api.allow-trusted-requests-only", false],
|
["full-screen-api.allow-trusted-requests-only", false],
|
||||||
["security.webauth.webauthn_enable_softtoken", true],
|
|
||||||
["security.webauth.webauthn_enable_usbtoken", false],
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
add_task(test_fullscreen_show_nav_toolbar);
|
add_task(test_fullscreen_show_nav_toolbar);
|
||||||
add_task(test_no_fullscreen_dom);
|
add_task(test_no_fullscreen_dom);
|
||||||
add_task(test_register_direct_with_consent);
|
add_task(async function test_setup_softtoken() {
|
||||||
add_task(test_register_direct_without_consent);
|
gAuthenticatorId = add_virtual_authenticator();
|
||||||
|
return SpecialPowers.pushPrefEnv({
|
||||||
|
set: [
|
||||||
|
["security.webauth.webauthn_enable_softtoken", true],
|
||||||
|
["security.webauth.webauthn_enable_usbtoken", false],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
add_task(test_register_direct_proceed);
|
||||||
|
add_task(test_register_direct_proceed_anon);
|
||||||
add_task(test_select_sign_result);
|
add_task(test_select_sign_result);
|
||||||
|
|
||||||
function promiseNavToolboxStatus(aExpectedStatus) {
|
function promiseNavToolboxStatus(aExpectedStatus) {
|
||||||
@@ -207,6 +215,53 @@ async function test_sign_escape() {
|
|||||||
await BrowserTestUtils.removeTab(tab);
|
await BrowserTestUtils.removeTab(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = promiseWebAuthnMakeCredential(tab, "direct")
|
||||||
|
.then(arrivingHereIsBad)
|
||||||
|
.catch(expectNotAllowedError)
|
||||||
|
.then(() => (active = false));
|
||||||
|
await promiseNotification("webauthn-prompt-register-direct");
|
||||||
|
|
||||||
|
// Cancel the request.
|
||||||
|
ok(active, "request should still be active");
|
||||||
|
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
// Close tab.
|
||||||
|
await BrowserTestUtils.removeTab(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test_register_direct_presence() {
|
||||||
|
// 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 = promiseWebAuthnMakeCredential(tab, "direct")
|
||||||
|
.then(arrivingHereIsBad)
|
||||||
|
.catch(expectNotAllowedError)
|
||||||
|
.then(() => (active = false));
|
||||||
|
await promiseNotification("webauthn-prompt-register-direct");
|
||||||
|
|
||||||
|
// Click "proceed" and wait for presence prompt
|
||||||
|
let presence = promiseNotification("webauthn-prompt-presence");
|
||||||
|
PopupNotifications.panel.firstElementChild.button.click();
|
||||||
|
await presence;
|
||||||
|
|
||||||
|
// Cancel the request.
|
||||||
|
ok(active, "request should still be active");
|
||||||
|
PopupNotifications.panel.firstElementChild.button.click();
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
// Close tab.
|
||||||
|
await BrowserTestUtils.removeTab(tab);
|
||||||
|
}
|
||||||
|
|
||||||
// Add two tabs, open WebAuthn in the first, switch, assert the prompt is
|
// Add two tabs, open WebAuthn in the first, switch, assert the prompt is
|
||||||
// not visible, switch back, assert the prompt is there and cancel it.
|
// not visible, switch back, assert the prompt is there and cancel it.
|
||||||
async function test_tab_switching() {
|
async function test_tab_switching() {
|
||||||
@@ -304,7 +359,7 @@ async function test_window_switching() {
|
|||||||
await BrowserTestUtils.removeTab(tab);
|
await BrowserTestUtils.removeTab(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function test_register_direct_with_consent() {
|
async function test_register_direct_proceed() {
|
||||||
// Open a new tab.
|
// Open a new tab.
|
||||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||||
|
|
||||||
@@ -312,7 +367,7 @@ async function test_register_direct_with_consent() {
|
|||||||
let request = promiseWebAuthnMakeCredential(tab, "direct");
|
let request = promiseWebAuthnMakeCredential(tab, "direct");
|
||||||
await promiseNotification("webauthn-prompt-register-direct");
|
await promiseNotification("webauthn-prompt-register-direct");
|
||||||
|
|
||||||
// Click "Allow".
|
// Proceed.
|
||||||
PopupNotifications.panel.firstElementChild.button.click();
|
PopupNotifications.panel.firstElementChild.button.click();
|
||||||
|
|
||||||
// Ensure we got "direct" attestation.
|
// Ensure we got "direct" attestation.
|
||||||
@@ -322,7 +377,7 @@ async function test_register_direct_with_consent() {
|
|||||||
await BrowserTestUtils.removeTab(tab);
|
await BrowserTestUtils.removeTab(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function test_register_direct_without_consent() {
|
async function test_register_direct_proceed_anon() {
|
||||||
// Open a new tab.
|
// Open a new tab.
|
||||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||||
|
|
||||||
@@ -330,8 +385,9 @@ async function test_register_direct_without_consent() {
|
|||||||
let request = promiseWebAuthnMakeCredential(tab, "direct");
|
let request = promiseWebAuthnMakeCredential(tab, "direct");
|
||||||
await promiseNotification("webauthn-prompt-register-direct");
|
await promiseNotification("webauthn-prompt-register-direct");
|
||||||
|
|
||||||
// Click "Block".
|
// Check "anonymize anyway" and proceed.
|
||||||
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
PopupNotifications.panel.firstElementChild.checkbox.checked = true;
|
||||||
|
PopupNotifications.panel.firstElementChild.button.click();
|
||||||
|
|
||||||
// Ensure we got "none" attestation.
|
// Ensure we got "none" attestation.
|
||||||
await request.then(verifyAnonymizedCertificate);
|
await request.then(verifyAnonymizedCertificate);
|
||||||
@@ -382,22 +438,23 @@ async function test_fullscreen_show_nav_toolbar() {
|
|||||||
|
|
||||||
await navToolboxHiddenPromise;
|
await navToolboxHiddenPromise;
|
||||||
|
|
||||||
// Request a new credential with direct attestation. The consent prompt will
|
// Request a new credential and wait for the direct attestation consent
|
||||||
// keep the request active until we can verify that the nav toolbar is shown.
|
// prompt.
|
||||||
let promptPromise = promiseNotification("webauthn-prompt-register-direct");
|
let promptPromise = promiseNotification("webauthn-prompt-register-direct");
|
||||||
let navToolboxShownPromise = promiseNavToolboxStatus("shown");
|
let navToolboxShownPromise = promiseNavToolboxStatus("shown");
|
||||||
|
|
||||||
let active = true;
|
let active = true;
|
||||||
let requestPromise = promiseWebAuthnMakeCredential(tab, "direct").then(
|
let requestPromise = promiseWebAuthnMakeCredential(tab, "direct")
|
||||||
() => (active = false)
|
.then(arrivingHereIsBad)
|
||||||
);
|
.catch(expectNotAllowedError)
|
||||||
|
.then(() => (active = false));
|
||||||
|
|
||||||
await Promise.all([promptPromise, navToolboxShownPromise]);
|
await Promise.all([promptPromise, navToolboxShownPromise]);
|
||||||
|
|
||||||
ok(active, "request is active");
|
ok(active, "request is active");
|
||||||
ok(window.fullScreen, "window is fullscreen");
|
ok(window.fullScreen, "window is fullscreen");
|
||||||
|
|
||||||
// Proceed through the consent prompt.
|
// Cancel the request.
|
||||||
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
||||||
await requestPromise;
|
await requestPromise;
|
||||||
|
|
||||||
@@ -418,22 +475,23 @@ async function test_no_fullscreen_dom() {
|
|||||||
await fullScreenPaintPromise;
|
await fullScreenPaintPromise;
|
||||||
ok(!!document.fullscreenElement, "a DOM element is fullscreen");
|
ok(!!document.fullscreenElement, "a DOM element is fullscreen");
|
||||||
|
|
||||||
// Request a new credential with direct attestation. The consent prompt will
|
// Request a new credential and wait for the direct attestation consent
|
||||||
// keep the request active until we can verify that we've left fullscreen.
|
// prompt.
|
||||||
let promptPromise = promiseNotification("webauthn-prompt-register-direct");
|
let promptPromise = promiseNotification("webauthn-prompt-register-direct");
|
||||||
fullScreenPaintPromise = promiseFullScreenPaint();
|
fullScreenPaintPromise = promiseFullScreenPaint();
|
||||||
|
|
||||||
let active = true;
|
let active = true;
|
||||||
let requestPromise = promiseWebAuthnMakeCredential(tab, "direct").then(
|
let requestPromise = promiseWebAuthnMakeCredential(tab, "direct")
|
||||||
() => (active = false)
|
.then(arrivingHereIsBad)
|
||||||
);
|
.catch(expectNotAllowedError)
|
||||||
|
.then(() => (active = false));
|
||||||
|
|
||||||
await Promise.all([promptPromise, fullScreenPaintPromise]);
|
await Promise.all([promptPromise, fullScreenPaintPromise]);
|
||||||
|
|
||||||
ok(active, "request is active");
|
ok(active, "request is active");
|
||||||
ok(!document.fullscreenElement, "no DOM element is fullscreen");
|
ok(!document.fullscreenElement, "no DOM element is fullscreen");
|
||||||
|
|
||||||
// Proceed through the consent prompt.
|
// Cancel the request.
|
||||||
await waitForPopupNotificationSecurityDelay();
|
await waitForPopupNotificationSecurityDelay();
|
||||||
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
PopupNotifications.panel.firstElementChild.secondaryButton.click();
|
||||||
await requestPromise;
|
await requestPromise;
|
||||||
|
|||||||
Reference in New Issue
Block a user