1765e11bc0
Re-exports types from the toplevel instead of placing them in individual modules (often which only contain one type). This makes the API easier for users to navigate, while still retaining the same module structure internally. Additionally, this commit uses the `uuid` crate for modeling UUIDs.
251 lines
6.3 KiB
Rust
251 lines
6.3 KiB
Rust
//! Integration tests
|
|
|
|
#![forbid(unsafe_code)]
|
|
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
|
|
|
|
use getrandom::getrandom;
|
|
use lazy_static::lazy_static;
|
|
use log::trace;
|
|
use rsa::{hash::Hash::SHA2_256, PaddingScheme, PublicKey};
|
|
use sha2::{Digest, Sha256};
|
|
use std::{
|
|
convert::{TryFrom, TryInto},
|
|
env,
|
|
sync::Mutex,
|
|
};
|
|
use x509::RelativeDistinguishedName;
|
|
use yubikey::{
|
|
certificate::{Certificate, PublicKeyInfo},
|
|
key::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
|
|
Error, MgmKey, PinPolicy, TouchPolicy, YubiKey,
|
|
};
|
|
|
|
lazy_static! {
|
|
/// Provide thread-safe access to a YubiKey
|
|
static ref YUBIKEY: Mutex<YubiKey> = init_yubikey();
|
|
}
|
|
|
|
/// One-time test initialization and setup
|
|
fn init_yubikey() -> Mutex<YubiKey> {
|
|
// Only show logs if `RUST_LOG` is set
|
|
if env::var("RUST_LOG").is_ok() {
|
|
env_logger::builder().format_timestamp(None).init();
|
|
}
|
|
|
|
let yubikey = YubiKey::open().unwrap();
|
|
trace!("serial: {}", yubikey.serial());
|
|
trace!("version: {}", yubikey.version());
|
|
|
|
Mutex::new(yubikey)
|
|
}
|
|
|
|
//
|
|
// CCCID support
|
|
//
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_get_cccid() {
|
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
|
|
|
match yubikey.cccid() {
|
|
Ok(cccid) => trace!("CCCID: {:?}", cccid),
|
|
Err(Error::NotFound) => trace!("CCCID not found"),
|
|
Err(err) => panic!("error getting CCCID: {:?}", err),
|
|
}
|
|
}
|
|
|
|
//
|
|
// CHUID support
|
|
//
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_get_chuid() {
|
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
|
|
|
match yubikey.chuid() {
|
|
Ok(chuid) => trace!("CHUID: {:?}", chuid),
|
|
Err(Error::NotFound) => trace!("CHUID not found"),
|
|
Err(err) => panic!("error getting CHUID: {:?}", err),
|
|
}
|
|
}
|
|
|
|
//
|
|
// Device config support
|
|
//
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_get_config() {
|
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
|
let config_result = yubikey.config();
|
|
assert!(config_result.is_ok());
|
|
trace!("config: {:?}", config_result.unwrap());
|
|
}
|
|
|
|
//
|
|
// Cryptographic key support
|
|
//
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_list_keys() {
|
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
|
let keys_result = Key::list(&mut yubikey);
|
|
assert!(keys_result.is_ok());
|
|
trace!("keys: {:?}", keys_result.unwrap());
|
|
}
|
|
|
|
//
|
|
// PIN support
|
|
//
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_verify_pin() {
|
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
|
assert!(yubikey.verify_pin(b"000000").is_err());
|
|
assert!(yubikey.verify_pin(b"123456").is_ok());
|
|
}
|
|
|
|
//
|
|
// Management key support
|
|
//
|
|
|
|
#[cfg(feature = "untested")]
|
|
#[test]
|
|
#[ignore]
|
|
fn test_set_mgmkey() {
|
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
|
|
|
assert!(yubikey.verify_pin(b"123456").is_ok());
|
|
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
|
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
|
|
|
|
// Set a protected management key.
|
|
assert!(MgmKey::generate()
|
|
.unwrap()
|
|
.set_protected(&mut yubikey)
|
|
.is_ok());
|
|
let protected = MgmKey::get_protected(&mut yubikey).unwrap();
|
|
assert!(yubikey.authenticate(MgmKey::default()).is_err());
|
|
assert!(yubikey.authenticate(protected.clone()).is_ok());
|
|
|
|
// Set a manual management key.
|
|
let manual = MgmKey::generate().unwrap();
|
|
assert!(manual.set_manual(&mut yubikey, false).is_ok());
|
|
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
|
assert!(yubikey.authenticate(MgmKey::default()).is_err());
|
|
assert!(yubikey.authenticate(protected.clone()).is_err());
|
|
assert!(yubikey.authenticate(manual.clone()).is_ok());
|
|
|
|
// Set back to the default management key.
|
|
assert!(MgmKey::set_default(&mut yubikey).is_ok());
|
|
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
|
assert!(yubikey.authenticate(protected).is_err());
|
|
assert!(yubikey.authenticate(manual).is_err());
|
|
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
|
|
}
|
|
|
|
//
|
|
// Certificate support
|
|
//
|
|
|
|
fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
|
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
|
|
|
assert!(yubikey.verify_pin(b"123456").is_ok());
|
|
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
|
|
|
|
let slot = SlotId::Retired(RetiredSlotId::R1);
|
|
|
|
// Generate a new key in the selected slot.
|
|
let generated = key::generate(
|
|
&mut yubikey,
|
|
slot,
|
|
algorithm,
|
|
PinPolicy::Default,
|
|
TouchPolicy::Default,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut serial = [0u8; 20];
|
|
getrandom(&mut serial).unwrap();
|
|
|
|
// Generate a self-signed certificate for the new key.
|
|
let extensions: &[x509::Extension<'_, &[u64]>] = &[];
|
|
let cert_result = Certificate::generate_self_signed(
|
|
&mut yubikey,
|
|
slot,
|
|
serial,
|
|
None,
|
|
&[RelativeDistinguishedName::common_name("testSubject")],
|
|
generated,
|
|
extensions,
|
|
);
|
|
|
|
assert!(cert_result.is_ok());
|
|
let cert = cert_result.unwrap();
|
|
trace!("cert: {:?}", cert);
|
|
cert
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn generate_self_signed_rsa_cert() {
|
|
let cert = generate_self_signed_cert(AlgorithmId::Rsa1024);
|
|
|
|
//
|
|
// Verify that the certificate is signed correctly
|
|
//
|
|
|
|
let pubkey = match cert.subject_pki() {
|
|
PublicKeyInfo::Rsa { pubkey, .. } => pubkey,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let data = cert.as_ref();
|
|
let tbs_cert_len = u16::from_be_bytes(data[6..8].try_into().unwrap()) as usize;
|
|
let msg = &data[4..8 + tbs_cert_len];
|
|
let sig = &data[data.len() - 128..];
|
|
|
|
let hash = Sha256::digest(msg);
|
|
|
|
assert!(pubkey
|
|
.verify(
|
|
PaddingScheme::PKCS1v15Sign {
|
|
hash: Some(SHA2_256)
|
|
},
|
|
&hash,
|
|
sig
|
|
)
|
|
.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn generate_self_signed_ec_cert() {
|
|
let cert = generate_self_signed_cert(AlgorithmId::EccP256);
|
|
|
|
//
|
|
// Verify that the certificate is signed correctly
|
|
//
|
|
|
|
let pubkey = match cert.subject_pki() {
|
|
PublicKeyInfo::EcP256(pubkey) => pubkey,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let data = cert.as_ref();
|
|
let tbs_cert_len = data[6] as usize;
|
|
let sig_algo_len = data[7 + tbs_cert_len + 1] as usize;
|
|
let sig_start = 7 + tbs_cert_len + 2 + sig_algo_len + 3;
|
|
let msg = &data[4..7 + tbs_cert_len];
|
|
let sig = p256::ecdsa::Signature::try_from(&data[sig_start..]).unwrap();
|
|
let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey.as_bytes()).unwrap();
|
|
|
|
use p256::ecdsa::signature::Verifier;
|
|
assert!(vk.verify(msg, &sig).is_ok());
|
|
}
|