Merge pull request #128 from BlackHoleFox/develop
Refactor key import function
This commit is contained in:
Generated
+3
-2
@@ -424,7 +424,6 @@ dependencies = [
|
|||||||
"autocfg 1.0.0",
|
"autocfg 1.0.0",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand 0.5.6",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1063,7 +1062,9 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"nom",
|
"nom",
|
||||||
"num-bigint",
|
"num-bigint-dig",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
"p256",
|
"p256",
|
||||||
"p384",
|
"p384",
|
||||||
"pbkdf2",
|
"pbkdf2",
|
||||||
|
|||||||
+3
-1
@@ -31,7 +31,9 @@ getrandom = "0.1"
|
|||||||
hmac = "0.7"
|
hmac = "0.7"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
nom = "5"
|
nom = "5"
|
||||||
num-bigint = { version = "0.2", features = ["rand"] }
|
num-bigint = { version = "0.6", features = ["rand"], package = "num-bigint-dig" }
|
||||||
|
num-traits = "0.2"
|
||||||
|
num-integer = "0.1"
|
||||||
pbkdf2 = "0.3"
|
pbkdf2 = "0.3"
|
||||||
p256 = "0.2"
|
p256 = "0.2"
|
||||||
p384 = "0.1"
|
p384 = "0.1"
|
||||||
|
|||||||
+2
-1
@@ -476,7 +476,8 @@ impl Certificate {
|
|||||||
_ => return Err(Error::InvalidObject),
|
_ => return Err(Error::InvalidObject),
|
||||||
};
|
};
|
||||||
|
|
||||||
let serial = parsed_cert.tbs_certificate.serial.into();
|
let serial = Serial::try_from(parsed_cert.tbs_certificate.serial.to_bytes_be().as_slice())
|
||||||
|
.map_err(|_| Error::InvalidObject)?;
|
||||||
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
|
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
|
||||||
let subject = parsed_cert.tbs_certificate.subject.to_string();
|
let subject = parsed_cert.tbs_certificate.subject.to_string();
|
||||||
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
|
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
|
||||||
|
|||||||
+161
-56
@@ -58,6 +58,12 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use elliptic_curve::weierstrass::PublicKey as EcPublicKey;
|
use elliptic_curve::weierstrass::PublicKey as EcPublicKey;
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
use num_bigint::traits::ModInverse;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
use num_integer::Integer;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
use num_traits::{FromPrimitive, One};
|
||||||
use rsa::{BigUint, RSAPublicKey};
|
use rsa::{BigUint, RSAPublicKey};
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
@@ -69,6 +75,12 @@ const TAG_RSA_MODULUS: u8 = 0x81;
|
|||||||
const TAG_RSA_EXP: u8 = 0x82;
|
const TAG_RSA_EXP: u8 = 0x82;
|
||||||
const TAG_ECC_POINT: u8 = 0x86;
|
const TAG_ECC_POINT: u8 = 0x86;
|
||||||
|
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
const KEYDATA_LEN: usize = 1024;
|
||||||
|
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
const KEYDATA_RSA_EXP: u64 = 65537;
|
||||||
|
|
||||||
/// Slot identifiers.
|
/// Slot identifiers.
|
||||||
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
|
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
@@ -380,6 +392,24 @@ impl AlgorithmId {
|
|||||||
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
|
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||||
Tlv::write(buf, 0x80, &[self.into()])
|
Tlv::write(buf, 0x80, &[self.into()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
fn get_elem_len(self) -> usize {
|
||||||
|
match self {
|
||||||
|
AlgorithmId::Rsa1024 => 64,
|
||||||
|
AlgorithmId::Rsa2048 => 128,
|
||||||
|
AlgorithmId::EccP256 => 32,
|
||||||
|
AlgorithmId::EccP384 => 48,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
fn get_param_tag(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01,
|
||||||
|
AlgorithmId::EccP256 | AlgorithmId::EccP384 => 0x6,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PIV cryptographic keys stored in a YubiKey
|
/// PIV cryptographic keys stored in a YubiKey
|
||||||
@@ -633,72 +663,26 @@ 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")]
|
#[cfg(feature = "untested")]
|
||||||
#[allow(clippy::too_many_arguments)]
|
fn write_key(
|
||||||
pub fn import(
|
|
||||||
yubikey: &mut YubiKey,
|
yubikey: &mut YubiKey,
|
||||||
key: SlotId,
|
slot: SlotId,
|
||||||
algorithm: AlgorithmId,
|
params: Vec<&[u8]>,
|
||||||
p: Option<&[u8]>,
|
|
||||||
q: Option<&[u8]>,
|
|
||||||
dp: Option<&[u8]>,
|
|
||||||
dq: Option<&[u8]>,
|
|
||||||
qinv: Option<&[u8]>,
|
|
||||||
ec_data: Option<&[u8]>,
|
|
||||||
pin_policy: PinPolicy,
|
pin_policy: PinPolicy,
|
||||||
touch_policy: TouchPolicy,
|
touch_policy: TouchPolicy,
|
||||||
|
algorithm: AlgorithmId,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut key_data = Zeroizing::new(vec![0u8; 1024]);
|
let mut key_data = Buffer::new(vec![0u8; KEYDATA_LEN]);
|
||||||
let templ = [0, Ins::ImportKey.code(), algorithm.into(), key.into()];
|
let templ = [0, Ins::ImportKey.code(), algorithm.into(), slot.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;
|
let mut offset = 0;
|
||||||
|
|
||||||
|
let elem_len = algorithm.get_elem_len();
|
||||||
|
let param_tag = algorithm.get_param_tag();
|
||||||
|
|
||||||
for (i, param) in params.into_iter().enumerate() {
|
for (i, param) in params.into_iter().enumerate() {
|
||||||
offset += Tlv::write_as(
|
offset += Tlv::write_as(
|
||||||
&mut key_data[offset..],
|
&mut key_data[offset..],
|
||||||
param_tag + i as u8,
|
param_tag + (i as u8),
|
||||||
elem_len,
|
elem_len,
|
||||||
|buf| {
|
|buf| {
|
||||||
let padding = elem_len - param.len();
|
let padding = elem_len - param.len();
|
||||||
@@ -726,6 +710,127 @@ pub fn import(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The key data that makes up an RSA key.
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub struct RsaKeyData {
|
||||||
|
/// The secret prime `p`.
|
||||||
|
p: Buffer,
|
||||||
|
/// The secret prime, `q`.
|
||||||
|
q: Buffer,
|
||||||
|
/// D mod (P-1)
|
||||||
|
dp: Buffer,
|
||||||
|
/// D mod (Q-1)
|
||||||
|
dq: Buffer,
|
||||||
|
/// Q^-1 mod P
|
||||||
|
qinv: Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
impl RsaKeyData {
|
||||||
|
/// Generates a new RSA key data set from two randomly generated, secret, primes.
|
||||||
|
///
|
||||||
|
/// Panics if `secret_p` or `secret_q` are invalid primes.
|
||||||
|
pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Self {
|
||||||
|
let p = BigUint::from_bytes_be(secret_p);
|
||||||
|
let q = BigUint::from_bytes_be(secret_q);
|
||||||
|
|
||||||
|
let totient = {
|
||||||
|
let p_t = &p - BigUint::one();
|
||||||
|
let q_t = &p - BigUint::one();
|
||||||
|
|
||||||
|
p_t.lcm(&q_t)
|
||||||
|
};
|
||||||
|
|
||||||
|
let exp = BigUint::from_u64(KEYDATA_RSA_EXP).unwrap();
|
||||||
|
|
||||||
|
let d = exp.mod_inverse(&totient).unwrap();
|
||||||
|
let d = d.to_biguint().unwrap();
|
||||||
|
|
||||||
|
// We calculate the optimization values ahead of time, instead of making the user
|
||||||
|
// do so.
|
||||||
|
|
||||||
|
let dp = &d % (&p - BigUint::one());
|
||||||
|
let dq = &d % (&q - BigUint::one());
|
||||||
|
|
||||||
|
let qinv = q.clone().mod_inverse(&p).unwrap();
|
||||||
|
let (_, qinv) = qinv.to_bytes_be();
|
||||||
|
|
||||||
|
RsaKeyData {
|
||||||
|
p: Zeroizing::new(p.to_bytes_be()),
|
||||||
|
q: Zeroizing::new(q.to_bytes_be()),
|
||||||
|
dp: Zeroizing::new(dp.to_bytes_be()),
|
||||||
|
dq: Zeroizing::new(dq.to_bytes_be()),
|
||||||
|
qinv: Zeroizing::new(qinv),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total_len(&self) -> usize {
|
||||||
|
self.p.len() + self.q.len() + self.dp.len() + self.qinv.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Imports a private RSA encryption or signing key into the YubiKey.
|
||||||
|
///
|
||||||
|
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`.
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn import_rsa_key(
|
||||||
|
yubikey: &mut YubiKey,
|
||||||
|
slot: SlotId,
|
||||||
|
algorithm: AlgorithmId,
|
||||||
|
key_data: RsaKeyData,
|
||||||
|
touch_policy: TouchPolicy,
|
||||||
|
pin_policy: PinPolicy,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match algorithm {
|
||||||
|
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => (),
|
||||||
|
_ => return Err(Error::AlgorithmError),
|
||||||
|
}
|
||||||
|
|
||||||
|
if key_data.total_len() > KEYDATA_LEN {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = vec![
|
||||||
|
key_data.p.as_slice(),
|
||||||
|
key_data.q.as_slice(),
|
||||||
|
key_data.dp.as_slice(),
|
||||||
|
key_data.dq.as_slice(),
|
||||||
|
key_data.qinv.as_slice(),
|
||||||
|
];
|
||||||
|
|
||||||
|
write_key(yubikey, slot, params, pin_policy, touch_policy, algorithm)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Imports a private ECC encryption or signing key into the YubiKey.
|
||||||
|
///
|
||||||
|
/// Errors if `algorithm` isn't `AlgorithmId::EccP256` or ` AlgorithmId::EccP384`.
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn import_ecc_key(
|
||||||
|
yubikey: &mut YubiKey,
|
||||||
|
slot: SlotId,
|
||||||
|
algorithm: AlgorithmId,
|
||||||
|
key_data: &[u8],
|
||||||
|
touch_policy: TouchPolicy,
|
||||||
|
pin_policy: PinPolicy,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match algorithm {
|
||||||
|
AlgorithmId::EccP256 | AlgorithmId::EccP384 => (),
|
||||||
|
_ => return Err(Error::AlgorithmError),
|
||||||
|
}
|
||||||
|
|
||||||
|
if key_data.len() > KEYDATA_LEN {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = vec![key_data];
|
||||||
|
|
||||||
|
write_key(yubikey, slot, params, pin_policy, touch_policy, algorithm)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate an attestation certificate for a stored key.
|
/// Generate an attestation certificate for a stored key.
|
||||||
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
|
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
|
|||||||
Reference in New Issue
Block a user