diff --git a/src/consts.rs b/src/consts.rs index 2988342..65242c0 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -158,15 +158,3 @@ pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12; pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13; pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14; pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15; - -pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa; -pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0; -pub const YKPIV_PINPOLICY_NEVER: u8 = 1; -pub const YKPIV_PINPOLICY_ONCE: u8 = 2; -pub const YKPIV_PINPOLICY_ALWAYS: u8 = 3; - -pub const YKPIV_TOUCHPOLICY_TAG: u8 = 0xab; -pub const YKPIV_TOUCHPOLICY_DEFAULT: u8 = 0; -pub const YKPIV_TOUCHPOLICY_NEVER: u8 = 1; -pub const YKPIV_TOUCHPOLICY_ALWAYS: u8 = 2; -pub const YKPIV_TOUCHPOLICY_CACHED: u8 = 3; diff --git a/src/key.rs b/src/key.rs index b54fa53..f14aa81 100644 --- a/src/key.rs +++ b/src/key.rs @@ -42,6 +42,7 @@ use crate::{ certificate::{self, Certificate}, consts::*, error::Error, + policy::{PinPolicy, TouchPolicy}, serialization::*, settings, yubikey::YubiKey, @@ -408,8 +409,8 @@ pub fn generate( yubikey: &mut YubiKey, slot: SlotId, algorithm: AlgorithmId, - pin_policy: u8, - touch_policy: u8, + pin_policy: PinPolicy, + touch_policy: TouchPolicy, ) -> Result { let mut in_data = [0u8; 11]; let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0]; @@ -476,16 +477,13 @@ pub fn generate( return Err(Error::AlgorithmError); } - if pin_policy != YKPIV_PINPOLICY_DEFAULT { - in_data[1] += 3; - in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_PINPOLICY_TAG, 1, pin_policy]); - offset += 3; - } + let pin_len = pin_policy.write(&mut in_data[offset..]); + in_data[1] += pin_len as u8; + offset += pin_len; - if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT { - in_data[1] += 3; - in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_TOUCHPOLICY_TAG, 1, touch_policy]); - } + let touch_len = touch_policy.write(&mut in_data[offset..]); + in_data[1] += touch_len as u8; + offset += touch_len; let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?; @@ -498,12 +496,12 @@ pub fn generate( return Err(Error::KeyError); } StatusWords::IncorrectParamError => { - if pin_policy != YKPIV_PINPOLICY_DEFAULT { - error!("{} (pin policy not supported?)", err_msg); - } else if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT { - error!("{} (touch policy not supported?)", err_msg); - } else { - error!("{} (algorithm not supported?)", err_msg); + match pin_policy { + PinPolicy::Default => match touch_policy { + TouchPolicy::Default => error!("{} (algorithm not supported?)", err_msg), + _ => error!("{} (touch policy not supported?)", err_msg), + }, + _ => error!("{} (pin policy not supported?)", err_msg), } return Err(Error::AlgorithmError); diff --git a/src/lib.rs b/src/lib.rs index 580ca93..04c022b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,6 +156,8 @@ mod metadata; pub mod mgm; #[cfg(feature = "untested")] pub mod msroots; +#[cfg(feature = "untested")] +pub mod policy; pub mod readers; #[cfg(feature = "untested")] mod serialization; diff --git a/src/policy.rs b/src/policy.rs new file mode 100644 index 0000000..a9650ab --- /dev/null +++ b/src/policy.rs @@ -0,0 +1,98 @@ +//! Enums representing key policies. + +/// Specifies how often the PIN needs to be entered for access to the credential in a +/// given slot. This policy must be set upon key generation or importation, and cannot be +/// changed later. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PinPolicy { + /// Use the default PIN policy for the slot. See the slot's documentation for details. + Default, + + /// The end user PIN is **NOT** required to perform private key operations. + Never, + + /// The end user PIN is required to perform any private key operations. Once the + /// correct PIN has been provided, multiple private key operations may be performed + /// without additional cardholder consent. + Once, + + /// The end user PIN is required to perform any private key operations. The PIN must + /// be submitted immediately before each operation to ensure cardholder participation. + Always, +} + +impl From for u8 { + fn from(policy: PinPolicy) -> u8 { + match policy { + PinPolicy::Default => 0, + PinPolicy::Never => 1, + PinPolicy::Once => 2, + PinPolicy::Always => 3, + } + } +} + +impl PinPolicy { + /// Writes the `PinPolicy` in the format the YubiKey expects during key generation or + /// importation. + pub(crate) fn write(self, buf: &mut [u8]) -> usize { + match self { + PinPolicy::Default => 0, + _ => { + buf[0] = 0xaa; + buf[1] = 0x01; + buf[2] = self.into(); + 3 + } + } + } +} + +/// Specifies under what conditions a physical touch on the metal contact is required, in +/// addition to the [`PinPolicy`]. This policy must be set upon key generation or +/// importation, and cannot be changed later. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TouchPolicy { + /// Use the default touch policy for the slot. + Default, + + /// A physical touch is **NOT** required to perform private key operations. + Never, + + /// A physical touch is required to perform any private key operations. The metal + /// contact must be touched during each operation to ensure cardholder participation. + Always, + + /// A physical touch is required to perform any private key operations. Each touch + /// is cached for 15 seconds, during which time multiple private key operations may be + /// performed without additional cardholder interaction. After 15 seconds the cached + /// touch is cleared, and further operations require another physical touch. + Cached, +} + +impl From for u8 { + fn from(policy: TouchPolicy) -> u8 { + match policy { + TouchPolicy::Default => 0, + TouchPolicy::Never => 1, + TouchPolicy::Always => 2, + TouchPolicy::Cached => 3, + } + } +} + +impl TouchPolicy { + /// Writes the `TouchPolicy` in the format the YubiKey expects during key generation + /// or importation. + pub(crate) fn write(self, buf: &mut [u8]) -> usize { + match self { + TouchPolicy::Default => 0, + _ => { + buf[0] = 0xab; + buf[1] = 0x01; + buf[2] = self.into(); + 3 + } + } + } +} diff --git a/src/yubikey.rs b/src/yubikey.rs index 7bb8db4..47c9986 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -39,6 +39,7 @@ use crate::{ key::{AlgorithmId, SlotId}, metadata, mgm::MgmKey, + policy::{PinPolicy, TouchPolicy}, serialization::*, Buffer, ObjectId, }; @@ -552,28 +553,12 @@ impl YubiKey { dq: Option<&[u8]>, qinv: Option<&[u8]>, ec_data: Option<&[u8]>, - pin_policy: u8, - touch_policy: u8, + pin_policy: PinPolicy, + touch_policy: TouchPolicy, ) -> Result<(), Error> { let mut key_data = Zeroizing::new(vec![0u8; 1024]); let templ = [0, Ins::ImportKey.code(), algorithm.into(), key.into()]; - if pin_policy != YKPIV_PINPOLICY_DEFAULT - && pin_policy != YKPIV_PINPOLICY_NEVER - && pin_policy != YKPIV_PINPOLICY_ONCE - && pin_policy != YKPIV_PINPOLICY_ALWAYS - { - return Err(Error::GenericError); - } - - if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT - && touch_policy != YKPIV_TOUCHPOLICY_NEVER - && touch_policy != YKPIV_TOUCHPOLICY_ALWAYS - && touch_policy != YKPIV_TOUCHPOLICY_CACHED - { - return Err(Error::GenericError); - } - let (elem_len, params, param_tag) = match algorithm { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => match (p, q, dp, dq, qinv) { (Some(p), Some(q), Some(dp), Some(dq), Some(qinv)) => { @@ -637,19 +622,8 @@ impl YubiKey { offset += param.len(); } - if pin_policy != YKPIV_PINPOLICY_DEFAULT { - key_data[offset] = YKPIV_PINPOLICY_TAG; - key_data[offset + 1] = 0x01; - key_data[offset + 2] = pin_policy; - offset += 3; - } - - if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT { - key_data[offset] = YKPIV_TOUCHPOLICY_TAG; - key_data[offset + 1] = 0x01; - key_data[offset + 2] = touch_policy; - offset += 3; - } + offset += pin_policy.write(&mut key_data[offset..]); + offset += touch_policy.write(&mut key_data[offset..]); let txn = self.begin_transaction()?;