Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sshcerts"
version = "0.13.2"
version = "0.14.0"
authors = ["Mitchell Grenier <[email protected]>"]
edition = "2021"
license-file = "LICENSE"
Expand Down Expand Up @@ -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"
Expand All @@ -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 }
Expand All @@ -84,7 +86,6 @@ authenticator = { version = "0.4.0-alpha.24", 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}
Expand All @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions benches/certs_per_second.rs
Original file line number Diff line number Diff line change
@@ -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];
Expand Down
26 changes: 16 additions & 10 deletions examples/yk-provision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -34,11 +29,22 @@ 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);
match alg {
"p256" => match yk.provision::<p256::NistP256>(&slot, subject, policy, PinPolicy::Never) {
Ok(pk) => {
println!("New hardware backed SSH Public Key: {}", pk);
}
Err(e) => panic!("Could not provision device with new key: {:?}", e),
},
_ => {
println!("Using P384");
match yk.provision::<p384::NistP384>(&slot, subject, policy, PinPolicy::Never) {
Ok(pk) => {
println!("New hardware backed SSH Public Key: {}", pk);
}
Err(e) => panic!("Could not provision device with new key: {:?}", e),
}
}
Err(e) => panic!("Could not provision device with new key: {:?}", e),
}
}

Expand Down
101 changes: 69 additions & 32 deletions src/yubikey/piv/management.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
use crate::PublicKey;

use der::oid::ObjectIdentifier;
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 der::Encode;
use x509_cert::name::Name;
use x509_cert::{serial_number::SerialNumber, time::Validity};

use std::str::FromStr;
use yubikey::certificate::yubikey_signer;

use x509_parser::der_parser::asn1_rs::ToDer;

#[derive(Debug)]
/// A struct that allows the generation of CSRs via the rcgen library. This is
/// only used when calling the `generate_csr` function.
Expand All @@ -21,16 +29,34 @@ pub struct CSRSigner {
algorithm: AlgorithmId,
}

const NISTP256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
const SECG384_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();
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hasn't been tested

let pki = cert.subject_pki();
let (public_key, algorithm) = match pki.algorithm.oid {
NISTP256_OID => {
// This is the OID for ECDSA with SHA256
(
pki.subject_public_key.raw_bytes().to_der_vec().unwrap(),
AlgorithmId::EccP256,
)
}
SECG384_OID => {
// This is the OID for ECDSA with SHA384
(
pki.subject_public_key.raw_bytes().to_der_vec().unwrap(),
AlgorithmId::EccP384,
)
}
_ => {
panic!("Unsupported algorithm");
}
};

Self {
Expand All @@ -54,7 +80,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 {
Expand Down Expand Up @@ -108,9 +135,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<PublicKeyInfo> {
pub fn configured(&mut self, slot: &SlotId) -> Result<Certificate> {
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
Expand All @@ -122,7 +149,9 @@ impl super::Yubikey {
/// Fetch the certificate from a given Yubikey slot.
pub fn fetch_certificate(&mut self, slot: &SlotId) -> Result<Vec<u8>> {
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.
Expand All @@ -142,45 +171,52 @@ impl super::Yubikey {
/// Generate CSR for slot
pub fn generate_csr(&mut self, slot: &SlotId, common_name: &str) -> Result<Vec<u8>> {
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();
params.alg = match pki.algorithm.oid {
NISTP256_OID => &rcgen::PKCS_ECDSA_P256_SHA256,
SECG384_OID => &rcgen::PKCS_ECDSA_P384_SHA384,
_ => return Err(Error::Unsupported),
};
params
.distinguished_name
.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<KT: yubikey_signer::KeyType>(
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we hide this away and just provide two non-generic functions for p256 and p384

&mut self,
slot: &SlotId,
common_name: &str,
alg: AlgorithmId,
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<PublicKey> {
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; 8]).unwrap(),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should generate a random serial

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)
Expand All @@ -191,11 +227,12 @@ 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<Vec<u8>> {
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 (slot_alg, hash_alg) = match pki.algorithm.oid {
NISTP256_OID => (AlgorithmId::EccP256, &digest::SHA256),
SECG384_OID => (AlgorithmId::EccP384, &digest::SHA384),
_ => return Err(Error::Unprovisioned),
};

if slot_alg != alg {
Expand Down