use std::{num::NonZeroUsize, ops::Shr};
use bherror::traits::{ErrorContext as _, ForeignError as _};
use iref::UriBuf;
use openssl::{
asn1::{Asn1Integer, Asn1Time},
bn::BigNum,
hash::MessageDigest,
pkey::{PKey, Private, Public},
x509::{
extension::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage,
SubjectAlternativeName as OpenSslSubjectAlternativeName, SubjectKeyIdentifier,
},
X509Name, X509NameBuilder, X509VerifyResult, X509,
},
};
use rand::RngCore;
use crate::{Error, Result, X509Trust, X5Chain};
type PrivateKey = PKey<Private>;
type PublicKey = PKey<Public>;
#[derive(Debug)]
struct CertificatePrivateKeyPair {
cert: X509,
private_key: PrivateKey,
}
const VERSION: i32 = 2;
const SERIAL_NUMBER_BITS: i32 = 159;
const MAXIMUM_SERIAL_NUMBER_HEX: &str = "7fffffffffffffffffffffffffffffffffffffff";
const VALIDITY_PERIOD_IN_DAYS: u32 = 365 * 10;
impl CertificatePrivateKeyPair {
fn from_private_key_and_cert(private_key: &str, cert: &str) -> Result<Self> {
let private_key = PrivateKey::private_key_from_pem(private_key.as_bytes())
.foreign_err(|| Error::Builder)
.ctx(|| "couldn't load private key")?;
let cert = X509::from_pem(cert.as_bytes())
.foreign_err(|| Error::Builder)
.ctx(|| "couldn't load certificate")?;
Ok(Self { cert, private_key })
}
pub(crate) fn generate_random_nonzero_bits_big_endian(n_bits: NonZeroUsize) -> Result<Vec<u8>> {
let mut rng = rand::rng();
let bytes: usize = n_bits.get().div_ceil(8);
debug_assert!(bytes >= 1);
let leading_zeros: u32 = (bytes * 8 - n_bits.get()) as u32;
debug_assert!(leading_zeros < 8);
let most_significant_byte_mask: u8 = u8::MAX.shr(leading_zeros);
debug_assert_eq!(most_significant_byte_mask.leading_zeros(), leading_zeros);
debug_assert_eq!(
most_significant_byte_mask.trailing_ones(),
8 - leading_zeros
);
let mut sample = vec![0u8; bytes];
const MAX_ITERATIONS: usize = 256;
for _ in 0..MAX_ITERATIONS {
rng.fill_bytes(&mut sample);
sample[0] &= most_significant_byte_mask;
if !sample.iter().all(|b| *b == 0) {
return Ok(sample);
}
}
Err(bherror::Error::root(Error::Builder)
.ctx("Failed to generate a nonzero random bit vector"))
}
fn generate_random_serial_number() -> Result<Asn1Integer> {
let bits = NonZeroUsize::new(SERIAL_NUMBER_BITS as usize).unwrap();
let serial_number = Self::generate_random_nonzero_bits_big_endian(bits)?;
let serial_number = BigNum::from_slice(&serial_number)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create serial number")?;
debug_assert!(serial_number > BigNum::from_u32(0).unwrap());
debug_assert!(serial_number.num_bits() <= SERIAL_NUMBER_BITS);
debug_assert!(serial_number <= BigNum::from_hex_str(MAXIMUM_SERIAL_NUMBER_HEX).unwrap());
let asn1_integer = serial_number
.to_asn1_integer()
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create asn1 integer")?;
debug_assert!(!asn1_integer.to_bn().unwrap().is_negative());
Ok(asn1_integer)
}
fn issue_certificate(
&self,
subject_public_key: &PublicKey,
subject_name: &X509Name,
subject_alternative_names: &[SubjectAlternativeName],
) -> Result<X509> {
let mut cert_builder = X509::builder()
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create cert builder")?;
cert_builder
.set_version(VERSION)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot set cert version")?;
let serial_number = Self::generate_random_serial_number()?;
cert_builder
.set_serial_number(&serial_number)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot set serial number")?;
cert_builder
.set_pubkey(subject_public_key)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot set public key")?;
cert_builder
.set_subject_name(subject_name)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot set subject name")?;
cert_builder
.set_issuer_name(self.cert.subject_name())
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot set issuer name")?;
let not_before = Asn1Time::days_from_now(0)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create `not_before` time")?;
cert_builder
.set_not_before(¬_before)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot set `not_before` time")?;
let not_after = Asn1Time::days_from_now(VALIDITY_PERIOD_IN_DAYS)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create `not_after` time")?;
cert_builder
.set_not_after(¬_after)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot set `not_after` time")?;
let basic_constraints = BasicConstraints::new();
let basic_constraints = basic_constraints
.build()
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create basic_constraints")?;
cert_builder
.append_extension(basic_constraints)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot append basic constraints")?;
let mut key_usage = KeyUsage::new();
key_usage.digital_signature().non_repudiation().critical();
let key_usage = key_usage
.build()
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create key_usage")?;
cert_builder
.append_extension(key_usage)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot append key usage")?;
let subject_key_identifier = SubjectKeyIdentifier::new();
let subject_key_identifier = subject_key_identifier
.build(&cert_builder.x509v3_context(Some(self.cert.as_ref()), None))
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create subject_key_identifier")?;
cert_builder
.append_extension(subject_key_identifier)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot append subject key identifier")?;
let mut authority_key_identifier = AuthorityKeyIdentifier::new();
authority_key_identifier.keyid(false).issuer(false);
let authority_key_identifier = authority_key_identifier
.build(&cert_builder.x509v3_context(Some(self.cert.as_ref()), None))
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create authority_key_identifier")?;
cert_builder
.append_extension(authority_key_identifier)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot append authority key identifier")?;
if !subject_alternative_names.is_empty() {
let mut subject_alternative_name = OpenSslSubjectAlternativeName::new();
for san in subject_alternative_names {
match san {
SubjectAlternativeName::Uri(uri) => subject_alternative_name.uri(uri),
};
}
let subject_alternative_name = subject_alternative_name
.build(&cert_builder.x509v3_context(Some(self.cert.as_ref()), None))
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot create `subject_alternative_name`")?;
cert_builder
.append_extension(subject_alternative_name)
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot append `subject_alternative_name`")?;
}
let issuer_private_key = self.private_key.as_ref();
cert_builder
.sign(issuer_private_key, MessageDigest::sha256())
.foreign_err(|| Error::Builder)
.ctx(|| "Cannot sign certificate")?;
Ok(cert_builder.build())
}
}
#[non_exhaustive]
enum SubjectAlternativeName {
Uri(UriBuf),
}
#[derive(Debug)]
pub struct Builder {
intermediary_key_pair: CertificatePrivateKeyPair,
trusted_root_certificate: X509,
}
impl Builder {
pub fn new(
intermediary_private_key: &str,
intermediary_certificate: &str,
trusted_root_certificate: &str,
) -> Result<Self> {
let trusted_root_certificate = X509::from_pem(trusted_root_certificate.as_bytes())
.foreign_err(|| Error::Builder)
.ctx(|| "invalid trusted root certificate")?;
let verify_relationship = trusted_root_certificate.issued(
X509::from_pem(intermediary_certificate.as_bytes())
.foreign_err(|| Error::Builder)?
.as_ref(),
);
if verify_relationship != X509VerifyResult::OK {
return Err(bherror::Error::root(Error::Builder))
.ctx(|| "intermediary certificate must be issued by trusted root");
}
let intermediary_certificate = std::str::from_utf8(intermediary_certificate.as_bytes())
.foreign_err(|| Error::Builder)
.ctx(|| "couldn't parse intermediary private key")?;
let intermediary_key_pair = CertificatePrivateKeyPair::from_private_key_and_cert(
intermediary_private_key,
intermediary_certificate,
)
.ctx(|| "invalid certificate and private key pair")?;
Ok(Self {
intermediary_key_pair,
trusted_root_certificate,
})
}
pub fn generate_x5chain(&self, leaf_public_key: &str, iss: Option<&UriBuf>) -> Result<X5Chain> {
let leaf_public_key = PublicKey::public_key_from_pem(leaf_public_key.as_bytes())
.foreign_err(|| Error::Builder)
.ctx(|| "couldn't load leaf public key")?;
let mut subject_name = X509NameBuilder::new()
.foreign_err(|| Error::Builder)
.ctx(|| "couldn't create `subject_name`")?;
subject_name
.append_entry_by_text("CN", "issuer")
.foreign_err(|| Error::Builder)
.ctx(|| "couldn't append entry to `subject_name`")?;
let subject_alternative_names = iss
.map(|iss| SubjectAlternativeName::Uri(iss.clone()))
.into_iter()
.collect::<Vec<_>>();
let leaf_certificate = self
.intermediary_key_pair
.issue_certificate(
&leaf_public_key,
&subject_name.build(),
&subject_alternative_names,
)
.ctx(|| "couldn't issue leaf certificate")?;
let intermediary_certificate = self.intermediary_key_pair.cert.clone();
let chain = X5Chain::new(vec![leaf_certificate, intermediary_certificate])?;
let trust = X509Trust::new(vec![self.trusted_root_certificate.clone()]);
chain.verify_against_trusted_roots(&trust)?;
Ok(chain)
}
#[cfg(any(feature = "test-utils", test))]
pub fn dummy() -> Self {
let trusted_root_certificate = "
-----BEGIN CERTIFICATE-----
MIICtzCCAl2gAwIBAgIUXT1Yn4YbUTz3wnpQC8RwexqCuDcwCgYIKoZIzj0EAwIw
ZTELMAkGA1UEBhMCSFIxFDASBgNVBAgMC0dyYWQgWmFncmViMQ8wDQYDVQQHDAZa
YWdyZWIxDTALBgNVBAoMBFRCVEwxETAPBgNVBAsMCFRlYW0gQmVlMQ0wCwYDVQQD
DARyb290MCAXDTI1MTIxMDEzMzMxN1oYDzIxMjUxMTE2MTMzMzE3WjBlMQswCQYD
VQQGEwJIUjEUMBIGA1UECAwLR3JhZCBaYWdyZWIxDzANBgNVBAcMBlphZ3JlYjEN
MAsGA1UECgwEVEJUTDERMA8GA1UECwwIVGVhbSBCZWUxDTALBgNVBAMMBHJvb3Qw
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARkIF0Dd+xzZrIofEhY/Um7TlMLDfpE
og+Moq7unhBn9NT1W/iIVuxiZJR9FTPLctUKjkPiV9rvDvULosK/aRD6o4HoMIHl
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHUFpeGAC0ZFRAQha7ykyHFmqekt
MIGiBgNVHSMEgZowgZeAFHUFpeGAC0ZFRAQha7ykyHFmqektoWmkZzBlMQswCQYD
VQQGEwJIUjEUMBIGA1UECAwLR3JhZCBaYWdyZWIxDzANBgNVBAcMBlphZ3JlYjEN
MAsGA1UECgwEVEJUTDERMA8GA1UECwwIVGVhbSBCZWUxDTALBgNVBAMMBHJvb3SC
FF09WJ+GG1E898J6UAvEcHsagrg3MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD
AgNIADBFAiBT9FLacHRRyUmLa8PdGEHbiJaE9p5rg9rzQSptcgfZKgIhAMVDZkEh
m0u5S+/UL3BnCba7Efw3lkBfTBkdKWgrdzEw
-----END CERTIFICATE-----
";
let private_key = "
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEEj8vDOFiTUR3e8N2mReCtigxZOqxwUwK7a7p8P9UtFoAoGCCqGSM49
AwEHoUQDQgAESIA1hbBh9izx+dXYnCRSso6g3c2peTYOgnzpLtXPIgyQO2/ZYQv5
RfvPNUnMQjxIx/Iyd/FB/DqqHSHN48+rFg==
-----END EC PRIVATE KEY-----
";
let certificate = "
-----BEGIN CERTIFICATE-----
MIICPDCCAeKgAwIBAgIUbxX7OZkOub1NGzC1D88yA6BDK4EwCgYIKoZIzj0EAwIw
ZTELMAkGA1UEBhMCSFIxFDASBgNVBAgMC0dyYWQgWmFncmViMQ8wDQYDVQQHDAZa
YWdyZWIxDTALBgNVBAoMBFRCVEwxETAPBgNVBAsMCFRlYW0gQmVlMQ0wCwYDVQQD
DARyb290MCAXDTI1MTIxMDEzMzMzMloYDzIxMjUxMTE2MTMzMzMyWjBtMQswCQYD
VQQGEwJIUjEUMBIGA1UECAwLR3JhZCBaYWdyZWIxDzANBgNVBAcMBlphZ3JlYjEN
MAsGA1UECgwEVEJUTDERMA8GA1UECwwIVGVhbSBCZWUxFTATBgNVBAMMDGludGVy
bWVkaWFyeTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEiANYWwYfYs8fnV2Jwk
UrKOoN3NqXk2DoJ86S7VzyIMkDtv2WEL+UX7zzVJzEI8SMfyMnfxQfw6qh0hzePP
qxajZjBkMB0GA1UdDgQWBBQnLZcuND2wi4LLgqEkCG/BTzl0TjAfBgNVHSMEGDAW
gBR1BaXhgAtGRUQEIWu8pMhxZqnpLTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1Ud
DwEB/wQEAwIBhjAKBggqhkjOPQQDAgNIADBFAiEA20t9kGAyOgDv/m4/GO09OVV2
EOpMWMcM9L4uOaPcoZgCIAeoA48SV5eN5TukS8UF8Q5Iu6y1zla1/SLb5isn3WYr
-----END CERTIFICATE-----
";
Self::new(private_key, certificate, trusted_root_certificate).unwrap()
}
}
#[cfg(test)]
mod tests {
use iref::UriBuf;
use super::Builder;
#[test]
fn dummy_generates_valid_x5chain() {
let public_key = "-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFIG72O1w04AJgPP/7D8j2oJsOlFD
lbTn6vhkz27afs3GyXfRCsdaMirozmhYm94VB4IdwyVYtSVz6rce4Ut+hg==
-----END PUBLIC KEY-----";
let iss = UriBuf::new("https://example.com/issuer".into()).unwrap();
Builder::dummy()
.generate_x5chain(public_key, Some(&iss))
.unwrap();
}
}