Rusting Up Your Own Self-Signed Certificate Generator
Luis Soares, M.Sc.
Lead Software Engineer | Blockchain & ZK Protocol Engineer | ?? Rust | C++ | Web3 | Solidity | Golang | Cryptography | Author
If you’ve dipped your toes in the sea of server administration or web development, you’ve likely come across SSL/TLS certificates. They’re the backbone of secure, encrypted connections, transforming our data into jumbled-up messes that, even if intercepted, can’t be read. And while acquiring a certificate from a trusted Certificate Authority (CA) is the norm for production systems, for development and testing, self-signed certificates often do the trick.
Today, I’m excited to walk you through the process of crafting your very own self-signed certificate generator, all using the versatile Rust programming language and the rust-openssl crate.
Setting the?Stage
Before diving in, you'll want to have the rust-openssl crate added to your project's Cargo.toml. If you haven't done so, it's as simple as:
[dependencies]
openssl = "0.10"
Choosing a Key Algorithm: RSA or?ECDSA?
Historically, RSA was the go-to algorithm for generating keys. However, times have changed, and ECDSA (Elliptic Curve Digital Signature Algorithm) is often favored for its combination of security and efficiency. For our adventure, we're setting sail with ECDSA. But don't fret! The transition from RSA to ECDSA is more a side-step than a leap.
Crafting the Certificate
Now, let's embark on our journey of crafting the certificate. At its core, a certificate binds together a public key with an identity (like a website's domain name). When someone requests a connection, the server presents this certificate as proof of its identity.
use openssl::ec::{EcGroup, EcKey};
use openssl::nid::Nid;
use openssl::x509::{X509, X509NameBuilder};
use openssl::x509::extension::{SubjectAlternativeName, KeyUsage, ExtendedKeyUsage};
use openssl::pkey::PKey;
Generating the ECDSA?Key
First, we pick an elliptic curve. For our purpose, prime256v1 is an excellent choice, balancing security and performance.
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
let eckey = EcKey::generate(&group).unwrap();
let pkey = PKey::from_ec_key(eckey).unwrap();
Building the Certificate
Next, we get down to the business of constructing the certificate. Every certificate has details like the issuer, validity period, and subject name. Since ours is self-signed, the issuer and subject are the same.
let mut builder = X509::builder().unwrap();
builder.set_version(2).unwrap();
A certificate isn't complete without a unique serial number. This helps CAs keep track of each issued certificate. For our local purposes, a simple constant like 1 suffices.
builder.set_serial_number(openssl::bn::BigNum::from_u32(1).unwrap().to_asn1_integer().unwrap()).unwrap();
For the subject and issuer, we're going with localhost, making this certificate perfect for local testing.
let mut name = X509NameBuilder::new().unwrap();
name.append_entry_by_text("CN", "localhost").unwrap();
let name = name.build();
builder.set_issuer_name(&name).unwrap();
builder.set_subject_name(&name).unwrap();
We'll also set the certificate's validity period. Let's keep things simple with a one-year lifespan.
let not_before = openssl::asn1::Asn1Time::days_from_now(0).unwrap();
let not_after = openssl::asn1::Asn1Time::days_from_now(365).unwrap();
builder.set_not_before(¬_before).unwrap();
builder.set_not_after(¬_after).unwrap();
For modern SSL/TLS configurations, the Subject Alternative Name (SAN) is essential. We're adding localhost as the SAN to ensure our certificate aligns with the best practices.
let mut san = SubjectAlternativeName::new();
san.dns("localhost");
let extension = san.build(&builder.x509v3_context(None, None)).unwrap();
builder.append_extension(extension).unwrap();
Lastly, we add key usage constraints. This defines what our certificate can be used for, enhancing security.
let key_usage = KeyUsage::new().digital_signature().build().unwrap();
builder.append_extension(key_usage).unwrap();
let server_auth = ExtendedKeyUsage::new().server_auth().build().unwrap();
builder.append_extension(server_auth).unwrap();
Signing and?Storing
To wrap things up, we sign our certificate with the private key and store both to disk.
builder.sign(&pkey, openssl::hash::MessageDigest::sha256()).unwrap();
let certificate = builder.sign(&pkey, openssl::hash::MessageDigest::sha256()).unwrap();
let certificate = builder.build();
Finally, having crafted a fine certificate, it’s time to save it (alongside its private key) so we can put it to good use:
// Save the private key and certificate in PEM format
let mut privkey_file = File::create("localhost.key").unwrap();
let mut cert_file = File::create("localhost.crt").unwrap();
privkey_file.write_all(pkey.private_key_to_pem_pkcs8().unwrap().as_ref()).unwrap();
cert_file.write_all(certificate.to_pem().unwrap().as_ref()).unwrap();
println!("Private key saved to: localhost.key");
println!("Certificate saved to: localhost.crt");
And there we have it! If you run this Rust code, you’ll have your very own self-signed certificate and corresponding ECDSA private key, perfect for local development and testing.
Validating and Testing Your Self-Signed Certificates
Having brewed our very own self-signed certificate, it's essential to not just store it on the shelf but to also give it a whirl and ensure its authenticity. Think of it as a freshly baked loaf of bread; you'd want to make sure it's perfectly baked before serving. Here's how you can validate and test the certificate and private key you've generated.
Using OpenSSL Command-Line Tools
One of the most common and versatile tools to validate certificates is the OpenSSL toolkit. It's essentially the Swiss army knife of cryptography.
You can print out the details of a certificate using the following command:
openssl x509 -in localhost.crt -text -noout
This will display the certificate's content, allowing you to inspect its Subject, Issuer, Validity period, Extensions, and other properties.
2. Validating the Private Key:
openssl ec -in localhost.key -check
领英推荐
openssl x509 -noout -modulus -in localhost.crt | openssl md5 openssl ec -noout -modulus -in localhost.key | openssl md5
If the outputs (MD5 hash values) of both commands match, it confirms that the certificate and private key are a pair.
Testing in a Local Development Environment
It’s not just about how the certificate looks, but also how it performs. Setting up a local HTTPS server is a great way to see your certificate in action.
Using Simple HTTP Servers:
For quick testing, tools like Python’s http.server module or Node.js's http-server can be used with your certificate and private key to serve content over HTTPS.
It's not just about how the certificate looks, but also how it performs. Setting up a local HTTPS server is a great way to see your certificate in action.
Browsers:
For quick testing, tools like Python's http.server module or Node.js's http-server can be used with your certificate and private key to serve content over HTTPS.
Certificate Trust:
When accessing your locally served HTTPS site, modern browsers will likely throw a warning since your certificate isn't signed by a trusted CA. This is expected. Most browsers allow you to proceed after providing a warning, letting you see your certificate in action. It's also an excellent way to test if applications handle self-signed certificates as expected.
Automated Testing
For prolonged local development, you can add your self-signed certificate to your system or browser's "trusted roots" store. This prevents the browser from warning you about the certificate every time. However, always be cautious; only trust certificates when you're sure of their origin.
The openssl Crate in the Limelight
The openssl crate serves as a bridge between the Rust programming language and the OpenSSL library. OpenSSL itself is a titan in the world of security, boasting a storied history spanning decades. Given its crucial role in web security, having a performant and type-safe interface in Rust is immensely valuable. The openssl crate is more than just a wrapper; it integrates OpenSSL's raw power into the Rust paradigm, ensuring memory safety and concurrency benefits.
Deep Dive into?Features
2. SSL/TLS Framework:
3. X.509 Certificate Management:
4. Key Management and Generation:
5. ASN.1 and DER Functionality:
6. Custom Extensions and Plugins: The crate is extensible, allowing for the integration of custom cryptographic methods and extensions, ensuring that developers are not limited by the provided set of features.
Wrapping up
Well, that wraps up our dive into creating self-signed certificates with Rust.?
If you’re looking to get a closer look at the code or perhaps want to try it out yourself, I’ve got you covered.?
You can find the complete implementation over at my GitHub repository: luishsr/rust-keycert .?
Your feedback, suggestions, or contributions are always welcome.?
Check out some interesting hands-on Rust articles:
?? Developing a Fully Functional API Gateway in Rust ? —?Discover how to set up a robust and scalable gateway that stands as the frontline for your microservices. ?? Implementing a Network Traffic Analyzer ? —?Ever wondered about the data packets zooming through your network? Unravel their mysteries with this deep dive into network analysis. ?? Building an Application Container in Rust ? —?Join us in creating a lightweight, performant, and secure container from scratch! Docker’s got nothing on this. ??
Happy coding, and keep those Rust gears turning! ??
Read more articles about Rust in my Rust Programming Library !
Visit my Blog for more articles, news, and software engineering stuff!
Leave a comment, and drop me a message!
All the best,
Luis Soares
CTO | Tech Lead | Senior Software Engineer | Cloud Solutions Architect | Rust ?? | Golang | Java | ML AI & Statistics | Web3 & Blockchain
Thanks for coming along on this journey, and I hope the repository proves useful for your projects!