зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1838894 - support adding and removing virtual authenticators in nsIWebAuthnTransport r=jschanck
Depends on D181301 Differential Revision: https://phabricator.services.mozilla.com/D183056
This commit is contained in:
Родитель
a27c448f15
Коммит
e64f2d53e9
|
@ -22,35 +22,67 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var _countCompletes = 0;
|
||||
var _expectedCompletes = 2; // 2 iframes
|
||||
|
||||
function handleEventMessage(event) {
|
||||
if ("test" in event.data) {
|
||||
let summary = event.data.test + ": " + event.data.msg;
|
||||
ok(event.data.status, summary);
|
||||
} else if ("done" in event.data) {
|
||||
_countCompletes += 1;
|
||||
if (_countCompletes == _expectedCompletes) {
|
||||
console.log("Test compeleted. Finished.");
|
||||
SimpleTest.finish();
|
||||
var _done = new Promise((resolve) => {
|
||||
function handleEventMessage(event) {
|
||||
if ("test" in event.data) {
|
||||
let summary = event.data.test + ": " + event.data.msg;
|
||||
ok(event.data.status, summary);
|
||||
} else if ("done" in event.data) {
|
||||
_countCompletes += 1;
|
||||
if (_countCompletes == _expectedCompletes) {
|
||||
console.log("Test compeleted. Finished.");
|
||||
resolve();
|
||||
}
|
||||
} else {
|
||||
ok(false, "Unexpected message in the test harness: " + event.data);
|
||||
}
|
||||
} else {
|
||||
ok(false, "Unexpected message in the test harness: " + event.data);
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleEventMessage);
|
||||
});
|
||||
|
||||
async function addVirtualAuthenticator() {
|
||||
let id = await SpecialPowers.spawnChrome([], () => {
|
||||
let webauthnTransport = Cc["@mozilla.org/webauthn/transport;1"].getService(
|
||||
Ci.nsIWebAuthnTransport
|
||||
);
|
||||
return webauthnTransport.addVirtualAuthenticator(
|
||||
"ctap2",
|
||||
"internal",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
SimpleTest.registerCleanupFunction(async () => {
|
||||
await SpecialPowers.spawnChrome([id], (authenticatorId) => {
|
||||
let webauthnTransport = Cc["@mozilla.org/webauthn/transport;1"].getService(
|
||||
Ci.nsIWebAuthnTransport
|
||||
);
|
||||
webauthnTransport.removeVirtualAuthenticator(authenticatorId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleEventMessage);
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", true],
|
||||
["security.webauth.webauthn_enable_usbtoken", false],
|
||||
["security.webauth.webauthn_enable_android_fido2", false]]},
|
||||
function() {
|
||||
add_task(async () => {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", true],
|
||||
["security.webauth.webauthn_enable_usbtoken", false],
|
||||
["security.webauth.webauthn_enable_android_fido2", false]]});
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
add_task(async () => {
|
||||
document.getElementById("frame_top").src = "https://example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
|
||||
|
||||
document.getElementById("frame_bottom").src = "https://test1.example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
|
||||
|
||||
await _done;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsTextFormatter.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
|
@ -177,7 +178,8 @@ nsCOMPtr<nsIWebAuthnTransport> WebAuthnController::GetTransportImpl() {
|
|||
return mTransportImpl;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWebAuthnTransport> transport = NewAuthrsTransport();
|
||||
nsCOMPtr<nsIWebAuthnTransport> transport(
|
||||
do_GetService("@mozilla.org/webauthn/transport;1"));
|
||||
transport->SetController(this);
|
||||
return transport;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ extern crate xpcom;
|
|||
use authenticator::{
|
||||
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
|
||||
ctap2::attestation::AttestationStatement,
|
||||
ctap2::commands::get_info::AuthenticatorVersion,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, User, UserVerificationRequirement,
|
||||
|
@ -23,7 +24,8 @@ use moz_task::RunnableBuilder;
|
|||
use nserror::{
|
||||
nsresult, NS_ERROR_DOM_INVALID_STATE_ERR, NS_ERROR_DOM_NOT_ALLOWED_ERR,
|
||||
NS_ERROR_DOM_NOT_SUPPORTED_ERR, NS_ERROR_DOM_UNKNOWN_ERR, NS_ERROR_FAILURE,
|
||||
NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER, NS_OK,
|
||||
NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER,
|
||||
NS_OK,
|
||||
};
|
||||
use nsstring::{nsACString, nsCString, nsString};
|
||||
use serde_cbor;
|
||||
|
@ -32,13 +34,13 @@ use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
|
|||
use std::sync::{Arc, Mutex};
|
||||
use thin_vec::ThinVec;
|
||||
use xpcom::interfaces::{
|
||||
nsICtapRegisterArgs, nsICtapRegisterResult, nsICtapSignArgs, nsICtapSignResult,
|
||||
nsIWebAuthnController, nsIWebAuthnTransport,
|
||||
nsICredentialParameters, nsICtapRegisterArgs, nsICtapRegisterResult, nsICtapSignArgs,
|
||||
nsICtapSignResult, nsIWebAuthnController, nsIWebAuthnTransport,
|
||||
};
|
||||
use xpcom::{xpcom_method, RefPtr};
|
||||
|
||||
mod test_token;
|
||||
use test_token::TestTokenManager;
|
||||
use test_token::{TestTokenManager, TestTokenManagerState, TestTokenTransport};
|
||||
|
||||
fn make_prompt(action: &str, tid: u64, origin: &str, browsing_context_id: u64) -> String {
|
||||
format!(
|
||||
|
@ -397,6 +399,7 @@ pub struct AuthrsTransport {
|
|||
auth_service: RefCell<AuthenticatorService>, // interior mutable for use in XPCOM methods
|
||||
controller: Controller,
|
||||
pin_receiver: Arc<Mutex<PinReceiver>>,
|
||||
test_token_manager: TestTokenManager,
|
||||
}
|
||||
|
||||
impl AuthrsTransport {
|
||||
|
@ -745,6 +748,114 @@ impl AuthrsTransport {
|
|||
Err(e) => Err(authrs_to_nserror(e)),
|
||||
}
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
add_virtual_authenticator => AddVirtualAuthenticator(
|
||||
protocol: *const nsACString,
|
||||
transport: *const nsACString,
|
||||
hasResidentKey: bool,
|
||||
hasUserVerification: bool,
|
||||
isUserConsenting: bool,
|
||||
isUserVerified: bool) -> u64
|
||||
);
|
||||
fn add_virtual_authenticator(
|
||||
&self,
|
||||
protocol: &nsACString,
|
||||
transport: &nsACString,
|
||||
has_resident_key: bool,
|
||||
has_user_verification: bool,
|
||||
is_user_consenting: bool,
|
||||
is_user_verified: bool,
|
||||
) -> Result<u64, nsresult> {
|
||||
let protocol = match protocol.to_string().as_str() {
|
||||
"ctap1/u2f" => AuthenticatorVersion::U2F_V2,
|
||||
"ctap2" => AuthenticatorVersion::FIDO_2_0,
|
||||
"ctap2_1" => AuthenticatorVersion::FIDO_2_1,
|
||||
_ => return Err(NS_ERROR_INVALID_ARG),
|
||||
};
|
||||
match transport.to_string().as_str() {
|
||||
"usb" | "nfc" | "ble" | "smart-card" | "hybrid" | "internal" => (),
|
||||
_ => return Err(NS_ERROR_INVALID_ARG),
|
||||
};
|
||||
self.test_token_manager.add_virtual_authenticator(
|
||||
protocol,
|
||||
has_resident_key,
|
||||
has_user_verification,
|
||||
is_user_consenting,
|
||||
is_user_verified,
|
||||
)
|
||||
}
|
||||
|
||||
xpcom_method!(remove_virtual_authenticator => RemoveVirtualAuthenticator(authenticatorId: u64));
|
||||
fn remove_virtual_authenticator(&self, authenticator_id: u64) -> Result<(), nsresult> {
|
||||
self.test_token_manager
|
||||
.remove_virtual_authenticator(authenticator_id)
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
add_credential => AddCredential(
|
||||
authenticatorId: u64,
|
||||
credentialId: *const nsACString,
|
||||
isResidentCredential: bool,
|
||||
rpId: *const nsACString,
|
||||
privateKey: *const nsACString,
|
||||
userHandle: *const nsACString,
|
||||
signCount: u32)
|
||||
);
|
||||
fn add_credential(
|
||||
&self,
|
||||
authenticator_id: u64,
|
||||
credential_id: &nsACString,
|
||||
is_resident_credential: bool,
|
||||
rp_id: &nsACString,
|
||||
private_key: &nsACString,
|
||||
user_handle: &nsACString,
|
||||
sign_count: u32,
|
||||
) -> Result<(), nsresult> {
|
||||
self.test_token_manager.add_credential(
|
||||
authenticator_id,
|
||||
credential_id.as_ref(),
|
||||
private_key.as_ref(),
|
||||
user_handle.as_ref(),
|
||||
sign_count,
|
||||
rp_id.to_string(),
|
||||
is_resident_credential,
|
||||
)
|
||||
}
|
||||
|
||||
xpcom_method!(get_credentials => GetCredentials(authenticatorId: u64) -> ThinVec<Option<RefPtr<nsICredentialParameters>>>);
|
||||
fn get_credentials(
|
||||
&self,
|
||||
authenticator_id: u64,
|
||||
) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
|
||||
self.test_token_manager.get_credentials(authenticator_id)
|
||||
}
|
||||
|
||||
xpcom_method!(remove_credential => RemoveCredential(authenticatorId: u64, credentialId: *const nsACString));
|
||||
fn remove_credential(
|
||||
&self,
|
||||
authenticator_id: u64,
|
||||
credential_id: &nsACString,
|
||||
) -> Result<(), nsresult> {
|
||||
self.test_token_manager
|
||||
.remove_credential(authenticator_id, credential_id.as_ref())
|
||||
}
|
||||
|
||||
xpcom_method!(remove_all_credentials => RemoveAllCredentials(authenticatorId: u64));
|
||||
fn remove_all_credentials(&self, authenticator_id: u64) -> Result<(), nsresult> {
|
||||
self.test_token_manager
|
||||
.remove_all_credentials(authenticator_id)
|
||||
}
|
||||
|
||||
xpcom_method!(set_user_verified => SetUserVerified(authenticatorId: u64, isUserVerified: bool));
|
||||
fn set_user_verified(
|
||||
&self,
|
||||
authenticator_id: u64,
|
||||
is_user_verified: bool,
|
||||
) -> Result<(), nsresult> {
|
||||
self.test_token_manager
|
||||
.set_user_verified(authenticator_id, is_user_verified)
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -761,15 +872,15 @@ pub extern "C" fn authrs_transport_constructor(
|
|||
auth_service.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
|
||||
match TestTokenManager::new() {
|
||||
Ok(transport) => auth_service.add_transport(Box::new(transport)),
|
||||
Err(_) => return NS_ERROR_FAILURE,
|
||||
}
|
||||
let test_token_manager_state = TestTokenManagerState::new();
|
||||
let transport = TestTokenTransport::new(Arc::clone(&test_token_manager_state));
|
||||
auth_service.add_transport(Box::new(transport));
|
||||
|
||||
let wrapper = AuthrsTransport::allocate(InitAuthrsTransport {
|
||||
auth_service: RefCell::new(auth_service),
|
||||
controller: Controller(RefCell::new(std::ptr::null())),
|
||||
pin_receiver: Arc::new(Mutex::new(None)),
|
||||
test_token_manager: TestTokenManager::new(test_token_manager_state),
|
||||
});
|
||||
unsafe {
|
||||
RefPtr::new(wrapper.coerce::<nsIWebAuthnTransport>()).forget(&mut *result);
|
||||
|
|
|
@ -2,20 +2,7 @@
|
|||
* 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 runloop::RunLoop;
|
||||
use std::{
|
||||
io,
|
||||
ops::DerefMut,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use authenticator::authenticatorservice::{AuthenticatorTransport, RegisterArgs, SignArgs};
|
||||
use authenticator::{RegisterResult, SignResult, StatusUpdate};
|
||||
|
||||
use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError};
|
||||
|
||||
use authenticator::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice};
|
||||
|
||||
use authenticator::crypto::{ecdsa_p256_sha256_sign_raw, COSEAlgorithm, COSEKey, SharedSecret};
|
||||
use authenticator::ctap2::{
|
||||
attestation::{
|
||||
|
@ -34,17 +21,25 @@ use authenticator::ctap2::{
|
|||
Request, RequestCtap1, RequestCtap2, StatusCode,
|
||||
},
|
||||
preflight::CheckKeyHandle,
|
||||
server::{PublicKeyCredentialDescriptor, RelyingPartyWrapper, User},
|
||||
server::{PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, User},
|
||||
};
|
||||
|
||||
use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError};
|
||||
use authenticator::{ctap2, statecallback::StateCallback};
|
||||
|
||||
use authenticator::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice};
|
||||
use authenticator::{RegisterResult, SignResult, StatusUpdate};
|
||||
use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_IMPLEMENTED, NS_OK};
|
||||
use nsstring::{nsACString, nsCString};
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
mpsc::Sender,
|
||||
};
|
||||
use runloop::RunLoop;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use thin_vec::ThinVec;
|
||||
use xpcom::interfaces::nsICredentialParameters;
|
||||
use xpcom::{xpcom_method, RefPtr};
|
||||
|
||||
// All TestTokens use this fixed, randomly generated, AAGUID
|
||||
const VIRTUAL_TOKEN_AAGUID: AAGuid = AAGuid([
|
||||
|
@ -52,13 +47,13 @@ const VIRTUAL_TOKEN_AAGUID: AAGuid = AAGuid([
|
|||
]);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestTokenCredential {
|
||||
pub id: Vec<u8>,
|
||||
pub privkey: Vec<u8>,
|
||||
pub user_handle: Vec<u8>,
|
||||
pub sign_count: AtomicU32,
|
||||
pub is_discoverable_credential: bool,
|
||||
pub rp: RelyingPartyWrapper,
|
||||
struct TestTokenCredential {
|
||||
id: Vec<u8>,
|
||||
privkey: Vec<u8>,
|
||||
user_handle: Vec<u8>,
|
||||
sign_count: AtomicU32,
|
||||
is_discoverable_credential: bool,
|
||||
rp: RelyingPartyWrapper,
|
||||
}
|
||||
|
||||
impl TestTokenCredential {
|
||||
|
@ -99,53 +94,45 @@ impl TestTokenCredential {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestToken {
|
||||
pub id: u64,
|
||||
pub protocol: FidoProtocol,
|
||||
pub versions: Vec<AuthenticatorVersion>,
|
||||
pub transport: String,
|
||||
pub is_user_consenting: bool,
|
||||
pub has_user_verification: bool,
|
||||
pub is_user_verified: bool,
|
||||
pub has_resident_key: bool,
|
||||
struct TestToken {
|
||||
protocol: FidoProtocol,
|
||||
versions: Vec<AuthenticatorVersion>,
|
||||
has_resident_key: bool,
|
||||
has_user_verification: bool,
|
||||
is_user_consenting: bool,
|
||||
is_user_verified: bool,
|
||||
// This is modified in `make_credentials` which takes a &TestToken, but we only allow one transaction at a time.
|
||||
pub credentials: RefCell<Vec<TestTokenCredential>>,
|
||||
pub pin_token: [u8; 32],
|
||||
pub shared_secret: Option<SharedSecret>,
|
||||
pub authenticator_info: Option<AuthenticatorInfo>,
|
||||
pub counter: AtomicU32,
|
||||
credentials: RefCell<Vec<TestTokenCredential>>,
|
||||
pin_token: [u8; 32],
|
||||
shared_secret: Option<SharedSecret>,
|
||||
authenticator_info: Option<AuthenticatorInfo>,
|
||||
}
|
||||
|
||||
impl TestToken {
|
||||
pub fn new(
|
||||
id: u64,
|
||||
fn new(
|
||||
versions: Vec<AuthenticatorVersion>,
|
||||
transport: String,
|
||||
is_user_consenting: bool,
|
||||
has_user_verification: bool,
|
||||
is_user_verified: bool,
|
||||
has_resident_key: bool,
|
||||
has_user_verification: bool,
|
||||
is_user_consenting: bool,
|
||||
is_user_verified: bool,
|
||||
) -> TestToken {
|
||||
let mut pin_token = [0u8; 32];
|
||||
thread_rng().fill_bytes(&mut pin_token);
|
||||
Self {
|
||||
id,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
versions,
|
||||
transport,
|
||||
is_user_consenting,
|
||||
has_user_verification,
|
||||
is_user_verified,
|
||||
has_resident_key,
|
||||
has_user_verification,
|
||||
is_user_consenting,
|
||||
is_user_verified,
|
||||
credentials: RefCell::new(vec![]),
|
||||
pin_token,
|
||||
shared_secret: None,
|
||||
authenticator_info: None,
|
||||
counter: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_credential(
|
||||
fn insert_credential(
|
||||
&self,
|
||||
id: &[u8],
|
||||
privkey: &[u8],
|
||||
|
@ -171,8 +158,11 @@ impl TestToken {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_credential(&mut self, id: &[u8]) -> bool {
|
||||
fn get_credentials(&self) -> Ref<Vec<TestTokenCredential>> {
|
||||
self.credentials.borrow()
|
||||
}
|
||||
|
||||
fn delete_credential(&mut self, id: &[u8]) -> bool {
|
||||
let mut credlist = self.credentials.borrow_mut();
|
||||
if let Ok(idx) = credlist.binary_search_by_key(&id, |probe| &probe.id) {
|
||||
credlist.remove(idx);
|
||||
|
@ -182,7 +172,11 @@ impl TestToken {
|
|||
false
|
||||
}
|
||||
|
||||
pub(crate) fn has_credential(&self, id: &[u8]) -> bool {
|
||||
fn delete_all_credentials(&mut self) {
|
||||
self.credentials.borrow_mut().clear();
|
||||
}
|
||||
|
||||
fn has_credential(&self, id: &[u8]) -> bool {
|
||||
self.credentials
|
||||
.borrow()
|
||||
.binary_search_by_key(&id, |probe| &probe.id)
|
||||
|
@ -555,44 +549,225 @@ impl VirtualFidoDevice for TestToken {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ManagerState {
|
||||
pub tokens: Vec<TestToken>,
|
||||
pub(crate) struct TestTokenManagerState {
|
||||
tokens: HashMap<u64, TestToken>,
|
||||
}
|
||||
|
||||
impl ManagerState {
|
||||
pub fn new() -> Arc<Mutex<ManagerState>> {
|
||||
Arc::new(Mutex::new(ManagerState { tokens: vec![] }))
|
||||
impl TestTokenManagerState {
|
||||
pub fn new() -> Arc<Mutex<TestTokenManagerState>> {
|
||||
Arc::new(Mutex::new(TestTokenManagerState {
|
||||
tokens: HashMap::new(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestTokenManager {
|
||||
state: Arc<Mutex<ManagerState>>,
|
||||
queue: Option<RunLoop>,
|
||||
#[xpcom(implement(nsICredentialParameters), atomic)]
|
||||
struct CredentialParameters {
|
||||
credential_id: Vec<u8>,
|
||||
is_resident_credential: bool,
|
||||
rp_id: String,
|
||||
private_key: Vec<u8>,
|
||||
user_handle: Vec<u8>,
|
||||
sign_count: u32,
|
||||
}
|
||||
|
||||
impl CredentialParameters {
|
||||
xpcom_method!(get_credential_id => GetCredentialId() -> nsACString);
|
||||
fn get_credential_id(&self) -> Result<nsCString, nsresult> {
|
||||
Ok(nsCString::from(&self.credential_id))
|
||||
}
|
||||
|
||||
xpcom_method!(get_is_resident_credential => GetIsResidentCredential() -> bool);
|
||||
fn get_is_resident_credential(&self) -> Result<bool, nsresult> {
|
||||
Ok(self.is_resident_credential)
|
||||
}
|
||||
|
||||
xpcom_method!(get_rp_id => GetRpId() -> nsACString);
|
||||
fn get_rp_id(&self) -> Result<nsCString, nsresult> {
|
||||
Ok(nsCString::from(&self.rp_id))
|
||||
}
|
||||
|
||||
xpcom_method!(get_private_key => GetPrivateKey() -> nsACString);
|
||||
fn get_private_key(&self) -> Result<nsCString, nsresult> {
|
||||
Ok(nsCString::from(&self.private_key))
|
||||
}
|
||||
|
||||
xpcom_method!(get_user_handle => GetUserHandle() -> nsACString);
|
||||
fn get_user_handle(&self) -> Result<nsCString, nsresult> {
|
||||
Ok(nsCString::from(&self.user_handle))
|
||||
}
|
||||
|
||||
xpcom_method!(get_sign_count => GetSignCount() -> u32);
|
||||
fn get_sign_count(&self) -> Result<u32, nsresult> {
|
||||
Ok(self.sign_count)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TestTokenManager {
|
||||
state: Arc<Mutex<TestTokenManagerState>>,
|
||||
}
|
||||
|
||||
impl TestTokenManager {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let state = ManagerState::new();
|
||||
{
|
||||
// Bug 1838894: Remove this when we can add/remove authenticators
|
||||
// dynamically
|
||||
let mut guard = state.lock().unwrap();
|
||||
guard.deref_mut().tokens.push(TestToken::new(
|
||||
/* id */ 0,
|
||||
/* versions */ vec![AuthenticatorVersion::FIDO_2_1],
|
||||
/* transport */ "usb".to_string(),
|
||||
/* is_user_consenting */ true,
|
||||
/* has_user_verification */ true,
|
||||
/* is_user_verified */ true,
|
||||
/* has_resident_key */ true,
|
||||
));
|
||||
}
|
||||
pub fn new(state: Arc<Mutex<TestTokenManagerState>>) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
Ok(Self { state, queue: None })
|
||||
pub fn add_virtual_authenticator(
|
||||
&self,
|
||||
protocol: AuthenticatorVersion,
|
||||
has_resident_key: bool,
|
||||
has_user_verification: bool,
|
||||
is_user_consenting: bool,
|
||||
is_user_verified: bool,
|
||||
) -> Result<u64, nsresult> {
|
||||
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
|
||||
let token = TestToken::new(
|
||||
vec![protocol],
|
||||
has_resident_key,
|
||||
has_user_verification,
|
||||
is_user_consenting,
|
||||
is_user_verified,
|
||||
);
|
||||
loop {
|
||||
let id = rand::random::<u64>() & 0x1f_ffff_ffff_ffffu64; // Make the id safe for JS (53 bits)
|
||||
match guard.deref_mut().tokens.entry(id) {
|
||||
Entry::Occupied(_) => continue,
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(token);
|
||||
return Ok(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_virtual_authenticator(&self, authenticator_id: u64) -> Result<(), nsresult> {
|
||||
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
|
||||
guard
|
||||
.deref_mut()
|
||||
.tokens
|
||||
.remove(&authenticator_id)
|
||||
.ok_or(NS_ERROR_INVALID_ARG)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_credential(
|
||||
&self,
|
||||
authenticator_id: u64,
|
||||
id: &[u8],
|
||||
privkey: &[u8],
|
||||
user_handle: &[u8],
|
||||
sign_count: u32,
|
||||
rp_id: String,
|
||||
is_resident_credential: bool,
|
||||
) -> Result<(), nsresult> {
|
||||
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
|
||||
let token = guard
|
||||
.deref_mut()
|
||||
.tokens
|
||||
.get_mut(&authenticator_id)
|
||||
.ok_or(NS_ERROR_INVALID_ARG)?;
|
||||
let rp = RelyingParty {
|
||||
id: rp_id,
|
||||
name: None,
|
||||
icon: None,
|
||||
};
|
||||
token.insert_credential(
|
||||
id,
|
||||
privkey,
|
||||
&RelyingPartyWrapper::Data(rp),
|
||||
is_resident_credential,
|
||||
user_handle,
|
||||
sign_count,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_credentials(
|
||||
&self,
|
||||
authenticator_id: u64,
|
||||
) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
|
||||
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
|
||||
let token = guard
|
||||
.tokens
|
||||
.get_mut(&authenticator_id)
|
||||
.ok_or(NS_ERROR_INVALID_ARG)?;
|
||||
let credentials = token.get_credentials();
|
||||
let mut credentials_parameters = ThinVec::with_capacity(credentials.len());
|
||||
for credential in credentials.deref() {
|
||||
// CTAP1 credentials are not currently supported here.
|
||||
let rp_id = credential
|
||||
.rp
|
||||
.id()
|
||||
.map(|id| id.clone())
|
||||
.ok_or(NS_ERROR_NOT_IMPLEMENTED)?;
|
||||
let credential_parameters = CredentialParameters::allocate(InitCredentialParameters {
|
||||
credential_id: credential.id.clone(),
|
||||
is_resident_credential: credential.is_discoverable_credential,
|
||||
rp_id,
|
||||
private_key: credential.privkey.clone(),
|
||||
user_handle: credential.user_handle.clone(),
|
||||
sign_count: credential.sign_count.load(Ordering::Relaxed),
|
||||
})
|
||||
.query_interface::<nsICredentialParameters>()
|
||||
.ok_or(NS_ERROR_FAILURE)?;
|
||||
credentials_parameters.push(Some(credential_parameters));
|
||||
}
|
||||
Ok(credentials_parameters)
|
||||
}
|
||||
|
||||
pub fn remove_credential(&self, authenticator_id: u64, id: &[u8]) -> Result<(), nsresult> {
|
||||
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
|
||||
let token = guard
|
||||
.deref_mut()
|
||||
.tokens
|
||||
.get_mut(&authenticator_id)
|
||||
.ok_or(NS_ERROR_INVALID_ARG)?;
|
||||
if token.delete_credential(id) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NS_ERROR_INVALID_ARG)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_all_credentials(&self, authenticator_id: u64) -> Result<(), nsresult> {
|
||||
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
|
||||
let token = guard
|
||||
.deref_mut()
|
||||
.tokens
|
||||
.get_mut(&authenticator_id)
|
||||
.ok_or(NS_ERROR_INVALID_ARG)?;
|
||||
token.delete_all_credentials();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_user_verified(
|
||||
&self,
|
||||
authenticator_id: u64,
|
||||
is_user_verified: bool,
|
||||
) -> Result<(), nsresult> {
|
||||
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
|
||||
let mut token = guard
|
||||
.deref_mut()
|
||||
.tokens
|
||||
.get_mut(&authenticator_id)
|
||||
.ok_or(NS_ERROR_INVALID_ARG)?;
|
||||
token.is_user_verified = is_user_verified;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestTokenManager {
|
||||
pub(crate) struct TestTokenTransport {
|
||||
state: Arc<Mutex<TestTokenManagerState>>,
|
||||
queue: Option<RunLoop>,
|
||||
}
|
||||
|
||||
impl TestTokenTransport {
|
||||
pub fn new(state: Arc<Mutex<TestTokenManagerState>>) -> Self {
|
||||
Self { state, queue: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestTokenTransport {
|
||||
fn drop(&mut self) {
|
||||
if let Some(queue) = self.queue.take() {
|
||||
queue.cancel();
|
||||
|
@ -600,7 +775,7 @@ impl Drop for TestTokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
impl AuthenticatorTransport for TestTokenManager {
|
||||
impl AuthenticatorTransport for TestTokenTransport {
|
||||
fn register(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
|
@ -621,7 +796,7 @@ impl AuthenticatorTransport for TestTokenManager {
|
|||
move |alive| {
|
||||
let mut state_obj = state.lock().unwrap();
|
||||
|
||||
for token in state_obj.tokens.deref_mut() {
|
||||
for token in state_obj.tokens.values_mut() {
|
||||
let _ = token.init();
|
||||
if ctap2::register(
|
||||
token,
|
||||
|
@ -666,7 +841,7 @@ impl AuthenticatorTransport for TestTokenManager {
|
|||
move |alive| {
|
||||
let mut state_obj = state.lock().unwrap();
|
||||
|
||||
for token in state_obj.tokens.deref_mut() {
|
||||
for token in state_obj.tokens.values_mut() {
|
||||
let _ = token.init();
|
||||
if ctap2::sign(
|
||||
token,
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
Classes = [
|
||||
{
|
||||
'cid': '{ebe8a51d-bd54-4838-b031-cd2289990e14}',
|
||||
'contract_ids': ['@mozilla.org/webauthn/transport;1'],
|
||||
'headers': ['/dom/webauthn/AuthrsTransport.h'],
|
||||
'constructor': 'mozilla::dom::NewAuthrsTransport',
|
||||
},
|
||||
]
|
|
@ -9,6 +9,10 @@ with Files("**"):
|
|||
|
||||
IPDL_SOURCES += ["PWebAuthnTransaction.ipdl"]
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
"components.conf",
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += ["nsIU2FTokenManager.idl", "nsIWebAuthnController.idl"]
|
||||
|
||||
XPIDL_MODULE = "dom_webauthn"
|
||||
|
|
|
@ -188,9 +188,20 @@ interface nsIWebAuthnController : nsIU2FTokenManager
|
|||
[noscript] void finishSign(in uint64_t aTransactionId, in Array<nsICtapSignResult> aResult);
|
||||
};
|
||||
|
||||
[scriptable, uuid(6c4ecd9f-57c0-4d7d-8080-bf6e4d499f8f)]
|
||||
interface nsICredentialParameters : nsISupports
|
||||
{
|
||||
readonly attribute ACString credentialId;
|
||||
readonly attribute bool isResidentCredential;
|
||||
readonly attribute ACString rpId;
|
||||
readonly attribute ACString privateKey;
|
||||
readonly attribute ACString userHandle;
|
||||
readonly attribute uint32_t signCount;
|
||||
};
|
||||
|
||||
// The nsIWebAuthnTransport interface allows a C++ implemented nsIWebAuthnController to interact
|
||||
// with authenticators written in both Rust and C++
|
||||
[uuid(e236a9b4-a26f-11ed-b6cc-07a9834e19b1)]
|
||||
[scriptable, uuid(e236a9b4-a26f-11ed-b6cc-07a9834e19b1)]
|
||||
interface nsIWebAuthnTransport : nsISupports
|
||||
{
|
||||
attribute nsIWebAuthnController controller;
|
||||
|
@ -198,6 +209,49 @@ interface nsIWebAuthnTransport : nsISupports
|
|||
void makeCredential(in uint64_t aTransactionId, in uint64_t browsingContextId, in nsICtapRegisterArgs args);
|
||||
void getAssertion(in uint64_t aTransactionId, in uint64_t browsingContextId, in nsICtapSignArgs args);
|
||||
|
||||
// Adds a virtual (software) authenticator for use in tests (particularly
|
||||
// tests run via WebDriver). See
|
||||
// https://w3c.github.io/webauthn/#sctn-automation-add-virtual-authenticator.
|
||||
uint64_t addVirtualAuthenticator(
|
||||
in ACString protocol,
|
||||
in ACString transport,
|
||||
in bool hasResidentKey,
|
||||
in bool hasUserVerification,
|
||||
in bool isUserConsenting,
|
||||
in bool isUserVerified);
|
||||
|
||||
// Removes a previously-added virtual authenticator, as identified by its
|
||||
// id. See
|
||||
// https://w3c.github.io/webauthn/#sctn-automation-remove-virtual-authenticator
|
||||
void removeVirtualAuthenticator(in uint64_t authenticatorId);
|
||||
|
||||
// Adds a credential to a previously-added authenticator. See
|
||||
// https://w3c.github.io/webauthn/#sctn-automation-add-credential
|
||||
void addCredential(
|
||||
in uint64_t authenticatorId,
|
||||
in ACString credentialId,
|
||||
in bool isResidentCredential,
|
||||
in ACString rpId,
|
||||
in ACString privateKey,
|
||||
in ACString userHandle,
|
||||
in uint32_t signCount);
|
||||
|
||||
// Gets all credentials that have been added to a virtual authenticator.
|
||||
// See https://w3c.github.io/webauthn/#sctn-automation-get-credentials
|
||||
Array<nsICredentialParameters> getCredentials(in uint64_t authenticatorId);
|
||||
|
||||
// Removes a credential from a virtual authenticator. See
|
||||
// https://w3c.github.io/webauthn/#sctn-automation-remove-credential
|
||||
void removeCredential(in uint64_t authenticatorId, in ACString credentialId);
|
||||
|
||||
// Removes all credentials from a virtual authenticator. See
|
||||
// https://w3c.github.io/webauthn/#sctn-automation-remove-all-credentials
|
||||
void removeAllCredentials(in uint64_t authenticatorId);
|
||||
|
||||
// Sets the "isUserVerified" bit on a virtual authenticator. See
|
||||
// https://w3c.github.io/webauthn/#sctn-automation-set-user-verified
|
||||
void setUserVerified(in uint64_t authenticatorId, in bool isUserVerified);
|
||||
|
||||
// These are prompt callbacks but they're not intended to be called directly from
|
||||
// JavaScript---they are proxied through the nsIWebAuthnController first.
|
||||
[noscript] void pinCallback(in uint64_t aTransactionId, in ACString aPin);
|
||||
|
|
|
@ -10,6 +10,8 @@ let expectNotSupportedError = expectError("NotSupported");
|
|||
let expectInvalidStateError = expectError("InvalidState");
|
||||
let expectSecurityError = expectError("Security");
|
||||
|
||||
add_virtual_authenticator();
|
||||
|
||||
add_task(async function test_appid_unused() {
|
||||
// Open a new tab.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
add_virtual_authenticator();
|
||||
|
||||
let expectSecurityError = expectError("Security");
|
||||
|
||||
add_task(async function test_setup() {
|
||||
|
|
|
@ -22,6 +22,7 @@ add_task(test_register_direct_cancel);
|
|||
add_task(test_tab_switching);
|
||||
add_task(test_window_switching);
|
||||
add_task(async function test_setup_softtoken() {
|
||||
add_virtual_authenticator();
|
||||
return SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["security.webauth.webauthn_enable_softtoken", true],
|
||||
|
|
|
@ -10,6 +10,8 @@ ChromeUtils.defineESModuleGetters(this, {
|
|||
|
||||
const TEST_URL = "https://example.com/";
|
||||
|
||||
add_virtual_authenticator();
|
||||
|
||||
function getTelemetryForScalar(aName) {
|
||||
let scalars = TelemetryTestUtils.getProcessScalars("parent", true);
|
||||
return scalars[aName] || 0;
|
||||
|
|
|
@ -22,6 +22,26 @@ for (let script of scripts) {
|
|||
);
|
||||
}
|
||||
|
||||
function add_virtual_authenticator(autoremove = true) {
|
||||
let webauthnTransport = Cc["@mozilla.org/webauthn/transport;1"].getService(
|
||||
Ci.nsIWebAuthnTransport
|
||||
);
|
||||
let id = webauthnTransport.addVirtualAuthenticator(
|
||||
"ctap2",
|
||||
"internal",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
if (autoremove) {
|
||||
registerCleanupFunction(() => {
|
||||
webauthnTransport.removeVirtualAuthenticator(id);
|
||||
});
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function memcmp(x, y) {
|
||||
let xb = new Uint8Array(x);
|
||||
let yb = new Uint8Array(y);
|
||||
|
|
|
@ -18,6 +18,7 @@ skip-if =
|
|||
win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
toolkit == 'android' # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android
|
||||
[test_webauthn_attestation_conveyance.html]
|
||||
fail-if = xorigin # NotAllowedError
|
||||
skip-if =
|
||||
|
@ -48,6 +49,7 @@ skip-if =
|
|||
win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
toolkit == 'android' # Test disables all tokens, which is an unsupported configuration on android.
|
||||
[test_webauthn_make_credential.html]
|
||||
fail-if = xorigin # NotAllowedError
|
||||
skip-if =
|
||||
|
@ -60,12 +62,14 @@ skip-if =
|
|||
win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
toolkit == 'android' # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android
|
||||
[test_webauthn_get_assertion_dead_object.html]
|
||||
skip-if =
|
||||
win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
[test_webauthn_override_request.html]
|
||||
skip-if = toolkit == 'android' # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android
|
||||
[test_webauthn_store_credential.html]
|
||||
fail-if = xorigin # NotAllowedError
|
||||
skip-if =
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
is(aResult.code, DOMException.ABORT_ERR, "Expecting an AbortError");
|
||||
}
|
||||
|
||||
add_task(() => {
|
||||
add_task(async () => {
|
||||
// Enable USB tokens.
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn_enable_softtoken", false],
|
||||
["security.webauth.webauthn_enable_usbtoken", true],
|
||||
]});
|
||||
|
|
|
@ -20,10 +20,11 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
add_task(() => {
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
add_task(async () => {
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn_testing_allow_direct_attestation", true],
|
||||
]});
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
function verifyAnonymizedCertificate(aResult) {
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
function arrivingHereIsGood(aResult) {
|
||||
ok(true, "Good result! Received a: " + aResult);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
function arrivingHereIsGood(aResult) {
|
||||
ok(true, "Good result! Received a: " + aResult);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
|
||||
isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
|
||||
isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
|
||||
|
@ -31,7 +35,6 @@
|
|||
let validCred = null;
|
||||
|
||||
add_task(test_setup_valid_credential);
|
||||
add_task(test_without_credential);
|
||||
add_task(test_with_credential);
|
||||
add_task(test_unexpected_option);
|
||||
add_task(test_unexpected_option_with_credential);
|
||||
|
|
|
@ -18,11 +18,16 @@
|
|||
"Due to the nature of this test, there's no way for the window we're opening to signal " +
|
||||
"that it's done (the `document.writeln('')` is essential and basically clears any state " +
|
||||
"we could use). So, we have to wait at least 15 seconds for the webauthn call to time out.");
|
||||
let win = window.open("https://example.com/tests/dom/webauthn/tests/get_assertion_dead_object.html");
|
||||
setTimeout(() => {
|
||||
win.close();
|
||||
SimpleTest.finish();
|
||||
}, 20000);
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn_enable_softtoken", false],
|
||||
["security.webauth.webauthn_enable_usbtoken", true],
|
||||
]}).then(() => {
|
||||
let win = window.open("https://example.com/tests/dom/webauthn/tests/get_assertion_dead_object.html");
|
||||
setTimeout(() => {
|
||||
win.close();
|
||||
SimpleTest.finish();
|
||||
}, 20000);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
// Execute the full-scope test
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
add_task(async function test_external_key_support() {
|
||||
PublicKeyCredential.isExternalCTAP2SecurityKeySupported()
|
||||
.then(aResult => ok(true, `Should always return either true or false: ${aResult}`))
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
// Execute the full-scope test
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
add_task(async function test_is_platform_available() {
|
||||
// This test ensures that isUserVerifyingPlatformAuthenticatorAvailable()
|
||||
// is a callable method, but with the softtoken enabled, it's not useful to
|
||||
|
|
|
@ -19,15 +19,13 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
// Execute the full-scope test
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn_testing_allow_direct_attestation", true]]},
|
||||
function() {
|
||||
is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
|
||||
isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
|
||||
isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
|
||||
isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn_testing_allow_direct_attestation", true]]});
|
||||
await addVirtualAuthenticator();
|
||||
is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
|
||||
isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
|
||||
isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
|
||||
isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
|
||||
|
||||
let credm = navigator.credentials;
|
||||
|
||||
|
@ -36,176 +34,141 @@ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoi
|
|||
let gAssertionChallenge = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(gAssertionChallenge);
|
||||
|
||||
testMakeCredential();
|
||||
await testMakeCredential();
|
||||
|
||||
function decodeCreatedCredential(aCredInfo) {
|
||||
/* PublicKeyCredential : Credential
|
||||
- rawId: Key Handle buffer pulled from U2F Register() Response
|
||||
- id: Key Handle buffer in base64url form, should == rawId
|
||||
- type: Literal 'public-key'
|
||||
- response : AuthenticatorAttestationResponse : AuthenticatorResponse
|
||||
- attestationObject: CBOR object
|
||||
- clientDataJSON: serialized JSON
|
||||
*/
|
||||
async function decodeCreatedCredential(aCredInfo) {
|
||||
/* PublicKeyCredential : Credential
|
||||
- rawId: Key Handle buffer pulled from U2F Register() Response
|
||||
- id: Key Handle buffer in base64url form, should == rawId
|
||||
- type: Literal 'public-key'
|
||||
- response : AuthenticatorAttestationResponse : AuthenticatorResponse
|
||||
- attestationObject: CBOR object
|
||||
- clientDataJSON: serialized JSON
|
||||
*/
|
||||
|
||||
is(aCredInfo.type, "public-key", "Credential type must be public-key")
|
||||
is(aCredInfo.type, "public-key", "Credential type must be public-key")
|
||||
|
||||
ok(aCredInfo.rawId.byteLength > 0, "Key ID exists");
|
||||
is(aCredInfo.id, bytesToBase64UrlSafe(aCredInfo.rawId), "Encoded Key ID and Raw Key ID match");
|
||||
ok(aCredInfo.rawId.byteLength > 0, "Key ID exists");
|
||||
is(aCredInfo.id, bytesToBase64UrlSafe(aCredInfo.rawId), "Encoded Key ID and Raw Key ID match");
|
||||
|
||||
ok(aCredInfo.rawId === aCredInfo.rawId, "PublicKeyCredential.RawID is SameObject");
|
||||
ok(aCredInfo.response === aCredInfo.response, "PublicKeyCredential.Response is SameObject");
|
||||
ok(aCredInfo.response.clientDataJSON === aCredInfo.response.clientDataJSON, "PublicKeyCredential.Response.ClientDataJSON is SameObject");
|
||||
ok(aCredInfo.response.attestationObject === aCredInfo.response.attestationObject, "PublicKeyCredential.Response.AttestationObject is SameObject");
|
||||
ok(aCredInfo.rawId === aCredInfo.rawId, "PublicKeyCredential.RawID is SameObject");
|
||||
ok(aCredInfo.response === aCredInfo.response, "PublicKeyCredential.Response is SameObject");
|
||||
ok(aCredInfo.response.clientDataJSON === aCredInfo.response.clientDataJSON, "PublicKeyCredential.Response.ClientDataJSON is SameObject");
|
||||
ok(aCredInfo.response.attestationObject === aCredInfo.response.attestationObject, "PublicKeyCredential.Response.AttestationObject is SameObject");
|
||||
|
||||
let clientData = JSON.parse(buffer2string(aCredInfo.response.clientDataJSON));
|
||||
is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct");
|
||||
is(clientData.origin, window.location.origin, "Origin is correct");
|
||||
is(clientData.type, "webauthn.create", "Type is correct");
|
||||
let clientData = JSON.parse(buffer2string(aCredInfo.response.clientDataJSON));
|
||||
is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct");
|
||||
is(clientData.origin, window.location.origin, "Origin is correct");
|
||||
is(clientData.type, "webauthn.create", "Type is correct");
|
||||
|
||||
return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject)
|
||||
.then(function(aAttestationObj) {
|
||||
let attestationObj = await webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject);
|
||||
// Make sure the RP ID hash matches what we calculate.
|
||||
return crypto.subtle.digest("SHA-256", string2buffer(document.domain))
|
||||
.then(function(calculatedRpIdHash) {
|
||||
let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
|
||||
let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(aAttestationObj.authDataObj.rpIdHash));
|
||||
|
||||
is(calcHashStr, providedHashStr,
|
||||
"Calculated RP ID hash must match what the browser derived.");
|
||||
return Promise.resolve(aAttestationObj);
|
||||
});
|
||||
})
|
||||
.then(function(aAttestationObj) {
|
||||
ok(true, aAttestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT));
|
||||
ok(aAttestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT) == (flag_TUP | flag_AT),
|
||||
let calculatedRpIdHash = await crypto.subtle.digest("SHA-256", string2buffer(document.domain));
|
||||
let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
|
||||
let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(attestationObj.authDataObj.rpIdHash));
|
||||
is(calcHashStr, providedHashStr, "Calculated RP ID hash must match what the browser derived.");
|
||||
ok(true, attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT));
|
||||
ok(attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT) == (flag_TUP | flag_AT),
|
||||
"User presence and Attestation Object flags must be set");
|
||||
|
||||
aCredInfo.clientDataObj = clientData;
|
||||
aCredInfo.publicKeyHandle = aAttestationObj.authDataObj.publicKeyHandle;
|
||||
aCredInfo.attestationObject = aAttestationObj.authDataObj.attestationAuthData;
|
||||
aCredInfo.publicKeyHandle = attestationObj.authDataObj.publicKeyHandle;
|
||||
aCredInfo.attestationObject = attestationObj.authDataObj.attestationAuthData;
|
||||
return aCredInfo;
|
||||
});
|
||||
}
|
||||
|
||||
function checkAssertionAndSigValid(aPublicKey, aAssertion) {
|
||||
/* PublicKeyCredential : Credential
|
||||
- rawId: ID of Credential from AllowList that succeeded
|
||||
- id: Key Handle buffer in base64url form, should == rawId
|
||||
- type: Literal 'public-key'
|
||||
- response : AuthenticatorAssertionResponse : AuthenticatorResponse
|
||||
- clientDataJSON: serialized JSON
|
||||
- authenticatorData: RP ID Hash || U2F Sign() Response
|
||||
- signature: U2F Sign() Response
|
||||
*/
|
||||
|
||||
is(aAssertion.type, "public-key", "Credential type must be public-key")
|
||||
|
||||
ok(aAssertion.rawId.byteLength > 0, "Key ID exists");
|
||||
is(aAssertion.id, bytesToBase64UrlSafe(new Uint8Array(aAssertion.rawId)), "Encoded Key ID and Raw Key ID match");
|
||||
|
||||
ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
|
||||
ok(aAssertion.response.authenticatorData instanceof ArrayBuffer, "AuthenticatorAssertionResponse.AuthenticatorData is an ArrayBuffer");
|
||||
ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
|
||||
ok(aAssertion.response.signature instanceof ArrayBuffer, "AuthenticatorAssertionResponse.Signature is an ArrayBuffer");
|
||||
ok(aAssertion.response.userHandle === null, "AuthenticatorAssertionResponse.UserHandle is null for u2f authenticators");
|
||||
|
||||
ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists");
|
||||
let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
|
||||
is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
|
||||
is(clientData.origin, window.location.origin, "Origin is correct");
|
||||
is(clientData.type, "webauthn.get", "Type is correct");
|
||||
|
||||
return webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData)
|
||||
.then(function(aAttestation) {
|
||||
ok(new Uint8Array(aAttestation.flags)[0] & flag_TUP == flag_TUP, "User presence flag must be set");
|
||||
is(aAttestation.counter.byteLength, 4, "Counter must be 4 bytes");
|
||||
return deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, aAttestation)
|
||||
})
|
||||
.then(function(aParams) {
|
||||
console.log(aParams);
|
||||
console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON));
|
||||
console.log("ClientDataHash: ", hexEncode(aParams.challengeParam));
|
||||
return assembleSignedData(aParams.appParam, aParams.attestation.flags,
|
||||
aParams.attestation.counter, aParams.challengeParam);
|
||||
})
|
||||
.then(function(aSignedData) {
|
||||
console.log(aPublicKey, aSignedData, aAssertion.response.signature);
|
||||
return verifySignature(aPublicKey, aSignedData, aAssertion.response.signature);
|
||||
})
|
||||
}
|
||||
|
||||
function testMakeCredential() {
|
||||
let rp = {id: document.domain, name: "none", icon: "none"};
|
||||
let user = {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"};
|
||||
let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
|
||||
let makeCredentialOptions = {
|
||||
rp,
|
||||
user,
|
||||
challenge: gCredentialChallenge,
|
||||
pubKeyCredParams: [param],
|
||||
attestation: "direct"
|
||||
};
|
||||
credm.create({publicKey: makeCredentialOptions})
|
||||
.then(decodeCreatedCredential)
|
||||
.then(testMakeDuplicate)
|
||||
.catch(function(aReason) {
|
||||
ok(false, aReason);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
function testMakeDuplicate(aCredInfo) {
|
||||
let rp = {id: document.domain, name: "none", icon: "none"};
|
||||
let user = {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"};
|
||||
let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
|
||||
let makeCredentialOptions = {
|
||||
rp,
|
||||
user,
|
||||
challenge: gCredentialChallenge,
|
||||
pubKeyCredParams: [param],
|
||||
excludeCredentials: [{type: "public-key", id: new Uint8Array(aCredInfo.rawId),
|
||||
transports: ["usb"]}]
|
||||
};
|
||||
credm.create({publicKey: makeCredentialOptions})
|
||||
.then(function() {
|
||||
// We should have errored here!
|
||||
ok(false, "The excludeList didn't stop a duplicate being created!");
|
||||
SimpleTest.finish();
|
||||
})
|
||||
.catch(function(aReason) {
|
||||
ok(aReason.toString().startsWith("InvalidStateError"), "Expect InvalidStateError, got " + aReason);
|
||||
testAssertion(aCredInfo);
|
||||
});
|
||||
}
|
||||
|
||||
function testAssertion(aCredInfo) {
|
||||
let newCredential = {
|
||||
type: "public-key",
|
||||
id: new Uint8Array(aCredInfo.rawId),
|
||||
transports: ["usb"],
|
||||
}
|
||||
|
||||
let publicKeyCredentialRequestOptions = {
|
||||
challenge: gAssertionChallenge,
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
rpId: document.domain,
|
||||
allowCredentials: [newCredential]
|
||||
};
|
||||
credm.get({publicKey: publicKeyCredentialRequestOptions})
|
||||
.then(function(aAssertion) {
|
||||
/* Pass along the pubKey. */
|
||||
return checkAssertionAndSigValid(aCredInfo.publicKeyHandle, aAssertion);
|
||||
})
|
||||
.then(function(aSigVerifyResult) {
|
||||
ok(aSigVerifyResult, "Signing signature verified");
|
||||
SimpleTest.finish();
|
||||
})
|
||||
.catch(function(reason) {
|
||||
ok(false, "Signing signature invalid: " + reason);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
async function checkAssertionAndSigValid(aPublicKey, aAssertion) {
|
||||
/* PublicKeyCredential : Credential
|
||||
- rawId: ID of Credential from AllowList that succeeded
|
||||
- id: Key Handle buffer in base64url form, should == rawId
|
||||
- type: Literal 'public-key'
|
||||
- response : AuthenticatorAssertionResponse : AuthenticatorResponse
|
||||
- clientDataJSON: serialized JSON
|
||||
- authenticatorData: RP ID Hash || U2F Sign() Response
|
||||
- signature: U2F Sign() Response
|
||||
*/
|
||||
is(aAssertion.type, "public-key", "Credential type must be public-key")
|
||||
ok(aAssertion.rawId.byteLength > 0, "Key ID exists");
|
||||
is(aAssertion.id, bytesToBase64UrlSafe(new Uint8Array(aAssertion.rawId)), "Encoded Key ID and Raw Key ID match");
|
||||
ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
|
||||
ok(aAssertion.response.authenticatorData instanceof ArrayBuffer, "AuthenticatorAssertionResponse.AuthenticatorData is an ArrayBuffer");
|
||||
ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
|
||||
ok(aAssertion.response.signature instanceof ArrayBuffer, "AuthenticatorAssertionResponse.Signature is an ArrayBuffer");
|
||||
ok(aAssertion.response.userHandle === null, "AuthenticatorAssertionResponse.UserHandle is null for u2f authenticators");
|
||||
|
||||
ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists");
|
||||
let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
|
||||
is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
|
||||
is(clientData.origin, window.location.origin, "Origin is correct");
|
||||
is(clientData.type, "webauthn.get", "Type is correct");
|
||||
|
||||
let attestation = await webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData);
|
||||
ok(new Uint8Array(attestation.flags)[0] & flag_TUP == flag_TUP, "User presence flag must be set");
|
||||
is(attestation.counter.byteLength, 4, "Counter must be 4 bytes");
|
||||
let params = await deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, attestation);
|
||||
console.log(params);
|
||||
console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON));
|
||||
console.log("ClientDataHash: ", hexEncode(params.challengeParam));
|
||||
let signedData = await assembleSignedData(params.appParam, params.attestation.flags,
|
||||
params.attestation.counter, params.challengeParam);
|
||||
console.log(aPublicKey, signedData, aAssertion.response.signature);
|
||||
return await verifySignature(aPublicKey, signedData, aAssertion.response.signature);
|
||||
}
|
||||
|
||||
async function testMakeCredential() {
|
||||
let rp = {id: document.domain, name: "none", icon: "none"};
|
||||
let user = {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"};
|
||||
let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
|
||||
let makeCredentialOptions = {
|
||||
rp,
|
||||
user,
|
||||
challenge: gCredentialChallenge,
|
||||
pubKeyCredParams: [param],
|
||||
attestation: "direct"
|
||||
};
|
||||
let credential = await credm.create({publicKey: makeCredentialOptions})
|
||||
let decodedCredential = await decodeCreatedCredential(credential);
|
||||
await testMakeDuplicate(decodedCredential);
|
||||
}
|
||||
|
||||
async function testMakeDuplicate(aCredInfo) {
|
||||
let rp = {id: document.domain, name: "none", icon: "none"};
|
||||
let user = {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"};
|
||||
let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
|
||||
let makeCredentialOptions = {
|
||||
rp,
|
||||
user,
|
||||
challenge: gCredentialChallenge,
|
||||
pubKeyCredParams: [param],
|
||||
excludeCredentials: [{type: "public-key", id: new Uint8Array(aCredInfo.rawId),
|
||||
transports: ["usb"]}]
|
||||
};
|
||||
await credm.create({publicKey: makeCredentialOptions})
|
||||
.then(function() {
|
||||
// We should have errored here!
|
||||
ok(false, "The excludeList didn't stop a duplicate being created!");
|
||||
}).catch((aReason) => {
|
||||
ok(aReason.toString().startsWith("InvalidStateError"), "Expect InvalidStateError, got " + aReason);
|
||||
});
|
||||
await testAssertion(aCredInfo);
|
||||
}
|
||||
|
||||
async function testAssertion(aCredInfo) {
|
||||
let newCredential = {
|
||||
type: "public-key",
|
||||
id: new Uint8Array(aCredInfo.rawId),
|
||||
transports: ["usb"],
|
||||
}
|
||||
|
||||
let publicKeyCredentialRequestOptions = {
|
||||
challenge: gAssertionChallenge,
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
rpId: document.domain,
|
||||
allowCredentials: [newCredential]
|
||||
};
|
||||
let assertion = await credm.get({publicKey: publicKeyCredentialRequestOptions});
|
||||
let sigVerifyResult = await checkAssertionAndSigValid(aCredInfo.publicKeyHandle, assertion);
|
||||
ok(sigVerifyResult, "Signing signature verified");
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
|
||||
isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
|
||||
isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
|
||||
|
|
|
@ -29,7 +29,7 @@ let assertionChallenge;
|
|||
let credentialId;
|
||||
|
||||
// Setup test env
|
||||
add_task(() => {
|
||||
add_task(async () => {
|
||||
credentialChallenge = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(credentialChallenge);
|
||||
assertionChallenge = new Uint8Array(16);
|
||||
|
@ -38,7 +38,7 @@ add_task(() => {
|
|||
window.crypto.getRandomValues(credentialId);
|
||||
credm = navigator.credentials;
|
||||
// Turn off all tokens. This should result in "not allowed" failures
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn_enable_softtoken", false],
|
||||
["security.webauth.webauthn_enable_usbtoken", false],
|
||||
]});
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
// Last request status.
|
||||
let status = "";
|
||||
|
||||
add_task(() => {
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
add_task(async () => {
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn_enable_softtoken", false],
|
||||
["security.webauth.webauthn_enable_usbtoken", true],
|
||||
]});
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
// Execute the full-scope test
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
|
||||
isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
// Execute the full-scope test
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
var gTrackedCredential = {};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<title>Tests for Store for W3C Web Authentication</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -13,6 +14,10 @@
|
|||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
|
||||
isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
|
||||
isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
|
||||
|
|
|
@ -20,6 +20,32 @@ var { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
|
|||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
async function addVirtualAuthenticator() {
|
||||
let id = await SpecialPowers.spawnChrome([], () => {
|
||||
let webauthnTransport = Cc["@mozilla.org/webauthn/transport;1"].getService(
|
||||
Ci.nsIWebAuthnTransport
|
||||
);
|
||||
let id = webauthnTransport.addVirtualAuthenticator(
|
||||
"ctap2",
|
||||
"internal",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return id;
|
||||
});
|
||||
|
||||
SimpleTest.registerCleanupFunction(async () => {
|
||||
await SpecialPowers.spawnChrome([id], id => {
|
||||
let webauthnTransport = Cc[
|
||||
"@mozilla.org/webauthn/transport;1"
|
||||
].getService(Ci.nsIWebAuthnTransport);
|
||||
webauthnTransport.removeVirtualAuthenticator(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleEventMessage(event) {
|
||||
if ("test" in event.data) {
|
||||
let summary = event.data.test + ": " + event.data.msg;
|
||||
|
|
Загрузка…
Ссылка в новой задаче