diff --git a/README.md b/README.md index 1242f42..6c59a62 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,13 @@ functions of the YubiKey: | | Module | Issue | Description | |----|---------------|-------|-------------| | 🚧 | `yubikey` | [#20] | Core functionality: auth, keys, PIN/PUK, encrypt, sign, attest | -| ⚠️ | `cccid` | [#21] | Cardholder Capability Container (CCC) IDs | +| 🚧 | `cccid` | [#21] | Cardholder Capability Container (CCC) IDs | | 🚧️ | `certificate` | [#22] | Certificates for stored keys | -| ⚠️ | `chuid` | [#23] | Cardholder Unique Identifier (CHUID) | +| 🚧 | `chuid` | [#23] | Cardholder Unique Identifier (CHUID) | | ✅️ | `config` | [#24] | Support for reading on-key configuration | | ⚠️ | `container` | [#25] | MS Container Map Records | | 🚧 | `key` | [#26] | Crypto key management: list, generate, import | -| ⚠️ | `mgm` | [#26] | Management Key (MGM) support: set, get, derive | +| 🚧 | `mgm` | [#26] | Management Key (MGM) support: set, get, derive | | ⚠️ | `msroots` | [#28] | `msroots` file: PKCS#7 formatted certificate store for enterprise trusted roots | Legend: diff --git a/src/cccid.rs b/src/cccid.rs index dbee031..424567a 100644 --- a/src/cccid.rs +++ b/src/cccid.rs @@ -30,8 +30,21 @@ // (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::{consts::*, error::Error, yubikey::YubiKey}; +use crate::{error::Error, yubikey::YubiKey}; use getrandom::getrandom; +use std::fmt::{self, Debug}; + +/// CCCID size +pub const CCCID_SIZE: usize = 14; + +/// CCC size +pub const CCC_SIZE: usize = 51; + +/// CCCID offset +const CCC_ID_OFFS: usize = 9; + +/// CCC Object ID +const OBJ_CAPABILITY: u32 = 0x005f_c107; /// Cardholder Capability Container (CCC) Template /// @@ -50,12 +63,12 @@ const CCC_TMPL: &[u8] = &[ /// Cardholder Capability Container (CCC) Identifier Card ID #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct CardId(pub [u8; YKPIV_CCCID_SIZE]); +pub struct CardId(pub [u8; CCCID_SIZE]); impl CardId { /// Generate a random CCC Card ID pub fn generate() -> Result { - let mut id = [0u8; YKPIV_CCCID_SIZE]; + let mut id = [0u8; CCCID_SIZE]; getrandom(&mut id).map_err(|_| Error::RandomnessError)?; Ok(Self(id)) } @@ -63,36 +76,43 @@ impl CardId { /// Cardholder Capability Container (CCC) Identifier #[derive(Copy, Clone)] -pub struct CCC(pub [u8; YKPIV_CCC_SIZE]); +pub struct CCC(pub [u8; CCC_SIZE]); impl CCC { /// Return CardId component of CCC pub fn cccid(&self) -> Result { - let mut cccid = [0u8; YKPIV_CCCID_SIZE]; - cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + YKPIV_CCCID_SIZE)]); + let mut cccid = [0u8; CCCID_SIZE]; + cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CCCID_SIZE)]); Ok(CardId(cccid)) } /// Get Cardholder Capability Container (CCC) ID pub fn get(yubikey: &mut YubiKey) -> Result { let txn = yubikey.begin_transaction()?; - let response = txn.fetch_object(YKPIV_OBJ_CAPABILITY)?; + let response = txn.fetch_object(OBJ_CAPABILITY)?; if response.len() != CCC_TMPL.len() { return Err(Error::GenericError); } - let mut ccc = [0u8; YKPIV_CCC_SIZE]; - ccc.copy_from_slice(&response[0..YKPIV_CCC_SIZE]); + let mut ccc = [0u8; CCC_SIZE]; + ccc.copy_from_slice(&response[0..CCC_SIZE]); Ok(Self(ccc)) } /// Get Cardholder Capability Container (CCC) ID + #[cfg(feature = "untested")] pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> { let mut buf = CCC_TMPL.to_vec(); buf[0..self.0.len()].copy_from_slice(&self.0); let txn = yubikey.begin_transaction()?; - txn.save_object(YKPIV_OBJ_CAPABILITY, &buf) + txn.save_object(OBJ_CAPABILITY, &buf) + } +} + +impl Debug for CCC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CCC({:?})", &self.0[..]) } } diff --git a/src/chuid.rs b/src/chuid.rs index b460b0d..ff9132e 100644 --- a/src/chuid.rs +++ b/src/chuid.rs @@ -30,8 +30,33 @@ // (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::{consts::*, error::Error, yubikey::YubiKey}; +use crate::{error::Error, yubikey::YubiKey}; use getrandom::getrandom; +use std::fmt::{self, Debug}; + +/// CHUID size +pub const CHUID_SIZE: usize = 59; + +/// CARDID size +pub const CARDID_SIZE: usize = 16; + +/// FASC-N component size +pub const FASCN_SIZE: usize = 25; + +/// Expiration size +pub const EXPIRATION_SIZE: usize = 8; + +/// FASC-N offset +const CHUID_FASCN_OFFS: usize = 2; + +/// GUID offset +const CHUID_GUID_OFFS: usize = 29; + +/// Expiration offset +const CHUID_EXPIRATION_OFFS: usize = 47; + +/// CHUID Object ID +const OBJ_CHUID: u32 = 0x005f_c102; /// Cardholder Unique Identifier (CHUID) Template /// @@ -57,12 +82,12 @@ const CHUID_TMPL: &[u8] = &[ /// Cardholder Unique Identifier (CHUID) Card UUID/GUID value #[derive(Copy, Clone, Debug)] -pub struct Uuid(pub [u8; YKPIV_CARDID_SIZE]); +pub struct Uuid(pub [u8; CARDID_SIZE]); impl Uuid { - /// Generate a random Cardholder Unique Identifier (CHUID) + /// Generate a random Cardholder Unique Identifier (CHUID) UUID pub fn generate() -> Result { - let mut id = [0u8; YKPIV_CARDID_SIZE]; + let mut id = [0u8; CARDID_SIZE]; getrandom(&mut id).map_err(|_| Error::RandomnessError)?; Ok(Self(id)) } @@ -70,28 +95,29 @@ impl Uuid { /// Cardholder Unique Identifier (CHUID) #[derive(Copy, Clone)] -pub struct CHUID(pub [u8; YKPIV_CHUID_SIZE]); +pub struct CHUID(pub [u8; CHUID_SIZE]); impl CHUID { /// Return FASC-N component of CHUID - pub fn fascn(&self) -> Result<[u8; YKPIV_FASCN_SIZE], Error> { - let mut fascn = [0u8; YKPIV_FASCN_SIZE]; - fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + YKPIV_FASCN_SIZE)]); + pub fn fascn(&self) -> Result<[u8; FASCN_SIZE], Error> { + let mut fascn = [0u8; FASCN_SIZE]; + fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + FASCN_SIZE)]); Ok(fascn) } /// Return Card UUID/GUID component of CHUID - pub fn uuid(&self) -> Result<[u8; YKPIV_CARDID_SIZE], Error> { - let mut uuid = [0u8; YKPIV_CARDID_SIZE]; - uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]); + pub fn uuid(&self) -> Result<[u8; CARDID_SIZE], Error> { + let mut uuid = [0u8; CARDID_SIZE]; + uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + CARDID_SIZE)]); Ok(uuid) } /// Return expiration date component of CHUID - pub fn expiration(&self) -> Result<[u8; YKPIV_EXPIRATION_SIZE], Error> { - let mut expiration = [0u8; YKPIV_EXPIRATION_SIZE]; + // TODO(tarcieri): parse expiration? + pub fn expiration(&self) -> Result<[u8; EXPIRATION_SIZE], Error> { + let mut expiration = [0u8; EXPIRATION_SIZE]; expiration.copy_from_slice( - &self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + YKPIV_EXPIRATION_SIZE)], + &self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + EXPIRATION_SIZE)], ); Ok(expiration) } @@ -99,24 +125,32 @@ impl CHUID { /// Get Cardholder Unique Identifier (CHUID) pub fn get(yubikey: &mut YubiKey) -> Result { let txn = yubikey.begin_transaction()?; - let response = txn.fetch_object(YKPIV_OBJ_CHUID)?; + let response = txn.fetch_object(OBJ_CHUID)?; if response.len() != CHUID_TMPL.len() { return Err(Error::GenericError); } - let mut chuid = [0u8; YKPIV_CHUID_SIZE]; - chuid.copy_from_slice(&response[0..YKPIV_CHUID_SIZE]); + let mut chuid = [0u8; CHUID_SIZE]; + chuid.copy_from_slice(&response[0..CHUID_SIZE]); let retval = CHUID { 0: chuid }; Ok(retval) } /// Set Cardholder Unique Identifier (CHUID) + #[cfg(feature = "untested")] + pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> { let mut buf = CHUID_TMPL.to_vec(); buf[0..self.0.len()].copy_from_slice(&self.0); let txn = yubikey.begin_transaction()?; - txn.save_object(YKPIV_OBJ_CHUID, &buf) + txn.save_object(OBJ_CHUID, &buf) + } +} + +impl Debug for CHUID { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CHUID({:?})", &self.0[..]) } } diff --git a/src/consts.rs b/src/consts.rs index fcd0255..6589472 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -67,12 +67,6 @@ pub const CB_OBJ_TAG_MAX: usize = (CB_OBJ_TAG_MIN + 2); // 1 byte tag + 3 bytes pub const CB_PAGE: usize = 4096; pub const CB_PIN_MAX: usize = 8; -pub const CCC_ID_OFFS: usize = 9; - -pub const CHUID_FASCN_OFFS: usize = 2; -pub const CHUID_GUID_OFFS: usize = 29; -pub const CHUID_EXPIRATION_OFFS: usize = 47; - pub const CHREF_ACT_CHANGE_PIN: i32 = 0; pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1; pub const CHREF_ACT_CHANGE_PUK: i32 = 2; @@ -128,21 +122,11 @@ pub const TAG_ECC_POINT: u8 = 0x86; pub const YKPIV_ALGO_3DES: u8 = 0x03; -pub const YKPIV_CHUID_SIZE: usize = 59; -pub const YKPIV_CARDID_SIZE: usize = 16; -pub const YKPIV_FASCN_SIZE: usize = 25; -pub const YKPIV_EXPIRATION_SIZE: usize = 8; - -pub const YKPIV_CCCID_SIZE: usize = 14; -pub const YKPIV_CCC_SIZE: usize = 51; - pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0; pub const YKPIV_CERTINFO_GZIP: u8 = 1; pub const YKPIV_KEY_CARDMGM: u8 = 0x9b; -pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107; -pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102; pub const YKPIV_OBJ_FINGERPRINTS: u32 = 0x005f_c103; pub const YKPIV_OBJ_SECURITY: u32 = 0x005f_c106; pub const YKPIV_OBJ_FACIAL: u32 = 0x005f_c108; diff --git a/src/error.rs b/src/error.rs index 79b7a78..9954dec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -88,6 +88,9 @@ pub enum Error { /// Not supported NotSupported, + + /// Not found + NotFound, } impl Error { @@ -113,6 +116,7 @@ impl Error { Error::ArgumentError => "YKPIV_ARGUMENT_ERROR", Error::RangeError => "YKPIV_RANGE_ERROR", Error::NotSupported => "YKPIV_NOT_SUPPORTED", + Error::NotFound => "", } } @@ -135,6 +139,7 @@ impl Error { Error::ArgumentError => "argument error", Error::RangeError => "range error", Error::NotSupported => "not supported", + Error::NotFound => "not found", } } } diff --git a/src/lib.rs b/src/lib.rs index 0f5a51c..0d4da9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,10 +136,8 @@ )] mod apdu; -#[cfg(feature = "untested")] pub mod cccid; pub mod certificate; -#[cfg(feature = "untested")] pub mod chuid; pub mod config; pub mod consts; @@ -159,10 +157,7 @@ pub mod settings; mod transaction; pub mod yubikey; -pub use self::{readers::Readers, yubikey::YubiKey}; - -#[cfg(feature = "untested")] -pub use self::{key::Key, mgm::MgmKey}; +pub use self::{error::Error, key::Key, mgm::MgmKey, readers::Readers, yubikey::YubiKey}; /// Object identifiers pub type ObjectId = u32; diff --git a/src/transaction.rs b/src/transaction.rs index 02a36f5..ebd6a8a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -448,7 +448,11 @@ impl<'tx> Transaction<'tx> { let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?; if !response.is_success() { - return Err(Error::GenericError); + if response.status_words() == StatusWords::NotFoundError { + return Err(Error::NotFound); + } else { + return Err(Error::GenericError); + } } let data = Buffer::new(response.data().into()); diff --git a/src/yubikey.rs b/src/yubikey.rs index 4dab5e3..9ae88a3 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -31,6 +31,8 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ + cccid::CCC, + chuid::CHUID, config::Config, error::Error, readers::{Reader, Readers}, @@ -152,12 +154,9 @@ impl YubiKey { } error!("no YubiKey detected!"); - Err(Error::GenericError) + Err(Error::NotFound) } - /// Open a connection to a YubiKey with the given serial number. - - /// Reconnect to a YubiKey #[cfg(feature = "untested")] pub fn reconnect(&mut self) -> Result<(), Error> { @@ -209,6 +208,16 @@ impl YubiKey { Config::get(self) } + /// Get CHUID + pub fn chuid(&mut self) -> Result { + CHUID::get(self) + } + + /// Get CCCID + pub fn cccid(&mut self) -> Result { + CCC::get(self) + } + /// Authenticate to the card using the provided management key (MGM). #[cfg(feature = "untested")] pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> { diff --git a/tests/integration.rs b/tests/integration.rs index 62a24cc..4662756 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6,7 +6,7 @@ use lazy_static::lazy_static; use log::trace; use std::{env, sync::Mutex}; -use yubikey_piv::{key::Key, YubiKey}; +use yubikey_piv::{key::Key, Error, YubiKey}; lazy_static! { /// Provide thread-safe access to a YubiKey @@ -27,6 +27,38 @@ fn init_yubikey() -> Mutex { Mutex::new(yubikey) } +// +// CCCID support +// + +#[test] +#[ignore] +fn test_get_cccid() { + let mut yubikey = YUBIKEY.lock().unwrap(); + + match yubikey.cccid() { + Ok(cccid) => trace!("CCCID: {:?}", cccid), + Err(Error::NotFound) => trace!("CCCID not found"), + Err(err) => panic!("error getting CCCID: {:?}", err), + } +} + +// +// CHUID support +// + +#[test] +#[ignore] +fn test_get_chuid() { + let mut yubikey = YUBIKEY.lock().unwrap(); + + match yubikey.chuid() { + Ok(chuid) => trace!("CHUID: {:?}", chuid), + Err(Error::NotFound) => trace!("CHUID not found"), + Err(err) => panic!("error getting CHUID: {:?}", err), + } +} + // // Device config support //