cert_helper/lib.rs
1//! # Cert-Helper
2//!
3//! A lightweight helper library for managing X.509 certificates using OpenSSL.
4//! Provides convenient tools for generating Certificate Signing Requests (CSRs),
5//! Certificate Revocation Lists (CRLs), and handling private keys.
6//!
7//! ## Description
8//!
9//! A minimal wrapper combining `openssl`, `yasna`, and `x509-parser` crates
10//! to simplify common certificate operations such as creation, signing, parsing, and revocation.
11//!
12//! The package has not been reviewed for any security issues and is intended for testing purposes only.
13//!
14//! This library provides a set of utility functions to simplify common tasks such as:
15//! - Creating self-signed or CA-signed certificates
16//! - Generating RSA, ECDSA,or Ed25519 private keys, note that Ed25519 do not require any hash variant
17//! - Optionally, post-quantum signing keys (ML-DSA, SLH-DSA) behind the `pqc` Cargo feature — see [Post-Quantum keys](#post-quantum-keys-experimental)
18//! - Creating Certificate Signing Requests (CSRs)
19//! - Signing certificates from CSRs using a CA certificate and key
20//! - Reading and writing certificates, keys, and CSRs in PEM format
21//! - Validating certificate chains and properties
22//! - Create or update certificate revocation list(crl)
23//! - Note that this is a simple crl parser that only handle the fields that are included then
24//! generating a crl with this code
25//!
26//! ### Post-Quantum keys (experimental)
27//!
28//! Build with `--features pqc` to enable NIST-standardized post-quantum
29//! algorithms as new `KeyType` variants. There are two distinct families with
30//! different roles and **different KeyUsage rules** — the library enforces these at
31//! build time on both the certificate and CSR paths.
32//!
33//! **Signature keys** — FIPS 204 / FIPS 205. These sign; they cannot encrypt.
34//!
35//! - `MlDsa44`, `MlDsa65`, `MlDsa87` — FIPS 204 (ML-DSA, formerly Dilithium)
36//! - `SlhDsaSha2_128s`, `SlhDsaSha2_192s`, `SlhDsaSha2_256s` — FIPS 205 (SLH-DSA, formerly SPHINCS+)
37//!
38//! - **KeyUsage:** use `digitalSignature` (`Usage::signature`), plus
39//! `keyCertSign`/`cRLSign` (`Usage::certsign` / `Usage::crlsign`) for a CA.
40//! - **Restriction:** `keyEncipherment` (`Usage::encipherment`) is **rejected** —
41//! these algorithms are signature-only and cannot perform key encipherment.
42//! - Can self-sign, sign CSRs, sign other certificates, and sign CRLs.
43//!
44//! **Key-encapsulation keys** — FIPS 203. These encapsulate (encrypt); they cannot sign.
45//!
46//! - `MlKem512`, `MlKem768`, `MlKem1024` — FIPS 203 (ML-KEM, formerly Kyber),
47//! OIDs `2.16.840.1.101.3.4.4.{1,2,3}`
48//!
49//! - **KeyUsage:** if KeyUsage is present it MUST be **exactly `keyEncipherment`**
50//! (`Usage::encipherment`) and nothing else — per
51//! [draft-ietf-lamps-kyber-certificates]. Any other bit (`digitalSignature`,
52//! `keyAgreement`, `dataEncipherment`, `keyCertSign`, `cRLSign`) is **rejected**.
53//! Although ML-KEM is a Key Encapsulation Mechanism, the LAMPS group modeled it
54//! like RSA key transport, so it lands on `keyEncipherment`, not `keyAgreement`.
55//! - **Restriction:** ML-KEM cannot produce signatures, so it can be neither
56//! self-signed (`build_and_self_sign`) nor used to sign a CSR
57//! (`certificate_signing_request`) — both return an error. Issue an ML-KEM
58//! certificate via `build_and_sign()` with a separate signing CA (e.g. an
59//! ML-DSA or ECDSA CA).
60//!
61//! **Runtime requirement:** OpenSSL **≥ 3.5** at build and runtime (enforced at
62//! `build.rs` time) — this covers both the FIPS 204/205 signature algorithms and
63//! FIPS 203 ML-KEM. The `openssl`
64//! Rust crate does not yet expose safe high-level wrappers for these algorithms —
65//! this implementation uses `openssl-sys` FFI directly, mirroring the Ed25519
66//! digest-less signing path. Availability and stability track upstream; expect
67//! churn until safe bindings land.
68//!
69//! ### Certificate Signing Requirements
70//! To sign another certificate, the signing certificate must:
71//! - Have the `CA` (Certificate Authority) flag set to `true`
72//! - Include the `KeyUsage` extension with the `keyCertSign` bit enabled
73//!
74//! These constraints ensure that the certificate is recognized as a valid CA and can be used to issue other certificates.
75//!
76//! ### Use Cases
77//! - Generating certificates for local development or internal services
78//! - Creating a simple certificate authority for testing
79//! - Validating certificate chains in custom TLS setups
80//! - Creating CSRs to be signed by external or internal CAs
81//! - Issuing signed certificates from CSRs for controlled certificate management
82//! - Create crl for testing how a client handle certificate revocations, optionally add crl reason for the revoked certificate
83//!
84//!
85//! ## Basic Example creating a certificate and private key
86//! ```rust
87//! use cert_helper::certificate::{CertBuilder, Certificate, HashAlg, KeyType, Usage, verify_cert, UseesBuilderFields};
88//!
89//! // create a self signed certificate with several optional values set
90//! let ca = CertBuilder::new()
91//! .common_name("My Test Ca")
92//! .country_name("SE")
93//! .state_province("Stockholm")
94//! .organization("my org")
95//! .locality_time("Stockholm")
96//! .is_ca(true)
97//! .key_type(KeyType::P521)
98//! .signature_alg(HashAlg::SHA512)
99//! .key_usage([Usage::certsign, Usage::crlsign].into_iter().collect());
100//! let root_cert = ca.build_and_self_sign();
101//! assert!(root_cert.is_ok())
102//! // to write data to file you need to use X509Common to access the save
103//! // ca.save("./certs/", "mytestca")?;
104//!```
105//! ## Basic Example creating a certificate signing request and private key
106//! ```rust
107//! use cert_helper::certificate::{Usage, Csr, verify_cert, UseesBuilderFields,CsrBuilder};
108//!
109//! // create a certificate signing request and private key
110//! let csr_builder = CsrBuilder::new()
111//! .common_name("example2.com")
112//! .country_name("SE")
113//! .state_province("Stockholm")
114//! .organization("My org")
115//! .locality_time("Stockholm")
116//! .alternative_names(vec!["example2.com", "www.example2.com"])
117//! .key_usage(
118//! [
119//! Usage::contentcommitment,
120//! Usage::encipherment,
121//! Usage::serverauth,
122//! ]
123//! .into_iter()
124//! .collect(),
125//! );
126//! let csr = csr_builder.certificate_signing_request();
127//! assert!(csr.is_ok());
128//!
129//! // to write data to file you need to use X509Common to access the save
130//! // csr.save("./certs/", "mytestca")?;
131//!
132//!```
133//! ## Basic Example creating a signed certificate from a signing request
134//! ```rust
135//! use cert_helper::certificate::{CertBuilder, Csr, verify_cert, UseesBuilderFields, CsrBuilder,CsrOptions};
136//!
137//! let ca = CertBuilder::new().common_name("My Test Ca").is_ca(true);
138//! let root_cert = ca.build_and_self_sign().expect("failed to create root certificate");
139//!
140//! let csr_builder = CsrBuilder::new().common_name("example2.com");
141//! let csr = csr_builder.certificate_signing_request().expect("Failed to generate csr");
142//! let options = CsrOptions::new();// used for enabling csr for CA certficates
143//! let cert = csr.build_signed_certificate(&root_cert, options);
144//! assert!(cert.is_ok());
145//! ```
146//!
147//! ## Basic Example creating a chain of signed certificates and verify the chain
148//! ```rust
149//! use cert_helper::certificate::{CertBuilder, verify_cert, UseesBuilderFields};
150//!
151//! let cert = CertBuilder::new().common_name("Cert-1").is_ca(true);
152//! let cert_1 = cert.build_and_self_sign().expect("Failed to create certificate");
153//! let cert = CertBuilder::new().common_name("Cert-2").is_ca(true);
154//! let cert_2 = cert.build_and_sign(&cert_1).expect("Failed to create certificate");
155//! let cert = CertBuilder::new().common_name("Cert-3");
156//! let cert_3 = cert.build_and_sign(&cert_2).expect("Failed to create certificate");
157//!
158//! match verify_cert(&cert_3.x509, &cert_1.x509, vec![&cert_2.x509]) {
159//! Ok(true) => println!("verify ok"),
160//! _ => println!("failed verify"),
161//! }
162//!
163//! ```
164//!
165//! ## Limiting CA chain depth with path length constraints
166//!
167//! `pathlen(n)` sets the BasicConstraints path-length constraint: at most `n`
168//! intermediate CAs may sit below this certificate. When issuing under a chain it
169//! is validated against the signer's remaining budget, so you can't mint a CA that
170//! exceeds what its issuer permits.
171//!
172//! ```rust
173//! use cert_helper::certificate::{CertBuilder, UseesBuilderFields};
174//!
175//! // Root CA that allows at most one CA beneath it.
176//! let root = CertBuilder::new()
177//! .common_name("My Root CA")
178//! .is_ca(true)
179//! .pathlen(1)
180//! .build_and_self_sign()
181//! .expect("self-sign root");
182//!
183//! // Intermediate CA (pathlen 0 → may only issue end-entity certs), signed by the
184//! // root. The chain is empty because the root is a self-signed trust anchor.
185//! let intermediate = CertBuilder::new()
186//! .common_name("My Intermediate CA")
187//! .is_ca(true)
188//! .pathlen(0)
189//! .build_and_sign_with_chain(&root, &[])
190//! .expect("issue intermediate under root");
191//!
192//! assert_eq!(intermediate.x509.pathlen(), Some(0));
193//! ```
194//!
195//! The same constraint applies when issuing from a CSR via
196//! [`CsrOptions`](certificate::CsrOptions). The chain to validate against is passed
197//! alongside the path length (empty here, since the signer is a self-signed root):
198//!
199//! ```rust
200//! use cert_helper::certificate::{CertBuilder, CsrBuilder, CsrOptions, UseesBuilderFields};
201//!
202//! let ca = CertBuilder::new()
203//! .common_name("My Root CA")
204//! .is_ca(true)
205//! .pathlen(2)
206//! .build_and_self_sign()
207//! .expect("self-sign root");
208//!
209//! let csr = CsrBuilder::new()
210//! .common_name("My Intermediate CA")
211//! .certificate_signing_request()
212//! .expect("build CSR");
213//!
214//! let cert = csr
215//! .build_signed_certificate(&ca, CsrOptions::new().is_ca(true).pathlen(1, vec![]))
216//! .expect("issue intermediate from CSR");
217//!
218//! assert_eq!(cert.x509.pathlen(), Some(1));
219//! ```
220//!
221//! ## Post-Quantum keys (experimental)
222//!
223//! Build with `--features pqc` to enable NIST-standardized post-quantum
224//! signature algorithms as new [`KeyType`](certificate::KeyType) variants:
225//!
226//! - `MlDsa44`, `MlDsa65`, `MlDsa87` — FIPS 204 (ML-DSA, formerly Dilithium)
227//! - `SlhDsaSha2_128s`, `SlhDsaSha2_192s`, `SlhDsaSha2_256s` — FIPS 205 (SLH-DSA, formerly SPHINCS+)
228//!
229//! **Runtime requirement:** OpenSSL **≥ 3.5** at build and runtime (enforced
230//! in `build.rs`). The `openssl` Rust crate does not yet expose safe high-level
231//! wrappers for these algorithms — this implementation uses `openssl-sys` FFI
232//! directly, reusing the Ed25519 digest-less signing path. Availability and
233//! stability track upstream; expect churn until safe bindings land.
234//!
235//! The following example only compiles when the `pqc` feature is enabled — it
236//! is hidden from the default doctest build and exercised by `cargo test --features pqc`.
237//!
238//! ```
239//! # #[cfg(feature = "pqc")] {
240//! use cert_helper::certificate::{CertBuilder, KeyType, UseesBuilderFields};
241//!
242//! // Self-signed CA with an ML-DSA-65 key. Same builder surface as classical keys —
243//! // the digest-less signing path and build-time OpenSSL 3.5+ check are implicit.
244//! let ca = CertBuilder::new()
245//! .common_name("My PQC CA")
246//! .is_ca(true)
247//! .key_type(KeyType::MlDsa65)
248//! .build_and_self_sign()
249//! .expect("self-sign ML-DSA-65");
250//!
251//! // PQC-signed certs are interoperable with OpenSSL's verifier; the signature
252//! // algorithm OID in the PEM will read "ML-DSA-65" (2.16.840.1.101.3.4.3.18).
253//! assert_eq!(
254//! ca.x509.issuer_name().to_der().ok(),
255//! ca.x509.subject_name().to_der().ok()
256//! );
257//! # }
258//! ```
259//!
260//! A PQC CA can also sign classical CSRs (and vice versa); see the
261//! `pqc_crl_example` and `pqc_all_variants` examples in `examples/` for full
262//! chain and CRL workflows.
263//!
264//! ## Example on how to create a certifcate revocation list(clr)
265//!
266//! Create a crl, with one revoked certificate that have CRL Reason: Key Compromise
267//!
268//! ```rust
269//! use cert_helper::certificate::{CertBuilder, UseesBuilderFields};
270//! use cert_helper::crl::{X509CrlBuilder,CrlReason,X509CrlWrapper};
271//! use chrono::Utc;
272//! use num_bigint::BigUint;
273//!
274//! let ca = CertBuilder::new()
275//! .common_name("My Test Ca")
276//! .is_ca(true)
277//! .build_and_self_sign()
278//! .unwrap();
279//! let mut builder = X509CrlBuilder::new(ca.clone());
280//! let revocked = CertBuilder::new()
281//! .common_name("My Test")
282//! .build_and_self_sign()
283//! .unwrap();
284//!
285//! let bytes = revocked.x509.serial_number().to_bn().unwrap().to_vec();
286//! builder.add_revoked_cert_with_reason(BigUint::from_bytes_be(&bytes),
287//! Utc::now(),
288//! vec![CrlReason::KeyCompromise]);
289//!
290//! let wrapper = builder.build_and_sign().unwrap();
291//! // to save crl as pem use the helper function
292//! // wrapper.save_as_pem("./certs", "crl.pem").expect("failed to save crl as pem file");
293//!
294//! // use the wrapper to check sign, revocations
295//! let result = wrapper.verify_signature(ca.x509.public_key().as_ref().unwrap());
296//! assert!(result.unwrap());
297//! let is_revoked = wrapper.revoked(revocked.x509.serial_number());
298//! assert!(is_revoked);
299//! ```
300//!
301//! ## Writing to file
302//!
303//! `save(path, filename)` comes from the `X509Common` trait and writes two files:
304//! the certificate (or CSR) as `<filename>_cert.pem` / `<filename>_csr.pem`, and
305//! the private key as `<filename>_pkey.pem`.
306//!
307//! On Unix the private key is created with mode `0600` and the certificate with
308//! `0644`. The mode is applied when the file is created rather than set
309//! afterwards, so the key is never briefly readable by others. Saving over an
310//! existing file replaces it rather than truncating in place, which means a key
311//! written by an older version of this crate — when keys were left at the umask
312//! default — is tightened to `0600` the next time it is saved.
313//!
314//! On non-Unix targets no permission guarantee is made.
315//!
316//! ## Config
317//!
318//! Values that can be selected for building a certificate
319//! | keyword | description | options |
320//! | ----------------- | --------------------------------------------------------------------------- | ----------------------------------- |
321//! | common_name | the common name this certificate shoud have, mandatory field. Also added to the SAN of end-entity certificates | string: www.foo.se |
322//! | key_type | key type to generate, defaults to RSA2048. Ignored when `private_key` is set | enum: RSA2048, RSA4096, P224, P256, P384, P521, Ed25519, and with `--features pqc`: MlDsa44, MlDsa65, MlDsa87, SlhDsaSha2_128s, SlhDsaSha2_192s, SlhDsaSha2_256s |
323//! | private_key | use a private key you already hold instead of generating a new one. Takes precedence over `key_type` | `PKey<Private>` |
324//! | ca | is this certificate used to sign other certificates, default value is false. CA certificates are issued without a SAN | boolean: true or false |
325//! | country_name | the country code to use,must follow the standard defined by ISO 3166-1 alpha-2. | string: SE |
326//! | organization | organisation name | string: test |
327//! | state_province | some name | string: test |
328//! | locality_time | Stockholm | string: Stockholm |
329//! | alternative_names | alternative names this certificate is valid for, see [Subject alternative names](#subject-alternative-names) below | string: dns names or IP literals |
330//! | signature_alg | which algorithm to be used for signature, default is SHA256 | enum: SHA1, SHA256, SHA384, SHA512 |
331//! | valid_from | Start date then the certificate is valid, default is now | string: 2010-01-01 |
332//! | valid_to | End date then the certificate is not valid, default is 1 year | string: 2020-01-01 |
333//! | usage | Key usage to add to the certificates, see list below for options | list of enums, defined in Key Usage table |
334//! | certificate_policy | optional certificate policies to add | AnyPolicy, DomainValidation, OrganizationValidated, IndividualValidated, ExtendedValidation|
335//! | pathlen | optional CA path length: max intermediate CAs allowed below this cert (only applies when ca is true) | u32: 0, 1, 2 … |
336//!
337//! ### Subject alternative names
338//!
339//! The SAN list is assembled when the certificate is built, from
340//! `alternative_names` plus — for end-entity certificates only — the common name.
341//!
342//! - An entry that parses as an IPv4 or IPv6 address becomes an `iPAddress`
343//! name; everything else becomes a `dNSName`.
344//! - End-entity certificates get the common name added automatically. RFC 6125
345//! verifiers match the hostname against the SAN and ignore the CN, so a
346//! certificate for `localhost` needs `DNS:localhost` to be usable at all.
347//! - **CA certificates get no SAN**, root and intermediate alike. A CA is
348//! identified during path validation by its distinguished name and key
349//! identifier, and no verifier consults its SAN. Setting `ca` to true
350//! therefore suppresses the extension, including the automatic CN entry.
351//! - If there would be no names at all the extension is omitted rather than
352//! written empty, which RFC 5280 §4.2.1.6 forbids.
353//!
354//! ### Key usage
355//!
356//! If CA is true the key usages to sign certificates and crl lists are added automatically.
357//!
358//! | keyword | description |
359//! | ----------------- | ---------------------------------------------------------- |
360//! | certsign | allowed to sign certificates |
361//! | crlsign | allowed to sign crl |
362//! | encipherment | allowed to enciphering private or secret keys |
363//! | clientauth | allowed to authenticate as client |
364//! | serverauth | allowed ot be used for server authenthication |
365//! | signature | allowed to perfom digital signature (For auth) |
366//! | contentcommitment | allowed to perfom document signature (prev non repudation) |
367
368pub mod certificate;
369pub mod crl;
370#[cfg(test)]
371mod test_der;