Merge pull request #40 from str4d/pin-fixups

PIN fixups
This commit is contained in:
Tony Arcieri
2019-11-30 11:43:35 -08:00
committed by GitHub
4 changed files with 78 additions and 29 deletions
+58 -8
View File
@@ -280,7 +280,7 @@ impl Response {
/// Get the raw [`StatusWords`] code for this response. /// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn code(&self) -> u32 { pub fn code(&self) -> u16 {
self.status_words.code() self.status_words.code()
} }
@@ -317,7 +317,7 @@ impl From<Vec<u8>> for Response {
} }
let sw = StatusWords::from( let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u32) << 8 | (bytes[bytes.len() - 1] as u32), (bytes[bytes.len() - 2] as u16) << 8 | (bytes[bytes.len() - 1] as u16),
); );
let len = bytes.len() - 2; let len = bytes.len() - 2;
@@ -352,15 +352,42 @@ pub(crate) enum StatusWords {
/// Successful execution /// Successful execution
Success, Success,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L53
NoInputDataError,
/// PIN verification failure
VerifyFailError {
/// Remaining verification attempts
tries: u8,
},
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L55
WrongLengthError,
/// Security status not satisfied /// Security status not satisfied
SecurityStatusError, SecurityStatusError,
/// Authentication method blocked /// Authentication method blocked
AuthBlockedError, AuthBlockedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L58
DataInvalidError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L59
ConditionsNotSatisfiedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L60
CommandNotAllowedError,
/// Incorrect parameter in command data field /// Incorrect parameter in command data field
IncorrectParamError, IncorrectParamError,
/// Data object or application not found
NotFoundError,
/// Not enough memory
NoSpaceError,
// //
// Custom Yubico Status Word extensions // Custom Yubico Status Word extensions
// //
@@ -370,20 +397,32 @@ pub(crate) enum StatusWords {
/// Not supported error /// Not supported error
NotSupportedError, NotSupportedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L65
CommandAbortedError,
/// Other/unrecognized status words /// Other/unrecognized status words
Other(u32), Other(u16),
} }
impl StatusWords { impl StatusWords {
/// Get the numerical response code for these status words /// Get the numerical response code for these status words
pub fn code(self) -> u32 { pub fn code(self) -> u16 {
match self { match self {
StatusWords::None => 0, StatusWords::None => 0,
StatusWords::NoInputDataError => 0x6285,
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
StatusWords::WrongLengthError => 0x6700,
StatusWords::SecurityStatusError => 0x6982, StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983, StatusWords::AuthBlockedError => 0x6983,
StatusWords::DataInvalidError => 0x6984,
StatusWords::ConditionsNotSatisfiedError => 0x6985,
StatusWords::CommandNotAllowedError => 0x6986,
StatusWords::IncorrectParamError => 0x6a80, StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84,
StatusWords::IncorrectSlotError => 0x6b00, StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00, StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00,
StatusWords::Success => 0x9000, StatusWords::Success => 0x9000,
StatusWords::Other(n) => n, StatusWords::Other(n) => n,
} }
@@ -395,23 +434,34 @@ impl StatusWords {
} }
} }
impl From<u32> for StatusWords { impl From<u16> for StatusWords {
fn from(sw: u32) -> Self { fn from(sw: u16) -> Self {
match sw { match sw {
0x0000 => StatusWords::None, 0x0000 => StatusWords::None,
0x6285 => StatusWords::NoInputDataError,
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
tries: (sw & 0x000f) as u8,
},
0x6700 => StatusWords::WrongLengthError,
0x6982 => StatusWords::SecurityStatusError, 0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError, 0x6983 => StatusWords::AuthBlockedError,
0x6984 => StatusWords::DataInvalidError,
0x6985 => StatusWords::ConditionsNotSatisfiedError,
0x6986 => StatusWords::CommandNotAllowedError,
0x6a80 => StatusWords::IncorrectParamError, 0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError,
0x6b00 => StatusWords::IncorrectSlotError, 0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError, 0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError,
0x9000 => StatusWords::Success, 0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw), _ => StatusWords::Other(sw),
} }
} }
} }
impl From<StatusWords> for u32 { impl From<StatusWords> for u16 {
fn from(sw: StatusWords) -> u32 { fn from(sw: StatusWords) -> u16 {
sw.code() sw.code()
} }
} }
+1 -1
View File
@@ -68,7 +68,7 @@ pub enum Error {
/// Wrong PIN /// Wrong PIN
WrongPin { WrongPin {
/// Number of tries remaining /// Number of tries remaining
tries: u32, tries: u8,
}, },
/// Invalid object /// Invalid object
+16 -8
View File
@@ -163,20 +163,28 @@ impl<'tx> Transaction<'tx> {
/// Verify device PIN. /// Verify device PIN.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> { pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> {
// TODO(tarcieri): allow unpadded (with `0xFF`) PIN shorter than CB_PIN_MAX? if pin.len() > CB_PIN_MAX {
if pin.len() != CB_PIN_MAX {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
let response = APDU::new(Ins::Verify) let mut query = APDU::new(Ins::Verify);
.params(0x00, 0x80) query.params(0x00, 0x80);
.data(pin)
.transmit(self, 261)?; // Empty pin means we are querying the number of retries. We set no data in this
// case; if we instead sent [0xff; CB_PIN_MAX] it would count as an attempt and
// decrease the retry counter.
if !pin.is_empty() {
let mut data = Zeroizing::new([0xff; CB_PIN_MAX]);
data[0..pin.len()].copy_from_slice(pin);
query.data(data.as_ref());
}
let response = query.transmit(self, 261)?;
match response.status_words() { match response.status_words() {
StatusWords::Success => Ok(()), StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }), StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }), StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => Err(Error::GenericError), _ => Err(Error::GenericError),
} }
} }
@@ -207,7 +215,7 @@ impl<'tx> Transaction<'tx> {
match status_words { match status_words {
StatusWords::Success => Ok(()), StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::PinLocked), StatusWords::AuthBlockedError => Err(Error::PinLocked),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }), StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => { _ => {
error!( error!(
"failed changing pin, token response code: {:x}.", "failed changing pin, token response code: {:x}.",
+3 -12
View File
@@ -396,7 +396,7 @@ impl YubiKey {
/// Get the number of PIN retries /// Get the number of PIN retries
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn get_pin_retries(&mut self) -> Result<u32, Error> { pub fn get_pin_retries(&mut self) -> Result<u8, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
// Force a re-select to unverify, because once verified the spec dictates that // Force a re-select to unverify, because once verified the spec dictates that
@@ -414,24 +414,15 @@ impl YubiKey {
/// Set the number of PIN retries /// Set the number of PIN retries
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn set_pin_retries(&mut self, pin_tries: usize, puk_tries: usize) -> Result<(), Error> { pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<(), Error> {
// Special case: if either retry count is 0, it's a successful no-op // Special case: if either retry count is 0, it's a successful no-op
if pin_tries == 0 || puk_tries == 0 { if pin_tries == 0 || puk_tries == 0 {
return Ok(()); return Ok(());
} }
if pin_tries > 0xff || puk_tries > 0xff || pin_tries < 1 || puk_tries < 1 {
return Err(Error::RangeError);
}
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
let templ = [ let templ = [0, Ins::SetPinRetries.code(), pin_tries, puk_tries];
0,
Ins::SetPinRetries.code(),
pin_tries as u8,
puk_tries as u8,
];
let status_words = txn.transfer_data(&templ, &[], 255)?.status_words(); let status_words = txn.transfer_data(&templ, &[], 255)?.status_words();