Skip to content

Instantly share code, notes, and snippets.

@RJ
Created October 14, 2024 08:40
Show Gist options
  • Save RJ/fcfcd5c9f6f644fdd38ec4731bed8196 to your computer and use it in GitHub Desktop.
Save RJ/fcfcd5c9f6f644fdd38ec4731bed8196 to your computer and use it in GitHub Desktop.
WebTransport compatible self signed certificate generation in rust using openssl bindings
// Ended up not using this because i discovered `wtransport` lib has a SelfSigned builder which does it for me.
use openssl::asn1::Asn1Time;
use openssl::ec::{EcGroup, EcKey};
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::PKey;
use openssl::x509::extension::SubjectAlternativeName;
use openssl::x509::extension::{BasicConstraints, SubjectKeyIdentifier};
use openssl::x509::X509NameBuilder;
use openssl::x509::X509;
use std::fs::File;
use std::io::Write;
/*
Generates a self-signed certificate and private key for new identity.
The certificate conforms to the W3C WebTransport specifications as follows:
The certificate MUST be an X.509v3 certificate as defined in RFC5280.
The key used in the Subject Public Key field MUST be one of the allowed public key algorithms. This function uses the ECDSA P-256 algorithm.
The current time MUST be within the validity period of the certificate as defined in Section 4.1.2.5 of RFC5280.
The total length of the validity period MUST NOT exceed two weeks.
*/
fn generate_self_signed_cert(fqdn: String, ip: String) -> Result<(), Box<dyn std::error::Error>> {
// 1. Generate EC Key with curve prime256v1
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
let ec_key = EcKey::generate(&group)?;
let pkey = PKey::from_ec_key(ec_key)?;
// 2. Create a new X509 Name and set the subject (CN = localhost)
let mut name_builder = X509NameBuilder::new()?;
name_builder.append_entry_by_text("CN", fqdn.as_str())?;
let name = name_builder.build();
// 3. Create a new X509 certificate
let mut x509_builder = X509::builder()?;
x509_builder.set_version(2)?; // X509v3 (version 2)
x509_builder.set_subject_name(&name)?;
x509_builder.set_issuer_name(&name)?; // Self-signed certificate, so issuer is the same as subject
x509_builder.set_pubkey(&pkey)?;
// Set a serial number (this should be unique, but only really matters for revocation i think?)
use openssl::bn::BigNum;
let serial_number = BigNum::from_u32(1)?.to_asn1_integer()?;
x509_builder.set_serial_number(&serial_number)?;
// 4. Set the certificate validity (14 days is the upper limit for webtransport self-certs)
x509_builder.set_not_before(&Asn1Time::days_from_now(0).unwrap())?; // Valid from now
x509_builder.set_not_after(&Asn1Time::days_from_now(14).unwrap())?; // Valid for 14 days
// 5. Add Subject Alternative Names (SAN)
// tossing in localhost and edgegap wildcard for good measure. useful for debugging.
let mut san = SubjectAlternativeName::new();
san.dns("localhost")
.dns("*.pr.edgegap.net")
.ip("127.0.0.1")
.ip(ip.as_str());
let san_extension = san.build(&x509_builder.x509v3_context(None, None))?;
x509_builder.append_extension(san_extension)?;
// 6. Other extensions
let basic_constraints = BasicConstraints::new().critical().ca().build()?; // don't think it strictly needs to be ca=true for webtransport self-cert..
let subject_key_id =
SubjectKeyIdentifier::new().build(&x509_builder.x509v3_context(None, None))?;
x509_builder.append_extension(basic_constraints)?;
x509_builder.append_extension(subject_key_id)?;
// 7. Sign the certificate with the private key using SHA-256
x509_builder.sign(&pkey, MessageDigest::sha256())?;
// 8. Write the private key to key.pem
let private_key_pem = pkey.private_key_to_pem_pkcs8()?;
let mut key_file = File::create("key.pem")?;
key_file.write_all(&private_key_pem)?;
// 9. Write the certificate to cert.pem
let cert = x509_builder.build();
let digest = get_cert_digest_sha256(&cert)?;
println!("Certificate digest: {}", digest);
let cert_pem = cert.to_pem()?;
let mut cert_file = File::create("cert.pem")?;
cert_file.write_all(&cert_pem)?;
println!("Certificate and key generated successfully.");
Ok(())
}
fn get_cert_digest_sha256(cert: &X509) -> Result<String, Box<dyn std::error::Error>> {
let digest = cert.digest(MessageDigest::sha256())?;
let digest_hex: String = digest
.iter()
.flat_map(|&byte| {
[
char::from_digit((byte >> 4) as u32, 16).unwrap(),
char::from_digit((byte & 0xf) as u32, 16).unwrap(),
':',
]
})
.collect();
Ok(digest_hex.trim_end_matches(':').to_string())
}
fn main() {
if let Err(e) = generate_self_signed_cert("example.com".to_string(), "127.0.0.1".to_string()) {
eprintln!("Error: {}", e);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment