GetNextAssertion command minimal implementation

This still lacks order of credentials and timeouts.
This commit is contained in:
Fabian Kaczmarczyck
2020-09-17 09:23:17 +02:00
parent 6a2a482b03
commit 3ae59ce1ec
3 changed files with 112 additions and 64 deletions

View File

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

View File

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

View File

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