Enable library users to detect if a smart card doesn't support PIV (#476)
* Enable library users to detect if a smart card doesn't support PIV Closes iqlusioninc/yubikey.rs#456. * Avoid resetting the card if we fail to select PIV or fetch version/serial
This commit is contained in:
@@ -45,6 +45,12 @@ pub enum Error {
|
||||
/// Applet error
|
||||
AppletError,
|
||||
|
||||
/// We tried to select an applet that could not be found.
|
||||
AppletNotFound {
|
||||
/// Human-readable name of the applet.
|
||||
applet_name: &'static str,
|
||||
},
|
||||
|
||||
/// Argument error
|
||||
ArgumentError,
|
||||
|
||||
@@ -125,6 +131,9 @@ impl Error {
|
||||
match self {
|
||||
Error::AlgorithmError => f.write_str("algorithm error"),
|
||||
Error::AppletError => f.write_str("applet error"),
|
||||
Error::AppletNotFound { applet_name } => {
|
||||
f.write_str(&format!("{} applet not found", applet_name))
|
||||
}
|
||||
Error::ArgumentError => f.write_str("argument error"),
|
||||
Error::AuthenticationError => f.write_str("authentication error"),
|
||||
Error::GenericError => f.write_str("generic error"),
|
||||
|
||||
@@ -49,6 +49,7 @@ mod mgm;
|
||||
mod mscmap;
|
||||
#[cfg(feature = "untested")]
|
||||
mod msroots;
|
||||
mod otp;
|
||||
pub mod piv;
|
||||
mod policy;
|
||||
pub mod reader;
|
||||
|
||||
+10
@@ -48,6 +48,16 @@ use des::{
|
||||
#[cfg(feature = "untested")]
|
||||
use {hmac::Hmac, pbkdf2::pbkdf2, sha1::Sha1};
|
||||
|
||||
/// YubiKey MGMT Applet Name
|
||||
#[cfg(feature = "untested")]
|
||||
pub(crate) const APPLET_NAME: &str = "YubiKey MGMT";
|
||||
|
||||
/// MGMT Applet ID.
|
||||
///
|
||||
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
|
||||
#[cfg(feature = "untested")]
|
||||
pub(crate) const APPLET_ID: &[u8] = &[0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
|
||||
|
||||
pub(crate) const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02;
|
||||
|
||||
#[cfg(feature = "untested")]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/// YubiKey OTP Applet Name
|
||||
pub(crate) const APPLET_NAME: &str = "YubiKey OTP";
|
||||
|
||||
/// YubiKey OTP Applet ID. Needed to query serial on YK4.
|
||||
pub(crate) const APPLET_ID: &[u8] = &[0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
|
||||
@@ -73,6 +73,12 @@ use {
|
||||
#[cfg(feature = "untested")]
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// PIV Applet Name
|
||||
pub(crate) const APPLET_NAME: &str = "PIV";
|
||||
|
||||
/// PIV Applet ID
|
||||
pub(crate) const APPLET_ID: &[u8] = &[0xa0, 0x00, 0x00, 0x03, 0x08];
|
||||
|
||||
const CB_ECC_POINTP256: usize = 65;
|
||||
const CB_ECC_POINTP384: usize = 97;
|
||||
|
||||
|
||||
+23
-13
@@ -5,7 +5,8 @@ use crate::{
|
||||
apdu::{Apdu, Ins, StatusWords},
|
||||
consts::{CB_BUF_MAX, CB_OBJ_MAX},
|
||||
error::{Error, Result},
|
||||
piv::{AlgorithmId, SlotId},
|
||||
otp,
|
||||
piv::{self, AlgorithmId, SlotId},
|
||||
serialization::*,
|
||||
yubikey::*,
|
||||
Buffer, ObjectId,
|
||||
@@ -16,12 +17,6 @@ use zeroize::Zeroizing;
|
||||
#[cfg(feature = "untested")]
|
||||
use crate::mgm::{MgmKey, DES_LEN_3DES};
|
||||
|
||||
/// PIV Applet ID
|
||||
const PIV_AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
|
||||
|
||||
/// YubiKey OTP Applet ID. Needed to query serial on YK4.
|
||||
const YK_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
|
||||
|
||||
const CB_PIN_MAX: usize = 8;
|
||||
|
||||
#[cfg(feature = "untested")]
|
||||
@@ -69,7 +64,7 @@ impl<'tx> Transaction<'tx> {
|
||||
pub fn select_application(&self) -> Result<()> {
|
||||
let response = Apdu::new(Ins::SelectApplication)
|
||||
.p1(0x04)
|
||||
.data(PIV_AID)
|
||||
.data(piv::APPLET_ID)
|
||||
.transmit(self, 0xFF)
|
||||
.map_err(|e| {
|
||||
error!("failed communicating with card: '{}'", e);
|
||||
@@ -81,7 +76,12 @@ impl<'tx> Transaction<'tx> {
|
||||
"failed selecting application: {:04x}",
|
||||
response.status_words().code()
|
||||
);
|
||||
return Err(Error::GenericError);
|
||||
return Err(match response.status_words() {
|
||||
StatusWords::NotFoundError => Error::AppletNotFound {
|
||||
applet_name: piv::APPLET_NAME,
|
||||
},
|
||||
_ => Error::GenericError,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -110,13 +110,18 @@ impl<'tx> Transaction<'tx> {
|
||||
4 => {
|
||||
let sw = Apdu::new(Ins::SelectApplication)
|
||||
.p1(0x04)
|
||||
.data(YK_AID)
|
||||
.data(otp::APPLET_ID)
|
||||
.transmit(self, 0xFF)?
|
||||
.status_words();
|
||||
|
||||
if !sw.is_success() {
|
||||
error!("failed selecting yk application: {:04x}", sw.code());
|
||||
return Err(Error::GenericError);
|
||||
return Err(match sw {
|
||||
StatusWords::NotFoundError => Error::AppletNotFound {
|
||||
applet_name: otp::APPLET_NAME,
|
||||
},
|
||||
_ => Error::GenericError,
|
||||
});
|
||||
}
|
||||
|
||||
let response = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?;
|
||||
@@ -133,13 +138,18 @@ impl<'tx> Transaction<'tx> {
|
||||
// reselect the PIV applet
|
||||
let sw = Apdu::new(Ins::SelectApplication)
|
||||
.p1(0x04)
|
||||
.data(PIV_AID)
|
||||
.data(piv::APPLET_ID)
|
||||
.transmit(self, 0xFF)?
|
||||
.status_words();
|
||||
|
||||
if !sw.is_success() {
|
||||
error!("failed selecting application: {:04x}", sw.code());
|
||||
return Err(Error::GenericError);
|
||||
return Err(match sw {
|
||||
StatusWords::NotFoundError => Error::AppletNotFound {
|
||||
applet_name: piv::APPLET_NAME,
|
||||
},
|
||||
_ => Error::GenericError,
|
||||
});
|
||||
}
|
||||
|
||||
response.data().try_into()
|
||||
|
||||
+35
-18
@@ -55,6 +55,7 @@ use {
|
||||
apdu::StatusWords,
|
||||
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_TIMESTAMP},
|
||||
metadata::AdminData,
|
||||
mgm,
|
||||
transaction::ChangeRefAction,
|
||||
Buffer, ObjectId,
|
||||
},
|
||||
@@ -71,12 +72,6 @@ pub(crate) const ALGO_3DES: u8 = 0x03;
|
||||
/// Card management key
|
||||
pub(crate) const KEY_CARDMGM: u8 = 0x9b;
|
||||
|
||||
/// MGMT Applet ID.
|
||||
///
|
||||
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
|
||||
#[cfg(feature = "untested")]
|
||||
const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
|
||||
|
||||
const TAG_DYN_AUTH: u8 = 0x7c;
|
||||
|
||||
/// Cached YubiKey PIN.
|
||||
@@ -387,7 +382,7 @@ impl YubiKey {
|
||||
|
||||
let status_words = Apdu::new(Ins::SelectApplication)
|
||||
.p1(0x04)
|
||||
.data(MGMT_AID)
|
||||
.data(mgm::APPLET_ID)
|
||||
.transmit(&txn, 255)?
|
||||
.status_words();
|
||||
|
||||
@@ -396,7 +391,12 @@ impl YubiKey {
|
||||
"Failed selecting mgmt application: {:04x}",
|
||||
status_words.code()
|
||||
);
|
||||
return Err(Error::GenericError);
|
||||
return Err(match status_words {
|
||||
StatusWords::NotFoundError => Error::AppletNotFound {
|
||||
applet_name: mgm::APPLET_NAME,
|
||||
},
|
||||
_ => Error::GenericError,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -682,23 +682,40 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
|
||||
|
||||
info!("connected to reader: {}", reader.name());
|
||||
|
||||
let (version, serial) = {
|
||||
let mut app_version_serial = || -> Result<(Version, Serial)> {
|
||||
let txn = Transaction::new(&mut card)?;
|
||||
txn.select_application()?;
|
||||
|
||||
let v = txn.get_version()?;
|
||||
let s = txn.get_serial(v)?;
|
||||
(v, s)
|
||||
Ok((v, s))
|
||||
};
|
||||
|
||||
let yubikey = YubiKey {
|
||||
card,
|
||||
name: String::from(reader.name()),
|
||||
pin: None,
|
||||
version,
|
||||
serial,
|
||||
};
|
||||
match app_version_serial() {
|
||||
Err(e) => {
|
||||
error!("Could not use reader: {}", e);
|
||||
|
||||
Ok(yubikey)
|
||||
// We were unable to use the card, so we've effectively only connected as
|
||||
// a side-effect of determining this. Avoid disrupting its internal state
|
||||
// any further (e.g. preserve the PIN cache of whatever applet is selected
|
||||
// currently).
|
||||
if let Err((_, e)) = card.disconnect(pcsc::Disposition::LeaveCard) {
|
||||
error!("Failed to disconnect gracefully from card: {}", e);
|
||||
}
|
||||
|
||||
Err(e)
|
||||
}
|
||||
Ok((version, serial)) => {
|
||||
let yubikey = YubiKey {
|
||||
card,
|
||||
name: String::from(reader.name()),
|
||||
pin: None,
|
||||
version,
|
||||
serial,
|
||||
};
|
||||
|
||||
Ok(yubikey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user