Merge pull request #62 from iqlusioninc/keys/move-import-and-attest
Move `import` and `attest` to the `key` module
This commit is contained in:
+152
@@ -56,6 +56,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
/// Slot identifiers.
|
/// Slot identifiers.
|
||||||
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
|
/// <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)
|
||||||
|
}
|
||||||
|
|||||||
+12
-168
@@ -30,42 +30,35 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// 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::{
|
use crate::{
|
||||||
consts::*,
|
consts::*,
|
||||||
error::Error,
|
error::Error,
|
||||||
readers::{Reader, Readers},
|
readers::{Reader, Readers},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "untested")]
|
|
||||||
use getrandom::getrandom;
|
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use pcsc::Card;
|
use pcsc::Card;
|
||||||
#[cfg(feature = "untested")]
|
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
fmt::{self, Display},
|
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")]
|
#[cfg(feature = "untested")]
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "untested")]
|
|
||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
/// PIV Application ID
|
/// PIV Application ID
|
||||||
pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
|
pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
|
||||||
@@ -300,34 +293,6 @@ impl YubiKey {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign data using a PIV key
|
|
||||||
#[cfg(feature = "untested")]
|
|
||||||
pub fn sign_data(
|
|
||||||
&mut self,
|
|
||||||
raw_in: &[u8],
|
|
||||||
algorithm: AlgorithmId,
|
|
||||||
key: SlotId,
|
|
||||||
) -> Result<Buffer, Error> {
|
|
||||||
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<Buffer, Error> {
|
|
||||||
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.
|
/// Verify device PIN.
|
||||||
pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> {
|
pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> {
|
||||||
{
|
{
|
||||||
@@ -540,127 +505,6 @@ impl YubiKey {
|
|||||||
txn.save_object(object_id, indata)
|
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.
|
|
||||||
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
|
|
||||||
#[cfg(feature = "untested")]
|
|
||||||
pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> {
|
|
||||||
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
|
/// Get an auth challenge
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
|
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
|
||||||
|
|||||||
Reference in New Issue
Block a user