use super::{SelfSignedData, SelfSignedKeyKind};
use crate::dep::boring::{
asn1::{Asn1Object, Asn1ObjectRef, Asn1Time},
bn::{BigNum, MsbOption},
ec::{EcGroup, EcKey},
hash::MessageDigest,
nid::Nid,
pkey::{Id, PKey, PKeyRef, Private},
rand::rand_bytes,
rsa::Rsa,
x509::{
X509, X509Extension, X509NameBuilder, X509Ref,
extension::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
SubjectKeyIdentifier,
},
},
};
use crate::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use rama_core::error::{BoxError, ErrorContext};
use rama_core::telemetry::tracing;
use rama_net::address::Domain;
#[expect(clippy::needless_pass_by_value)]
pub(super) fn self_signed_server_auth(
data: SelfSignedData,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
let (ca_cert, ca_key) = self_signed_server_auth_gen_ca(&data)?;
let (cert, key) = self_signed_server_auth_gen_cert(&data, &ca_cert, &ca_key)?;
let cert_der = CertificateDer::from(
cert.to_der()
.context("boring self-signed: serialize leaf cert to DER")?,
);
let ca_der = CertificateDer::from(
ca_cert
.to_der()
.context("boring self-signed: serialize ca cert to DER")?,
);
let key_pkcs8 = key
.private_key_to_der_pkcs8()
.context("boring self-signed: serialize leaf key to PKCS#8 DER")?;
let key_der: PrivateKeyDer<'static> = PrivatePkcs8KeyDer::from(key_pkcs8).into();
Ok((vec![cert_der, ca_der], key_der))
}
pub fn aki_from_ca_pubkey_keyid(ca_cert: &X509Ref) -> Result<X509Extension, BoxError> {
let digest = ca_cert
.pubkey_digest(MessageDigest::sha1())
.context("compute SHA-1 of CA SubjectPublicKey BIT STRING")?;
let keyid: &[u8] = &digest[..];
debug_assert_eq!(keyid.len(), 20, "SHA-1 digest must be 20 bytes");
let mut payload = Vec::with_capacity(4 + keyid.len());
payload.push(0x30); payload.push((2 + keyid.len()) as u8);
payload.push(0x80); payload.push(keyid.len() as u8);
payload.extend_from_slice(keyid);
let aki_oid =
Asn1Object::from_str("2.5.29.35").context("construct AuthorityKeyIdentifier OID object")?;
X509Extension::from_der_payload(aki_oid.as_ref(), false, &payload)
.context("build AuthorityKeyIdentifier extension from raw DER payload")
}
pub fn signing_digest_for(key: &PKeyRef<Private>) -> MessageDigest {
match key.id() {
Id::ED25519 | Id::ED448 => {
unsafe { MessageDigest::from_ptr(std::ptr::null()) }
}
Id::EC => match key.ec_key().ok().and_then(|ec| ec.group().curve_name()) {
Some(Nid::SECP521R1) => MessageDigest::sha512(),
Some(Nid::SECP384R1) => MessageDigest::sha384(),
_ => MessageDigest::sha256(),
},
_ => MessageDigest::sha256(),
}
}
fn generate_self_signed_key(kind: SelfSignedKeyKind) -> Result<PKey<Private>, BoxError> {
fn ec(curve: Nid) -> Result<PKey<Private>, BoxError> {
let group =
EcGroup::from_curve_name(curve).context("create EC group for self-signed key")?;
let ec_key = EcKey::generate(&group).context("generate EC key for self-signed key")?;
PKey::from_ec_key(ec_key).context("create private key from generated EC key")
}
match kind {
SelfSignedKeyKind::Rsa2048 => {
let rsa = Rsa::generate(2048).context("generate 2048-bit RSA key")?;
PKey::from_rsa(rsa).context("create private key from 2048-bit RSA key")
}
SelfSignedKeyKind::Rsa4096 => {
let rsa = Rsa::generate(4096).context("generate 4096-bit RSA key")?;
PKey::from_rsa(rsa).context("create private key from 4096-bit RSA key")
}
SelfSignedKeyKind::EcP256 => ec(Nid::X9_62_PRIME256V1),
SelfSignedKeyKind::EcP384 => ec(Nid::SECP384R1),
SelfSignedKeyKind::EcP521 => ec(Nid::SECP521R1),
SelfSignedKeyKind::Ed25519 => {
let mut seed = [0_u8; 32];
rand_bytes(&mut seed).context("generate Ed25519 private key bytes")?;
PKey::from_ed25519_private_key(&seed)
.context("create private key from Ed25519 key bytes")
}
}
}
pub fn self_signed_server_auth_gen_cert(
data: &SelfSignedData,
ca_cert: &X509,
ca_privkey: &PKey<Private>,
) -> Result<(X509, PKey<Private>), BoxError> {
let privkey = generate_self_signed_key(data.key_kind)?;
let common_name = data
.common_name
.clone()
.unwrap_or_else(|| Domain::from_static("localhost"));
let mut x509_name = X509NameBuilder::new().context("create x509 name builder")?;
x509_name
.append_entry_by_nid(
Nid::ORGANIZATIONNAME,
data.organisation_name.as_deref().unwrap_or("Anonymous"),
)
.context("append organisation name to x509 name builder")?;
x509_name
.append_entry_by_nid(Nid::COMMONNAME, common_name.as_str())
.context("append common name to x509 name builder")?;
let x509_name = x509_name.build();
let mut cert_builder = X509::builder().context("create x509 (cert) builder")?;
cert_builder
.set_version(2)
.context("x509 cert builder: set version = 2")?;
let serial_number = {
let mut serial = BigNum::new().context("x509 cert builder: create big num (serial")?;
serial
.rand(159, MsbOption::MAYBE_ZERO, false)
.context("x509 cert builder: randomise serial number (big num)")?;
serial
.to_asn1_integer()
.context("x509 cert builder: convert serial to ASN1 integer")?
};
cert_builder
.set_serial_number(&serial_number)
.context("x509 cert builder: set serial number")?;
cert_builder
.set_issuer_name(ca_cert.subject_name())
.context("x509 cert builder: set issuer name")?;
cert_builder
.set_pubkey(&privkey)
.context("x509 cert builder: set pub key")?;
cert_builder
.set_subject_name(&x509_name)
.context("x509 cert builder: set subject name")?;
cert_builder
.set_pubkey(&privkey)
.context("x509 cert builder: set public key using private key (ref)")?;
let not_before =
Asn1Time::days_from_now(0).context("x509 cert builder: create ASN1Time for today")?;
cert_builder
.set_not_before(¬_before)
.context("x509 cert builder: set not before to today")?;
let not_after = Asn1Time::days_from_now(90)
.context("x509 cert builder: create ASN1Time for 90 days in future")?;
cert_builder
.set_not_after(¬_after)
.context("x509 cert builder: set not after to 90 days in future")?;
cert_builder
.append_extension(
BasicConstraints::new()
.build()
.context("x509 cert builder: build basic constraints")?
.as_ref(),
)
.context("x509 cert builder: add basic constraints as x509 extension")?;
cert_builder
.append_extension(
KeyUsage::new()
.critical()
.non_repudiation()
.digital_signature()
.key_encipherment()
.build()
.context("x509 cert builder: create key usage")?
.as_ref(),
)
.context("x509 cert builder: add key usage x509 extension")?;
let mut subject_alt_name = SubjectAlternativeName::new();
subject_alt_name.dns(common_name.as_str());
for extra_san in data.subject_alternative_names.iter().flatten() {
if extra_san.as_str() != common_name.as_str() {
subject_alt_name.dns(extra_san.as_str());
}
}
let subject_alt_name = subject_alt_name
.build(&cert_builder.x509v3_context(Some(ca_cert), None))
.context("x509 cert builder: build subject alt name")?;
cert_builder
.append_extension(subject_alt_name.as_ref())
.context("x509 cert builder: add subject alt name")?;
let subject_key_identifier = SubjectKeyIdentifier::new()
.build(&cert_builder.x509v3_context(Some(ca_cert), None))
.context("x509 cert builder: build subject key id")?;
cert_builder
.append_extension(subject_key_identifier.as_ref())
.context("x509 cert builder: add subject key id x509 extension")?;
if ca_cert.subject_key_id().is_some() {
let auth_key_identifier = AuthorityKeyIdentifier::new()
.keyid(false)
.issuer(false)
.build(&cert_builder.x509v3_context(Some(ca_cert), None))
.context("x509 cert builder: build auth key id")?;
cert_builder
.append_extension(auth_key_identifier.as_ref())
.context("x509 cert builder: set auth key id extension")?;
} else {
let auth_key_identifier = aki_from_ca_pubkey_keyid(ca_cert)?;
cert_builder
.append_extension(auth_key_identifier.as_ref())
.context("x509 cert builder: set derived auth key id extension")?;
}
cert_builder
.sign(ca_privkey, signing_digest_for(ca_privkey))
.context("x509 cert builder: sign cert")?;
let cert = cert_builder.build();
Ok((cert, privkey))
}
pub fn self_signed_server_auth_gen_ca(
data: &SelfSignedData,
) -> Result<(X509, PKey<Private>), BoxError> {
let privkey = generate_self_signed_key(data.key_kind)?;
let mut x509_name = X509NameBuilder::new().context("create x509 name builder")?;
x509_name
.append_entry_by_nid(
Nid::ORGANIZATIONNAME,
data.organisation_name.as_deref().unwrap_or("Anonymous"),
)
.context("append organisation name to x509 name builder")?;
if let Some(cn) = data.common_name.as_ref() {
x509_name
.append_entry_by_nid(Nid::COMMONNAME, cn.as_str())
.context("append common name to x509 name builder")?;
}
let x509_name = x509_name.build();
let mut ca_cert_builder = X509::builder().context("create x509 (cert) builder")?;
ca_cert_builder
.set_version(2)
.context("x509 cert builder: set version = 2")?;
let serial_number = {
let mut serial = BigNum::new().context("x509 cert builder: create big num (serial")?;
serial
.rand(159, MsbOption::MAYBE_ZERO, false)
.context("x509 cert builder: randomise serial number (big num)")?;
serial
.to_asn1_integer()
.context("x509 cert builder: convert serial to ASN1 integer")?
};
ca_cert_builder
.set_serial_number(&serial_number)
.context("x509 cert builder: set serial number")?;
ca_cert_builder
.set_subject_name(&x509_name)
.context("x509 cert builder: set subject name")?;
ca_cert_builder
.set_issuer_name(&x509_name)
.context("x509 cert builder: set issuer (self-signed")?;
ca_cert_builder
.set_pubkey(&privkey)
.context("x509 cert builder: set public key using private key (ref)")?;
let not_before =
Asn1Time::days_from_now(0).context("x509 cert builder: create ASN1Time for today")?;
ca_cert_builder
.set_not_before(¬_before)
.context("x509 cert builder: set not before to today")?;
let not_after = Asn1Time::days_from_now(365 * 20)
.context("x509 cert builder: create ASN1Time for 20 years in future")?;
ca_cert_builder
.set_not_after(¬_after)
.context("x509 cert builder: set not after to 20 years in future")?;
ca_cert_builder
.append_extension(
BasicConstraints::new()
.critical()
.ca()
.build()
.context("x509 cert builder: build basic constraints")?
.as_ref(),
)
.context("x509 cert builder: add basic constraints as x509 extension")?;
ca_cert_builder
.append_extension(
KeyUsage::new()
.critical()
.key_cert_sign()
.crl_sign()
.build()
.context("x509 cert builder: create key usage")?
.as_ref(),
)
.context("x509 cert builder: add key usage x509 extension")?;
let subject_key_identifier = SubjectKeyIdentifier::new()
.build(&ca_cert_builder.x509v3_context(None, None))
.context("x509 cert builder: build subject key id")?;
ca_cert_builder
.append_extension(subject_key_identifier.as_ref())
.context("x509 cert builder: add subject key id x509 extension")?;
ca_cert_builder
.sign(&privkey, signing_digest_for(&privkey))
.context("x509 cert builder: sign cert")?;
let cert = ca_cert_builder.build();
Ok((cert, privkey))
}
const OID_TLS_FEATURE: &str = "1.3.6.1.5.5.7.1.24";
const OID_SCT_LIST: &str = "1.3.6.1.4.1.11129.2.4.2";
fn mirror_strip_oid_texts() -> Vec<String> {
[OID_TLS_FEATURE, OID_SCT_LIST]
.into_iter()
.filter_map(|oid| Asn1Object::from_str(oid).ok().map(|obj| obj.to_string()))
.collect()
}
fn should_strip_mirrored_extension(
ext_nid: Nid,
ext_obj: &Asn1ObjectRef,
strip_oid_texts: &[String],
) -> bool {
if ext_nid == Nid::CRL_DISTRIBUTION_POINTS
|| ext_nid == Nid::INFO_ACCESS
|| ext_nid == Nid::FRESHEST_CRL
{
return true;
}
let ext_text = ext_obj.to_string();
strip_oid_texts.contains(&ext_text)
}
pub fn self_signed_server_auth_mirror_cert(
source_cert: &X509Ref,
ca_cert: &X509,
ca_privkey: &PKey<Private>,
) -> Result<(X509, PKey<Private>), BoxError> {
self_signed_server_auth_mirror_cert_with_extensions(source_cert, ca_cert, ca_privkey, &[])
}
pub fn self_signed_server_auth_mirror_cert_with_extensions(
source_cert: &X509Ref,
ca_cert: &X509,
ca_privkey: &PKey<Private>,
extra_extensions: &[X509Extension],
) -> Result<(X509, PKey<Private>), BoxError> {
let source_pubkey = source_cert
.public_key()
.context("x509 cert builder: read source public key")?;
let privkey = match source_pubkey.id() {
Id::RSA | Id::RSAPSS => {
let bits = source_pubkey.bits().max(2048);
let rsa =
Rsa::generate(bits).with_context(|| format!("generate {bits}-bit RSA key"))?;
PKey::from_rsa(rsa)
.with_context(|| format!("create private key from {bits}-bit RSA key"))?
}
Id::EC => {
let source_ec_key = source_pubkey
.ec_key()
.context("x509 cert builder: read source EC key")?;
let ec_key = EcKey::generate(source_ec_key.group())
.context("x509 cert builder: generate mirrored EC key")?;
PKey::from_ec_key(ec_key)
.context("x509 cert builder: create private key from EC key")?
}
Id::ED25519 => {
let mut key = [0_u8; 32];
rand_bytes(&mut key).context("generate Ed25519 private key bytes")?;
PKey::from_ed25519_private_key(&key)
.context("create private key from Ed25519 key bytes")?
}
other => {
tracing::debug!(
key_type = ?other,
"source cert key type cannot serve as a TLS leaf key; using RSA-2048 for the mirrored leaf"
);
let rsa = Rsa::generate(2048).context("generate fallback 2048 RSA key")?;
PKey::from_rsa(rsa).context("create private key from fallback 2048 RSA key")?
}
};
let mut cert_builder = X509::builder().context("create x509 (cert) builder")?;
cert_builder
.set_version(2)
.context("x509 cert builder: set version = 2")?;
let serial_number = {
let mut serial = BigNum::new().context("x509 cert builder: create big num (serial")?;
serial
.rand(159, MsbOption::MAYBE_ZERO, false)
.context("x509 cert builder: randomise serial number (big num)")?;
serial
.to_asn1_integer()
.context("x509 cert builder: convert serial to ASN1 integer")?
};
cert_builder
.set_serial_number(&serial_number)
.context("x509 cert builder: set serial number")?;
cert_builder
.set_issuer_name(ca_cert.subject_name())
.context("x509 cert builder: set issuer name from CA")?;
cert_builder
.set_subject_name(source_cert.subject_name())
.context("x509 cert builder: set mirrored subject name")?;
cert_builder
.set_pubkey(&privkey)
.context("x509 cert builder: set public key using generated private key (ref)")?;
let not_before = if source_cert.not_before() < ca_cert.not_before() {
ca_cert.not_before()
} else {
source_cert.not_before()
};
let not_after = if source_cert.not_after() > ca_cert.not_after() {
ca_cert.not_after()
} else {
source_cert.not_after()
};
cert_builder
.set_not_before(not_before)
.context("x509 cert builder: set mirrored not-before (clamped to CA)")?;
cert_builder
.set_not_after(not_after)
.context("x509 cert builder: set mirrored not-after (clamped to CA)")?;
let source_had_ski = source_cert.subject_key_id().is_some();
let source_had_aki = source_cert.authority_key_id().is_some();
let strip_oid_texts = mirror_strip_oid_texts();
for source_ext in source_cert.extensions() {
let ext_nid = source_ext.object().nid();
if ext_nid == Nid::SUBJECT_KEY_IDENTIFIER || ext_nid == Nid::AUTHORITY_KEY_IDENTIFIER {
tracing::trace!(
?ext_nid,
"skip source key identifier extension (will regenerate if applicable)"
);
continue;
}
if should_strip_mirrored_extension(ext_nid, source_ext.object(), &strip_oid_texts) {
tracing::trace!(
?ext_nid,
"skip source extension invalid for a re-signed MITM leaf \
(issuer-bound revocation/authority pointer, or assertion we cannot honour)"
);
continue;
}
cert_builder
.append_extension_der_payload(
source_ext.object(),
source_ext.critical(),
source_ext.data().as_slice(),
)
.context("x509 cert builder: append mirrored source extension")?;
}
if source_had_ski {
let subject_key_identifier = SubjectKeyIdentifier::new()
.build(&cert_builder.x509v3_context(Some(ca_cert), None))
.context("x509 cert builder: build mirrored subject key identifier")?;
cert_builder
.append_extension(subject_key_identifier.as_ref())
.context("x509 cert builder: append mirrored subject key identifier")?;
}
if source_had_aki {
if ca_cert.subject_key_id().is_some() {
let auth_key_identifier = AuthorityKeyIdentifier::new()
.keyid(false)
.issuer(false)
.build(&cert_builder.x509v3_context(Some(ca_cert), None))
.context("x509 cert builder: build mirrored authority key identifier")?;
cert_builder
.append_extension(auth_key_identifier.as_ref())
.context("x509 cert builder: append mirrored authority key identifier")?;
} else {
let auth_key_identifier = aki_from_ca_pubkey_keyid(ca_cert)?;
cert_builder
.append_extension(auth_key_identifier.as_ref())
.context("x509 cert builder: append derived mirrored authority key identifier")?;
}
}
for ext in extra_extensions {
cert_builder
.append_extension(ext.as_ref())
.context("x509 cert builder: append extra revocation extension")?;
}
cert_builder
.sign(ca_privkey, signing_digest_for(ca_privkey))
.context("x509 cert builder: sign mirrored cert")?;
Ok((cert_builder.build(), privkey))
}
#[cfg(test)]
#[path = "boring_tests.rs"]
mod tests;