Move sign/decrypt/import/attest to the key module
These are crypto key-related functions and are better factored under this module.
This commit is contained in:
+152
@@ -56,6 +56,8 @@ use crate::{
|
||||
};
|
||||
#[cfg(feature = "untested")]
|
||||
use log::{error, warn};
|
||||
#[cfg(feature = "untested")]
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// Slot identifiers.
|
||||
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
|
||||
@@ -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.
|
||||
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
|
||||
#[cfg(feature = "untested")]
|
||||
pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer, Error> {
|
||||
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<Buffer, Error> {
|
||||
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<Buffer, Error> {
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user