//! Application Protocol Data Unit (APDU) // Adapted from yubico-piv-tool: // // // Copyright (c) 2014-2016 Yubico AB // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (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, transaction::Transaction, Buffer}; use log::trace; use zeroize::{Zeroize, Zeroizing}; /// Maximum amount of command data that can be included in an APDU const APDU_DATA_MAX: usize = 0xFF; /// Application Protocol Data Unit (APDU). /// /// These messages are packets used to communicate with the YubiKey. #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct APDU { /// Instruction class: indicates the type of command (e.g. inter-industry or proprietary) cla: u8, /// Instruction code: indicates the specific command (e.g. "write data") ins: Ins, /// Instruction parameter 1 for the command (e.g. offset into file at which to write the data) p1: u8, /// Instruction parameter 2 for the command p2: u8, /// Command data to be sent (`lc` is calculated as `data.len()`) data: Vec, } impl APDU { /// Create a new APDU with the given instruction code pub fn new(ins: impl Into) -> Self { Self { cla: 0, ins: ins.into(), p1: 0, p2: 0, data: vec![], } } /// Set this APDU's class #[cfg(feature = "untested")] pub fn cla(&mut self, value: u8) -> &mut Self { self.cla = value; self } /// Set this APDU's first parameter only pub fn p1(&mut self, value: u8) -> &mut Self { self.p1 = value; self } /// Set both parameters for this APDU #[cfg(feature = "untested")] pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self { self.p1 = p1; self.p2 = p2; self } /// Set the command data for this APDU. /// /// Panics if the byte slice is more than 255 bytes! pub fn data(&mut self, bytes: impl AsRef<[u8]>) -> &mut Self { assert!(self.data.is_empty(), "APDU command already set!"); let bytes = bytes.as_ref(); assert!( bytes.len() <= APDU_DATA_MAX, "APDU command data too long: {} (max: {})", bytes.len(), APDU_DATA_MAX ); self.data.extend_from_slice(bytes); self } /// Transmit this APDU using the given card transaction pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result { trace!(">>> {:?}", self); let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?); trace!("<<< {:?}", &response); Ok(response) } /// 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); bytes.push(self.ins.code()); bytes.push(self.p1); bytes.push(self.p2); bytes.push(self.data.len() as u8); bytes.extend_from_slice(self.data.as_ref()); Zeroizing::new(bytes) } } impl Drop for APDU { fn drop(&mut self) { self.zeroize(); } } impl Zeroize for APDU { fn zeroize(&mut self) { // Only `data` may contain secrets self.data.zeroize(); } } /// APDU instruction codes #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Ins { /// Verify Verify, /// Change reference ChangeReference, /// Reset retry ResetRetry, /// Generate asymmetric GenerateAsymmetric, /// Authenticate Authenticate, /// Get data GetData, /// Put data PutData, /// Select application SelectApplication, /// Get response APDU GetResponseApdu, // Yubico vendor specific instructions // /// Set MGM key SetMgmKey, /// Import key ImportKey, /// Get version GetVersion, /// Reset device Reset, /// Set PIN retries SetPinRetries, /// Generate attestation certificate for asymmetric key Attest, /// Get device serial GetSerial, /// Other/unrecognized instruction codes Other(u8), } impl Ins { /// Get the code that corresponds to this instruction pub fn code(self) -> u8 { match self { Ins::Verify => 0x20, Ins::ChangeReference => 0x24, Ins::ResetRetry => 0x2c, Ins::GenerateAsymmetric => 0x47, Ins::Authenticate => 0x87, Ins::GetData => 0xcb, Ins::PutData => 0xdb, Ins::SelectApplication => 0xa4, Ins::GetResponseApdu => 0xc0, Ins::SetMgmKey => 0xff, Ins::ImportKey => 0xfe, Ins::GetVersion => 0xfd, Ins::Reset => 0xfb, Ins::SetPinRetries => 0xfa, Ins::Attest => 0xf9, Ins::GetSerial => 0xf8, Ins::Other(code) => code, } } } impl From for Ins { fn from(code: u8) -> Self { match code { 0x20 => Ins::Verify, 0x24 => Ins::ChangeReference, 0x2c => Ins::ResetRetry, 0x47 => Ins::GenerateAsymmetric, 0x87 => Ins::Authenticate, 0xcb => Ins::GetData, 0xdb => Ins::PutData, 0xa4 => Ins::SelectApplication, 0xc0 => Ins::GetResponseApdu, 0xff => Ins::SetMgmKey, 0xfe => Ins::ImportKey, 0xfd => Ins::GetVersion, 0xfb => Ins::Reset, 0xfa => Ins::SetPinRetries, 0xf9 => Ins::Attest, 0xf8 => Ins::GetSerial, code => Ins::Other(code), } } } impl From for u8 { fn from(ins: Ins) -> u8 { ins.code() } } /// APDU responses #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Response { /// Status words status_words: StatusWords, /// Buffer data: Vec, } impl Response { /// Create a new response from the given status words and buffer #[cfg(feature = "untested")] pub fn new(status_words: StatusWords, data: Vec) -> 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> for Response { fn from(mut bytes: Vec) -> 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) { // Only `data` may contain secrets 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: /// #[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 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 for u32 { fn from(sw: StatusWords) -> u32 { sw.code() } }