From d6cd0130d381d0f8bb7475f19d4057be6f011ce3 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 7 Dec 2019 10:26:06 -0800 Subject: [PATCH] Move `sign`/`decrypt`/`import`/`attest` to the `key` module These are crypto key-related functions and are better factored under this module. --- src/key.rs | 152 +++++++++++++++++++++++++++++++++++++++++ src/yubikey.rs | 180 ++++--------------------------------------------- 2 files changed, 164 insertions(+), 168 deletions(-) diff --git a/src/key.rs b/src/key.rs index 76c9c94..d503eed 100644 --- a/src/key.rs +++ b/src/key.rs @@ -56,6 +56,8 @@ use crate::{ }; #[cfg(feature = "untested")] use log::{error, warn}; +#[cfg(feature = "untested")] +use zeroize::Zeroizing; /// Slot identifiers. /// @@ -587,3 +589,153 @@ pub fn generate( } } } + +/// Import a private encryption or signing key into the YubiKey +// TODO(tarcieri): refactor this into separate methods per key type +#[cfg(feature = "untested")] +#[allow(clippy::too_many_arguments)] +pub fn import( + yubikey: &mut YubiKey, + key: SlotId, + algorithm: AlgorithmId, + p: Option<&[u8]>, + q: Option<&[u8]>, + dp: Option<&[u8]>, + dq: Option<&[u8]>, + qinv: Option<&[u8]>, + ec_data: Option<&[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()]; + + 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)) => { + if p.len() + q.len() + dp.len() + dq.len() + qinv.len() >= key_data.len() { + return Err(Error::SizeError); + } + + ( + match algorithm { + AlgorithmId::Rsa1024 => 64, + AlgorithmId::Rsa2048 => 128, + _ => unreachable!(), + }, + vec![p, q, dp, dq, qinv], + 0x01, + ) + } + _ => return Err(Error::GenericError), + }, + AlgorithmId::EccP256 | AlgorithmId::EccP384 => match ec_data { + Some(ec_data) => { + if ec_data.len() >= key_data.len() { + // This can never be true, but check to be explicit. + return Err(Error::SizeError); + } + + ( + match algorithm { + AlgorithmId::EccP256 => 32, + AlgorithmId::EccP384 => 48, + _ => unreachable!(), + }, + vec![ec_data], + 0x06, + ) + } + _ => return Err(Error::GenericError), + }, + }; + + let mut offset = 0; + + for (i, param) in params.into_iter().enumerate() { + key_data[offset] = param_tag + i as u8; + offset += 1; + + offset += set_length(&mut key_data[offset..], elem_len); + + let padding = elem_len - param.len(); + let remaining = key_data.len() - offset; + + if padding > remaining { + return Err(Error::AlgorithmError); + } + + for b in &mut key_data[offset..offset + padding] { + *b = 0; + } + offset += padding; + key_data[offset..offset + param.len()].copy_from_slice(param); + offset += param.len(); + } + + offset += pin_policy.write(&mut key_data[offset..]); + offset += touch_policy.write(&mut key_data[offset..]); + + let txn = yubikey.begin_transaction()?; + + let status_words = txn + .transfer_data(&templ, &key_data[..offset], 256)? + .status_words(); + + match status_words { + StatusWords::Success => Ok(()), + StatusWords::SecurityStatusError => Err(Error::AuthenticationError), + _ => Err(Error::GenericError), + } +} + +/// Generate an attestation certificate for a stored key. +/// +#[cfg(feature = "untested")] +pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result { + let templ = [0, Ins::Attest.code(), key.into(), 0]; + let txn = yubikey.begin_transaction()?; + let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?; + + if !response.is_success() { + if response.status_words() == StatusWords::NotSupportedError { + return Err(Error::NotSupported); + } else { + return Err(Error::GenericError); + } + } + + if response.data()[0] != 0x30 { + return Err(Error::GenericError); + } + + Ok(Buffer::new(response.data().into())) +} + +/// Sign data using a PIV key +#[cfg(feature = "untested")] +pub fn sign_data( + yubikey: &mut YubiKey, + raw_in: &[u8], + algorithm: AlgorithmId, + key: SlotId, +) -> Result { + let txn = yubikey.begin_transaction()?; + + // don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS + txn.authenticated_command(raw_in, algorithm, key, false) +} + +/// Decrypt data using a PIV key +#[cfg(feature = "untested")] +pub fn decrypt_data( + yubikey: &mut YubiKey, + input: &[u8], + algorithm: AlgorithmId, + key: SlotId, +) -> Result { + let txn = yubikey.begin_transaction()?; + + // don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS + txn.authenticated_command(input, algorithm, key, true) +} diff --git a/src/yubikey.rs b/src/yubikey.rs index bc62cce..fdefc19 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -30,42 +30,35 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#![allow(non_snake_case, non_upper_case_globals)] -#![allow(clippy::too_many_arguments, clippy::missing_safety_doc)] - -#[cfg(feature = "untested")] -use crate::{ - apdu::{Ins, StatusWords, APDU}, - key::{AlgorithmId, SlotId}, - metadata, - mgm::MgmKey, - policy::{PinPolicy, TouchPolicy}, - serialization::*, - Buffer, ObjectId, -}; use crate::{ consts::*, error::Error, readers::{Reader, Readers}, transaction::Transaction, }; -#[cfg(feature = "untested")] -use getrandom::getrandom; use log::{error, info, warn}; use pcsc::Card; -#[cfg(feature = "untested")] -use secrecy::ExposeSecret; use std::{ convert::TryFrom, fmt::{self, Display}, }; + +#[cfg(feature = "untested")] +use crate::{ + apdu::{Ins, StatusWords, APDU}, + metadata, + mgm::MgmKey, + Buffer, ObjectId, +}; +#[cfg(feature = "untested")] +use getrandom::getrandom; +#[cfg(feature = "untested")] +use secrecy::ExposeSecret; #[cfg(feature = "untested")] use std::{ convert::TryInto, time::{SystemTime, UNIX_EPOCH}, }; -#[cfg(feature = "untested")] -use zeroize::Zeroizing; /// PIV Application ID pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08]; @@ -300,34 +293,6 @@ impl YubiKey { Ok(()) } - /// Sign data using a PIV key - #[cfg(feature = "untested")] - pub fn sign_data( - &mut self, - raw_in: &[u8], - algorithm: AlgorithmId, - key: SlotId, - ) -> Result { - let txn = self.begin_transaction()?; - - // don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS - txn.authenticated_command(raw_in, algorithm, key, false) - } - - /// Decrypt data using a PIV key - #[cfg(feature = "untested")] - pub fn decrypt_data( - &mut self, - input: &[u8], - algorithm: AlgorithmId, - key: SlotId, - ) -> Result { - let txn = self.begin_transaction()?; - - // don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS - txn.authenticated_command(input, algorithm, key, true) - } - /// Verify device PIN. pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> { { @@ -540,127 +505,6 @@ impl YubiKey { txn.save_object(object_id, indata) } - /// Import a private encryption or signing key into the YubiKey - // TODO(tarcieri): refactor this into separate methods per key type - #[cfg(feature = "untested")] - pub fn import_private_key( - &mut self, - key: SlotId, - algorithm: AlgorithmId, - p: Option<&[u8]>, - q: Option<&[u8]>, - dp: Option<&[u8]>, - dq: Option<&[u8]>, - qinv: Option<&[u8]>, - ec_data: Option<&[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()]; - - 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)) => { - if p.len() + q.len() + dp.len() + dq.len() + qinv.len() >= key_data.len() { - return Err(Error::SizeError); - } - - ( - match algorithm { - AlgorithmId::Rsa1024 => 64, - AlgorithmId::Rsa2048 => 128, - _ => unreachable!(), - }, - vec![p, q, dp, dq, qinv], - 0x01, - ) - } - _ => return Err(Error::GenericError), - }, - AlgorithmId::EccP256 | AlgorithmId::EccP384 => match ec_data { - Some(ec_data) => { - if ec_data.len() >= key_data.len() { - // This can never be true, but check to be explicit. - return Err(Error::SizeError); - } - - ( - match algorithm { - AlgorithmId::EccP256 => 32, - AlgorithmId::EccP384 => 48, - _ => unreachable!(), - }, - vec![ec_data], - 0x06, - ) - } - _ => return Err(Error::GenericError), - }, - }; - - let mut offset = 0; - - for (i, param) in params.into_iter().enumerate() { - key_data[offset] = param_tag + i as u8; - offset += 1; - - offset += set_length(&mut key_data[offset..], elem_len); - - let padding = elem_len - param.len(); - let remaining = key_data.len() - offset; - - if padding > remaining { - return Err(Error::AlgorithmError); - } - - for b in &mut key_data[offset..offset + padding] { - *b = 0; - } - offset += padding; - key_data[offset..offset + param.len()].copy_from_slice(param); - offset += param.len(); - } - - offset += pin_policy.write(&mut key_data[offset..]); - offset += touch_policy.write(&mut key_data[offset..]); - - let txn = self.begin_transaction()?; - - let status_words = txn - .transfer_data(&templ, &key_data[..offset], 256)? - .status_words(); - - match status_words { - StatusWords::Success => Ok(()), - StatusWords::SecurityStatusError => Err(Error::AuthenticationError), - _ => Err(Error::GenericError), - } - } - - /// Generate an attestation certificate for a stored key. - /// - #[cfg(feature = "untested")] - pub fn attest(&mut self, key: SlotId) -> Result { - let templ = [0, Ins::Attest.code(), key.into(), 0]; - let txn = self.begin_transaction()?; - let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?; - - if !response.is_success() { - if response.status_words() == StatusWords::NotSupportedError { - return Err(Error::NotSupported); - } else { - return Err(Error::GenericError); - } - } - - if response.data()[0] != 0x30 { - return Err(Error::GenericError); - } - - Ok(Buffer::new(response.data().into())) - } - /// Get an auth challenge #[cfg(feature = "untested")] pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {