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.
#[cfg(feature = "untested")]
pub fn code(&self) -> u32 {
pub fn code(&self) -> u16 {
self.status_words.code()
}
@@ -317,7 +317,7 @@ impl From<Vec<u8>> for Response {
}
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;
@@ -352,15 +352,42 @@ pub(crate) enum StatusWords {
/// Successful execution
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
SecurityStatusError,
/// Authentication method blocked
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
IncorrectParamError,
/// Data object or application not found
NotFoundError,
/// Not enough memory
NoSpaceError,
//
// Custom Yubico Status Word extensions
//
@@ -370,20 +397,32 @@ pub(crate) enum StatusWords {
/// Not supported error
NotSupportedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L65
CommandAbortedError,
/// Other/unrecognized status words
Other(u32),
Other(u16),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u32 {
pub fn code(self) -> u16 {
match self {
StatusWords::None => 0,
StatusWords::NoInputDataError => 0x6285,
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
StatusWords::WrongLengthError => 0x6700,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::DataInvalidError => 0x6984,
StatusWords::ConditionsNotSatisfiedError => 0x6985,
StatusWords::CommandNotAllowedError => 0x6986,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
@@ -395,23 +434,34 @@ impl StatusWords {
}
}
impl From<u32> for StatusWords {
fn from(sw: u32) -> Self {
impl From<u16> for StatusWords {
fn from(sw: u16) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6285 => StatusWords::NoInputDataError,
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
tries: (sw & 0x000f) as u8,
},
0x6700 => StatusWords::WrongLengthError,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6984 => StatusWords::DataInvalidError,
0x6985 => StatusWords::ConditionsNotSatisfiedError,
0x6986 => StatusWords::CommandNotAllowedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u32 {
fn from(sw: StatusWords) -> u32 {
impl From<StatusWords> for u16 {
fn from(sw: StatusWords) -> u16 {
sw.code()
}
}
+1 -1
View File
@@ -68,7 +68,7 @@ pub enum Error {
/// Wrong PIN
WrongPin {
/// Number of tries remaining
tries: u32,
tries: u8,
},
/// Invalid object
+16 -8
View File
@@ -163,20 +163,28 @@ impl<'tx> Transaction<'tx> {
/// Verify device PIN.
#[cfg(feature = "untested")]
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);
}
let response = APDU::new(Ins::Verify)
.params(0x00, 0x80)
.data(pin)
.transmit(self, 261)?;
let mut query = APDU::new(Ins::Verify);
query.params(0x00, 0x80);
// 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() {
StatusWords::Success => Ok(()),
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),
}
}
@@ -207,7 +215,7 @@ impl<'tx> Transaction<'tx> {
match status_words {
StatusWords::Success => Ok(()),
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!(
"failed changing pin, token response code: {:x}.",
+3 -12
View File
@@ -396,7 +396,7 @@ impl YubiKey {
/// Get the number of PIN retries
#[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()?;
// 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
#[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
if pin_tries == 0 || puk_tries == 0 {
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 templ = [
0,
Ins::SetPinRetries.code(),
pin_tries as u8,
puk_tries as u8,
];
let templ = [0, Ins::SetPinRetries.code(), pin_tries, puk_tries];
let status_words = txn.transfer_data(&templ, &[], 255)?.status_words();