Overview

cutil is designed for developers who need to manage their own internal PKI infrastructure. It provides both a robust Rust library and a command-line interface for certificate operations, making it suitable for development, testing, and production environments.
Features
Installation
Add CUtil to your Cargo.toml:
[dependencies]
cutil = "0.1"
Quick Start
Library Usage
use cutil::ca::CertificateAuthority;
use cutil::cert::CertificateBuilder;
use cutil::types::{CertSigAlgo, DistinguishedName};
use cutil::error::Result;
fn main() -> Result<()> {
let subject = DistinguishedName::new("Example Root CA")
.with_organization("Example Corp")
.with_country("US");
let ca = CertificateAuthority::new_root(
subject,
CertSigAlgo::EcdsaP256,
3650, )?;
ca.save_pem("ca.pem", "ca-key.pem")?;
let mut loaded_ca = CertificateAuthority::load_pem(
"ca.pem",
"ca-key.pem",
CertSigAlgo::EcdsaP256,
)?;
let cert = CertificateBuilder::server("example.com")
.with_dns_san("www.example.com")
.with_dns_san("api.example.com")
.with_validity_days(365)
.issue(&mut loaded_ca)?;
cert.save_pem("server.pem", "server-key.pem")?;
Ok(())
}
Command-Line Interface
cutil init --cn "My Root CA" --org "My Company" --country US
cutil cert --cn example.com --dns example.com,www.example.com --validity 365
cutil fetch google.com:443 --format pretty
Library API Reference
Certificate Authority Operations
Creating a Root CA
use cutil::ca::CertificateAuthority;
use cutil::types::{CertSigAlgo, DistinguishedName};
use cutil::error::Result;
fn create_root_ca() -> Result<CertificateAuthority> {
let subject = DistinguishedName::new("Example Root CA")
.with_organization("Example Corporation")
.with_organizational_unit("Security")
.with_country("US")
.with_state("California")
.with_locality("San Francisco");
let ca = CertificateAuthority::new_root(
subject,
CertSigAlgo::EcdsaP256,
3650, )?;
ca.save_pem("root-ca.pem", "root-ca-key.pem")?;
Ok(ca)
}
Creating an Intermediate CA
use cutil::ca::CertificateAuthority;
use cutil::types::{CertSigAlgo, DistinguishedName};
use cutil::error::Result;
fn create_intermediate_ca() -> Result<CertificateAuthority> {
let parent_ca = CertificateAuthority::load_pem(
"root-ca.pem",
"root-ca-key.pem",
CertSigAlgo::EcdsaP256,
)?;
let subject = DistinguishedName::new("Example Intermediate CA")
.with_organization("Example Corporation")
.with_organizational_unit("Security");
let intermediate_ca = CertificateAuthority::new_intermediate(
subject,
CertSigAlgo::EcdsaP256,
1825, &parent_ca,
)?;
intermediate_ca.save_pem("intermediate-ca.pem", "intermediate-ca-key.pem")?;
Ok(intermediate_ca)
}
Loading an Existing CA
use cutil::ca::CertificateAuthority;
use cutil::types::CertSigAlgo;
use cutil::error::Result;
fn load_ca() -> Result<CertificateAuthority> {
let ca = CertificateAuthority::load_pem(
"ca.pem",
"ca-key.pem",
CertSigAlgo::EcdsaP256,
)?;
Ok(ca)
}
Certificate Issuance
Server Certificates
use cutil::cert::CertificateBuilder;
use cutil::types::{CertSigAlgo, DistinguishedName};
use cutil::ca::CertificateAuthority;
use cutil::error::Result;
fn issue_server_certificate(ca: &mut CertificateAuthority) -> Result<()> {
let cert = CertificateBuilder::server("example.com")
.with_dns_san("example.com")
.with_dns_san("www.example.com")
.with_dns_san("api.example.com")
.with_dns_san("*.internal.example.com")
.with_validity_days(365)
.with_algorithm(CertSigAlgo::EcdsaP256)
.issue(ca)?;
cert.save_pem("server.pem", "server-key.pem")?;
Ok(())
}
Server Certificates with Custom Subject
use cutil::cert::CertificateBuilder;
use cutil::types::{CertType, DistinguishedName};
use cutil::ca::CertificateAuthority;
use cutil::error::Result;
fn issue_server_with_custom_subject(ca: &mut CertificateAuthority) -> Result<()> {
let subject = DistinguishedName::new("example.com")
.with_organization("Example Corporation")
.with_organizational_unit("Web Services")
.with_locality("San Francisco")
.with_state("California")
.with_country("US");
let cert = CertificateBuilder::new("example.com".to_string(), CertType::Server)
.with_subject(subject)
.with_dns_san("example.com")
.with_dns_san("www.example.com")
.with_validity_days(365)
.issue(ca)?;
cert.save_pem("server.pem", "server-key.pem")?;
Ok(())
}
Client Certificates
use cutil::cert::CertificateBuilder;
use cutil::ca::CertificateAuthority;
use cutil::error::Result;
fn issue_client_certificate(ca: &mut CertificateAuthority) -> Result<()> {
let cert = CertificateBuilder::client("user@example.com")
.with_email_san("user@example.com")
.with_validity_days(365)
.issue(ca)?;
cert.save_pem("client.pem", "client-key.pem")?;
Ok(())
}
Certificates with IP SANs
use cutil::cert::CertificateBuilder;
use cutil::ca::CertificateAuthority;
use cutil::error::Result;
use std::net::IpAddr;
fn issue_certificate_with_ip_sans(ca: &mut CertificateAuthority) -> Result<()> {
let cert = CertificateBuilder::server("internal-service")
.with_dns_san("internal-service.local")
.with_ip_san("192.168.1.100".parse::<IpAddr>().unwrap())
.with_ip_san("10.0.0.50".parse::<IpAddr>().unwrap())
.with_validity_days(365)
.issue(ca)?;
cert.save_pem("internal-service.pem", "internal-service-key.pem")?;
Ok(())
}
Certificates with CRL and OCSP URLs
use cutil::cert::CertificateBuilder;
use cutil::ca::CertificateAuthority;
use cutil::error::Result;
fn issue_certificate_with_revocation_urls(ca: &mut CertificateAuthority) -> Result<()> {
let cert = CertificateBuilder::server("example.com")
.with_dns_san("example.com")
.with_crl_distribution_point("http://crl.example.com/ca.crl")
.with_ocsp_server("http://ocsp.example.com")
.with_validity_days(365)
.issue(ca)?;
cert.save_pem("server.pem", "server-key.pem")?;
Ok(())
}
Certificate Export Formats
Exporting Certificate Chains
use cutil::ca::{CertificateAuthority, IssuedCertificate};
use cutil::error::Result;
fn export_certificate_chain(cert: &IssuedCertificate) -> Result<()> {
cert.save_chain("fullchain.pem")?;
Ok(())
}
Exporting as PKCS#12
use cutil::ca::IssuedCertificate;
use cutil::error::Result;
fn export_pkcs12(cert: &IssuedCertificate) -> Result<()> {
let p12_data = cert.export_pkcs12(
"secure_password_123",
"My Server Certificate",
)?;
std::fs::write("certificate.p12", p12_data)?;
Ok(())
}
Certificate Revocation
Revoking a Certificate
use cutil::ca::CertificateAuthority;
use cutil::types::RevocationReason;
use cutil::error::Result;
fn revoke_certificate(ca: &mut CertificateAuthority, serial: Vec<u8>) -> Result<()> {
ca.revoke_certificate(serial, RevocationReason::KeyCompromise)?;
Ok(())
}
Generating a Certificate Revocation List (CRL)
use cutil::ca::CertificateAuthority;
use cutil::error::Result;
fn generate_crl(ca: &CertificateAuthority) -> Result<()> {
let crl_pem = ca.generate_crl()?;
std::fs::write("ca.crl", crl_pem)?;
println!("CRL generated with {} revoked certificates",
ca.revoked_certificates().len());
Ok(())
}
Checking Revoked Certificates
use cutil::ca::CertificateAuthority;
use cutil::error::Result;
fn list_revoked_certificates(ca: &CertificateAuthority) -> Result<()> {
for revoked in ca.revoked_certificates() {
println!("Serial: {:?}", revoked.serial_number);
println!("Revocation Date: {}", revoked.revocation_date);
println!("Reason: {:?}", revoked.reason);
println!("---");
}
Ok(())
}
Remote Certificate Inspection
Fetching Certificate Chains
use cutil::fetch::fetch_certificate_chain;
use cutil::error::Result;
fn fetch_remote_chain() -> Result<()> {
let chain = fetch_certificate_chain("google.com", 443)?;
println!("Server: {}", chain.server);
println!("Number of certificates: {}", chain.certificates.len());
for (i, cert) in chain.certificates.iter().enumerate() {
println!("\nCertificate {}:", i);
println!(" Subject: {}", cert.subject);
println!(" Issuer: {}", cert.issuer);
println!(" Valid from: {}", cert.not_before);
println!(" Valid until: {}", cert.not_after);
println!(" Is CA: {}", cert.is_ca);
}
Ok(())
}
Displaying Certificate Chain Information
use cutil::fetch::{fetch_certificate_chain, display_certificate_chain, OutputFormat};
use cutil::error::Result;
fn display_remote_chain_pretty() -> Result<()> {
let chain = fetch_certificate_chain("github.com", 443)?;
let output = display_certificate_chain(&chain, OutputFormat::Pretty)?;
println!("{}", output);
Ok(())
}
fn display_remote_chain_json() -> Result<()> {
let chain = fetch_certificate_chain("github.com", 443)?;
let output = display_certificate_chain(&chain, OutputFormat::Json)?;
println!("{}", output);
Ok(())
}
Saving Certificate Chain to File
use cutil::fetch::{fetch_certificate_chain, display_certificate_chain, OutputFormat};
use cutil::error::Result;
fn save_chain_info(host: &str, port: u16, output_file: &str) -> Result<()> {
let chain = fetch_certificate_chain(host, port)?;
let output = display_certificate_chain(&chain, OutputFormat::Json)?;
std::fs::write(output_file, output)?;
Ok(())
}
Signature Algorithms
CUtil supports multiple signature algorithms with varying security levels and performance characteristics:
Ed25519 (Recommended for Modern Systems)
use cutil::types::CertSigAlgo;
let algorithm = CertSigAlgo::Ed25519;
- Modern elliptic curve signature algorithm
- Fast signing and verification
- Compact signatures and keys
- High security level
ECDSA with NIST Curves
use cutil::types::CertSigAlgo;
let p256 = CertSigAlgo::EcdsaP256;
let p384 = CertSigAlgo::EcdsaP384;
RSA
use cutil::types::CertSigAlgo;
let rsa2048 = CertSigAlgo::Rsa2048; let rsa3072 = CertSigAlgo::Rsa3072; let rsa4096 = CertSigAlgo::Rsa4096;
Algorithm Selection Guide
- Ed25519: Best choice for new deployments (fast, secure, modern)
- ECDSA P-256: Good compatibility, widely supported
- ECDSA P-384: Higher security requirements
- RSA 2048: Legacy compatibility (minimum acceptable)
- RSA 3072/4096: Long-term security, regulatory compliance
Distinguished Names
Creating Distinguished Names
use cutil::types::DistinguishedName;
let dn = DistinguishedName::new("example.com")
.with_organization("Example Corporation")
.with_organizational_unit("IT Department")
.with_country("US")
.with_state("California")
.with_locality("San Francisco");
Available Fields
- Common Name (CN): Primary identifier (required)
- Organization (O): Company or organization name
- Organizational Unit (OU): Department or division
- Country (C): Two-letter country code (ISO 3166-1 alpha-2)
- State (ST): State or province
- Locality (L): City or locality
Revocation Reasons
use cutil::types::RevocationReason;
let reasons = [
RevocationReason::Unspecified,
RevocationReason::KeyCompromise,
RevocationReason::CACompromise,
RevocationReason::AffiliationChanged,
RevocationReason::Superseded,
RevocationReason::CessationOfOperation,
];
Error Handling
CUtil uses a custom Result type for comprehensive error handling:
use cutil::error::{Result, Error};
fn handle_errors() -> Result<()> {
match some_operation() {
Ok(value) => {
println!("Success: {:?}", value);
Ok(())
}
Err(Error::IoError(e)) => {
eprintln!("I/O error: {}", e);
Err(Error::IoError(e))
}
Err(Error::CertificateGeneration(msg)) => {
eprintln!("Certificate generation failed: {}", msg);
Err(Error::CertificateGeneration(msg))
}
Err(e) => {
eprintln!("Other error: {}", e);
Err(e)
}
}
}
Command-Line Interface
The CUtil CLI provides a complete command-line interface for all PKI operations.
Initialize a Root CA
cutil init \
--cn "Example Root CA" \
--org "Example Corporation" \
--ou "Security" \
--country US \
--state California \
--locality "San Francisco" \
--algorithm ecdsa-p256 \
--validity 3650 \
--cert-out root-ca.pem \
--key-out root-ca-key.pem
Create an Intermediate CA
cutil init \
--cn "Example Intermediate CA" \
--org "Example Corporation" \
--algorithm ecdsa-p256 \
--validity 1825 \
--intermediate \
--parent-cert root-ca.pem \
--parent-key root-ca-key.pem \
--cert-out intermediate-ca.pem \
--key-out intermediate-ca-key.pem
Issue a Server Certificate
cutil cert \
--cn example.com \
--cert-type server \
--dns example.com \
--dns www.example.com \
--dns api.example.com \
--org "Example Corporation" \
--validity 365 \
--ca-cert ca.pem \
--ca-key ca-key.pem \
--cert-out server.pem \
--key-out server-key.pem \
--chain-out fullchain.pem
Issue a Client Certificate
cutil cert \
--cn "John Doe" \
--cert-type client \
--email john.doe@example.com \
--org "Example Corporation" \
--validity 365 \
--ca-cert ca.pem \
--ca-key ca-key.pem \
--cert-out client.pem \
--key-out client-key.pem
Export Certificate as PKCS#12
cutil cert \
--cn example.com \
--cert-type server \
--dns example.com \
--ca-cert ca.pem \
--ca-key ca-key.pem \
--cert-out server.pem \
--key-out server-key.pem \
--p12-out server.p12 \
--p12-password "secure_password"
Fetch Remote Certificate Chain
cutil fetch google.com:443
cutil fetch github.com:443 --format json
cutil fetch example.com:443 --format json --output example-chain.json
Revoke a Certificate
cutil revoke \
--serial 01:02:03:04:05:06:07:08 \
--reason key-compromise \
--ca-cert ca.pem \
--ca-key ca-key.pem
Available revocation reasons:
unspecified
key-compromise or keycompromise
ca-compromise or cacompromise
affiliation-changed or affiliationchanged
superseded
cessation or cessationofoperation
Generate Certificate Revocation List
cutil crl \
--ca-cert ca.pem \
--ca-key ca-key.pem \
--output ca.crl
Display Certificate Chain
cutil chain server.pem
Best Practices
Development and Testing
use cutil::ca::CertificateAuthority;
use cutil::cert::CertificateBuilder;
use cutil::types::{CertSigAlgo, DistinguishedName};
use cutil::error::Result;
fn create_test_pki() -> Result<()> {
let ca_subject = DistinguishedName::new("Test CA");
let mut ca = CertificateAuthority::new_root(
ca_subject,
CertSigAlgo::EcdsaP256,
365,
)?;
let cert = CertificateBuilder::server("localhost")
.with_dns_san("localhost")
.with_ip_san("127.0.0.1".parse().unwrap())
.with_validity_days(30)
.issue(&mut ca)?;
cert.save_pem("test-cert.pem", "test-key.pem")?;
Ok(())
}
Examples
Complete working examples are available in the repository:
- basic_ca.rs: Create a CA and issue server/client certificates
- fetch_remote.rs: Fetch and inspect remote TLS certificates
Run examples with:
cargo run --example basic_ca
cargo run --example fetch_remote
Dependencies
CUtil is built on industry-standard cryptographic libraries:
- rcgen: Certificate generation and signing
- rustls: TLS protocol implementation
- rustls-pemfile: PEM file parsing
- webpki: Web PKI certificate validation
- x509-parser: X.509 certificate parsing
- thiserror: Ergonomic error handling
- chrono: Date and time operations
- colored: Terminal output formatting
- serde / serde_json: JSON serialization support
- clap: Command-line argument parsing
- p12: PKCS#12 archive support