Ins (APDU instruction codes) enum

Converts a bag of constant values (`YKPIV_INS_*`) into an enum
representing APDU instruction codes (a.k.a. `ins`).

Among other things, this makes the `Debug` output for `APDU` more human
meaningful, since it can print a text label for the instruction rather
than a code number, which is helpful in trace debugging.
This commit is contained in:
Tony Arcieri
2019-11-26 09:52:19 -08:00
parent 3fa5555943
commit debde6e765
6 changed files with 167 additions and 80 deletions
+4 -3
View File
@@ -112,15 +112,16 @@ 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::apdu] >>> APDU { cla: 0, ins: SelectApplication, p1: 4, p2: 0, data: [160, 0, 0, 3, 8] }
[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
[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::apdu] >>> APDU { cla: 0, ins: GetVersion, p1: 0, p2: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 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::apdu] >>> APDU { cla: 0, ins: GetSerial, p1: 0, p2: 0, data: [] }
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0]
[TRACE yubikey_piv::apdu] <<< Response { status_words: Success, data: [0, 115, 0, 178] }
test connect ... ok
+120 -26
View File
@@ -32,7 +32,6 @@
use crate::{error::Error, transaction::Transaction, Buffer};
use log::trace;
use std::fmt::{self, Debug};
use zeroize::{Zeroize, Zeroizing};
/// Maximum amount of command data that can be included in an APDU
@@ -41,13 +40,13 @@ const APDU_DATA_MAX: usize = 0xFF;
/// Application Protocol Data Unit (APDU).
///
/// These messages are packets used to communicate with the YubiKey.
#[derive(Clone)]
#[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: u8,
ins: Ins,
/// Instruction parameter 1 for the command (e.g. offset into file at which to write the data)
p1: u8,
@@ -61,10 +60,10 @@ pub(crate) struct APDU {
impl APDU {
/// Create a new APDU with the given instruction code
pub fn new(ins: u8) -> Self {
pub fn new(ins: impl Into<Ins>) -> Self {
Self {
cla: 0,
ins,
ins: ins.into(),
p1: 0,
p2: 0,
data: vec![],
@@ -123,7 +122,7 @@ impl APDU {
pub fn to_bytes(&self) -> Buffer {
let mut bytes = Vec::with_capacity(5 + self.data.len());
bytes.push(self.cla);
bytes.push(self.ins);
bytes.push(self.ins.code());
bytes.push(self.p1);
bytes.push(self.p2);
bytes.push(self.data.len() as u8);
@@ -132,21 +131,6 @@ impl APDU {
}
}
impl Debug for APDU {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"APDU {{ cla: {}, ins: {}, p1: {}, p2: {}, lc: {}, data: {:?} }}",
self.cla,
self.ins,
self.p1,
self.p2,
self.data.len(),
self.data.as_slice()
)
}
}
impl Drop for APDU {
fn drop(&mut self) {
self.zeroize();
@@ -155,16 +139,125 @@ impl Drop for APDU {
impl Zeroize for APDU {
fn zeroize(&mut self) {
self.cla.zeroize();
self.ins.zeroize();
self.p1.zeroize();
self.p2.zeroize();
// 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
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
/// 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<u8> 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<Ins> for u8 {
fn from(ins: Ins) -> u8 {
ins.code()
}
}
/// APDU responses
#[derive(Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
@@ -239,6 +332,7 @@ impl From<Vec<u8>> for Response {
impl Zeroize for Response {
fn zeroize(&mut self) {
// Only `data` may contain secrets
self.data.zeroize();
}
}
-20
View File
@@ -136,26 +136,6 @@ pub const YKPIV_CCCID_SIZE: usize = 14;
pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0;
pub const YKPIV_CERTINFO_GZIP: u8 = 1;
pub const YKPIV_INS_VERIFY: u8 = 0x20;
pub const YKPIV_INS_CHANGE_REFERENCE: u8 = 0x24;
pub const YKPIV_INS_RESET_RETRY: u8 = 0x2c;
pub const YKPIV_INS_GENERATE_ASYMMETRIC: u8 = 0x47;
pub const YKPIV_INS_AUTHENTICATE: u8 = 0x87;
pub const YKPIV_INS_GET_DATA: u8 = 0xcb;
pub const YKPIV_INS_PUT_DATA: u8 = 0xdb;
pub const YKPIV_INS_SELECT_APPLICATION: u8 = 0xa4;
pub const YKPIV_INS_GET_RESPONSE_APDU: u8 = 0xc0;
// Yubico vendor specific instructions
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
pub const YKPIV_INS_SET_MGMKEY: u8 = 0xff;
pub const YKPIV_INS_IMPORT_KEY: u8 = 0xfe;
pub const YKPIV_INS_GET_VERSION: u8 = 0xfd;
pub const YKPIV_INS_RESET: u8 = 0xfb;
pub const YKPIV_INS_SET_PIN_RETRIES: u8 = 0xfa;
pub const YKPIV_INS_ATTEST: u8 = 0xf9;
pub const YKPIV_INS_GET_SERIAL: u8 = 0xf8;
pub const YKPIV_KEY_AUTHENTICATION: u8 = 0x9a;
pub const YKPIV_KEY_CARDMGM: u8 = 0x9b;
pub const YKPIV_KEY_SIGNATURE: u8 = 0x9c;
+2 -2
View File
@@ -38,7 +38,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
apdu::StatusWords,
apdu::{Ins, StatusWords},
certificate::{self, Certificate},
consts::*,
error::Error,
@@ -199,7 +199,7 @@ pub fn generate(
touch_policy: u8,
) -> Result<GeneratedKey, Error> {
let mut in_data = [0u8; 11];
let mut templ = [0, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0];
let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0];
let setting_roca: settings::BoolValue;
if yubikey.device_model() == DEVTYPE_YK4
+20 -19
View File
@@ -1,9 +1,14 @@
//! YubiKey PC/SC transactions
use crate::{apdu::APDU, consts::*, error::Error, yubikey::*};
use crate::{
apdu::{Ins, APDU},
error::Error,
yubikey::*,
};
#[cfg(feature = "untested")]
use crate::{
apdu::{Response, StatusWords},
consts::*,
mgm::MgmKey,
serialization::*,
Buffer, ObjectId,
@@ -60,7 +65,7 @@ impl<'tx> Transaction<'tx> {
/// Select application.
pub fn select_application(&self) -> Result<(), Error> {
let response = APDU::new(YKPIV_INS_SELECT_APPLICATION)
let response = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.transmit(self, 0xFF)
@@ -83,7 +88,7 @@ impl<'tx> Transaction<'tx> {
/// Get the version of the PIV application installed on the YubiKey
pub fn get_version(&self) -> Result<Version, Error> {
// get version from device
let response = APDU::new(YKPIV_INS_GET_VERSION).transmit(self, 261)?;
let response = APDU::new(Ins::GetVersion).transmit(self, 261)?;
if !response.is_success() {
return Err(Error::GenericError);
@@ -93,11 +98,7 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError);
}
Ok(Version {
major: response.data()[0],
minor: response.data()[1],
patch: response.data()[2],
})
Ok(Version::new(response.data()[..3].try_into().unwrap()))
}
/// Get YubiKey device serial number
@@ -106,7 +107,7 @@ impl<'tx> Transaction<'tx> {
let response = if version.major < 5 {
// get serial from neo/yk4 devices using the otp applet
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
let sw = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&yk_applet)
.transmit(self, 0xFF)?
@@ -128,7 +129,7 @@ impl<'tx> Transaction<'tx> {
}
// reselect the PIV applet
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
let sw = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.transmit(self, 0xFF)?
@@ -142,7 +143,7 @@ impl<'tx> Transaction<'tx> {
resp
} else {
// get serial from yk5 and later devices using the f8 command
let resp = APDU::new(YKPIV_INS_GET_SERIAL).transmit(self, 0xFF)?;
let resp = APDU::new(Ins::GetSerial).transmit(self, 0xFF)?;
if !resp.is_success() {
error!(
@@ -169,7 +170,7 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError);
}
let response = APDU::new(YKPIV_INS_VERIFY)
let response = APDU::new(Ins::Verify)
.params(0x00, 0x80)
.data(pin)
.transmit(self, 261)?;
@@ -185,7 +186,7 @@ impl<'tx> Transaction<'tx> {
/// Change the PIN
#[cfg(feature = "untested")]
pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
let mut templ = [0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80];
let mut templ = [0, Ins::ChangeReference.code(), 0, 0x80];
let mut indata = Zeroizing::new([0u8; 16]);
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
@@ -193,7 +194,7 @@ impl<'tx> Transaction<'tx> {
}
if action == CHREF_ACT_UNBLOCK_PIN {
templ[1] = YKPIV_INS_RESET_RETRY;
templ[1] = Ins::ResetRetry.code();
} else if action == CHREF_ACT_CHANGE_PUK {
templ[3] = 0x81;
}
@@ -259,7 +260,7 @@ impl<'tx> Transaction<'tx> {
data[2] = DES_LEN_3DES as u8;
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
let status_words = APDU::new(YKPIV_INS_SET_MGMKEY)
let status_words = APDU::new(Ins::SetMgmKey)
.params(0xff, p2)
.data(&data)
.transmit(self, 261)?
@@ -290,7 +291,7 @@ impl<'tx> Transaction<'tx> {
) -> Result<(), Error> {
let in_len = sign_in.len();
let mut indata = [0u8; 1024];
let templ = [0, YKPIV_INS_AUTHENTICATE, algorithm, key];
let templ = [0, Ins::Authenticate.code(), algorithm, key];
let mut len: usize = 0;
match algorithm {
@@ -454,7 +455,7 @@ impl<'tx> Transaction<'tx> {
sw & 0xff
);
let response = APDU::new(YKPIV_INS_GET_RESPONSE_APDU).transmit(self, 261)?;
let response = APDU::new(Ins::GetResponseApdu).transmit(self, 261)?;
sw = response.status_words().code();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
@@ -481,7 +482,7 @@ impl<'tx> Transaction<'tx> {
#[cfg(feature = "untested")]
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
let mut indata = [0u8; 5];
let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff];
let templ = [0, Ins::GetData.code(), 0x3f, 0xff];
let mut inlen = indata.len();
let indata_remaining = set_object(object_id, &mut indata);
@@ -524,7 +525,7 @@ impl<'tx> Transaction<'tx> {
/// Save an object
#[cfg(feature = "untested")]
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff];
let templ = [0, Ins::PutData.code(), 0x3f, 0xff];
// TODO(tarcieri): replace with vector
let mut data = [0u8; CB_BUF_MAX];
+21 -10
View File
@@ -35,7 +35,7 @@
#[cfg(feature = "untested")]
use crate::{
apdu::{StatusWords, APDU},
apdu::{Ins, StatusWords, APDU},
key::SlotId,
metadata,
mgm::MgmKey,
@@ -99,6 +99,17 @@ pub struct Version {
pub patch: u8,
}
impl Version {
/// Parse a version from bytes
pub fn new(bytes: [u8; 3]) -> Version {
Version {
major: bytes[0],
minor: bytes[1],
patch: bytes[2],
}
}
}
/// YubiKey Device: this is the primary API for opening a session and
/// performing various operations.
///
@@ -270,7 +281,7 @@ impl YubiKey {
let txn = self.begin_transaction()?;
// get a challenge from the card
let challenge = APDU::new(YKPIV_INS_AUTHENTICATE)
let challenge = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x80, 0x00])
.transmit(&txn, 261)?;
@@ -299,7 +310,7 @@ impl YubiKey {
let mut challenge = [0u8; 8];
challenge.copy_from_slice(&data[14..22]);
let authentication = APDU::new(YKPIV_INS_AUTHENTICATE)
let authentication = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.data(&data)
.transmit(&txn, 261)?;
@@ -324,7 +335,7 @@ impl YubiKey {
pub fn deauthenticate(&mut self) -> Result<(), Error> {
let txn = self.begin_transaction()?;
let status_words = APDU::new(YKPIV_INS_SELECT_APPLICATION)
let status_words = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(MGMT_AID)
.transmit(&txn, 255)?
@@ -422,7 +433,7 @@ impl YubiKey {
let templ = [
0,
YKPIV_INS_SET_PIN_RETRIES,
Ins::SetPinRetries.code(),
pin_tries as u8,
puk_tries as u8,
];
@@ -644,7 +655,7 @@ impl YubiKey {
let mut key_data = Zeroizing::new(vec![0u8; 1024]);
let mut in_ptr: *mut u8 = key_data.as_mut_ptr();
let templ = [0, YKPIV_INS_IMPORT_KEY, algorithm, key];
let templ = [0, Ins::ImportKey.code(), algorithm, key];
let mut elem_len: u32 = 0;
let mut params: [*const u8; 5] = [ptr::null(); 5];
let mut lens = [0usize; 5];
@@ -795,7 +806,7 @@ impl YubiKey {
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")]
pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> {
let templ = [0, YKPIV_INS_ATTEST, key, 0];
let templ = [0, Ins::Attest.code(), key, 0];
let txn = self.begin_transaction()?;
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
@@ -819,7 +830,7 @@ impl YubiKey {
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
let txn = self.begin_transaction()?;
let response = APDU::new(YKPIV_INS_AUTHENTICATE)
let response = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x81, 0x00])
.transmit(&txn, 261)?;
@@ -844,7 +855,7 @@ impl YubiKey {
let txn = self.begin_transaction()?;
// send the response to the card and a challenge of our own.
let status_words = APDU::new(YKPIV_INS_AUTHENTICATE)
let status_words = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.data(&data)
.transmit(&txn, 261)?
@@ -864,7 +875,7 @@ impl YubiKey {
/// The reset function is only available when both pins are blocked.
#[cfg(feature = "untested")]
pub fn reset_device(&mut self) -> Result<(), Error> {
let templ = [0, YKPIV_INS_RESET, 0, 0];
let templ = [0, Ins::Reset.code(), 0, 0];
let txn = self.begin_transaction()?;
let status_words = txn.transfer_data(&templ, &[], 255)?.status_words();