diff --git a/src/apdu.rs b/src/apdu.rs index d8942a3..890638b 100644 --- a/src/apdu.rs +++ b/src/apdu.rs @@ -82,6 +82,12 @@ impl Apdu { self } + /// Set this APDU's second parameter only + pub(crate) fn p2(&mut self, value: u8) -> &mut Self { + self.p2 = value; + self + } + /// Set both parameters for this APDU pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self { self.p1 = p1; diff --git a/src/mgm.rs b/src/mgm.rs index 17df19d..960f49a 100644 --- a/src/mgm.rs +++ b/src/mgm.rs @@ -39,6 +39,8 @@ use zeroize::Zeroize; use crate::{ consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_PROTECTED_MGM}, metadata::{AdminData, ProtectedData}, + piv::{ManagementSlotId, SlotAlgorithmId}, + transaction::Transaction, yubikey::YubiKey, }; use des::{ @@ -110,6 +112,27 @@ impl From for u8 { } } +impl MgmAlgorithmId { + /// Looks up the algorithm for the given Yubikey's current management key. + #[cfg(feature = "untested")] + fn query(txn: &Transaction<'_>) -> Result { + match txn.get_metadata(crate::piv::SlotId::Management(ManagementSlotId::Management)) { + Ok(metadata) => match metadata.algorithm { + SlotAlgorithmId::Management(alg) => Ok(alg), + // We specifically queried the management key slot; getting a known + // non-management algorithm back from the Yubikey is invalid. + _ => Err(Error::InvalidObject), + }, + // Firmware versions without `GET METADATA` only support 3DES. + Err(Error::NotSupported) => Ok(MgmAlgorithmId::ThreeDes), + // `Error::AlgorithmError` only occurs when a new algorithm is encountered. + Err(Error::AlgorithmError) => Err(Error::NotSupported), + // Raise other errors as-is. + Err(e) => Err(e), + } + } +} + /// Management Key (MGM). /// /// This key is used to authenticate to the management applet running on @@ -155,6 +178,12 @@ impl MgmKey { pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result { let txn = yubikey.begin_transaction()?; + // Check the key algorithm. + let alg = MgmAlgorithmId::query(&txn)?; + if alg != MgmAlgorithmId::ThreeDes { + return Err(Error::NotSupported); + } + // recover management key let admin_data = AdminData::read(&txn)?; let salt = admin_data.get_item(TAG_ADMIN_SALT)?; @@ -179,6 +208,12 @@ impl MgmKey { pub fn get_protected(yubikey: &mut YubiKey) -> Result { let txn = yubikey.begin_transaction()?; + // Check the key algorithm. + let alg = MgmAlgorithmId::query(&txn)?; + if alg != MgmAlgorithmId::ThreeDes { + return Err(Error::NotSupported); + } + let protected_data = ProtectedData::read(&txn) .inspect_err(|e| error!("could not read protected data (err: {:?})", e))?; diff --git a/src/piv.rs b/src/piv.rs index a55896d..e59e40c 100644 --- a/src/piv.rs +++ b/src/piv.rs @@ -45,7 +45,6 @@ use crate::{ apdu::{Ins, StatusWords}, certificate::{self, Certificate}, - consts::CB_OBJ_MAX, error::{Error, Result}, mgm::MgmAlgorithmId, policy::{PinPolicy, TouchPolicy}, @@ -75,6 +74,9 @@ use { #[cfg(feature = "untested")] use zeroize::Zeroizing; +#[cfg(feature = "untested")] +use crate::consts::CB_OBJ_MAX; + /// PIV Applet Name pub(crate) const APPLET_NAME: &str = "PIV"; @@ -925,21 +927,8 @@ pub fn decrypt_data( /// Read metadata pub fn metadata(yubikey: &mut YubiKey, slot: SlotId) -> Result { let txn = yubikey.begin_transaction()?; - let templ = [0, Ins::GetMetadata.code(), 0, slot.into()]; - - let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?; - - if !response.is_success() { - if response.status_words() == StatusWords::NotSupportedError { - return Err(Error::NotSupported); // Requires firmware 5.2.3 - } else { - return Err(Error::GenericError); - } - } - - let buf = Buffer::new(response.data().into()); - SlotMetadata::try_from(buf) + txn.get_metadata(slot) } /// Metadata from a slot diff --git a/src/transaction.rs b/src/transaction.rs index 227bf43..727f23d 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -168,6 +168,25 @@ impl<'tx> Transaction<'tx> { } } + /// Read metadata + pub(crate) fn get_metadata(&self, slot: SlotId) -> Result { + let response = Apdu::new(Ins::GetMetadata) + .p2(slot.into()) + .transmit(self, CB_OBJ_MAX)?; + + if !response.is_success() { + if response.status_words() == StatusWords::NotSupportedError { + return Err(Error::NotSupported); // Requires firmware 5.2.3 + } else { + return Err(Error::GenericError); + } + } + + let buf = Buffer::new(response.data().into()); + + piv::SlotMetadata::try_from(buf) + } + /// Verify device PIN. pub fn verify_pin(&self, pin: &[u8]) -> Result<()> { if pin.len() > CB_PIN_MAX {