Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RESET RETRY COUNTER #63

Merged
merged 8 commits into from
Oct 17, 2022
96 changes: 92 additions & 4 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ mod pso;
use iso7816::Status;

use crate::card::{Context, LoadedContext, RID};
use crate::state::{LifeCycle, State, MAX_GENERIC_LENGTH};
use crate::error::Error;
use crate::state::{
LifeCycle, State, MAX_GENERIC_LENGTH, MAX_PIN_LENGTH, MIN_LENGTH_ADMIN_PIN,
MIN_LENGTH_RESET_CODE, MIN_LENGTH_USER_PIN,
};
use crate::tlv;
use crate::types::*;
use trussed::config::MAX_MESSAGE_LENGTH;
Expand Down Expand Up @@ -77,6 +81,7 @@ impl Command {
Self::ActivateFile => activate_file(context),
Self::SelectData(occurrence) => select_data(context, *occurrence),
Self::GetChallenge(length) => get_challenge(context, *length),
Self::ResetRetryCounter(mode) => reset_retry_conter(context.load_state()?, *mode),
_ => {
error!("Command not yet implemented: {:x?}", self);
Err(Status::FunctionNotSupported)
Expand Down Expand Up @@ -184,6 +189,7 @@ impl<const C: usize> TryFrom<&iso7816::Command<C>> for Command {
pub enum Password {
Pw1,
Pw3,
ResetCode,
}

impl From<PasswordMode> for Password {
Expand Down Expand Up @@ -245,7 +251,7 @@ impl TryFrom<u8> for VerifyMode {
}
}

#[derive(Debug, Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum ResetRetryCounterMode {
ResettingCode,
Verify,
Expand Down Expand Up @@ -378,11 +384,10 @@ fn change_reference_data<const R: usize, T: trussed::Client>(
context: LoadedContext<'_, R, T>,
password: Password,
) -> Result<(), Status> {
const MIN_LENGTH_ADMIN_PIN: usize = 8;
const MIN_LENGTH_USER_PIN: usize = 6;
let min_len = match password {
Password::Pw1 => MIN_LENGTH_USER_PIN,
Password::Pw3 => MIN_LENGTH_ADMIN_PIN,
Password::ResetCode => unreachable!(),
};

if context.data.len() < 2 * min_len {
Expand Down Expand Up @@ -504,6 +509,89 @@ fn activate_file<const R: usize, T: trussed::Client>(
Ok(())
}

// § 7.2.4
fn reset_retry_conter<const R: usize, T: trussed::Client>(
ctx: LoadedContext<'_, R, T>,
mode: ResetRetryCounterMode,
) -> Result<(), Status> {
match mode {
ResetRetryCounterMode::Verify => reset_retry_conter_with_p3(ctx),
ResetRetryCounterMode::ResettingCode => reset_retry_conter_with_code(ctx),
}
}

fn reset_retry_conter_with_p3<const R: usize, T: trussed::Client>(
ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
if ctx.data.len() < MIN_LENGTH_USER_PIN || ctx.data.len() > MAX_PIN_LENGTH {
warn!(
"Attempt to change PIN with incorrect lenght: {}",
ctx.data.len()
);
return Err(Status::IncorrectDataParameter);
}

if !ctx.state.runtime.admin_verified {
return Err(Status::SecurityStatusNotSatisfied);
}

ctx.state
.internal
.change_pin(ctx.backend.client_mut(), ctx.data, Password::Pw1)
.map_err(|_err| {
error!("Failed to change PIN: {_err}");
Status::UnspecifiedNonpersistentExecutionError
})
}

fn reset_retry_conter_with_code<const R: usize, T: trussed::Client>(
ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
let code_len = ctx.state.internal.reset_code_len().ok_or_else(|| {
warn!("Attempt to use reset when not set");
Status::SecurityStatusNotSatisfied
})?;
if ctx.data.len() < MIN_LENGTH_RESET_CODE + MIN_LENGTH_USER_PIN {
warn!("Attempt to reset with too small new pin");
return Err(Status::SecurityStatusNotSatisfied);
}
let (old, new) = if ctx.data.len() < code_len {
(ctx.data, [].as_slice())
} else {
ctx.data.split_at(code_len)
};

let res = ctx
.state
.internal
.verify_pin(ctx.backend.client_mut(), old, Password::ResetCode);
match res {
Err(Error::TooManyTries) | Err(Error::InvalidPin) => {
return Err(Status::RemainingRetries(
ctx.state.internal.remaining_tries(Password::ResetCode),
))
}
Err(_err) => {
error!("Failed to check reset code: {_err:?}");
return Err(Status::UnspecifiedNonpersistentExecutionError);
}
Ok(()) => {}
}

if new.len() > MAX_PIN_LENGTH || new.len() < MIN_LENGTH_USER_PIN {
warn!("Attempt to set resetting code with invalid length");
return Err(Status::IncorrectDataParameter);
}

ctx.state
.internal
.change_pin(ctx.backend.client_mut(), new, Password::Pw1)
robin-nitrokey marked this conversation as resolved.
Show resolved Hide resolved
.map_err(|_err| {
error!("Failed to change PIN: {_err:?}");
Status::UnspecifiedNonpersistentExecutionError
})
}

// § 7.2.5
fn select_data<const R: usize, T: trussed::Client>(
ctx: Context<'_, R, T>,
Expand Down
29 changes: 20 additions & 9 deletions src/command/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
command::{GetDataMode, Password, PutDataMode, Tag},
state::{
ArbitraryDO, KeyOrigin, PermissionRequirement, Sex, State, MAX_GENERIC_LENGTH,
MAX_GENERIC_LENGTH_BE, MAX_PIN_LENGTH,
MAX_GENERIC_LENGTH_BE, MAX_PIN_LENGTH, MIN_LENGTH_RESET_CODE,
},
types::*,
utils::InspectErr,
Expand Down Expand Up @@ -210,7 +210,7 @@ enum_u16! {
KeyInformation = 0x00DE,
SMkEnc = 0x00D1,
SMkMac = 0x00D2,
ResetingCode = 0x00D3,
ResettingCode = 0x00D3,
PSOEncDecKey = 0x00D5,
SMEncMac = 0x00F4,
UifCds = 0x00D6,
Expand Down Expand Up @@ -792,7 +792,7 @@ enum_subset! {
SignGenerationDate,
DecGenerationDate,
AuthGenerationDate,
ResetingCode,
ResettingCode,
PSOEncDecKey,
UifCds,
UifDec,
Expand Down Expand Up @@ -841,7 +841,7 @@ impl PutDataObject {
Self::CaFingerprint1 => put_ca_fingerprint(ctx.load_state()?, KeyType::Aut)?,
Self::CaFingerprint2 => put_ca_fingerprint(ctx.load_state()?, KeyType::Dec)?,
Self::CaFingerprint3 => put_ca_fingerprint(ctx.load_state()?, KeyType::Sign)?,
Self::ResetingCode => put_reseting_code(ctx.load_state()?)?,
Self::ResettingCode => put_resetting_code(ctx.load_state()?)?,
Self::PSOEncDecKey => put_enc_dec_key(ctx.load_state()?)?,
Self::UifCds => put_uif(ctx.load_state()?, KeyType::Sign)?,
Self::UifDec => put_uif(ctx.load_state()?, KeyType::Dec)?,
Expand Down Expand Up @@ -874,12 +874,23 @@ fn put_enc_dec_key<const R: usize, T: trussed::Client>(
Err(Status::FunctionNotSupported)
}

fn put_reseting_code<const R: usize, T: trussed::Client>(
_ctx: LoadedContext<'_, R, T>,
fn put_resetting_code<const R: usize, T: trussed::Client>(
ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
// TODO: implement
error!("Put data in even mode not yet implemented");
Err(Status::FunctionNotSupported)
if ctx.data.len() < MIN_LENGTH_RESET_CODE || ctx.data.len() > MAX_PIN_LENGTH {
warn!(
"Attempt to set invalid size of resetting code: {}",
ctx.data.len()
);
return Err(Status::IncorrectDataParameter);
}
ctx.state
.internal
.change_pin(ctx.backend.client_mut(), ctx.data, Password::ResetCode)
.map_err(|_err| {
error!("Failed to change resetting code: {_err}");
Status::UnspecifiedNonpersistentExecutionError
})
}

fn put_uif<const R: usize, T: trussed::Client>(
Expand Down
39 changes: 34 additions & 5 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ use crate::utils::serde_bytes;

/// Maximum supported length for PW1 and PW3
pub const MAX_PIN_LENGTH: usize = 127;
pub const MIN_LENGTH_RESET_CODE: usize = 8;
pub const MIN_LENGTH_ADMIN_PIN: usize = 8;
pub const MIN_LENGTH_USER_PIN: usize = 6;

/// Default value for PW1
pub const DEFAULT_USER_PIN: &[u8] = b"123456";
Expand Down Expand Up @@ -274,9 +277,11 @@ pub enum KeyOrigin {
pub struct Internal {
user_pin_tries: u8,
admin_pin_tries: u8,
reset_code_tries: u8,
pw1_valid_multiple: bool,
user_pin: Bytes<MAX_PIN_LENGTH>,
admin_pin: Bytes<MAX_PIN_LENGTH>,
reset_code_pin: Option<Bytes<MAX_PIN_LENGTH>>,
signing_key: Option<(KeyId, KeyOrigin)>,
confidentiality_key: Option<(KeyId, KeyOrigin)>,
aut_key: Option<(KeyId, KeyOrigin)>,
Expand Down Expand Up @@ -309,6 +314,8 @@ impl Internal {
Self {
user_pin_tries: 0,
admin_pin_tries: 0,
reset_code_tries: 0,
reset_code_pin: None,
pw1_valid_multiple: false,
admin_pin,
user_pin,
Expand Down Expand Up @@ -369,13 +376,15 @@ impl Internal {
match password {
Password::Pw1 => Self::MAX_RETRIES.saturating_sub(self.user_pin_tries),
Password::Pw3 => Self::MAX_RETRIES.saturating_sub(self.admin_pin_tries),
Password::ResetCode => Self::MAX_RETRIES.saturating_sub(self.reset_code_tries),
}
}

pub fn is_locked(&self, password: Password) -> bool {
match password {
Password::Pw1 => self.user_pin_tries >= Self::MAX_RETRIES,
Password::Pw3 => self.admin_pin_tries >= Self::MAX_RETRIES,
Password::ResetCode => self.reset_code_tries >= Self::MAX_RETRIES,
}
}

Expand All @@ -388,6 +397,7 @@ impl Internal {
match password {
Password::Pw1 => self.user_pin_tries += 1,
Password::Pw3 => self.admin_pin_tries += 1,
Password::ResetCode => self.reset_code_tries += 1,
}
self.save(client)
} else {
Expand All @@ -403,14 +413,16 @@ impl Internal {
match password {
Password::Pw1 => self.user_pin_tries = 0,
Password::Pw3 => self.admin_pin_tries = 0,
Password::ResetCode => self.reset_code_tries = 0,
}
self.save(client)
}

fn pin(&self, password: Password) -> &[u8] {
fn pin(&self, password: Password) -> Option<&[u8]> {
match password {
Password::Pw1 => &self.user_pin,
Password::Pw3 => &self.admin_pin,
Password::Pw1 => Some(&self.user_pin),
Password::Pw3 => Some(&self.admin_pin),
Password::ResetCode => self.reset_code_pin.as_ref().map(|d| &d[..]),
}
}

Expand All @@ -425,32 +437,49 @@ impl Internal {
}

self.decrement_counter(client, password)?;
if (!value.ct_eq(self.pin(password))).into() {
let pin = self.pin(password).ok_or(Error::BadRequest)?;
if (!value.ct_eq(pin)).into() {
return Err(Error::InvalidPin);
}

self.reset_counter(client, password)?;
Ok(())
}

/// Panics if password is ResetCode, use [reset_code_len](Self::reset_code_len) instead
pub fn pin_len(&self, password: Password) -> usize {
match password {
Password::Pw1 => self.user_pin.len(),
Password::Pw3 => self.admin_pin.len(),
Password::ResetCode => unreachable!(),
}
}

/// Returns None if no code has been set
pub fn reset_code_len(&self) -> Option<usize> {
self.reset_code_pin.as_ref().map(|d| d.len())
}

pub fn change_pin<T: trussed::Client>(
&mut self,
client: &mut T,
value: &[u8],
password: Password,
) -> Result<(), Error> {
let new_pin = Bytes::from_slice(value).map_err(|_| Error::RequestTooLarge)?;
let (pin, tries) = match password {
Password::Pw1 => (&mut self.user_pin, &mut self.user_pin_tries),
Password::Pw3 => (&mut self.admin_pin, &mut self.admin_pin_tries),
Password::ResetCode => {
self.reset_code_pin = Some(Default::default());
sosthene-nitrokey marked this conversation as resolved.
Show resolved Hide resolved
(
#[allow(clippy::unwrap_used)]
self.reset_code_pin.as_mut().unwrap(),
&mut self.reset_code_tries,
)
}
};
*pin = Bytes::from_slice(value).map_err(|_| Error::RequestTooLarge)?;
*pin = new_pin;
*tries = 0;
self.save(client)
}
Expand Down
Loading