+58
-8
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user