@@ -122,7 +122,6 @@ pub const TAG_RSA_MODULUS: u8 = 0x81;
|
|||||||
pub const TAG_RSA_EXP: u8 = 0x82;
|
pub const TAG_RSA_EXP: u8 = 0x82;
|
||||||
pub const TAG_ECC_POINT: u8 = 0x86;
|
pub const TAG_ECC_POINT: u8 = 0x86;
|
||||||
|
|
||||||
pub const YKPIV_ALGO_TAG: u8 = 0x80;
|
|
||||||
pub const YKPIV_ALGO_3DES: u8 = 0x03;
|
pub const YKPIV_ALGO_3DES: u8 = 0x03;
|
||||||
|
|
||||||
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
|
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
|
||||||
@@ -158,15 +157,3 @@ pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12;
|
|||||||
pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
|
pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
|
||||||
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
|
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
|
||||||
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
|
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;
|
|
||||||
|
|||||||
+31
-33
@@ -42,6 +42,7 @@ use crate::{
|
|||||||
certificate::{self, Certificate},
|
certificate::{self, Certificate},
|
||||||
consts::*,
|
consts::*,
|
||||||
error::Error,
|
error::Error,
|
||||||
|
policy::{PinPolicy, TouchPolicy},
|
||||||
serialization::*,
|
serialization::*,
|
||||||
settings,
|
settings,
|
||||||
yubikey::YubiKey,
|
yubikey::YubiKey,
|
||||||
@@ -311,6 +312,16 @@ impl From<AlgorithmId> for u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AlgorithmId {
|
||||||
|
/// Writes the `AlgorithmId` in the format the YubiKey expects during key generation.
|
||||||
|
pub(crate) fn write(self, buf: &mut [u8]) -> usize {
|
||||||
|
buf[0] = 0x80;
|
||||||
|
buf[1] = 0x01;
|
||||||
|
buf[2] = self.into();
|
||||||
|
3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// PIV cryptographic keys stored in a YubiKey
|
/// PIV cryptographic keys stored in a YubiKey
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
@@ -408,11 +419,9 @@ pub fn generate(
|
|||||||
yubikey: &mut YubiKey,
|
yubikey: &mut YubiKey,
|
||||||
slot: SlotId,
|
slot: SlotId,
|
||||||
algorithm: AlgorithmId,
|
algorithm: AlgorithmId,
|
||||||
pin_policy: u8,
|
pin_policy: PinPolicy,
|
||||||
touch_policy: u8,
|
touch_policy: TouchPolicy,
|
||||||
) -> Result<GeneratedKey, Error> {
|
) -> Result<GeneratedKey, Error> {
|
||||||
let mut in_data = [0u8; 11];
|
|
||||||
let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0];
|
|
||||||
let setting_roca: settings::BoolValue;
|
let setting_roca: settings::BoolValue;
|
||||||
|
|
||||||
match algorithm {
|
match algorithm {
|
||||||
@@ -460,32 +469,21 @@ pub fn generate(
|
|||||||
|
|
||||||
let txn = yubikey.begin_transaction()?;
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
templ[3] = slot.into();
|
let templ = [0, Ins::GenerateAsymmetric.code(), 0, slot.into()];
|
||||||
|
|
||||||
|
let mut in_data = [0u8; 11];
|
||||||
|
in_data[0] = 0xac;
|
||||||
|
in_data[1] = 3; // length sans this 2-byte header
|
||||||
|
assert_eq!(algorithm.write(&mut in_data[2..]), 3);
|
||||||
let mut offset = 5;
|
let mut offset = 5;
|
||||||
in_data[..offset].copy_from_slice(&[
|
|
||||||
0xac,
|
|
||||||
3, // length sans this 2-byte header
|
|
||||||
YKPIV_ALGO_TAG,
|
|
||||||
1,
|
|
||||||
algorithm.into(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if in_data[4] == 0 {
|
let pin_len = pin_policy.write(&mut in_data[offset..]);
|
||||||
error!("unexpected algorithm");
|
in_data[1] += pin_len as u8;
|
||||||
return Err(Error::AlgorithmError);
|
offset += pin_len;
|
||||||
}
|
|
||||||
|
|
||||||
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
|
let touch_len = touch_policy.write(&mut in_data[offset..]);
|
||||||
in_data[1] += 3;
|
in_data[1] += touch_len as u8;
|
||||||
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_PINPOLICY_TAG, 1, pin_policy]);
|
offset += touch_len;
|
||||||
offset += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
|
let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
|
||||||
|
|
||||||
@@ -498,12 +496,12 @@ pub fn generate(
|
|||||||
return Err(Error::KeyError);
|
return Err(Error::KeyError);
|
||||||
}
|
}
|
||||||
StatusWords::IncorrectParamError => {
|
StatusWords::IncorrectParamError => {
|
||||||
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
|
match pin_policy {
|
||||||
error!("{} (pin policy not supported?)", err_msg);
|
PinPolicy::Default => match touch_policy {
|
||||||
} else if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
|
TouchPolicy::Default => error!("{} (algorithm not supported?)", err_msg),
|
||||||
error!("{} (touch policy not supported?)", err_msg);
|
_ => error!("{} (touch policy not supported?)", err_msg),
|
||||||
} else {
|
},
|
||||||
error!("{} (algorithm not supported?)", err_msg);
|
_ => error!("{} (pin policy not supported?)", err_msg),
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(Error::AlgorithmError);
|
return Err(Error::AlgorithmError);
|
||||||
@@ -513,7 +511,7 @@ pub fn generate(
|
|||||||
return Err(Error::AuthenticationError);
|
return Err(Error::AuthenticationError);
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
error!("{} (error {:x})", err_msg, other.code());
|
error!("{} (error {:?})", err_msg, other);
|
||||||
return Err(Error::GenericError);
|
return Err(Error::GenericError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,8 @@ mod metadata;
|
|||||||
pub mod mgm;
|
pub mod mgm;
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
pub mod msroots;
|
pub mod msroots;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub mod policy;
|
||||||
pub mod readers;
|
pub mod readers;
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
mod serialization;
|
mod serialization;
|
||||||
|
|||||||
+1
-1
@@ -268,7 +268,7 @@ impl MgmKey {
|
|||||||
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
|
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
|
||||||
let mut output = input.to_owned();
|
let mut output = input.to_owned();
|
||||||
TdesEde3::new(GenericArray::from_slice(&self.0))
|
TdesEde3::new(GenericArray::from_slice(&self.0))
|
||||||
.encrypt_block(GenericArray::from_mut_slice(&mut output));
|
.decrypt_block(GenericArray::from_mut_slice(&mut output));
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<PinPolicy> 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<TouchPolicy> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -375,7 +375,7 @@ impl<'tx> Transaction<'tx> {
|
|||||||
) -> Result<Response, Error> {
|
) -> Result<Response, Error> {
|
||||||
let mut in_offset = 0;
|
let mut in_offset = 0;
|
||||||
let mut out_data = vec![];
|
let mut out_data = vec![];
|
||||||
let mut sw = 0;
|
let mut sw;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut this_size = 0xff;
|
let mut this_size = 0xff;
|
||||||
@@ -395,13 +395,13 @@ impl<'tx> Transaction<'tx> {
|
|||||||
.data(&in_data[in_offset..(in_offset + this_size)])
|
.data(&in_data[in_offset..(in_offset + this_size)])
|
||||||
.transmit(self, 261)?;
|
.transmit(self, 261)?;
|
||||||
|
|
||||||
if !response.is_success() && (response.status_words().code() >> 8 != 0x61) {
|
sw = response.status_words().code();
|
||||||
|
|
||||||
|
if !response.is_success() && (sw >> 8 != 0x61) {
|
||||||
// TODO(tarcieri): is this really OK?
|
// TODO(tarcieri): is this really OK?
|
||||||
return Ok(Response::new(sw.into(), out_data));
|
return Ok(Response::new(sw.into(), out_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
sw = response.status_words().code();
|
|
||||||
|
|
||||||
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
|
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
|
||||||
error!(
|
error!(
|
||||||
"output buffer too small: wanted to write {}, max was {}",
|
"output buffer too small: wanted to write {}, max was {}",
|
||||||
|
|||||||
+9
-33
@@ -39,6 +39,7 @@ use crate::{
|
|||||||
key::{AlgorithmId, SlotId},
|
key::{AlgorithmId, SlotId},
|
||||||
metadata,
|
metadata,
|
||||||
mgm::MgmKey,
|
mgm::MgmKey,
|
||||||
|
policy::{PinPolicy, TouchPolicy},
|
||||||
serialization::*,
|
serialization::*,
|
||||||
Buffer, ObjectId,
|
Buffer, ObjectId,
|
||||||
};
|
};
|
||||||
@@ -226,10 +227,12 @@ impl YubiKey {
|
|||||||
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> {
|
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> {
|
||||||
let txn = self.begin_transaction()?;
|
let txn = self.begin_transaction()?;
|
||||||
|
|
||||||
|
const TAG_DYN_AUTH: u8 = 0x7c;
|
||||||
|
|
||||||
// get a challenge from the card
|
// get a challenge from the card
|
||||||
let challenge = APDU::new(Ins::Authenticate)
|
let challenge = APDU::new(Ins::Authenticate)
|
||||||
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
|
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
|
||||||
.data(&[0x7c, 0x02, 0x80, 0x00])
|
.data(&[TAG_DYN_AUTH, 0x02, 0x80, 0x00])
|
||||||
.transmit(&txn, 261)?;
|
.transmit(&txn, 261)?;
|
||||||
|
|
||||||
if !challenge.is_success() || challenge.data().len() < 12 {
|
if !challenge.is_success() || challenge.data().len() < 12 {
|
||||||
@@ -240,7 +243,7 @@ impl YubiKey {
|
|||||||
let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap());
|
let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap());
|
||||||
|
|
||||||
let mut data = [0u8; 22];
|
let mut data = [0u8; 22];
|
||||||
data[0] = 0x7c;
|
data[0] = TAG_DYN_AUTH;
|
||||||
data[1] = 20; // 2 + 8 + 2 +8
|
data[1] = 20; // 2 + 8 + 2 +8
|
||||||
data[2] = 0x80;
|
data[2] = 0x80;
|
||||||
data[3] = 8;
|
data[3] = 8;
|
||||||
@@ -552,28 +555,12 @@ impl YubiKey {
|
|||||||
dq: Option<&[u8]>,
|
dq: Option<&[u8]>,
|
||||||
qinv: Option<&[u8]>,
|
qinv: Option<&[u8]>,
|
||||||
ec_data: Option<&[u8]>,
|
ec_data: Option<&[u8]>,
|
||||||
pin_policy: u8,
|
pin_policy: PinPolicy,
|
||||||
touch_policy: u8,
|
touch_policy: TouchPolicy,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut key_data = Zeroizing::new(vec![0u8; 1024]);
|
let mut key_data = Zeroizing::new(vec![0u8; 1024]);
|
||||||
let templ = [0, Ins::ImportKey.code(), algorithm.into(), key.into()];
|
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 {
|
let (elem_len, params, param_tag) = match algorithm {
|
||||||
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => match (p, q, dp, dq, qinv) {
|
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => match (p, q, dp, dq, qinv) {
|
||||||
(Some(p), Some(q), Some(dp), Some(dq), Some(qinv)) => {
|
(Some(p), Some(q), Some(dp), Some(dq), Some(qinv)) => {
|
||||||
@@ -637,19 +624,8 @@ impl YubiKey {
|
|||||||
offset += param.len();
|
offset += param.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
|
offset += pin_policy.write(&mut key_data[offset..]);
|
||||||
key_data[offset] = YKPIV_PINPOLICY_TAG;
|
offset += touch_policy.write(&mut key_data[offset..]);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let txn = self.begin_transaction()?;
|
let txn = self.begin_transaction()?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user