refactors the client PIN implementation into a new module
This commit is contained in:
484
src/ctap/mod.rs
484
src/ctap/mod.rs
@@ -18,6 +18,7 @@ mod ctap1;
|
||||
pub mod data_formats;
|
||||
pub mod hid;
|
||||
mod key_material;
|
||||
mod pin_protocol_v1;
|
||||
pub mod response;
|
||||
pub mod status_code;
|
||||
mod storage;
|
||||
@@ -32,15 +33,15 @@ use self::command::{
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
use self::data_formats::AuthenticatorTransport;
|
||||
use self::data_formats::{
|
||||
ClientPinSubCommand, CoseKey, CredentialProtectionPolicy, GetAssertionHmacSecretInput,
|
||||
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
||||
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
||||
SignatureAlgorithm,
|
||||
CredentialProtectionPolicy, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
||||
PublicKeyCredentialParameter, PublicKeyCredentialSource, PublicKeyCredentialType,
|
||||
PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
||||
};
|
||||
use self::hid::ChannelID;
|
||||
use self::pin_protocol_v1::PinProtocolV1;
|
||||
use self::response::{
|
||||
AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse,
|
||||
AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData,
|
||||
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
||||
AuthenticatorMakeCredentialResponse, ResponseData,
|
||||
};
|
||||
use self::status_code::Ctap2StatusCode;
|
||||
use self::storage::PersistentStore;
|
||||
@@ -50,18 +51,16 @@ use alloc::collections::BTreeMap;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use core::convert::TryInto;
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
use core::fmt::Write;
|
||||
use crypto::cbc::{cbc_decrypt, cbc_encrypt};
|
||||
use crypto::hmac::{hmac_256, verify_hmac_256, verify_hmac_256_first_128bits};
|
||||
use crypto::hmac::{hmac_256, verify_hmac_256};
|
||||
use crypto::rng256::Rng256;
|
||||
use crypto::sha256::Sha256;
|
||||
use crypto::Hash256;
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
use libtock::console::Console;
|
||||
use libtock::timer::{Duration, Timestamp};
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by
|
||||
// this setting. The basic attestation uses the signing key from key_material.rs
|
||||
@@ -75,10 +74,6 @@ const USE_BATCH_ATTESTATION: bool = false;
|
||||
// need a flash storage friendly way to implement this feature. The implemented
|
||||
// solution is a compromise to be compatible with U2F and not wasting storage.
|
||||
const USE_SIGNATURE_COUNTER: bool = true;
|
||||
// Those constants have to be multiples of 16, the AES block size.
|
||||
const PIN_AUTH_LENGTH: usize = 16;
|
||||
const PIN_TOKEN_LENGTH: usize = 32;
|
||||
const PIN_PADDED_LENGTH: usize = 64;
|
||||
// Our credential ID consists of
|
||||
// - 16 byte initialization vector for AES-256,
|
||||
// - 32 byte ECDSA private key for the credential,
|
||||
@@ -114,74 +109,6 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
|
||||
// - Some(CredentialProtectionPolicy::UserVerificationRequired)
|
||||
const DEFAULT_CRED_PROTECT: Option<CredentialProtectionPolicy> = None;
|
||||
|
||||
fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool {
|
||||
if pin_auth.len() != PIN_AUTH_LENGTH {
|
||||
return false;
|
||||
}
|
||||
verify_hmac_256_first_128bits::<Sha256>(
|
||||
hmac_key,
|
||||
hmac_contents,
|
||||
array_ref![pin_auth, 0, PIN_AUTH_LENGTH],
|
||||
)
|
||||
}
|
||||
|
||||
// Decrypts the HMAC secret salt(s) that were encrypted with the shared secret.
|
||||
// The credRandom is used as a secret to HMAC those salts.
|
||||
// The last step is to re-encrypt the outputs.
|
||||
pub fn encrypt_hmac_secret_output(
|
||||
shared_secret: &[u8; 32],
|
||||
salt_enc: &[u8],
|
||||
cred_random: &[u8],
|
||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||
if salt_enc.len() != 32 && salt_enc.len() != 64 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
if cred_random.len() != 32 {
|
||||
// We are strict here. We need at least 32 byte, but expect exactly 32.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||
// The specification specifically asks for a zero IV.
|
||||
let iv = [0; 16];
|
||||
|
||||
let mut cred_random_secret = [0; 32];
|
||||
cred_random_secret.clone_from_slice(cred_random);
|
||||
|
||||
// Initialization of 4 blocks in any case makes this function more readable.
|
||||
let mut blocks = [[0u8; 16]; 4];
|
||||
let block_len = salt_enc.len() / 16;
|
||||
for i in 0..block_len {
|
||||
blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
cbc_decrypt(&aes_dec_key, iv, &mut blocks[..block_len]);
|
||||
|
||||
let mut decrypted_salt1 = [0; 32];
|
||||
decrypted_salt1[..16].clone_from_slice(&blocks[0]);
|
||||
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
|
||||
decrypted_salt1[16..].clone_from_slice(&blocks[1]);
|
||||
for i in 0..2 {
|
||||
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
|
||||
if block_len == 4 {
|
||||
let mut decrypted_salt2 = [0; 32];
|
||||
decrypted_salt2[..16].clone_from_slice(&blocks[2]);
|
||||
decrypted_salt2[16..].clone_from_slice(&blocks[3]);
|
||||
let output2 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt2[..]);
|
||||
for i in 0..2 {
|
||||
blocks[i + 2].copy_from_slice(&output2[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
}
|
||||
|
||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks[..block_len]);
|
||||
let mut encrypted_output = Vec::with_capacity(salt_enc.len());
|
||||
for b in &blocks[..block_len] {
|
||||
encrypted_output.extend(b);
|
||||
}
|
||||
Ok(encrypted_output)
|
||||
}
|
||||
|
||||
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
|
||||
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
|
||||
// We change the return value, since we don't need the bool.
|
||||
@@ -205,9 +132,7 @@ pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(
|
||||
// false otherwise.
|
||||
check_user_presence: CheckUserPresence,
|
||||
persistent_store: PersistentStore,
|
||||
key_agreement_key: crypto::ecdh::SecKey,
|
||||
pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH],
|
||||
consecutive_pin_mismatches: u64,
|
||||
pin_protocol_v1: PinProtocolV1,
|
||||
// This variable will be irreversibly set to false RESET_TIMEOUT_MS milliseconds after boot.
|
||||
accepts_reset: bool,
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
@@ -225,16 +150,13 @@ where
|
||||
rng: &'a mut R,
|
||||
check_user_presence: CheckUserPresence,
|
||||
) -> CtapState<'a, R, CheckUserPresence> {
|
||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(rng);
|
||||
let pin_uv_auth_token = rng.gen_uniform_u8x32();
|
||||
let persistent_store = PersistentStore::new(rng);
|
||||
let pin_protocol_v1 = PinProtocolV1::new(rng);
|
||||
CtapState {
|
||||
rng,
|
||||
check_user_presence,
|
||||
persistent_store,
|
||||
key_agreement_key,
|
||||
pin_uv_auth_token,
|
||||
consecutive_pin_mismatches: 0,
|
||||
pin_protocol_v1,
|
||||
accepts_reset: true,
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
u2f_up_state: U2fUserPresenceState::new(
|
||||
@@ -485,7 +407,10 @@ where
|
||||
// Specification is unclear, could be CTAP2_ERR_INVALID_OPTION.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
||||
}
|
||||
if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) {
|
||||
if !self
|
||||
.pin_protocol_v1
|
||||
.check_pin_auth_token(&client_data_hash, &pin_auth)
|
||||
{
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
||||
@@ -660,7 +585,10 @@ where
|
||||
// Specification is unclear, could be CTAP2_ERR_UNSUPPORTED_OPTION.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
||||
}
|
||||
if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) {
|
||||
if !self
|
||||
.pin_protocol_v1
|
||||
.check_pin_auth_token(&client_data_hash, &pin_auth)
|
||||
{
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
UV_FLAG
|
||||
@@ -724,25 +652,9 @@ where
|
||||
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
|
||||
// Process extensions.
|
||||
if let Some(hmac_secret_input) = hmac_secret_input {
|
||||
let GetAssertionHmacSecretInput {
|
||||
key_agreement,
|
||||
salt_enc,
|
||||
salt_auth,
|
||||
} = hmac_secret_input;
|
||||
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
||||
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
|
||||
// HMAC-secret does the same 16 byte truncated check.
|
||||
if !check_pin_auth(&shared_secret, &salt_enc, &salt_auth) {
|
||||
// Again, hard to tell what the correct error code here is.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
|
||||
let encrypted_output = match &credential.cred_random {
|
||||
Some(cr) => encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cr)?,
|
||||
// This is the case if the credential was not created with HMAC-secret.
|
||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
@@ -823,323 +735,12 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
fn check_and_store_new_pin(
|
||||
&mut self,
|
||||
aes_dec_key: &crypto::aes256::DecryptionKey,
|
||||
new_pin_enc: Vec<u8>,
|
||||
) -> bool {
|
||||
if new_pin_enc.len() != PIN_PADDED_LENGTH {
|
||||
return false;
|
||||
}
|
||||
let iv = [0; 16];
|
||||
// Assuming PIN_PADDED_LENGTH % block_size == 0 here.
|
||||
let mut blocks = [[0u8; 16]; PIN_PADDED_LENGTH / 16];
|
||||
for i in 0..PIN_PADDED_LENGTH / 16 {
|
||||
blocks[i].copy_from_slice(&new_pin_enc[i * 16..(i + 1) * 16]);
|
||||
}
|
||||
cbc_decrypt(aes_dec_key, iv, &mut blocks);
|
||||
let mut pin = vec![];
|
||||
'pin_block_loop: for block in blocks.iter().take(PIN_PADDED_LENGTH / 16) {
|
||||
for cur_char in block.iter() {
|
||||
if *cur_char != 0 {
|
||||
pin.push(*cur_char);
|
||||
} else {
|
||||
break 'pin_block_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
if pin.len() < 4 || pin.len() == PIN_PADDED_LENGTH {
|
||||
// TODO(kaczmarczyck) check 4 code point minimum instead
|
||||
return false;
|
||||
}
|
||||
let mut pin_hash = [0; 16];
|
||||
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
|
||||
self.persistent_store.set_pin_hash(&pin_hash);
|
||||
true
|
||||
}
|
||||
|
||||
fn check_pin_hash_enc(
|
||||
&mut self,
|
||||
aes_dec_key: &crypto::aes256::DecryptionKey,
|
||||
pin_hash_enc: Vec<u8>,
|
||||
) -> Result<(), Ctap2StatusCode> {
|
||||
match self.persistent_store.pin_hash() {
|
||||
Some(pin_hash) => {
|
||||
if self.consecutive_pin_mismatches >= 3 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||
}
|
||||
// We need to copy the pin hash, because decrementing the pin retries below may
|
||||
// invalidate the reference (if the page containing the pin hash is compacted).
|
||||
let pin_hash = pin_hash.to_vec();
|
||||
self.persistent_store.decr_pin_retries();
|
||||
if pin_hash_enc.len() != PIN_AUTH_LENGTH {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
|
||||
let iv = [0; 16];
|
||||
let mut blocks = [[0u8; 16]; 1];
|
||||
blocks[0].copy_from_slice(&pin_hash_enc[0..PIN_AUTH_LENGTH]);
|
||||
cbc_decrypt(aes_dec_key, iv, &mut blocks);
|
||||
|
||||
let pin_comparison = array_ref![pin_hash, 0, PIN_AUTH_LENGTH].ct_eq(&blocks[0]);
|
||||
if !bool::from(pin_comparison) {
|
||||
self.key_agreement_key = crypto::ecdh::SecKey::gensk(self.rng);
|
||||
if self.persistent_store.pin_retries() == 0 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
self.consecutive_pin_mismatches += 1;
|
||||
if self.consecutive_pin_mismatches >= 3 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||
}
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
}
|
||||
// This status code is not explicitly mentioned in the specification.
|
||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED),
|
||||
}
|
||||
self.persistent_store.reset_pin_retries();
|
||||
self.consecutive_pin_mismatches = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_get_pin_retries(&self) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||
Ok(AuthenticatorClientPinResponse {
|
||||
key_agreement: None,
|
||||
pin_token: None,
|
||||
retries: Some(self.persistent_store.pin_retries() as u64),
|
||||
})
|
||||
}
|
||||
|
||||
fn process_get_key_agreement(&self) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||
let pk = self.key_agreement_key.genpk();
|
||||
Ok(AuthenticatorClientPinResponse {
|
||||
key_agreement: Some(CoseKey::from(pk)),
|
||||
pin_token: None,
|
||||
retries: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn process_set_pin(
|
||||
&mut self,
|
||||
key_agreement: CoseKey,
|
||||
pin_auth: Vec<u8>,
|
||||
new_pin_enc: Vec<u8>,
|
||||
) -> Result<(), Ctap2StatusCode> {
|
||||
if self.persistent_store.pin_hash().is_some() {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
||||
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
|
||||
|
||||
if !check_pin_auth(&shared_secret, &new_pin_enc, &pin_auth) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret);
|
||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||
if !self.check_and_store_new_pin(&aes_dec_key, new_pin_enc) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
self.persistent_store.reset_pin_retries();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_change_pin(
|
||||
&mut self,
|
||||
key_agreement: CoseKey,
|
||||
pin_auth: Vec<u8>,
|
||||
new_pin_enc: Vec<u8>,
|
||||
pin_hash_enc: Vec<u8>,
|
||||
) -> Result<(), Ctap2StatusCode> {
|
||||
if self.persistent_store.pin_retries() == 0 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
||||
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
|
||||
|
||||
let mut auth_param_data = new_pin_enc.clone();
|
||||
auth_param_data.extend(&pin_hash_enc);
|
||||
if !check_pin_auth(&shared_secret, &auth_param_data, &pin_auth) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret);
|
||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||
self.check_pin_hash_enc(&aes_dec_key, pin_hash_enc)?;
|
||||
|
||||
if !self.check_and_store_new_pin(&aes_dec_key, new_pin_enc) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
self.pin_uv_auth_token = self.rng.gen_uniform_u8x32();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_get_pin_token(
|
||||
&mut self,
|
||||
key_agreement: CoseKey,
|
||||
pin_hash_enc: Vec<u8>,
|
||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||
if self.persistent_store.pin_retries() == 0 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
||||
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
|
||||
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret);
|
||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||
self.check_pin_hash_enc(&aes_dec_key, pin_hash_enc)?;
|
||||
|
||||
// Assuming PIN_TOKEN_LENGTH % block_size == 0 here.
|
||||
let iv = [0; 16];
|
||||
let mut blocks = [[0u8; 16]; PIN_TOKEN_LENGTH / 16];
|
||||
for (i, item) in blocks.iter_mut().take(PIN_TOKEN_LENGTH / 16).enumerate() {
|
||||
item.copy_from_slice(&self.pin_uv_auth_token[i * 16..(i + 1) * 16]);
|
||||
}
|
||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
||||
let mut pin_token = vec![];
|
||||
for item in blocks.iter().take(PIN_TOKEN_LENGTH / 16) {
|
||||
pin_token.extend(item);
|
||||
}
|
||||
|
||||
Ok(AuthenticatorClientPinResponse {
|
||||
key_agreement: None,
|
||||
pin_token: Some(pin_token),
|
||||
retries: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
fn process_get_pin_uv_auth_token_using_uv_with_permissions(
|
||||
&self,
|
||||
_: CoseKey,
|
||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||
Ok(AuthenticatorClientPinResponse {
|
||||
// User verifications is only supported through PIN currently.
|
||||
key_agreement: None,
|
||||
pin_token: Some(vec![]),
|
||||
retries: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
fn process_get_uv_retries(&self) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||
// User verifications is only supported through PIN currently.
|
||||
Ok(AuthenticatorClientPinResponse {
|
||||
key_agreement: None,
|
||||
pin_token: None,
|
||||
retries: Some(0),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
fn process_set_min_pin_length(
|
||||
&mut self,
|
||||
_min_pin_length: u64,
|
||||
_min_pin_length_rp_ids: Vec<String>,
|
||||
_pin_auth: Vec<u8>,
|
||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||
// TODO
|
||||
Ok(AuthenticatorClientPinResponse {
|
||||
key_agreement: None,
|
||||
pin_token: None,
|
||||
retries: Some(0),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
fn process_get_pin_uv_auth_token_using_pin_with_permissions(
|
||||
&mut self,
|
||||
_key_agreement: CoseKey,
|
||||
_pin_hash_enc: Vec<u8>,
|
||||
_permissions: u8,
|
||||
_permissions_rp_id: String,
|
||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||
// TODO
|
||||
Ok(AuthenticatorClientPinResponse {
|
||||
key_agreement: None,
|
||||
pin_token: None,
|
||||
retries: Some(0),
|
||||
})
|
||||
}
|
||||
|
||||
fn process_client_pin(
|
||||
&mut self,
|
||||
client_pin_params: AuthenticatorClientPinParameters,
|
||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||
let AuthenticatorClientPinParameters {
|
||||
pin_protocol,
|
||||
sub_command,
|
||||
key_agreement,
|
||||
pin_auth,
|
||||
new_pin_enc,
|
||||
pin_hash_enc,
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
min_pin_length,
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
min_pin_length_rp_ids,
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
permissions,
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
permissions_rp_id,
|
||||
} = client_pin_params;
|
||||
|
||||
if pin_protocol != 1 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
let response = match sub_command {
|
||||
ClientPinSubCommand::GetPinRetries => Some(self.process_get_pin_retries()?),
|
||||
ClientPinSubCommand::GetKeyAgreement => Some(self.process_get_key_agreement()?),
|
||||
ClientPinSubCommand::SetPin => {
|
||||
self.process_set_pin(
|
||||
key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
new_pin_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
)?;
|
||||
None
|
||||
}
|
||||
ClientPinSubCommand::ChangePin => {
|
||||
self.process_change_pin(
|
||||
key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
new_pin_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
pin_hash_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
)?;
|
||||
None
|
||||
}
|
||||
ClientPinSubCommand::GetPinToken => Some(self.process_get_pin_token(
|
||||
key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
pin_hash_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
)?),
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some(
|
||||
self.process_get_pin_uv_auth_token_using_uv_with_permissions(
|
||||
key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
)?,
|
||||
),
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
ClientPinSubCommand::GetUvRetries => Some(self.process_get_uv_retries()?),
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
ClientPinSubCommand::SetMinPinLength => {
|
||||
self.process_set_min_pin_length(
|
||||
min_pin_length.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
min_pin_length_rp_ids.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
)?;
|
||||
None
|
||||
}
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => {
|
||||
self.process_get_pin_uv_auth_token_using_pin_with_permissions(
|
||||
key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
pin_hash_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
permissions.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
permissions_rp_id.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||
)?;
|
||||
None
|
||||
}
|
||||
};
|
||||
Ok(ResponseData::AuthenticatorClientPin(response))
|
||||
self.pin_protocol_v1
|
||||
.process(self.rng, &mut self.persistent_store, client_pin_params)
|
||||
}
|
||||
|
||||
fn process_reset(&mut self, cid: ChannelID) -> Result<ResponseData, Ctap2StatusCode> {
|
||||
@@ -1150,9 +751,7 @@ where
|
||||
(self.check_user_presence)(cid)?;
|
||||
|
||||
self.persistent_store.reset(self.rng);
|
||||
self.key_agreement_key = crypto::ecdh::SecKey::gensk(self.rng);
|
||||
self.pin_uv_auth_token = self.rng.gen_uniform_u8x32();
|
||||
self.consecutive_pin_mismatches = 0;
|
||||
self.pin_protocol_v1.reset(self.rng);
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
{
|
||||
self.u2f_up_state = U2fUserPresenceState::new(
|
||||
@@ -1192,8 +791,9 @@ where
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::data_formats::{
|
||||
GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
||||
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||
CoseKey, GetAssertionExtensions, GetAssertionHmacSecretInput, GetAssertionOptions,
|
||||
MakeCredentialExtensions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
|
||||
PublicKeyCredentialUserEntity,
|
||||
};
|
||||
use super::*;
|
||||
use crypto::rng256::ThreadRng256;
|
||||
@@ -1829,32 +1429,4 @@ mod test {
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_hmac_secret_output() {
|
||||
let shared_secret = [0x55; 32];
|
||||
let salt_enc = [0x5E; 32];
|
||||
let cred_random = [0xC9; 32];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(output.unwrap().len(), 32);
|
||||
|
||||
let salt_enc = [0x5E; 48];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(
|
||||
output,
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||
);
|
||||
|
||||
let salt_enc = [0x5E; 64];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(output.unwrap().len(), 64);
|
||||
|
||||
let salt_enc = [0x5E; 32];
|
||||
let cred_random = [0xC9; 33];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(
|
||||
output,
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user