Fix StatusWords::code output for StatusWords::VerifyFailError (#479)
* Fix `StatusWords::code` output for `StatusWords::VerifyFailError` Closes iqlusioninc/yubikey.rs#473. * Refactor `Transaction::transfer_data` to match on `StatusWords` This makes the code more reliable, such that it would have avoided the bug in iqlusioninc/yubikey.rs#473.
This commit is contained in:
@@ -19,6 +19,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
calling `YubiKey::disconnect(pcsc::Disposition::LeaveCard)` if `Reader::open`
|
calling `YubiKey::disconnect(pcsc::Disposition::LeaveCard)` if `Reader::open`
|
||||||
succeeds).
|
succeeds).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `StatusWords::code` now returns the correct code (including embedded `tries`
|
||||||
|
count) for `StatusWords::VerifyFailError`. Previously the returned code lost
|
||||||
|
information and was not round-trip compatible with `StatusWords::from(u16)`.
|
||||||
|
|
||||||
## 0.7.0 (2022-11-14)
|
## 0.7.0 (2022-11-14)
|
||||||
### Added
|
### Added
|
||||||
- Display inner PC/SC errors ([#420])
|
- Display inner PC/SC errors ([#420])
|
||||||
|
|||||||
+48
-1
@@ -353,6 +353,12 @@ pub(crate) enum StatusWords {
|
|||||||
/// Successful execution
|
/// Successful execution
|
||||||
Success,
|
Success,
|
||||||
|
|
||||||
|
/// The requested data was too large for the response, and there is data remaining.
|
||||||
|
BytesRemaining {
|
||||||
|
/// The number of bytes remaining, as indicated in the response.
|
||||||
|
len: u8,
|
||||||
|
},
|
||||||
|
|
||||||
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L53
|
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L53
|
||||||
NoInputDataError,
|
NoInputDataError,
|
||||||
|
|
||||||
@@ -410,8 +416,9 @@ impl StatusWords {
|
|||||||
pub fn code(self) -> u16 {
|
pub fn code(self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
StatusWords::None => 0,
|
StatusWords::None => 0,
|
||||||
|
StatusWords::BytesRemaining { len } => 0x6100 | len as u16,
|
||||||
StatusWords::NoInputDataError => 0x6285,
|
StatusWords::NoInputDataError => 0x6285,
|
||||||
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
|
StatusWords::VerifyFailError { tries } => 0x63c0 | tries as u16,
|
||||||
StatusWords::WrongLengthError => 0x6700,
|
StatusWords::WrongLengthError => 0x6700,
|
||||||
StatusWords::SecurityStatusError => 0x6982,
|
StatusWords::SecurityStatusError => 0x6982,
|
||||||
StatusWords::AuthBlockedError => 0x6983,
|
StatusWords::AuthBlockedError => 0x6983,
|
||||||
@@ -439,6 +446,9 @@ impl From<u16> for StatusWords {
|
|||||||
fn from(sw: u16) -> Self {
|
fn from(sw: u16) -> Self {
|
||||||
match sw {
|
match sw {
|
||||||
0x0000 => StatusWords::None,
|
0x0000 => StatusWords::None,
|
||||||
|
sw if sw & 0xff00 == 0x6100 => Self::BytesRemaining {
|
||||||
|
len: (sw & 0x00ff) as u8,
|
||||||
|
},
|
||||||
0x6285 => StatusWords::NoInputDataError,
|
0x6285 => StatusWords::NoInputDataError,
|
||||||
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
|
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
|
||||||
tries: (sw & 0x000f) as u8,
|
tries: (sw & 0x000f) as u8,
|
||||||
@@ -466,3 +476,40 @@ impl From<StatusWords> for u16 {
|
|||||||
sw.code()
|
sw.code()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::StatusWords;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn status_words_round_trip() {
|
||||||
|
let round_trip = |sw: StatusWords| {
|
||||||
|
assert_eq!(StatusWords::from(sw.code()), sw);
|
||||||
|
};
|
||||||
|
|
||||||
|
round_trip(StatusWords::None);
|
||||||
|
round_trip(StatusWords::BytesRemaining { len: 1 });
|
||||||
|
round_trip(StatusWords::BytesRemaining { len: 10 });
|
||||||
|
round_trip(StatusWords::BytesRemaining { len: 0xFF });
|
||||||
|
round_trip(StatusWords::Success);
|
||||||
|
round_trip(StatusWords::NoInputDataError);
|
||||||
|
round_trip(StatusWords::VerifyFailError { tries: 0x0F });
|
||||||
|
round_trip(StatusWords::VerifyFailError { tries: 3 });
|
||||||
|
round_trip(StatusWords::VerifyFailError { tries: 2 });
|
||||||
|
round_trip(StatusWords::VerifyFailError { tries: 1 });
|
||||||
|
round_trip(StatusWords::VerifyFailError { tries: 0 });
|
||||||
|
round_trip(StatusWords::WrongLengthError);
|
||||||
|
round_trip(StatusWords::SecurityStatusError);
|
||||||
|
round_trip(StatusWords::AuthBlockedError);
|
||||||
|
round_trip(StatusWords::DataInvalidError);
|
||||||
|
round_trip(StatusWords::ConditionsNotSatisfiedError);
|
||||||
|
round_trip(StatusWords::CommandNotAllowedError);
|
||||||
|
round_trip(StatusWords::IncorrectParamError);
|
||||||
|
round_trip(StatusWords::NotFoundError);
|
||||||
|
round_trip(StatusWords::NoSpaceError);
|
||||||
|
round_trip(StatusWords::IncorrectSlotError);
|
||||||
|
round_trip(StatusWords::NotSupportedError);
|
||||||
|
round_trip(StatusWords::CommandAbortedError);
|
||||||
|
round_trip(StatusWords::Other(0x1337));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+11
-12
@@ -400,11 +400,12 @@ impl<'tx> Transaction<'tx> {
|
|||||||
.data(&in_data[in_offset..(in_offset + this_size)])
|
.data(&in_data[in_offset..(in_offset + this_size)])
|
||||||
.transmit(self, 261)?;
|
.transmit(self, 261)?;
|
||||||
|
|
||||||
sw = response.status_words().code();
|
sw = response.status_words();
|
||||||
|
|
||||||
if !response.is_success() && (sw >> 8 != 0x61) {
|
match sw {
|
||||||
|
StatusWords::Success | StatusWords::BytesRemaining { .. } => (),
|
||||||
// TODO(tarcieri): is this really OK?
|
// TODO(tarcieri): is this really OK?
|
||||||
return Ok(Response::new(sw.into(), out_data));
|
_ => return Ok(Response::new(sw, out_data)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
|
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
|
||||||
@@ -425,17 +426,15 @@ impl<'tx> Transaction<'tx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while sw >> 8 == 0x61 {
|
while let StatusWords::BytesRemaining { len } = sw {
|
||||||
trace!(
|
trace!("The card indicates there is {} bytes more data for us", len);
|
||||||
"The card indicates there is {} bytes more data for us",
|
|
||||||
sw & 0xff
|
|
||||||
);
|
|
||||||
|
|
||||||
let response = Apdu::new(Ins::GetResponseApdu).transmit(self, 261)?;
|
let response = Apdu::new(Ins::GetResponseApdu).transmit(self, 261)?;
|
||||||
sw = response.status_words().code();
|
sw = response.status_words();
|
||||||
|
|
||||||
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
|
match sw {
|
||||||
return Ok(Response::new(sw.into(), vec![]));
|
StatusWords::Success | StatusWords::BytesRemaining { .. } => (),
|
||||||
|
_ => return Ok(Response::new(sw, vec![])),
|
||||||
}
|
}
|
||||||
|
|
||||||
if out_data.len() + response.data().len() > max_out {
|
if out_data.len() + response.data().len() > max_out {
|
||||||
@@ -451,7 +450,7 @@ impl<'tx> Transaction<'tx> {
|
|||||||
out_data.extend_from_slice(&response.data()[..response.data().len()]);
|
out_data.extend_from_slice(&response.data()[..response.data().len()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Response::new(sw.into(), out_data))
|
Ok(Response::new(sw, out_data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch an object.
|
/// Fetch an object.
|
||||||
|
|||||||
Reference in New Issue
Block a user