Skip to content

Commit

Permalink
Factor Response into apdu module; improved debugging
Browse files Browse the repository at this point in the history
This commit merges the `apdu` and `response` modules: the responses are
APDU responses, and so the two are related.

This also moves the `trace` logging into the APDU type, which allows it
to display `Debug` output for APDUs and responses, which makes it easier
to understand what's going on (and will be even better once instructions
are converted into an enum so you can actually see what's happening).
  • Loading branch information
tarcieri committed Nov 26, 2019
1 parent 5fab09e commit d3af2f2
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 229 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
args: --all-features -- -D warnings

# TODO: use actions-rs/audit-check
security_audit:
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,17 @@ To trace every message sent to/from the card i.e. the raw
Application Protocol Data Unit (APDU) messages, use the `trace` log level:

```
running 1 test
[INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
[INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: 164, p1: 4, p2: 0, lc: 5, data: [160, 0, 0, 3, 8] }
[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[TRACE yubikey_piv::transaction] <<< [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8, 144, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: 253, p1: 0, p2: 0, lc: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0]
[TRACE yubikey_piv::transaction] <<< [5, 1, 2, 144, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [5, 1, 2] }
[TRACE yubikey_piv::apdu] >>> APDU { cla: 0, ins: 248, p1: 0, p2: 0, lc: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey_piv::transaction] <<< [0, 115, 0, 178, 144, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] }
test connect ... ok
```

Expand Down
170 changes: 166 additions & 4 deletions src/apdu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use crate::{error::Error, response::Response, transaction::Transaction, Buffer};
use crate::{error::Error, transaction::Transaction, Buffer};
use log::trace;
use std::fmt::{self, Debug};
use zeroize::{Zeroize, Zeroizing};

Expand Down Expand Up @@ -112,11 +113,13 @@ impl APDU {

/// Transmit this APDU using the given card transaction
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response, Error> {
let response_bytes = txn.transmit(&self.to_bytes(), recv_len)?;
Ok(Response::from_bytes(response_bytes))
trace!(">>> {:?}", self);
let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?);
trace!("<<< {:?}", &response);
Ok(response)
}

/// Consume this APDU and return a self-zeroizing buffer
/// Serialize this APDU as a self-zeroizing byte buffer
pub fn to_bytes(&self) -> Buffer {
let mut bytes = Vec::with_capacity(5 + self.data.len());
bytes.push(self.cla);
Expand Down Expand Up @@ -159,3 +162,162 @@ impl Zeroize for APDU {
self.data.zeroize();
}
}

/// APDU responses
#[derive(Debug)]
pub(crate) struct Response {
/// Status words
status_words: StatusWords,

/// Buffer
data: Vec<u8>,
}

impl Response {
/// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data }
}

/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}

/// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u32 {
self.status_words.code()
}

/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}

/// Borrow the response data
pub fn data(&self) -> &[u8] {
self.data.as_ref()
}
}

impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.data()
}
}

impl Drop for Response {
fn drop(&mut self) {
self.zeroize();
}
}

impl From<Vec<u8>> for Response {
fn from(mut bytes: Vec<u8>) -> Self {
if bytes.len() < 2 {
return Response {
status_words: StatusWords::None,
data: bytes,
};
}

let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u32) << 8 | (bytes[bytes.len() - 1] as u32),
);

let len = bytes.len() - 2;
bytes.truncate(len);

Response {
status_words: sw,
data: bytes,
}
}
}

impl Zeroize for Response {
fn zeroize(&mut self) {
self.data.zeroize();
}
}

/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,

/// Successful execution
Success,

/// Security status not satisfied
SecurityStatusError,

/// Authentication method blocked
AuthBlockedError,

/// Incorrect parameter in command data field
IncorrectParamError,

//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,

/// Not supported error
NotSupportedError,

/// Other/unrecognized status words
Other(u32),
}

impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u32 {
match self {
StatusWords::None => 0,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}

/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}

impl From<u32> for StatusWords {
fn from(sw: u32) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}

impl From<StatusWords> for u32 {
fn from(sw: StatusWords) -> u32 {
sw.code()
}
}
6 changes: 3 additions & 3 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use crate::{
apdu::StatusWords,
certificate::{self, Certificate},
consts::*,
error::Error,
response::StatusWords,
serialization::*,
settings,
yubikey::YubiKey,
AlgorithmId, ObjectId,
AlgorithmId, Buffer, ObjectId,
};
use log::{debug, error, warn};

Expand Down Expand Up @@ -309,7 +309,7 @@ pub fn generate(
}
}

let data = response.into_buffer();
let data = Buffer::new(response.data().into());

match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ mod metadata;
pub mod mgm;
#[cfg(feature = "untested")]
pub mod msroots;
mod response;
#[cfg(feature = "untested")]
mod serialization;
#[cfg(feature = "untested")]
Expand Down
Loading

0 comments on commit d3af2f2

Please sign in to comment.