Skip to content

Commit

Permalink
Merge pull request #32 from tarcieri/factor-responses-into-apdu-module
Browse files Browse the repository at this point in the history
Factor `Response` into `apdu` module; improved debugging
  • Loading branch information
tarcieri authored Nov 26, 2019
2 parents 5fab09e + d3af2f2 commit 3fa5555
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 3fa5555

Please sign in to comment.