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:
Dana Keeler 2023-08-08 18:00:19 +00:00
Родитель a27c448f15
Коммит e64f2d53e9
29 изменённых файлов: 741 добавлений и 307 удалений

Просмотреть файл

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