use crate::{error::Error, x509::extract_ssh_pubkey_from_x509_certificate, PublicKey};
use x509_parser::der_parser::ber::BerObjectContent;
use x509_parser::der_parser::der::parse_der_integer;
use x509_parser::prelude::*;
use std::convert::TryInto;
const YUBICO_PIV_ROOT_CA: &str = "-----BEGIN CERTIFICATE-----
MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1
YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY
DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg
U2VyaWFsIDI2Mzc1MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMN2
cMTNR6YCdcTFRxuPy31PabRn5m6pJ+nSE0HRWpoaM8fc8wHC+Tmb98jmNvhWNE2E
ilU85uYKfEFP9d6Q2GmytqBnxZsAa3KqZiCCx2LwQ4iYEOb1llgotVr/whEpdVOq
joU0P5e1j1y7OfwOvky/+AXIN/9Xp0VFlYRk2tQ9GcdYKDmqU+db9iKwpAzid4oH
BVLIhmD3pvkWaRA2H3DA9t7H/HNq5v3OiO1jyLZeKqZoMbPObrxqDg+9fOdShzgf
wCqgT3XVmTeiwvBSTctyi9mHQfYd2DwkaqxRnLbNVyK9zl+DzjSGp9IhVPiVtGet
X02dxhQnGS7K6BO0Qe8CAwEAAaNCMEAwHQYDVR0OBBYEFMpfyvLEojGc6SJf8ez0
1d8Cv4O/MA8GA1UdEwQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBCwUAA4IBAQBc7Ih8Bc1fkC+FyN1fhjWioBCMr3vjneh7MLbA6kSoyWF70N3s
XhbXvT4eRh0hvxqvMZNjPU/VlRn6gLVtoEikDLrYFXN6Hh6Wmyy1GTnspnOvMvz2
lLKuym9KYdYLDgnj3BeAvzIhVzzYSeU77/Cupofj093OuAswW0jYvXsGTyix6B3d
bW5yWvyS9zNXaqGaUmP3U9/b6DlHdDogMLu3VLpBB9bm5bjaKWWJYgWltCVgUbFq
Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8
SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7
-----END CERTIFICATE-----";
#[derive(Debug)]
pub struct ValidPIVKey {
pub public_key: PublicKey,
pub firmware: String,
pub serial: u64,
pub touch_policy: u8,
pub pin_policy: u8,
}
fn extract_certificate_extension_data(
public_key: PublicKey,
certificate: &X509Certificate<'_>,
) -> Result<ValidPIVKey, Error> {
let mut firmware: Option<String> = None;
let mut serial: Option<u64> = None;
let mut policies: Option<[u8; 2]> = None;
let extensions = certificate.extensions();
for ext in extensions.iter() {
match ext.oid.to_id_string().as_str() {
"1.3.6.1.4.1.41482.3.3" => {
if ext.value.len() != 3 {
continue;
}
firmware = Some(format!(
"{}.{}.{}",
ext.value[0], ext.value[1], ext.value[2]
));
}
"1.3.6.1.4.1.41482.3.7" => {
let (_, obj) = parse_der_integer(ext.value).map_err(|_| Error::ParsingError)?;
if let BerObjectContent::Integer(s) = obj.content {
if s.len() > 8 {
continue;
}
let mut padded_serial = vec![0; 8 - s.len()];
padded_serial.extend_from_slice(s);
serial = Some(u64::from_be_bytes(
padded_serial.try_into().map_err(|_| Error::ParsingError)?,
));
}
}
"1.3.6.1.4.1.41482.3.8" => {
policies = Some([ext.value[0], ext.value[1]]);
}
_ => (),
}
}
if firmware.is_none() || serial.is_none() || policies.is_none() {
return Err(Error::ParsingError);
}
let policies = policies.unwrap();
let pin_policy = policies[0];
let touch_policy = policies[1];
Ok(ValidPIVKey {
public_key,
firmware: firmware.unwrap(),
serial: serial.unwrap(),
touch_policy,
pin_policy,
})
}
pub fn verify_certificate_chain(
client: &[u8],
intermediate: &[u8],
root_pem: Option<&str>,
) -> Result<ValidPIVKey, Error> {
let root_ca_pem = root_pem.unwrap_or(YUBICO_PIV_ROOT_CA);
let (_, root_ca) = parse_x509_pem(root_ca_pem.as_bytes()).unwrap();
let root_ca = Pem::parse_x509(&root_ca).unwrap();
let (_, parsed_intermediate) =
parse_x509_certificate(intermediate).map_err(|_| Error::ParsingError)?;
let (_, parsed_client) = parse_x509_certificate(client).map_err(|_| Error::ParsingError)?;
parsed_intermediate
.verify_signature(Some(&root_ca.tbs_certificate.subject_pki))
.map_err(|_| Error::InvalidSignature)?;
parsed_client
.verify_signature(Some(&parsed_intermediate.tbs_certificate.subject_pki))
.map_err(|_| Error::InvalidSignature)?;
let public_key = match extract_ssh_pubkey_from_x509_certificate(client) {
Ok(ssh) => ssh,
Err(_) => return Err(Error::ParsingError),
};
extract_certificate_extension_data(public_key, &parsed_client)
}