diff --git a/Cargo.toml b/Cargo.toml index 6cc0653..543a3ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sshcerts" -version = "0.13.2" +version = "0.14.0" authors = ["Mitchell Grenier "] edition = "2021" license-file = "LICENSE" @@ -48,7 +48,7 @@ fido-lite = ["minicbor", "x509-parser"] rsa-signing = ["simple_asn1", "num-bigint"] x509-support = ["der-parser", "x509", "x509-parser"] yubikey-support = ["rcgen", "yubikey", "yubikey-lite"] -yubikey-lite = ["x509-support"] +yubikey-lite = ["x509-support", "der", "x509-cert"] [dependencies] base64 = "0.13" @@ -61,12 +61,14 @@ simple_asn1 = { version = "0.5", optional = true } num-bigint = { version = "0.4", optional = true } # Dependencies for yubikey-support -yubikey = { version = "0.7", features = ["untested"], optional = true } +yubikey = { version = "0.8", features = ["untested"], optional = true } lexical-core = { version = ">0.7.4", optional = true } rcgen = { version = "0.11", optional = true } x509 = { version = "0.2", optional = true } x509-parser = { version = "0.15", features = ["verify"], optional = true } der-parser = { version = "5", optional = true } +der = { version = "0.7", optional = true } +x509-cert = { version = "0.2", optional = true } # Dependencies for encrypted-keys aes = { version = "0.7", features = ["ctr"], optional = true } @@ -77,14 +79,13 @@ ctr = { version = "0.8", optional = true } minicbor = { version = "0.13", optional = true } # Dependencies for fido-support-mozilla -authenticator = { version = "0.4.0-alpha.24", default-features = false, features = [ +authenticator = { version = "0.4.1", default-features = false, features = [ "crypto_openssl", ], optional = true } # authenticator = { path = "../authenticator-rs", default-features = false, features = [ # "crypto_openssl", # ], optional = true } - # Dependencies for fido-support ctap-hid-fido2 = { version = "3", optional = true } #ctap-hid-fido2 = {git = "https://github.com/gebogebogebo/ctap-hid-fido2", branch="master", optional = true} @@ -96,6 +97,8 @@ env_logger = "0.8.2" hex = "0.4.2" clap = "3.0.5" criterion = "0.3" +p256 = "*" +p384 = "*" [[bench]] name = "certs_per_second" diff --git a/benches/certs_per_second.rs b/benches/certs_per_second.rs index 489c7e5..7efed67 100644 --- a/benches/certs_per_second.rs +++ b/benches/certs_per_second.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; - -use sshcerts::yubikey::{RetiredSlotId, SlotId, Yubikey}; +use sshcerts::yubikey::piv::Yubikey; +use yubikey::piv::{RetiredSlotId, SlotId}; fn generate_certs(n: u64) -> () { let data = [0; 32]; diff --git a/examples/sign-cert-with-yubikey.rs b/examples/sign-cert-with-yubikey.rs index 5d24523..981d987 100644 --- a/examples/sign-cert-with-yubikey.rs +++ b/examples/sign-cert-with-yubikey.rs @@ -36,14 +36,22 @@ fn slot_validator(slot: &str) -> Result<(), String> { struct YubikeySigner { slot: SlotId, + pin: String, + mgm_key: Vec, } impl SSHCertificateSigner for YubikeySigner { fn sign(&self, buffer: &[u8]) -> Option> { let mut yk = Yubikey::new().unwrap(); + yk.unlock(self.pin.as_bytes(), &self.mgm_key).unwrap(); + println!("Unlocking Successful"); + match yk.ssh_cert_signer(buffer, &self.slot) { Ok(sig) => Some(sig), - Err(_) => None, + Err(e) => { + println!("Error signing: {:?}", e); + None + } } } } @@ -79,14 +87,37 @@ fn main() { .required(true) .takes_value(true), ) + .arg( + Arg::new("pin") + .help("Provision this slot with a new private key. The pin number must be passed as parameter here") + .default_value("123456") + .long("pin") + .short('p') + .takes_value(true), + ) + .arg( + Arg::new("management-key") + .help("Provision this slot with a new private key. The pin number must be passed as parameter here") + .default_value("010203040506070801020304050607080102030405060708") + .long("mgmkey") + .short('m') + .takes_value(true), + ) .get_matches(); let slot = slot_parser(matches.value_of("slot").unwrap()).unwrap(); let mut yk = Yubikey::new().unwrap(); + let yk_pubkey = yk.ssh_cert_fetch_pubkey(&slot).unwrap(); + let ssh_pubkey = PublicKey::from_path(matches.value_of("key").unwrap()).unwrap(); + println!("Signing {ssh_pubkey} with {yk_pubkey}"); - let yk_signer = YubikeySigner { slot }; + let yk_signer = YubikeySigner { + slot, + pin: matches.value_of("pin").unwrap().to_string(), + mgm_key: hex::decode(matches.value_of("management-key").unwrap()).unwrap(), + }; let user_cert = Certificate::builder(&ssh_pubkey, CertType::User, &yk_pubkey) .unwrap() diff --git a/examples/yk-provision.rs b/examples/yk-provision.rs index 60c4333..14d5db7 100644 --- a/examples/yk-provision.rs +++ b/examples/yk-provision.rs @@ -3,7 +3,7 @@ use std::env; use clap::{Arg, Command}; use sshcerts::yubikey::piv::Yubikey; -use sshcerts::yubikey::piv::{AlgorithmId, PinPolicy, RetiredSlotId, SlotId, TouchPolicy}; +use sshcerts::yubikey::piv::{PinPolicy, RetiredSlotId, SlotId, TouchPolicy}; use std::convert::TryFrom; @@ -15,11 +15,6 @@ fn provision_new_key( alg: &str, secure: bool, ) { - let alg = match alg { - "p256" => AlgorithmId::EccP256, - _ => AlgorithmId::EccP384, - }; - println!( "Provisioning new {:?} key called [{}] in slot: {:?}", alg, subject, slot @@ -34,10 +29,15 @@ fn provision_new_key( let mut yk = Yubikey::new().unwrap(); yk.unlock(pin.as_bytes(), mgm_key).unwrap(); - match yk.provision(&slot, subject, alg, policy, PinPolicy::Never) { - Ok(pk) => { - println!("New hardware backed SSH Public Key: {}", pk); + let result = match alg { + "p256" => yk.provision::(&slot, subject, policy, PinPolicy::Never), + _ => { + println!("Using P384"); + yk.provision::(&slot, subject, policy, PinPolicy::Never) } + }; + match result { + Ok(pk) => println!("New hardware backed SSH Public Key: {}", pk), Err(e) => panic!("Could not provision device with new key: {:?}", e), } } diff --git a/src/yubikey/piv/management.rs b/src/yubikey/piv/management.rs index 686cb22..a3cc01e 100644 --- a/src/yubikey/piv/management.rs +++ b/src/yubikey/piv/management.rs @@ -2,15 +2,23 @@ use crate::PublicKey; use ring::digest; -use yubikey::certificate::{Certificate, PublicKeyInfo}; +use yubikey::certificate::Certificate; use yubikey::piv::{attest, sign_data as yk_sign_data, AlgorithmId, SlotId}; use yubikey::{MgmKey, YubiKey}; use yubikey::{PinPolicy, TouchPolicy}; -use x509::RelativeDistinguishedName; - use super::{Error, Result}; +use x509_cert::{ + der::{oid::ObjectIdentifier, Encode}, + name::Name, + serial_number::SerialNumber, + time::Validity, +}; + +use std::str::FromStr; +use yubikey::certificate::yubikey_signer; + #[derive(Debug)] /// A struct that allows the generation of CSRs via the rcgen library. This is /// only used when calling the `generate_csr` function. @@ -21,16 +29,44 @@ pub struct CSRSigner { algorithm: AlgorithmId, } +const NISTP256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); +const SECP384_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.34"); + impl CSRSigner { /// Create a new certificate signer based on a Yubikey serial /// and slot pub fn new(serial: u32, slot: SlotId) -> Self { let mut yk = super::Yubikey::open(serial).unwrap(); - let pki = yk.configured(&slot).unwrap(); - let (public_key, algorithm) = match pki { - PublicKeyInfo::Rsa { pubkey: _, .. } => panic!("RSA keys not supported"), - PublicKeyInfo::EcP256(pubkey) => (pubkey.as_bytes().to_vec(), AlgorithmId::EccP256), - PublicKeyInfo::EcP384(pubkey) => (pubkey.as_bytes().to_vec(), AlgorithmId::EccP384), + let cert = yk.configured(&slot).unwrap(); + let pki = cert.subject_pki(); + let oid_alg = pki.algorithm.parameters_oid().unwrap(); + + let (public_key, algorithm) = match oid_alg { + NISTP256_OID => { + // This is the OID for ECDSA with SHA256 + ( + pki.subject_public_key + .raw_bytes() + .to_vec() + .to_der() + .unwrap(), + AlgorithmId::EccP256, + ) + } + SECP384_OID => { + // This is the OID for ECDSA with SHA384 + ( + pki.subject_public_key + .raw_bytes() + .to_vec() + .to_der() + .unwrap(), + AlgorithmId::EccP384, + ) + } + _ => { + panic!("Unsupported algorithm"); + } }; Self { @@ -54,7 +90,8 @@ impl rcgen::RemoteKeyPair for CSRSigner { return Err(rcgen::RcgenError::RemoteKeyError); }; - yk.sign_data(message, self.algorithm, &self.slot).map_err(|_| rcgen::RcgenError::RemoteKeyError) + yk.sign_data(message, self.algorithm, &self.slot) + .map_err(|_| rcgen::RcgenError::RemoteKeyError) } fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { @@ -108,9 +145,9 @@ impl super::Yubikey { } /// Check to see that a provided Yubikey and slot is configured for signing - pub fn configured(&mut self, slot: &SlotId) -> Result { + pub fn configured(&mut self, slot: &SlotId) -> Result { let cert = Certificate::read(&mut self.yk, *slot)?; - Ok(cert.subject_pki().clone()) + Ok(cert) } /// Check to see that a provided Yubikey and slot is configured for signing @@ -122,7 +159,9 @@ impl super::Yubikey { /// Fetch the certificate from a given Yubikey slot. pub fn fetch_certificate(&mut self, slot: &SlotId) -> Result> { let cert = Certificate::read(&mut self.yk, *slot)?; - Ok(cert.as_ref().to_vec()) + Ok(cert.cert.to_der().map_err(|e| { + Error::InternalYubiKeyError(format!("Failed to encode certificate: {}", e)) + })?) } /// Write the certificate from a given Yubikey slot. @@ -142,9 +181,16 @@ impl super::Yubikey { /// Generate CSR for slot pub fn generate_csr(&mut self, slot: &SlotId, common_name: &str) -> Result> { let mut params = rcgen::CertificateParams::new(vec![]); - params.alg = match self.configured(slot)? { - PublicKeyInfo::EcP256(_) => &rcgen::PKCS_ECDSA_P256_SHA256, - PublicKeyInfo::EcP384(_) => &rcgen::PKCS_ECDSA_P384_SHA384, + let cert = self.configured(&slot).unwrap(); + let pki = cert.subject_pki(); + let oid_alg = pki + .algorithm + .parameters_oid() + .map_err(|_| Error::Unsupported)?; + + params.alg = match oid_alg { + NISTP256_OID => &rcgen::PKCS_ECDSA_P256_SHA256, + SECP384_OID => &rcgen::PKCS_ECDSA_P384_SHA384, _ => return Err(Error::Unsupported), }; params @@ -152,35 +198,40 @@ impl super::Yubikey { .push(rcgen::DnType::CommonName, common_name.to_string()); let csr_signer = CSRSigner::new(self.yk.serial().into(), *slot); - params.key_pair = Some(rcgen::KeyPair::from_remote(Box::new(csr_signer)).map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?); + params.key_pair = Some( + rcgen::KeyPair::from_remote(Box::new(csr_signer)) + .map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?, + ); - let csr = rcgen::Certificate::from_params(params).map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?; - let csr = csr.serialize_request_der().map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?; + let csr = rcgen::Certificate::from_params(params) + .map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?; + let csr = csr + .serialize_request_der() + .map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?; Ok(csr) } /// Provisions the YubiKey with a new certificate generated on the device. /// Only keys that are generated this way can use the attestation functionality. - pub fn provision( + pub fn provision( &mut self, slot: &SlotId, common_name: &str, - alg: AlgorithmId, touch_policy: TouchPolicy, pin_policy: PinPolicy, ) -> Result { - let key_info = yubikey::piv::generate(&mut self.yk, *slot, alg, pin_policy, touch_policy)?; - let extensions: &[x509::Extension<'_, &[u64]>] = &[]; + let key_info = + yubikey::piv::generate(&mut self.yk, *slot, KT::ALGORITHM, pin_policy, touch_policy)?; // Generate a self-signed certificate for the new key. - Certificate::generate_self_signed( + Certificate::generate_self_signed::<_, KT>( &mut self.yk, *slot, - [0u8; 20], - None, - &[RelativeDistinguishedName::common_name(common_name)], + SerialNumber::new(&[0; 20]).unwrap(), + Validity::from_now(std::time::Duration::new(3600 * 24 * 3650, 0)).unwrap(), + Name::from_str(&format!("CN={}", common_name)).unwrap(), key_info, - extensions, + |_builder| Ok(()), )?; self.ssh_cert_fetch_pubkey(slot) @@ -191,11 +242,17 @@ impl super::Yubikey { /// If the requested algorithm doesn't match the key in the slot (or the slot /// is empty) this will error. pub fn sign_data(&mut self, data: &[u8], alg: AlgorithmId, slot: &SlotId) -> Result> { - let (slot_alg, hash_alg) = match self.configured(slot) { - Ok(PublicKeyInfo::EcP256(_)) => (AlgorithmId::EccP256, &digest::SHA256), - Ok(PublicKeyInfo::EcP384(_)) => (AlgorithmId::EccP384, &digest::SHA384), - Ok(_) => (AlgorithmId::Rsa2048, &digest::SHA256), // RSAish - Err(_) => return Err(Error::Unprovisioned), + let cert = self.configured(&slot).unwrap(); + let pki = cert.subject_pki(); + let oid_alg = pki + .algorithm + .parameters_oid() + .map_err(|_| Error::Unprovisioned)?; + + let (slot_alg, hash_alg) = match oid_alg { + NISTP256_OID => (AlgorithmId::EccP256, &digest::SHA256), + SECP384_OID => (AlgorithmId::EccP384, &digest::SHA384), + _ => return Err(Error::Unprovisioned), }; if slot_alg != alg {