GetNextAssertion command minimal implementation
This still lacks order of credentials and timeouts.
This commit is contained in:
@@ -57,8 +57,8 @@ impl Command {
|
|||||||
const AUTHENTICATOR_GET_INFO: u8 = 0x04;
|
const AUTHENTICATOR_GET_INFO: u8 = 0x04;
|
||||||
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
|
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
|
||||||
const AUTHENTICATOR_RESET: u8 = 0x07;
|
const AUTHENTICATOR_RESET: u8 = 0x07;
|
||||||
// TODO(kaczmarczyck) use or remove those constants
|
|
||||||
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
|
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
|
||||||
|
// TODO(kaczmarczyck) use or remove those constants
|
||||||
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
|
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
|
||||||
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
||||||
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
||||||
|
|||||||
@@ -308,7 +308,8 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub struct GetAssertionHmacSecretInput {
|
pub struct GetAssertionHmacSecretInput {
|
||||||
pub key_agreement: CoseKey,
|
pub key_agreement: CoseKey,
|
||||||
pub salt_enc: Vec<u8>,
|
pub salt_enc: Vec<u8>,
|
||||||
@@ -603,7 +604,8 @@ impl PublicKeyCredentialSource {
|
|||||||
// TODO(kaczmarczyck) we could decide to split this data type up
|
// TODO(kaczmarczyck) we could decide to split this data type up
|
||||||
// It depends on the algorithm though, I think.
|
// It depends on the algorithm though, I think.
|
||||||
// So before creating a mess, this is my workaround.
|
// So before creating a mess, this is my workaround.
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>);
|
pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>);
|
||||||
|
|
||||||
// This is the algorithm specifier that is supposed to be used in a COSE key
|
// This is the algorithm specifier that is supposed to be used in a COSE key
|
||||||
|
|||||||
152
src/ctap/mod.rs
152
src/ctap/mod.rs
@@ -33,9 +33,9 @@ use self::command::{
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
use self::data_formats::AuthenticatorTransport;
|
use self::data_formats::AuthenticatorTransport;
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
CredentialProtectionPolicy, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
CredentialProtectionPolicy, GetAssertionHmacSecretInput, PackedAttestationStatement,
|
||||||
PublicKeyCredentialParameter, PublicKeyCredentialSource, PublicKeyCredentialType,
|
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
|
||||||
PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
||||||
};
|
};
|
||||||
use self::hid::ChannelID;
|
use self::hid::ChannelID;
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
@@ -133,6 +133,14 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AssertionInput {
|
||||||
|
client_data_hash: Vec<u8>,
|
||||||
|
auth_data: Vec<u8>,
|
||||||
|
uv: bool,
|
||||||
|
hmac_secret_input: Option<GetAssertionHmacSecretInput>,
|
||||||
|
}
|
||||||
|
|
||||||
// This struct currently holds all state, not only the persistent memory. The persistent members are
|
// This struct currently holds all state, not only the persistent memory. The persistent members are
|
||||||
// in the persistent store field.
|
// in the persistent store field.
|
||||||
pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>>
|
pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>>
|
||||||
@@ -147,6 +155,8 @@ pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(
|
|||||||
accepts_reset: bool,
|
accepts_reset: bool,
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
pub u2f_up_state: U2fUserPresenceState,
|
pub u2f_up_state: U2fUserPresenceState,
|
||||||
|
next_credentials: Vec<PublicKeyCredentialSource>,
|
||||||
|
next_assertion_input: Option<AssertionInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence>
|
impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence>
|
||||||
@@ -173,6 +183,8 @@ where
|
|||||||
U2F_UP_PROMPT_TIMEOUT,
|
U2F_UP_PROMPT_TIMEOUT,
|
||||||
Duration::from_ms(TOUCH_TIMEOUT_MS),
|
Duration::from_ms(TOUCH_TIMEOUT_MS),
|
||||||
),
|
),
|
||||||
|
next_credentials: vec![],
|
||||||
|
next_assertion_input: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,13 +326,13 @@ where
|
|||||||
Command::AuthenticatorGetAssertion(params) => {
|
Command::AuthenticatorGetAssertion(params) => {
|
||||||
self.process_get_assertion(params, cid)
|
self.process_get_assertion(params, cid)
|
||||||
}
|
}
|
||||||
|
Command::AuthenticatorGetNextAssertion => self.process_get_next_assertion(),
|
||||||
Command::AuthenticatorGetInfo => self.process_get_info(),
|
Command::AuthenticatorGetInfo => self.process_get_info(),
|
||||||
Command::AuthenticatorClientPin(params) => self.process_client_pin(params),
|
Command::AuthenticatorClientPin(params) => self.process_client_pin(params),
|
||||||
Command::AuthenticatorReset => self.process_reset(cid),
|
Command::AuthenticatorReset => self.process_reset(cid),
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
Command::AuthenticatorSelection => self.process_selection(cid),
|
Command::AuthenticatorSelection => self.process_selection(cid),
|
||||||
// TODO(kaczmarczyck) implement GetNextAssertion and FIDO 2.1 commands
|
// TODO(kaczmarczyck) implement FIDO 2.1 commands
|
||||||
_ => self.process_unknown_command(),
|
|
||||||
};
|
};
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
||||||
@@ -557,6 +569,63 @@ where
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assertion_response(
|
||||||
|
&self,
|
||||||
|
credential: &PublicKeyCredentialSource,
|
||||||
|
assertion_input: AssertionInput,
|
||||||
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
|
let AssertionInput {
|
||||||
|
client_data_hash,
|
||||||
|
mut auth_data,
|
||||||
|
uv,
|
||||||
|
hmac_secret_input,
|
||||||
|
} = assertion_input;
|
||||||
|
|
||||||
|
// Process extensions.
|
||||||
|
if let Some(hmac_secret_input) = hmac_secret_input {
|
||||||
|
let encrypted_output = self
|
||||||
|
.pin_protocol_v1
|
||||||
|
.process_hmac_secret(hmac_secret_input, &credential.cred_random)?;
|
||||||
|
let extensions_output = cbor_map! {
|
||||||
|
"hmac-secret" => encrypted_output,
|
||||||
|
};
|
||||||
|
if !cbor::write(extensions_output, &mut auth_data) {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut signature_data = auth_data.clone();
|
||||||
|
signature_data.extend(client_data_hash);
|
||||||
|
let signature = credential
|
||||||
|
.private_key
|
||||||
|
.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data);
|
||||||
|
|
||||||
|
let cred_desc = PublicKeyCredentialDescriptor {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
key_id: credential.credential_id.clone(),
|
||||||
|
transports: None, // You can set USB as a hint here.
|
||||||
|
};
|
||||||
|
let user = if uv && !credential.user_handle.is_empty() {
|
||||||
|
Some(PublicKeyCredentialUserEntity {
|
||||||
|
user_id: credential.user_handle.clone(),
|
||||||
|
user_name: None,
|
||||||
|
user_display_name: credential.other_ui.clone(),
|
||||||
|
user_icon: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(ResponseData::AuthenticatorGetAssertion(
|
||||||
|
AuthenticatorGetAssertionResponse {
|
||||||
|
credential: Some(cred_desc),
|
||||||
|
auth_data,
|
||||||
|
signature: signature.to_asn1_der(),
|
||||||
|
user,
|
||||||
|
number_of_credentials: None,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn process_get_assertion(
|
fn process_get_assertion(
|
||||||
&mut self,
|
&mut self,
|
||||||
get_assertion_params: AuthenticatorGetAssertionParameters,
|
get_assertion_params: AuthenticatorGetAssertionParameters,
|
||||||
@@ -643,11 +712,13 @@ where
|
|||||||
}
|
}
|
||||||
found_credentials
|
found_credentials
|
||||||
} else {
|
} else {
|
||||||
// TODO(kaczmarczyck) use GetNextAssertion
|
|
||||||
self.persistent_store.filter_credential(&rp_id, !has_uv)?
|
self.persistent_store.filter_credential(&rp_id, !has_uv)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let credential = if let Some(credential) = credentials.first() {
|
let credential =
|
||||||
|
if let Some((credential, remaining_credentials)) = credentials.split_first() {
|
||||||
|
// TODO(kaczmarczyck) correct credential order
|
||||||
|
self.next_credentials = remaining_credentials.to_vec();
|
||||||
credential
|
credential
|
||||||
} else {
|
} else {
|
||||||
decrypted_credential
|
decrypted_credential
|
||||||
@@ -661,50 +732,30 @@ where
|
|||||||
|
|
||||||
self.increment_global_signature_counter()?;
|
self.increment_global_signature_counter()?;
|
||||||
|
|
||||||
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
|
let assertion_input = AssertionInput {
|
||||||
// Process extensions.
|
client_data_hash,
|
||||||
if let Some(hmac_secret_input) = hmac_secret_input {
|
auth_data: self.generate_auth_data(&rp_id_hash, flags)?,
|
||||||
let encrypted_output = self
|
uv: flags & UV_FLAG != 0,
|
||||||
.pin_protocol_v1
|
hmac_secret_input,
|
||||||
.process_hmac_secret(hmac_secret_input, &credential.cred_random)?;
|
|
||||||
let extensions_output = cbor_map! {
|
|
||||||
"hmac-secret" => encrypted_output,
|
|
||||||
};
|
};
|
||||||
if !cbor::write(extensions_output, &mut auth_data) {
|
if !self.next_credentials.is_empty() {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
self.next_assertion_input = Some(assertion_input.clone());
|
||||||
}
|
}
|
||||||
|
self.assertion_response(credential, assertion_input)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut signature_data = auth_data.clone();
|
fn process_get_next_assertion(&mut self) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
signature_data.extend(client_data_hash);
|
// TODO(kaczmarczyck) introduce timer
|
||||||
let signature = credential
|
let assertion_input = self
|
||||||
.private_key
|
.next_assertion_input
|
||||||
.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data);
|
.clone()
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
|
||||||
let cred_desc = PublicKeyCredentialDescriptor {
|
if let Some(credential) = self.next_credentials.pop() {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
self.assertion_response(&credential, assertion_input)
|
||||||
key_id: credential.credential_id.clone(),
|
|
||||||
transports: None, // You can set USB as a hint here.
|
|
||||||
};
|
|
||||||
let user = if (flags & UV_FLAG != 0) && !credential.user_handle.is_empty() {
|
|
||||||
Some(PublicKeyCredentialUserEntity {
|
|
||||||
user_id: credential.user_handle.clone(),
|
|
||||||
user_name: None,
|
|
||||||
user_display_name: credential.other_ui.clone(),
|
|
||||||
user_icon: None,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
self.next_assertion_input = None;
|
||||||
};
|
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
|
||||||
Ok(ResponseData::AuthenticatorGetAssertion(
|
}
|
||||||
AuthenticatorGetAssertionResponse {
|
|
||||||
credential: Some(cred_desc),
|
|
||||||
auth_data,
|
|
||||||
signature: signature.to_asn1_der(),
|
|
||||||
user,
|
|
||||||
number_of_credentials: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_get_info(&self) -> Result<ResponseData, Ctap2StatusCode> {
|
fn process_get_info(&self) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
@@ -786,10 +837,6 @@ where
|
|||||||
Ok(ResponseData::AuthenticatorSelection)
|
Ok(ResponseData::AuthenticatorSelection)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_unknown_command(&self) -> Result<ResponseData, Ctap2StatusCode> {
|
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_auth_data(
|
pub fn generate_auth_data(
|
||||||
&self,
|
&self,
|
||||||
rp_id_hash: &[u8],
|
rp_id_hash: &[u8],
|
||||||
@@ -813,9 +860,8 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
CoseKey, GetAssertionExtensions, GetAssertionHmacSecretInput, GetAssertionOptions,
|
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
||||||
MakeCredentialExtensions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
|
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||||
PublicKeyCredentialUserEntity,
|
|
||||||
};
|
};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
|
|||||||
Reference in New Issue
Block a user