Bug 1406467 - Web Authentication - WD-07 Updates to Make Assertion r=jcj,smaug

Summary:
Add support for PublicKeyCredentialRequestOptions.userVerification. For now
this basically means that we'll abort the operation with NotAllowed, as we
don't support user verification yet.

Pass PublicKeyCredentialDescriptor.transports through to the token manager
implementations. The softoken will ignore those and pretend to support all
transports defined by the spec. The USB HID token will check for the "usb"
transport and either ignore credentials accordingly, or abort the operation.

Note: The `UserVerificationRequirement` in WebIDL is defined at https://w3c.github.io/webauthn/#assertion-options

Reviewers: jcj, smaug

Reviewed By: jcj, smaug

Bug #: 1406467

Differential Revision: https://phabricator.services.mozilla.com/D338

--HG--
extra : amend_source : 314cadb3bc40bbbee2a414bc5f13caed55f9d720
This commit is contained in:
Tim Taubert 2018-01-09 07:27:35 +01:00
Родитель ae9a064d8e
Коммит c3180f09e1
19 изменённых файлов: 416 добавлений и 70 удалений

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

@ -96,7 +96,7 @@ AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
static void
RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
const nsTArray<RegisteredKey>& aKeys,
nsTArray<WebAuthnScopedCredentialDescriptor>& aList)
nsTArray<WebAuthnScopedCredential>& aList)
{
for (const RegisteredKey& key : aKeys) {
// Check for required attributes
@ -116,7 +116,7 @@ RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
continue;
}
WebAuthnScopedCredentialDescriptor c;
WebAuthnScopedCredential c;
c.id() = keyHandle;
aList.AppendElement(c);
}
@ -392,7 +392,7 @@ U2F::Register(const nsAString& aAppId,
}
// Build the exclusion list, if any
nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
nsTArray<WebAuthnScopedCredential> excludeList;
RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
excludeList);
@ -540,7 +540,7 @@ U2F::Sign(const nsAString& aAppId,
}
// Build the key list, if any
nsTArray<WebAuthnScopedCredentialDescriptor> permittedList;
nsTArray<WebAuthnScopedCredential> permittedList;
RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
permittedList);
@ -573,6 +573,7 @@ U2F::Sign(const nsAString& aAppId,
clientDataHash,
adjustedTimeoutMillis,
permittedList,
false, /* requireUserVerification */
extensions);
MOZ_ASSERT(mTransaction.isNothing());

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

@ -20,13 +20,14 @@ namespace mozilla {
namespace dom {
struct WebAuthnAuthenticatorSelection {
bool requireResidentKey;
bool requireUserVerification;
bool requirePlatformAttachment;
bool requireResidentKey;
bool requireUserVerification;
bool requirePlatformAttachment;
};
struct WebAuthnScopedCredentialDescriptor {
struct WebAuthnScopedCredential {
uint8_t[] id;
uint8_t transports;
};
struct WebAuthnExtension {
@ -37,7 +38,7 @@ struct WebAuthnMakeCredentialInfo {
uint8_t[] RpIdHash;
uint8_t[] ClientDataHash;
uint32_t TimeoutMS;
WebAuthnScopedCredentialDescriptor[] ExcludeList;
WebAuthnScopedCredential[] ExcludeList;
WebAuthnExtension[] Extensions;
WebAuthnAuthenticatorSelection AuthenticatorSelection;
};
@ -46,7 +47,8 @@ struct WebAuthnGetAssertionInfo {
uint8_t[] RpIdHash;
uint8_t[] ClientDataHash;
uint32_t TimeoutMS;
WebAuthnScopedCredentialDescriptor[] AllowList;
WebAuthnScopedCredential[] AllowList;
bool RequireUserVerification;
WebAuthnExtension[] Extensions;
};

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

@ -100,7 +100,7 @@ U2FHIDTokenManager::~U2FHIDTokenManager()
// * attestation signature
//
RefPtr<U2FRegisterPromise>
U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
@ -130,7 +130,7 @@ U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>&
aChallenge.Length(),
aApplication.Elements(),
aApplication.Length(),
U2FKeyHandles(aDescriptors).Get());
U2FKeyHandles(aCredentials).Get());
if (mTransactionId == 0) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
@ -156,22 +156,31 @@ U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>&
// * Signature
//
RefPtr<U2FSignPromise>
U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
bool aRequireUserVerification,
uint32_t aTimeoutMS)
{
MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
uint64_t signFlags = 0;
// Set flags for credential requests.
if (aRequireUserVerification) {
signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
}
ClearPromises();
mTransactionId = rust_u2f_mgr_sign(mU2FManager,
signFlags,
(uint64_t)aTimeoutMS,
u2f_sign_callback,
aChallenge.Elements(),
aChallenge.Length(),
aApplication.Elements(),
aApplication.Length(),
U2FKeyHandles(aDescriptors).Get());
U2FKeyHandles(aCredentials).Get());
if (mTransactionId == 0) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
}

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

@ -20,12 +20,15 @@ namespace dom {
class U2FKeyHandles {
public:
explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors)
explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredential>& aCredentials)
{
mKeyHandles = rust_u2f_khs_new();
for (auto desc: aDescriptors) {
rust_u2f_khs_add(mKeyHandles, desc.id().Elements(), desc.id().Length());
for (auto cred: aCredentials) {
rust_u2f_khs_add(mKeyHandles,
cred.id().Elements(),
cred.id().Length(),
cred.transports());
}
}
@ -91,16 +94,17 @@ public:
explicit U2FHIDTokenManager();
virtual RefPtr<U2FRegisterPromise>
Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
uint32_t aTimeoutMS) override;
virtual RefPtr<U2FSignPromise>
Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
bool aRequireUserVerification,
uint32_t aTimeoutMS) override;
void Cancel() override;

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

@ -625,7 +625,7 @@ U2FSoftTokenManager::IsRegistered(const nsTArray<uint8_t>& aKeyHandle,
// * attestation signature
//
RefPtr<U2FRegisterPromise>
U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
@ -652,9 +652,9 @@ U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>
}
// Optional exclusion list.
for (auto desc: aDescriptors) {
for (auto cred: aCredentials) {
bool isRegistered = false;
nsresult rv = IsRegistered(desc.id(), aApplication, isRegistered);
nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
if (NS_FAILED(rv)) {
return U2FRegisterPromise::CreateAndReject(rv, __func__);
}
@ -758,9 +758,10 @@ U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>
// * Signature
//
RefPtr<U2FSignPromise>
U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
bool aRequireUserVerification,
uint32_t aTimeoutMS)
{
nsNSSShutDownPreventionLock locker;
@ -768,12 +769,17 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aD
return U2FSignPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
}
// The U2F softtoken doesn't support user verification.
if (aRequireUserVerification) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
}
nsTArray<uint8_t> keyHandle;
for (auto desc: aDescriptors) {
for (auto cred: aCredentials) {
bool isRegistered = false;
nsresult rv = IsRegistered(desc.id(), aApplication, isRegistered);
nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
if (NS_SUCCEEDED(rv) && isRegistered) {
keyHandle.Assign(desc.id());
keyHandle.Assign(cred.id());
break;
}
}

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

@ -26,16 +26,17 @@ public:
explicit U2FSoftTokenManager(uint32_t aCounter);
virtual RefPtr<U2FRegisterPromise>
Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
uint32_t aTimeoutMS) override;
virtual RefPtr<U2FSignPromise>
Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
bool aRequireUserVerification,
uint32_t aTimeoutMS) override;
virtual void Cancel() override;

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

@ -322,6 +322,7 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
mTokenManagerImpl->Sign(aTransactionInfo.AllowList(),
aTransactionInfo.RpIdHash(),
aTransactionInfo.ClientDataHash(),
aTransactionInfo.RequireUserVerification(),
aTransactionInfo.TimeoutMS())
->Then(GetCurrentThreadSerialEventTarget(), __func__,
[tid, startTime](U2FSignResult&& aResult) {

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

@ -63,16 +63,17 @@ public:
U2FTokenTransport() {}
virtual RefPtr<U2FRegisterPromise>
Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
uint32_t aTimeoutMS) = 0;
virtual RefPtr<U2FSignPromise>
Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
bool aRequireUserVerification,
uint32_t aTimeoutMS) = 0;
virtual void Cancel() = 0;

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

@ -361,9 +361,9 @@ WebAuthnManager::MakeCredential(const MakePublicKeyCredentialOptions& aOptions,
return promise.forget();
}
nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
nsTArray<WebAuthnScopedCredential> excludeList;
for (const auto& s: aOptions.mExcludeCredentials) {
WebAuthnScopedCredentialDescriptor c;
WebAuthnScopedCredential c;
CryptoBuffer cb;
cb.Assign(s.mId);
c.id() = cb;
@ -533,13 +533,33 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
return promise.forget();
}
nsTArray<WebAuthnScopedCredentialDescriptor> allowList;
nsTArray<WebAuthnScopedCredential> allowList;
for (const auto& s: aOptions.mAllowCredentials) {
WebAuthnScopedCredentialDescriptor c;
CryptoBuffer cb;
cb.Assign(s.mId);
c.id() = cb;
allowList.AppendElement(c);
if (s.mType == PublicKeyCredentialType::Public_key) {
WebAuthnScopedCredential c;
CryptoBuffer cb;
cb.Assign(s.mId);
c.id() = cb;
// Serialize transports.
if (s.mTransports.WasPassed()) {
uint8_t transports = 0;
for (const auto& t: s.mTransports.Value()) {
if (t == AuthenticatorTransport::Usb) {
transports |= U2F_AUTHENTICATOR_TRANSPORT_USB;
}
if (t == AuthenticatorTransport::Nfc) {
transports |= U2F_AUTHENTICATOR_TRANSPORT_NFC;
}
if (t == AuthenticatorTransport::Ble) {
transports |= U2F_AUTHENTICATOR_TRANSPORT_BLE;
}
}
c.transports() = transports;
}
allowList.AppendElement(c);
}
}
if (!MaybeCreateBackgroundActor()) {
@ -547,6 +567,10 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
return promise.forget();
}
// Does the RP require user verification?
bool requireUserVerification =
aOptions.mUserVerification == UserVerificationRequirement::Required;
// TODO: Add extension list building
// If extensions was specified, process any extensions supported by this
// client platform, to produce the extension data that needs to be sent to the
@ -559,6 +583,7 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
clientDataHash,
adjustedTimeout,
allowList,
requireUserVerification,
extensions);
ListenForVisibilityEvents();

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

@ -8,6 +8,7 @@ scheme = https
[test_webauthn_abort_signal.html]
[test_webauthn_authenticator_selection.html]
[test_webauthn_authenticator_transports.html]
[test_webauthn_loopback.html]
[test_webauthn_no_token.html]
[test_webauthn_make_credential.html]

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

@ -11,6 +11,7 @@
<h1>W3C Web Authentication - Authenticator Selection Criteria</h1>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406462">Mozilla Bug 1406462</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406467">Mozilla Bug 1406467</a>
<script class="testbody" type="text/javascript">
"use strict";
@ -27,6 +28,10 @@
ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError, got " + aResult);
}
// We store the credential of the first successful make credential
// operation so we can use it for get assertion tests later.
let gCredential;
add_task(() => {
// Enable the softtoken.
return SpecialPowers.pushPrefEnv({"set": [
@ -50,10 +55,34 @@
return navigator.credentials.create({publicKey});
}
// Test success cases.
// Start a new GetAssertion() request.
function requestGetAssertion(userVerification) {
let newCredential = {
type: "public-key",
id: gCredential,
transports: ["usb"],
};
let publicKey = {
challenge: crypto.getRandomValues(new Uint8Array(16)),
timeout: 5000, // the minimum timeout is actually 15 seconds
rpId: document.domain,
allowCredentials: [newCredential]
};
if (userVerification) {
publicKey.userVerification = userVerification;
}
return navigator.credentials.get({publicKey});
}
// Test success cases for make credential.
add_task(async () => {
// No selection criteria.
await requestMakeCredential({})
// Save the credential so we can use it for sign success tests.
.then(res => gCredential = res.rawId)
.then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
@ -78,7 +107,25 @@
.catch(arrivingHereIsBad);
});
// Test the failure cases.
// Test success cases for get assertion.
add_task(async () => {
// No selection criteria.
await requestGetAssertion()
.then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
// Prefer user verification.
await requestGetAssertion("preferred")
.then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
// Discourage user verification.
await requestGetAssertion("discouraged")
.then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
});
// Test failure cases for make credential.
add_task(async () => {
// Request a platform authenticator.
await requestMakeCredential({authenticatorAttachment: "platform"})
@ -95,6 +142,14 @@
.then(arrivingHereIsBad)
.catch(expectNotAllowedError);
});
// Test failure cases for get assertion.
add_task(async () => {
// Require user verification.
await requestGetAssertion("required")
.then(arrivingHereIsBad)
.catch(expectNotAllowedError);
});
</script>
</body>

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

@ -0,0 +1,150 @@
<!DOCTYPE html>
<meta charset=utf-8>
<head>
<title>W3C Web Authentication - Authenticator Transports</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="u2futil.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<h1>W3C Web Authentication - Authenticator Transports</h1>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406467">Mozilla Bug 1406467</a>
<script class="testbody" type="text/javascript">
"use strict";
function arrivingHereIsGood(aResult) {
ok(true, "Good result! Received a: " + aResult);
}
function arrivingHereIsBad(aResult) {
ok(false, "Bad result! Received a: " + aResult);
}
function expectNotAllowedError(aResult) {
ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError, got " + aResult);
}
// Store the credential of the first successful make credential
// operation so we can use it to get assertions later.
let gCredential;
add_task(() => {
// Enable the softtoken.
return SpecialPowers.pushPrefEnv({"set": [
["security.webauth.webauthn", true],
["security.webauth.webauthn_enable_softtoken", true],
["security.webauth.webauthn_enable_usbtoken", false],
]});
});
// Start a new MakeCredential() request.
function requestMakeCredential(excludeCredentials) {
let publicKey = {
rp: {id: document.domain, name: "none", icon: "none"},
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
challenge: crypto.getRandomValues(new Uint8Array(16)),
timeout: 5000, // the minimum timeout is actually 15 seconds
pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
excludeCredentials
};
return navigator.credentials.create({publicKey});
}
// Start a new GetAssertion() request.
function requestGetAssertion(allowCredentials) {
let publicKey = {
challenge: crypto.getRandomValues(new Uint8Array(16)),
timeout: 5000, // the minimum timeout is actually 15 seconds
rpId: document.domain,
allowCredentials
};
return navigator.credentials.get({publicKey});
}
// Test make credential behavior.
add_task(async () => {
// Make a credential.
await requestMakeCredential([])
// Save the credential for later.
.then(res => gCredential = res.rawId)
.then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
// Pass a random credential to exclude.
await requestMakeCredential([{
type: "public-key",
id: crypto.getRandomValues(new Uint8Array(16)),
transports: ["usb"],
}]).then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
// Pass gCredential with transport=usb.
await requestMakeCredential([{
type: "public-key",
id: gCredential,
transports: ["usb"],
}]).then(arrivingHereIsBad)
.catch(expectNotAllowedError);
// Pass gCredential with transport=nfc.
// The softoken pretends to support all transports.
await requestMakeCredential([{
type: "public-key",
id: gCredential,
transports: ["nfc"],
}]).then(arrivingHereIsBad)
.catch(expectNotAllowedError);
// Pass gCredential with an empty transports list.
await requestMakeCredential([{
type: "public-key",
id: gCredential,
transports: [],
}]).then(arrivingHereIsBad)
.catch(expectNotAllowedError);
});
// Test get assertion behavior.
add_task(async () => {
// Request an assertion for gCredential.
await requestGetAssertion([{
type: "public-key",
id: gCredential,
transports: ["usb"],
}]).then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
// Request an assertion for a random credential.
await requestGetAssertion([{
type: "public-key",
id: crypto.getRandomValues(new Uint8Array(16)),
transports: ["usb"],
}]).then(arrivingHereIsBad)
.catch(expectNotAllowedError);
// Request an assertion for gCredential with transport=nfc.
// The softoken pretends to support all transports.
await requestGetAssertion([{
type: "public-key",
id: gCredential,
transports: ["nfc"],
}]).then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
// Request an assertion for gCredential with an empty transports list.
await requestGetAssertion([{
type: "public-key",
id: gCredential,
transports: [],
}]).then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
});
</script>
</body>
</html>

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

@ -9,7 +9,7 @@ use crypto::digest::Digest;
use crypto::sha2::Sha256;
use std::io;
use std::sync::mpsc::channel;
use u2fhid::{RegisterFlags, U2FManager};
use u2fhid::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, U2FManager};
extern crate log;
extern crate env_logger;
@ -66,11 +66,17 @@ fn main() {
let register_data = rx.recv().unwrap();
println!("Register result: {}", base64::encode(&register_data));
println!("Asking a security key to sign now, with the data from the register...");
let key_handle = u2f_get_key_handle_from_register_response(&register_data).unwrap();
let credential = u2f_get_key_handle_from_register_response(&register_data).unwrap();
let key_handle = KeyHandle {
credential,
transports: AuthenticatorTransports::empty(),
};
let flags = SignFlags::empty();
let (tx, rx) = channel();
manager
.sign(
flags,
15_000,
chall_bytes,
app_bytes,

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

@ -9,7 +9,7 @@ use std::{ptr, slice};
use U2FManager;
type U2FKeyHandles = Vec<Vec<u8>>;
type U2FKeyHandles = Vec<::KeyHandle>;
type U2FResult = HashMap<u8, Vec<u8>>;
type U2FCallback = extern "C" fn(u64, *mut U2FResult);
@ -52,8 +52,12 @@ pub unsafe extern "C" fn rust_u2f_khs_add(
khs: *mut U2FKeyHandles,
key_handle_ptr: *const u8,
key_handle_len: usize,
transports: u8,
) {
(*khs).push(from_raw(key_handle_ptr, key_handle_len));
(*khs).push(::KeyHandle {
credential: from_raw(key_handle_ptr, key_handle_len),
transports: ::AuthenticatorTransports::from_bits_truncate(transports),
});
}
#[no_mangle]
@ -156,6 +160,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_register(
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_sign(
mgr: *mut U2FManager,
flags: u64,
timeout: u64,
callback: U2FCallback,
challenge_ptr: *const u8,
@ -178,21 +183,29 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
return 0;
}
let flags = ::SignFlags::from_bits_truncate(flags);
let challenge = from_raw(challenge_ptr, challenge_len);
let application = from_raw(application_ptr, application_len);
let key_handles = (*khs).clone();
let tid = new_tid();
let res = (*mgr).sign(timeout, challenge, application, key_handles, move |rv| {
if let Ok((key_handle, signature)) = rv {
let mut result = U2FResult::new();
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
result.insert(RESBUF_ID_SIGNATURE, signature);
callback(tid, Box::into_raw(Box::new(result)));
} else {
callback(tid, ptr::null_mut());
};
});
let res = (*mgr).sign(
flags,
timeout,
challenge,
application,
key_handles,
move |rv| {
if let Ok((key_handle, signature)) = rv {
let mut result = U2FResult::new();
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
result.insert(RESBUF_ID_SIGNATURE, signature);
callback(tid, Box::into_raw(Box::new(result)));
} else {
callback(tid, ptr::null_mut());
};
},
);
if res.is_ok() { tid } else { 0 }
}

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

@ -56,6 +56,24 @@ bitflags! {
const REQUIRE_PLATFORM_ATTACHMENT = 4;
}
}
bitflags! {
pub struct SignFlags: u64 {
const REQUIRE_USER_VERIFICATION = 1;
}
}
bitflags! {
pub struct AuthenticatorTransports: u8 {
const USB = 1;
const NFC = 2;
const BLE = 4;
}
}
#[derive(Clone)]
pub struct KeyHandle {
pub credential: Vec<u8>,
pub transports: AuthenticatorTransports,
}
#[cfg(fuzzing)]
pub use u2fprotocol::*;

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

@ -17,14 +17,15 @@ enum QueueAction {
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<Vec<u8>>,
},
Sign {
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
},
Cancel,
@ -64,6 +65,7 @@ impl U2FManager {
);
}
Ok(QueueAction::Sign {
flags,
timeout,
challenge,
application,
@ -71,7 +73,14 @@ impl U2FManager {
callback,
}) => {
// This must not block, otherwise we can't cancel.
sm.sign(timeout, challenge, application, key_handles, callback);
sm.sign(
flags,
timeout,
challenge,
application,
key_handles,
callback,
);
}
Ok(QueueAction::Cancel) => {
// Cancelling must block so that we don't start a new
@ -101,7 +110,7 @@ impl U2FManager {
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
key_handles: Vec<::KeyHandle>,
callback: F,
) -> io::Result<()>
where
@ -116,7 +125,7 @@ impl U2FManager {
}
for key_handle in &key_handles {
if key_handle.len() > 256 {
if key_handle.credential.len() > 256 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Key handle too large",
@ -138,10 +147,11 @@ impl U2FManager {
pub fn sign<F>(
&self,
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
key_handles: Vec<::KeyHandle>,
callback: F,
) -> io::Result<()>
where
@ -163,7 +173,7 @@ impl U2FManager {
}
for key_handle in &key_handles {
if key_handle.len() > 256 {
if key_handle.credential.len() > 256 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Key handle too large",
@ -173,6 +183,7 @@ impl U2FManager {
let callback = OnceCallback::new(callback);
let action = QueueAction::Sign {
flags,
timeout,
challenge,
application,

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

@ -10,6 +10,10 @@ use std::time::Duration;
use util::{io_err, OnceCallback};
use u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
fn is_valid_transport(transports: ::AuthenticatorTransports) -> bool {
transports.is_empty() || transports.contains(::AuthenticatorTransports::USB)
}
#[derive(Default)]
pub struct StateMachine {
transaction: Option<Transaction>,
@ -26,7 +30,7 @@ impl StateMachine {
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<Vec<u8>>,
) {
// Abort any prior register/sign calls.
@ -60,8 +64,9 @@ impl StateMachine {
// Iterate the exclude list and see if there are any matches.
// Abort the state machine if we found a valid key handle.
if key_handles.iter().any(|key_handle| {
u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
.unwrap_or(false) /* no match on failure */
is_valid_transport(key_handle.transports) &&
u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
.unwrap_or(false) /* no match on failure */
})
{
return;
@ -85,10 +90,11 @@ impl StateMachine {
pub fn sign(
&mut self,
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
key_handles: Vec<Vec<u8>>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
) {
// Abort any prior register/sign calls.
@ -108,15 +114,38 @@ impl StateMachine {
return;
}
// We currently don't support user verification because we can't
// ask tokens whether they do support that. If the flag is set,
// ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
// Find all matching key handles.
let key_handles = key_handles
.iter()
.filter(|key_handle| {
u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
.unwrap_or(false) /* no match on failure */
})
.collect::<Vec<_>>();
// Aggregate distinct transports from all given credentials.
let transports = key_handles.iter().fold(
::AuthenticatorTransports::empty(),
|t, k| t | k.transports,
);
// We currently only support USB. If the RP specifies transports
// and doesn't include USB it's probably lying.
if !is_valid_transport(transports) {
return;
}
while alive() {
// If the device matches none of the given key handles
// then just make it blink with bogus data.
@ -129,8 +158,14 @@ impl StateMachine {
} else {
// Otherwise, try to sign.
for key_handle in &key_handles {
if let Ok(bytes) = u2f_sign(dev, &challenge, &application, key_handle) {
callback.call(Ok((key_handle.to_vec(), bytes)));
if let Ok(bytes) = u2f_sign(
dev,
&challenge,
&application,
&key_handle.credential,
)
{
callback.call(Ok((key_handle.credential.clone(), bytes)));
break;
}
}

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

@ -19,6 +19,10 @@ const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1;
const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
const uint64_t U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT = 4;
const uint8_t U2F_AUTHENTICATOR_TRANSPORT_USB = 1;
const uint8_t U2F_AUTHENTICATOR_TRANSPORT_NFC = 2;
const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
// NOTE: Preconditions
// * All rust_u2f_mgr* pointers must refer to pointers which are returned
// by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free.
@ -56,6 +60,7 @@ uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr,
const rust_u2f_key_handles* khs);
uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
uint64_t flags,
uint64_t timeout,
rust_u2f_callback,
const uint8_t* challenge_ptr,
@ -72,7 +77,8 @@ uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
rust_u2f_key_handles* rust_u2f_khs_new();
void rust_u2f_khs_add(rust_u2f_key_handles* khs,
const uint8_t* key_handle,
size_t key_handle_len);
size_t key_handle_len,
uint8_t transports);
/* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs);

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

@ -4,7 +4,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* https://www.w3.org/TR/webauthn/
* https://w3c.github.io/webauthn/
*/
/***** Interfaces to Data *****/
@ -94,6 +94,7 @@ dictionary PublicKeyCredentialRequestOptions {
unsigned long timeout;
USVString rpId;
sequence<PublicKeyCredentialDescriptor> allowCredentials = [];
UserVerificationRequirement userVerification = "preferred";
// Extensions are not supported yet.
// AuthenticationExtensions extensions; // Add in Bug 1406458
};