kanidm_lib_crypto/
mtls.rs

1use crate::CryptoError;
2use openssl::asn1;
3use openssl::bn;
4use openssl::ec;
5use openssl::error::ErrorStack as OpenSSLError;
6use openssl::hash;
7use openssl::nid::Nid;
8use openssl::pkey::{PKey, Private};
9use openssl::x509::extension::BasicConstraints;
10use openssl::x509::extension::ExtendedKeyUsage;
11use openssl::x509::extension::KeyUsage;
12use openssl::x509::extension::SubjectAlternativeName;
13use openssl::x509::extension::SubjectKeyIdentifier;
14use openssl::x509::X509NameBuilder;
15use openssl::x509::X509;
16use rustls::pki_types::ServerName;
17use uuid::Uuid;
18
19/// Gets an [ec::EcGroup] for P-256
20pub fn get_group() -> Result<ec::EcGroup, OpenSSLError> {
21    ec::EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)
22}
23
24pub fn build_self_signed_server_and_client_identity(
25    cn: Uuid,
26    domain_name: &str,
27    expiration_days: u32,
28) -> Result<(PKey<Private>, X509), CryptoError> {
29    let ecgroup = get_group()?;
30    let eckey = ec::EcKey::generate(&ecgroup)?;
31    let ca_key = PKey::from_ec_key(eckey)?;
32    let mut x509_name = X509NameBuilder::new()?;
33
34    // x509_name.append_entry_by_text("C", "AU")?;
35    // x509_name.append_entry_by_text("ST", "QLD")?;
36    x509_name.append_entry_by_text("O", "Kanidm Replication")?;
37    x509_name.append_entry_by_text("CN", &cn.as_hyphenated().to_string())?;
38    let x509_name = x509_name.build();
39
40    let mut cert_builder = X509::builder()?;
41    // Yes, 2 actually means 3 here ...
42    cert_builder.set_version(2)?;
43
44    let serial_number = bn::BigNum::from_u32(1).and_then(|serial| serial.to_asn1_integer())?;
45
46    cert_builder.set_serial_number(&serial_number)?;
47    cert_builder.set_subject_name(&x509_name)?;
48    cert_builder.set_issuer_name(&x509_name)?;
49
50    let not_before = asn1::Asn1Time::days_from_now(0)?;
51    cert_builder.set_not_before(&not_before)?;
52    let not_after = asn1::Asn1Time::days_from_now(expiration_days)?;
53    cert_builder.set_not_after(&not_after)?;
54
55    // Do we need pathlen 0?
56    cert_builder.append_extension(BasicConstraints::new().critical().build()?)?;
57    cert_builder.append_extension(
58        KeyUsage::new()
59            .critical()
60            .digital_signature()
61            .key_encipherment()
62            .build()?,
63    )?;
64
65    cert_builder.append_extension(
66        ExtendedKeyUsage::new()
67            .server_auth()
68            .client_auth()
69            .build()?,
70    )?;
71
72    let subject_key_identifier =
73        SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?;
74    cert_builder.append_extension(subject_key_identifier)?;
75
76    // How does rustls interpret this name?
77    let Ok(server_name) = ServerName::try_from(domain_name.to_owned()) else {
78        return Err(CryptoError::InvalidServerName);
79    };
80
81    let subject_alt_name = match server_name {
82        ServerName::DnsName(_) => SubjectAlternativeName::new()
83            .dns(domain_name)
84            .build(&cert_builder.x509v3_context(None, None))?,
85        ServerName::IpAddress(_) => SubjectAlternativeName::new()
86            .ip(domain_name)
87            .build(&cert_builder.x509v3_context(None, None))?,
88        _ => return Err(CryptoError::InvalidServerName),
89    };
90
91    cert_builder.append_extension(subject_alt_name)?;
92
93    cert_builder.set_pubkey(&ca_key)?;
94
95    cert_builder.sign(&ca_key, hash::MessageDigest::sha256())?;
96    let ca_cert = cert_builder.build();
97
98    Ok((ca_key, ca_cert))
99}