synta_certificate/lib.rs
1//! X.509 Certificate Structures
2//!
3//! This crate provides typed X.509 certificate structures based on RFC 5280.
4//! The structures are auto-generated from ASN.1 schemas using synta-codegen.
5//!
6//! # Features
7//!
8//! - Complete X.509 v3 certificate structures
9//! - Based on RFC 5280 (Internet X.509 Public Key Infrastructure)
10//! - Auto-generated from ASN.1 schema
11//! - X.509v3 extension parsing and formatting
12//! - Support for post-quantum algorithms (ML-DSA, ML-KEM)
13//! - Helper functions for algorithm identification
14//!
15//! # Two Certificate variants
16//!
17//! ## Borrowed (crate root) — parse-only workloads
18//!
19//! The types at the crate root use zero-copy borrowed representations:
20//!
21//! - `signature_value`, `subject_public_key`: `BitStringRef<'a>` — borrow from the input buffer
22//! - `issuer`, `subject`: `RawDer<'a>` — raw DER bytes, decoded lazily on demand
23//! - `extensions`: `Option<RawDer<'a>>` — raw DER bytes for the extensions sequence
24//! - `extn_value`: `OctetStringRef<'a>` — zero-copy
25//!
26//! ```rust,ignore
27//! use synta_certificate::Certificate;
28//! use synta::{Decoder, Encoding};
29//!
30//! let mut decoder = Decoder::new(der_bytes, Encoding::Der);
31//! let cert: Certificate = decoder.decode().unwrap();
32//! println!("Serial: {:?}", cert.tbs_certificate.serial_number);
33//! ```
34//!
35//! ## Owned ([`owned`]) — constructing certificates programmatically
36//!
37//! The types in [`owned`] use heap-allocating representations:
38//!
39//! - `signature_value`, `subject_public_key`: [`BitString`] — owned byte buffer
40//! - `issuer`, `subject`: `Name<'a>` — fully parsed distinguished name
41//! - `extensions`: `Option<Vec<Extension>>` — owned extension list
42//! - `extn_value`: [`OctetString`] — owned byte buffer
43//!
44//! ```rust,ignore
45//! use synta_certificate::owned::{Certificate, TBSCertificate, AlgorithmIdentifier, Name};
46//! use synta::BitString;
47//!
48//! // Build a certificate without encode+decode workarounds
49//! let cert = Certificate {
50//! signature_value: BitString::new(sig_bytes.to_vec(), 0).unwrap(),
51//! // ... other fields
52//! };
53//! ```
54
55#![cfg_attr(not(feature = "std"), no_std)]
56
57#[cfg(feature = "alloc")]
58extern crate alloc;
59
60// Test module for lifetime issues
61mod test_lifetime;
62
63// RFC 7512 PKCS#11 URI parser (crate-private).
64pub(crate) mod pkcs11_uri;
65/// RFC 7512 `pkcs11:` URI — holds the verbatim URI string and decoded
66/// attributes (token label, object label, CKA_ID, PIN). Returned by
67/// [`Pkcs11Uri`] for HSM-backed keys.
68pub use pkcs11_uri::{merge_object_label, pct_encode_path, Pkcs11Uri, Pkcs11UriAttributes};
69
70// PKCS#11 token management (slot listing, key find/list/delete/generate-in-token).
71#[cfg(feature = "pkcs11-mgmt")]
72pub mod pkcs11_mgmt;
73#[cfg(feature = "pkcs11-mgmt")]
74pub use crypto::token_manager::{Pkcs11KeyInfo, SlotInfo, TokenManager};
75
76/// Return a [`Pkcs11Manager`] using the active module (resolved from
77/// `PKCS11_MODULE_PATH` env var or `/usr/lib64/pkcs11/p11-kit-proxy.so`).
78///
79/// [`Pkcs11Manager`]: pkcs11_mgmt::Pkcs11Manager
80#[cfg(feature = "pkcs11-mgmt")]
81pub fn pkcs11_manager() -> Result<pkcs11_mgmt::Pkcs11Manager, crypto::PrivateKeyError> {
82 pkcs11_mgmt::Pkcs11Manager::from_env()
83}
84
85/// List all PKCS#11 token slots using the active module.
86///
87/// Equivalent to `pkcs11_manager()?.list_slots()`.
88#[cfg(feature = "pkcs11-mgmt")]
89pub fn list_pkcs11_slots() -> Result<Vec<SlotInfo>, crypto::PrivateKeyError> {
90 pkcs11_manager()?.list_slots()
91}
92
93// RFC 4514-style Distinguished Name formatter
94pub mod name;
95pub use name::{decode_string_value, format_dn, format_dn_slash, parse_name_attrs, NameBuilder};
96
97// Public key algorithm identification and decoded key data
98pub mod pubkey;
99pub use pubkey::{decode_public_key_info, PublicKeyInfo};
100
101// Well-known OID component arrays for algorithms and DN attribute types
102pub mod oids;
103
104// Human-readable algorithm name strings returned by the identify_* helpers
105pub mod names;
106
107// Dependency-free PEM → DER decoder
108pub mod pem;
109pub use pem::{decode_base64, der_to_pem, encode_base64, pem_blocks, pem_to_der};
110
111// Shared time-string parsing helpers used by the builder modules.
112pub mod time_utils;
113pub use time_utils::{parse_generalized_time, parse_time};
114
115// Zero-copy borrowed types at crate root (optimal for parse-only workloads).
116// OCTET STRING / BIT STRING fields borrow from the input buffer;
117// issuer, subject, and extensions are stored as raw DER for lazy parsing.
118include!(concat!(env!("OUT_DIR"), "/x509_borrowed.rs"));
119
120/// Owned X.509 types for constructing certificates programmatically.
121///
122/// Unlike the zero-copy borrowed types at the crate root, these types use
123/// heap-allocating representations for binary fields and fully-parsed types
124/// for `issuer`, `subject`, and `extensions`. They are convenient when you
125/// need to build or mutate certificate structures without keeping a reference
126/// to an input byte slice alive.
127pub mod owned {
128 include!(concat!(env!("OUT_DIR"), "/x509_owned.rs"));
129}
130
131/// PKCS #10 Certificate Signing Request (RFC 2986) types.
132///
133/// Generated from `asn1/PKCS10-CSR.asn1`. Shared types (`AlgorithmIdentifier`,
134/// `SubjectPublicKeyInfo`, `Name`) are imported from the crate root via
135/// `use crate::*` in the generated file.
136pub mod csr {
137 include!(concat!(env!("OUT_DIR"), "/csr_borrowed.rs"));
138}
139
140/// X.509 Certificate Revocation List (RFC 5280 §5) types.
141///
142/// Generated from `asn1/X509-CRL.asn1`. Shared types (`AlgorithmIdentifier`,
143/// `Name`, `Time`, `Extensions`) are imported from the crate root.
144pub mod crl {
145 include!(concat!(env!("OUT_DIR"), "/crl_borrowed.rs"));
146}
147
148/// Online Certificate Status Protocol (RFC 6960) types.
149///
150/// Generated from `asn1/OCSP.asn1`. Shared types (`AlgorithmIdentifier`,
151/// `Extensions`) are imported from the crate root. The `responder_id` field
152/// on `ResponseData` is stored as `RawDer<'a>` for lazy decoding.
153pub mod ocsp {
154 include!(concat!(env!("OUT_DIR"), "/ocsp_borrowed.rs"));
155}
156
157/// CMS / PKCS#7 OID constants and ContentInfo type.
158///
159/// Generated from `asn1/PKCS7-CMS.asn1`. Use [`certs_from_pkcs7`] to extract
160/// certificates from a PKCS#7 SignedData blob without importing this module
161/// directly.
162pub mod pkcs7_types {
163 include!(concat!(env!("OUT_DIR"), "/pkcs7_generated.rs"));
164}
165
166/// PKCS#12 OID constants and parameter types.
167///
168/// Generated from `asn1/PKCS12.asn1`. Use [`certs_from_pkcs12`] to extract
169/// certificates from a PKCS#12 archive without importing this module directly.
170pub mod pkcs12_types {
171 include!(concat!(env!("OUT_DIR"), "/pkcs12_generated.rs"));
172}
173
174/// PKCS #9 attribute type OID constants.
175///
176/// Generated from `asn1/PKCS9.asn1`. Covers RFC 2985 attribute OIDs and the
177/// well-known CMS signed-attribute OIDs from RFC 5652 §11 (`id-contentType`,
178/// `id-messageDigest`, `id-signingTime`, `id-countersignature`), PKCS #10
179/// request attributes (`id-extensionRequest`, `id-challengePassword`), and
180/// PKCS #12 bag attributes (`id-friendlyName`, `id-localKeyId`).
181///
182/// Key OIDs re-exported in [`crate::oids`]:
183/// - [`crate::oids::PKCS9_CONTENT_TYPE`] — `id-contentType` (1.2.840.113549.1.9.3)
184/// - [`crate::oids::PKCS9_MESSAGE_DIGEST`] — `id-messageDigest` (1.2.840.113549.1.9.4)
185/// - [`crate::oids::PKCS9_SIGNING_TIME`] — `id-signingTime` (1.2.840.113549.1.9.5)
186/// - [`crate::oids::PKCS9_COUNTERSIGNATURE`] — `id-countersignature` (1.2.840.113549.1.9.6)
187/// - [`crate::oids::PKCS9_EXTENSION_REQUEST`] — `id-extensionRequest` (1.2.840.113549.1.9.14)
188pub mod pkcs9_types {
189 include!(concat!(env!("OUT_DIR"), "/pkcs9_generated.rs"));
190}
191
192/// PKCS #1 RSA key structures and algorithm parameters (RFC 8017).
193///
194/// Generated from `asn1/PKCS1.asn1`. Defines the RSA public and private key
195/// DER structures, RSASSA-PSS and RSAES-OAEP algorithm parameters, DigestInfo,
196/// and the PKCS #1 OID constants not already in [`crate::oids`].
197///
198/// [`RsaPublicKey`] is re-exported at the crate root so that existing callers
199/// using `synta_certificate::RsaPublicKey` continue to work.
200///
201/// Key types:
202/// - [`RsaPublicKey`] — `SEQUENCE { modulus INTEGER, publicExponent INTEGER }`
203/// - [`RSAPrivateKey`] — multi-prime RSA private key (RFC 8017 §3.2)
204/// - [`RsassaPssParams`] — RSASSA-PSS algorithm parameters (RFC 8017 §A.2.3)
205/// - [`RsaesOaepParams`] — RSAES-OAEP algorithm parameters (RFC 8017 §A.2.1)
206/// - [`DigestInfo`] — `SEQUENCE { digestAlgorithm, digest OCTET STRING }`
207///
208/// [`RsaPublicKey`]: pkcs1_types::RsaPublicKey
209/// [`RSAPrivateKey`]: pkcs1_types::RSAPrivateKey
210/// [`RsassaPssParams`]: pkcs1_types::RsassaPssParams
211/// [`RsaesOaepParams`]: pkcs1_types::RsaesOaepParams
212/// [`DigestInfo`]: pkcs1_types::DigestInfo
213pub mod pkcs1_types {
214 include!(concat!(env!("OUT_DIR"), "/pkcs1_generated.rs"));
215}
216
217// Re-export at crate root so `crate::RsaPublicKey` keeps working.
218pub use pkcs1_types::RsaPublicKey;
219
220// Re-export ML-DSA types at crate root.
221pub use mldsa_types::{
222 MlDsa44PrivateKey, MlDsa44PrivateKeyBoth, MlDsa44PublicKey, MlDsa65PrivateKey,
223 MlDsa65PrivateKeyBoth, MlDsa65PublicKey, MlDsa87PrivateKey, MlDsa87PrivateKeyBoth,
224 MlDsa87PublicKey,
225};
226
227/// ML-DSA key structure types (RFC 9881 / FIPS 204).
228///
229/// Generated from `asn1/MLDSA.asn1`. Defines the public and private key types
230/// for the three ML-DSA parameter sets:
231///
232/// | Type | Parameter set | Pub key | Seed | Expanded key |
233/// |------|--------------|---------|------|-------------|
234/// | [`MlDsa44PrivateKey`] | ML-DSA-44 | 1312 B | 32 B | 2560 B |
235/// | [`MlDsa65PrivateKey`] | ML-DSA-65 | 1952 B | 32 B | 4032 B |
236/// | [`MlDsa87PrivateKey`] | ML-DSA-87 | 2592 B | 32 B | 4896 B |
237///
238/// Each private key is a CHOICE of `seed`, `expandedKey`, or `both` (RFC 9881 §4).
239/// The `both` variant has a synthetic helper struct (e.g. [`MlDsa44PrivateKeyBoth`])
240/// that holds both seed and expanded key together.
241///
242/// OIDs are in [`crate::oids`]: [`crate::oids::ML_DSA_44`], [`crate::oids::ML_DSA_65`],
243/// [`crate::oids::ML_DSA_87`].
244///
245/// [`MlDsa44PrivateKey`]: mldsa_types::MlDsa44PrivateKey
246/// [`MlDsa65PrivateKey`]: mldsa_types::MlDsa65PrivateKey
247/// [`MlDsa87PrivateKey`]: mldsa_types::MlDsa87PrivateKey
248/// [`MlDsa44PrivateKeyBoth`]: mldsa_types::MlDsa44PrivateKeyBoth
249pub mod mldsa_types {
250 include!(concat!(env!("OUT_DIR"), "/mldsa_generated.rs"));
251}
252
253/// RFC 3279 algorithm parameter and signature types (DSA, DH, ECDSA).
254///
255/// Generated from `asn1/PKIXAlgs.asn1`. Covers the algorithm identifiers
256/// and associated parameter structures defined in RFC 3279 and X9.62:
257///
258/// - [`DssParms`] — DSA domain parameters (p, q, g) for use in SubjectPublicKeyInfo
259/// - [`DssSigValue`] — DER encoding of a DSA signature (r, s)
260/// - [`EcdsaSigValue`] — DER encoding of an ECDSA signature (r, s)
261/// - [`DomainParameters`] — Diffie-Hellman domain parameters
262/// - [`SpecifiedECDomain`] — Full EC domain parameters (FieldID, Curve, base point)
263/// - [`ECParameters`] — EC algorithm parameters CHOICE (named curve OID or explicit)
264///
265/// Named curve OIDs: `prime192v1`, `prime256v1`, `secp224r1`,
266/// `secp384r1`, `secp521r1`, `id-ecPublicKey`, `ecdsa-with-SHA256`, etc.
267///
268/// [`DssParms`]: pkixalgs_types::DssParms
269/// [`DssSigValue`]: pkixalgs_types::DssSigValue
270/// [`EcdsaSigValue`]: pkixalgs_types::EcdsaSigValue
271/// [`DomainParameters`]: pkixalgs_types::DomainParameters
272/// [`SpecifiedECDomain`]: pkixalgs_types::SpecifiedECDomain
273/// [`ECParameters`]: pkixalgs_types::ECParameters
274pub mod pkixalgs_types {
275 include!(concat!(env!("OUT_DIR"), "/pkixalgs_generated.rs"));
276}
277
278/// RFC 5755 X.509 Attribute Certificate v2 types.
279///
280/// Generated from `asn1/AttributeCertificate.asn1`. An Attribute Certificate
281/// (AC) binds attributes (roles, clearances, service info) to a holder
282/// without re-issuing the underlying Public Key Certificate (PKC).
283///
284/// Key types:
285/// - [`AttributeCertificate`] — outer signed structure (acinfo + signature)
286/// - [`AttributeCertificateInfo`] — inner TBS with holder, issuer, attributes
287/// - [`Holder`] — links AC to PKC via baseCertificateID, entityName, or digest
288/// - [`AttCertIssuer`] — CHOICE between v1Form (GeneralNames) and v2Form
289/// - [`IetfAttrSyntax`] — standard attribute value syntax (OID/octet/string)
290/// - [`RoleSyntax`] — role attribute value (id-at-role)
291/// - [`Clearance`] — clearance attribute value (id-at-clearance)
292/// - [`AAControls`] — AA Controls extension (id-pe-aaControls)
293/// - [`Targets`] / [`Target`] — target information extension
294///
295/// [`AttributeCertificate`]: attribute_cert_types::AttributeCertificate
296/// [`AttributeCertificateInfo`]: attribute_cert_types::AttributeCertificateInfo
297/// [`Holder`]: attribute_cert_types::Holder
298/// [`AttCertIssuer`]: attribute_cert_types::AttCertIssuer
299/// [`IetfAttrSyntax`]: attribute_cert_types::IetfAttrSyntax
300/// [`RoleSyntax`]: attribute_cert_types::RoleSyntax
301/// [`Clearance`]: attribute_cert_types::Clearance
302/// [`AAControls`]: attribute_cert_types::AAControls
303/// [`Targets`]: attribute_cert_types::Targets
304/// [`Target`]: attribute_cert_types::Target
305#[allow(clippy::large_enum_variant)]
306pub mod attribute_cert_types {
307 include!(concat!(env!("OUT_DIR"), "/attribute_cert_generated.rs"));
308}
309
310/// RFC 4211 Certificate Request Message Format (CRMF) types.
311///
312/// Generated from `asn1/CRMF.asn1`. CRMF defines the wire format for
313/// certificate requests sent to a CA via CMP (RFC 9810) or other protocols.
314///
315/// Key types:
316/// - [`CertReqMessages`] — SEQUENCE OF CertReqMsg (outer container)
317/// - [`CertReqMsg`] — single request: CertRequest + optional POP + regInfo
318/// - [`CertRequest`] — request ID + CertTemplate + optional Controls
319/// - [`CertTemplate`] — all-optional \[0\]-\[9\] IMPLICIT template fields
320/// - [`ProofOfPossession`] — CHOICE: raVerified, signature, keyEncipherment, keyAgreement
321/// - [`POPOSigningKey`] — signature-based proof of possession
322/// - [`PBMParameter`] — password-based MAC parameters
323/// - [`EncryptedKey`] — CHOICE: EncryptedValue or EnvelopedData (as [`RawDer`])
324/// - [`PKIArchiveOptions`] — key archival options
325/// - [`PKIPublicationInfo`] — certificate publication options
326///
327/// Complex `ANY` fields (e.g. `AttributeTypeAndValue.value`, EnvelopedData arms)
328/// are stored as [`RawDer<'a>`] for lazy decoding.
329///
330/// [`CertReqMessages`]: crmf_types::CertReqMessages
331/// [`CertReqMsg`]: crmf_types::CertReqMsg
332/// [`CertRequest`]: crmf_types::CertRequest
333/// [`CertTemplate`]: crmf_types::CertTemplate
334/// [`ProofOfPossession`]: crmf_types::ProofOfPossession
335/// [`POPOSigningKey`]: crmf_types::POPOSigningKey
336/// [`PBMParameter`]: crmf_types::PBMParameter
337/// [`EncryptedKey`]: crmf_types::EncryptedKey
338/// [`PKIArchiveOptions`]: crmf_types::PKIArchiveOptions
339/// [`PKIPublicationInfo`]: crmf_types::PKIPublicationInfo
340/// [`RawDer`]: synta::RawDer
341/// [`RawDer<'a>`]: synta::RawDer
342#[allow(clippy::large_enum_variant)]
343pub mod crmf_types {
344 include!(concat!(env!("OUT_DIR"), "/crmf_generated.rs"));
345}
346
347/// Builder for CRMF (RFC 4211) certificate request messages.
348///
349/// [`CertReqMsgBuilder`] assembles a single `CertReqMsg` DER from pre-encoded
350/// Name and SubjectPublicKeyInfo bytes. [`CertReqMessagesBuilder`] wraps a
351/// list of pre-encoded `CertReqMsg` DER blobs in the outer SEQUENCE envelope.
352pub mod crmf_builder;
353pub use crmf_builder::{
354 CertReqMessagesBuilder, CertReqMsgBuilder, PUB_METHOD_DONT_CARE, PUB_METHOD_LDAP,
355 PUB_METHOD_WEB, PUB_METHOD_X500,
356};
357
358/// RFC 9810 Certificate Management Protocol (CMP) v3 types.
359///
360/// Generated from `asn1/CMP.asn1`. CMP provides a complete PKI management
361/// protocol for certificate issuance, renewal, revocation, key recovery,
362/// and CA key update. RFC 9810 (CMP v3) supersedes RFC 4210 and adds
363/// KEM-based MAC (`KemBMParameter`) and v3 CA key update (`RootCaKeyUpdateContent`).
364///
365/// Key types:
366/// - [`PKIMessage`] — outer CMP message (header + body + optional protection)
367/// - [`PKIHeader`] — message header (sender, recipient, transactionID, nonces, …)
368/// - [`PKIBody`] — CHOICE of 27 message types; all arms are [`RawDer<'a>`] for
369/// lazy decoding; callers dispatch on the known message type
370/// - [`PKIStatusInfo`] — status code + optional text + optional failure info
371/// - [`PBMParameter`] — password-based MAC parameters
372/// - [`DHBMParameter`] — DH-based MAC parameters
373/// - [`KemBMParameter`] — KEM-based MAC parameters (new in v3)
374/// - [`CertRepMessage`] / [`CertResponse`] — ip/cp/kup/ccp response body
375/// - [`CertConfirmContent`] / [`CertStatus`] — certConf body
376/// - [`RevReqContent`] / [`RevRepContent`] — rr/rp revocation body
377/// - [`ErrorMsgContent`] — error message body
378/// - [`InfoTypeAndValue`] — genm/genp body elements
379/// - [`PollReqContent`] / [`PollRepContent`] — pollReq/pollRep body
380///
381/// [`PKIMessage`]: cmp_types::PKIMessage
382/// [`PKIHeader`]: cmp_types::PKIHeader
383/// [`PKIBody`]: cmp_types::PKIBody
384/// [`PKIStatusInfo`]: cmp_types::PKIStatusInfo
385/// [`PBMParameter`]: cmp_types::PBMParameter
386/// [`DHBMParameter`]: cmp_types::DHBMParameter
387/// [`KemBMParameter`]: cmp_types::KemBMParameter
388/// [`CertRepMessage`]: cmp_types::CertRepMessage
389/// [`CertResponse`]: cmp_types::CertResponse
390/// [`CertConfirmContent`]: cmp_types::CertConfirmContent
391/// [`CertStatus`]: cmp_types::CertStatus
392/// [`RevReqContent`]: cmp_types::RevReqContent
393/// [`RevRepContent`]: cmp_types::RevRepContent
394/// [`ErrorMsgContent`]: cmp_types::ErrorMsgContent
395/// [`InfoTypeAndValue`]: cmp_types::InfoTypeAndValue
396/// [`PollReqContent`]: cmp_types::PollReqContent
397/// [`PollRepContent`]: cmp_types::PollRepContent
398/// [`RawDer<'a>`]: synta::RawDer
399pub mod cmp_types {
400 include!(concat!(env!("OUT_DIR"), "/cmp_generated.rs"));
401}
402
403pub mod cmp_builder;
404pub use cmp_builder::CMPMessageBuilder;
405
406/// RFC 9925 Unsigned X.509 Certificate OID constants.
407///
408/// Generated from `asn1/RFC9925-UnsignedCert.asn1`. RFC 9925 defines a
409/// profile for unsigned X.509 certificates — certificates used as containers
410/// for subject information without any cryptographic binding to an issuer.
411/// The standard Certificate structure (RFC 5280) is used unchanged; RFC 9925
412/// only introduces two OID assignments:
413///
414/// - [`ID_ALG_UNSIGNED`] — signature algorithm identifier placed in both
415/// `Certificate.signatureAlgorithm` and `TBSCertificate.signature`.
416/// Parameters MUST be absent. The `signatureValue` field MUST be a
417/// zero-length BIT STRING (DER: `03 01 00`).
418///
419/// - [`ID_RDNA_UNSIGNED`] — optional placeholder RDN attribute for the
420/// issuer field. Value is a zero-length UTF8String (`0C 00`).
421///
422/// These OIDs are re-exported in [`crate::oids`] as
423/// [`crate::oids::ALG_UNSIGNED`] and [`crate::oids::RDNA_UNSIGNED`].
424///
425/// [`ID_ALG_UNSIGNED`]: rfc9925_types::ID_ALG_UNSIGNED
426/// [`ID_RDNA_UNSIGNED`]: rfc9925_types::ID_RDNA_UNSIGNED
427pub mod rfc9925_types {
428 include!(concat!(env!("OUT_DIR"), "/rfc9925_generated.rs"));
429}
430/// RFC 7773 Authentication Context Certificate Extension (ACE-88, 1988 syntax).
431///
432/// Generated from `asn1/ACE-88.asn1`. Defines the X.509 extension type for
433/// linking authentication contexts to end-entity certificates, as used by
434/// Swedish e-ID infrastructure (e-Legitimationsnämnden).
435///
436/// Key types:
437/// - [`AuthenticationContexts`] — SEQUENCE SIZE (1..MAX) OF AuthenticationContext;
438/// the extension value wrapped in an OCTET STRING
439/// - [`AuthenticationContext`] — a single context entry: `contextType` UTF8String
440/// and optional `contextInfo` UTF8String
441///
442/// OID: `id-ce-authContext` (1.2.752.201.5.1)
443///
444/// [`AuthenticationContexts`]: ace88_types::AuthenticationContexts
445/// [`AuthenticationContext`]: ace88_types::AuthenticationContext
446pub mod ace88_types {
447 include!(concat!(env!("OUT_DIR"), "/ace88_generated.rs"));
448}
449
450/// RFC 8769 CBOR content type OID constants for CMS.
451///
452/// Generated from `asn1/CborContentTypes.asn1`. Defines two OBJECT IDENTIFIER
453/// constants for use in CMS `ContentInfo.contentType` when the content is
454/// CBOR-encoded data (RFC 8949):
455///
456/// - [`ID_CT_CBOR`] — `id-ct-cbor` (1.2.840.113549.1.9.16.1.44) — a single
457/// CBOR data item
458/// - [`ID_CT_CBOR_SEQUENCE`] — `id-ct-cborSequence` (1.2.840.113549.1.9.16.1.45)
459/// — a sequence of CBOR data items concatenated without any outer wrapper
460///
461/// The content for both types is encoded directly (raw bytes, no ASN.1
462/// structure); no additional Rust types are generated beyond the OID constants.
463///
464/// [`ID_CT_CBOR`]: cbor_content_types::ID_CT_CBOR
465/// [`ID_CT_CBOR_SEQUENCE`]: cbor_content_types::ID_CT_CBOR_SEQUENCE
466pub mod cbor_content_types {
467 include!(concat!(env!("OUT_DIR"), "/cbor_content_types_generated.rs"));
468}
469
470/// RFC 8737: ACME TLS-ALPN-01 identifier extension (`id-pe-acmeIdentifier`).
471///
472/// Generated from `asn1/ACME-RFC8737.asn1`. Defines the X.509 extension and
473/// type used by an ACME server to validate domain control via the TLS-ALPN-01
474/// challenge (RFC 8737 §3):
475///
476/// - [`ID_PE_ACME_IDENTIFIER`] — OID `id-pe-acmeIdentifier`
477/// (1.3.6.1.5.5.7.1.31). The extension MUST be marked critical.
478/// - [`Authorization`] — `OCTET STRING (SIZE (32))`. The extension value is
479/// the DER encoding of a 32-byte SHA-256 digest of the ACME key
480/// authorization string for the challenge token.
481///
482/// The OID is re-exported in [`crate::oids`] as
483/// [`crate::oids::PE_ACME_IDENTIFIER`].
484///
485/// [`ID_PE_ACME_IDENTIFIER`]: acme_types::ID_PE_ACME_IDENTIFIER
486/// [`Authorization`]: acme_types::Authorization
487pub mod acme_types {
488 include!(concat!(env!("OUT_DIR"), "/acme_rfc8737_generated.rs"));
489}
490
491/// `FileAndHash` for RPKI signed object manifest content type.
492pub mod rpki_manifest_types {
493 include!(concat!(env!("OUT_DIR"), "/rpki_manifest_generated.rs"));
494}
495
496/// for S/MIME 4.0 message handling.
497pub mod smime_v3dot1_types {
498 include!(concat!(env!("OUT_DIR"), "/smime_v3dot1_generated.rs"));
499}
500
501/// RPKI signed manifest types (RFC 9286).
502///
503/// Generated from `asn1/RPKIManifest.asn1`. Defines `Manifest` and
504/// use in PKCS#12 MAC computation.
505pub mod pkcs12_pbmac1_2023_types {
506 include!(concat!(env!("OUT_DIR"), "/pkcs12_pbmac1_2023_generated.rs"));
507}
508
509/// S/MIME v3.1 message types and OIDs (RFC 8551).
510///
511/// Generated from `asn1/SecureMimeMessageV3dot1.asn1`. Defines
512/// `SMIMECapability`, `SMIMECapabilities`, and related OID constants
513/// wrapping via HKDF-SHA-256.
514pub mod cms_cek_hkdf_sha256_2023_types {
515 include!(concat!(
516 env!("OUT_DIR"),
517 "/cms_cek_hkdf_sha256_2023_generated.rs"
518 ));
519}
520
521/// PKCS#12 PBMAC1 MAC parameters (RFC 9879).
522///
523/// Generated from `asn1/PKCS12-PBMAC1-2023.asn1`. Defines
524/// `PBMAC1-params` and HMAC OID constants for SHA-2 based PBKDF2/PBMAC1
525/// `CMSORIforPSKOtherInfo` for PSK-based CMS recipient info.
526pub mod cms_ori_for_psk_2019_types {
527 include!(concat!(
528 env!("OUT_DIR"),
529 "/cms_ori_for_psk_2019_generated.rs"
530 ));
531}
532
533/// CMS CEK-HKDF-SHA256 Algorithm OID (RFC 9709).
534///
535/// Generated from `asn1/CMS-CEK-HKDF-SHA256-Module-2023.asn1`.
536/// Defines `id-alg-cek-hkdf-sha256` for content encryption key
537/// the `GMACParameters` structure.
538pub mod cms_gmac_algorithms_types {
539 include!(concat!(
540 env!("OUT_DIR"),
541 "/cms_gmac_algorithms_generated.rs"
542 ));
543}
544
545/// CMS OtherRecipientInfo for Pre-Shared Key (RFC 8696).
546///
547/// Generated from `asn1/CMSORIforPSK-2019.asn1`. Defines
548/// `KeyTransPSKRecipientInfo`, `KeyAgreePSKRecipientInfo`, and
549/// `id-kp-rpcTLSClient` and `id-kp-rpcTLSServer` EKU OIDs.
550pub mod rpc_with_tls_2021_types {
551 include!(concat!(env!("OUT_DIR"), "/rpc_with_tls_2021_generated.rs"));
552}
553
554/// CMS GMAC Algorithm OIDs and parameters (RFC 9044).
555///
556/// Generated from `asn1/CryptographicMessageSyntaxGMACAlgorithms.asn1`.
557/// Defines `id-aes128-GMAC`, `id-aes192-GMAC`, `id-aes256-GMAC`, and
558/// `id-alg-hkdf-with-sha512` for use with HKDF key derivation.
559pub mod hkdf_oid_2019_types {
560 include!(concat!(env!("OUT_DIR"), "/hkdf_oid_2019_generated.rs"));
561}
562
563/// PKCS #8 private key structure types.
564///
565/// Generated from `asn1/PKCS8.asn1`. Defines [`OneAsymmetricKey`] (RFC 5958)
566/// and its backward-compatible alias [`PrivateKeyInfo`] (RFC 5208) — the
567/// standard DER container for private keys, typically PEM-armoured as
568/// `"-----BEGIN PRIVATE KEY-----"`.
569///
570/// The `attributes` and `publicKey` fields are stored as [`RawDer<'a>`] for
571/// lazy decoding; the caller must decode them according to the private key
572/// algorithm.
573///
574/// [`OneAsymmetricKey`]: pkcs8_types::OneAsymmetricKey
575/// [`PrivateKeyInfo`]: pkcs8_types::PrivateKeyInfo
576/// [`RawDer<'a>`]: synta::RawDer
577pub mod pkcs8_types {
578 include!(concat!(env!("OUT_DIR"), "/pkcs8_generated.rs"));
579}
580
581/// PKINIT OID constants and protocol structures.
582///
583/// Generated from `asn1/PKINIT.asn1` based on RFC 4556 (PKINIT), RFC 6112
584/// (anonymous PKINIT), and RFC 8636 (PKINIT algorithm agility).
585/// Key OIDs re-exported in [`crate::oids`]:
586/// - [`crate::oids::ID_PKINIT_SAN`] — KRB5PrincipalName SAN OtherName type (1.3.6.1.5.2.2)
587/// - [`crate::oids::ID_PKINIT_KPCLIENT_AUTH`] — PKINIT client EKU (1.3.6.1.5.2.3.4)
588/// - [`crate::oids::ID_PKINIT_KPKDC`] — PKINIT KDC EKU (1.3.6.1.5.2.3.5)
589pub mod pkinit_types {
590 include!(concat!(env!("OUT_DIR"), "/pkinit_generated.rs"));
591}
592
593/// Microsoft PKI OID constants and AD CS extension structures.
594///
595/// Generated from `asn1/MicrosoftPKI.asn1`. Covers Active Directory Certificate
596/// Services (AD CS) OIDs from [MS-WCCE] and related specifications.
597/// Key OIDs re-exported in [`crate::oids`]:
598/// - [`crate::oids::ID_MS_CERTIFICATE_TEMPLATE_NAME`] — MS template name V1 (1.3.6.1.4.1.311.20.2)
599/// - [`crate::oids::ID_MS_CERTIFICATE_TEMPLATE`] — MS template info V2 (1.3.6.1.4.1.311.21.7)
600/// - [`crate::oids::ID_MS_SAN_UPN`] — UPN SAN OtherName type (1.3.6.1.4.1.311.20.2.3)
601pub mod ms_pki_types {
602 include!(concat!(env!("OUT_DIR"), "/ms_pki_generated.rs"));
603}
604
605/// PKCS #5 v2.1 parameter types and OID constants (RFC 8018).
606///
607/// Generated from `asn1/PKCS5v2-1.asn1`. Defines parameter structures for
608/// the PBKDF2 key derivation function, PBES1/PBES2 encryption schemes, and
609/// PBMAC1 MAC scheme, along with all supporting OID constants.
610///
611/// Key types:
612/// - `Pkcs5AlgorithmIdentifier` — local AlgorithmIdentifier (avoids crate-root clash)
613/// - `Pkcs5Pbkdf2Params` — PBKDF2 parameters (RFC 8018 §5.2)
614/// - `Pkcs5PbeParameter` — PBES1 parameters (RFC 8018 §6.1)
615/// - `Pkcs5Pbes2Params` — PBES2 parameters (RFC 8018 §6.2)
616/// - `Pkcs5Pbmac1Params` — PBMAC1 parameters (RFC 8018 §7.1)
617/// - `Rc2CbcParameter` — RC2 cipher parameters (RFC 8018 Appendix B.2.3)
618/// - `Rc5CbcParameters` — RC5 cipher parameters (RFC 8018 Appendix B.2.4)
619///
620/// Key OID constants:
621/// - `id_PBKDF2` — PBKDF2 (1.2.840.113549.1.5.12)
622/// - `id_PBES2` — PBES2 (1.2.840.113549.1.5.13)
623/// - `id_PBMAC1` — PBMAC1 (1.2.840.113549.1.5.14)
624/// - `aes128_CBC_PAD` / `aes192_CBC_PAD` / `aes256_CBC_PAD` — AES-CBC-PAD OIDs
625pub mod pkcs5_types {
626 include!(concat!(env!("OUT_DIR"), "/pkcs5_generated.rs"));
627}
628
629/// RFC 5912 PKIX-CommonTypes-2009 — information object class definitions and
630/// parameterized helper types used across the 2009-syntax PKIX module suite.
631///
632/// Defines the `ATTRIBUTE`, `EXTENSION`, and `MATCHING-RULE` information object
633/// classes (generated as comments only — no DER structure) and the concrete
634/// parameterized types:
635/// - `AttributeSet` — `SEQUENCE { type ATTRIBUTE.&id, values SET OF ATTRIBUTE.&Type }`
636/// - `SingleAttribute` — single-valued ATTRIBUTE instance
637/// - `Extension` — `SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }`
638/// - `SecurityCategory` — security category label
639///
640/// Source: RFC 5912 §2.
641pub mod pkix_common_types {
642 include!(concat!(env!("OUT_DIR"), "/pkix_common_types_generated.rs"));
643}
644
645/// RFC 5912 AlgorithmInformation-2009 — algorithm information object classes
646/// and the parameterized `AlgorithmIdentifier` type for 2009-syntax modules.
647///
648/// The generated code provides:
649/// - `ParamOptions` — ENUMERATED describing parameter presence requirements
650/// - `AlgorithmIdentifier2009` — `SEQUENCE { algorithm OID, parameters ANY OPTIONAL }`
651/// - `SmimeCapability` — `SEQUENCE { capabilityID OID, parameters ANY OPTIONAL }`
652/// - `SmimeCapabilities` — `SEQUENCE OF SmimeCapability`
653///
654/// The CLASS definitions (`DIGEST-ALGORITHM`, `SIGNATURE-ALGORITHM`, `PUBLIC-KEY`,
655/// etc.) carry no DER encoding; they are emitted as documentation comments only.
656///
657/// Source: RFC 5912 §3.
658pub mod alg_info_types {
659 include!(concat!(env!("OUT_DIR"), "/alg_info_generated.rs"));
660}
661
662/// RFC 5912 PKIXAlgs-2009 — 2009-syntax restatement of RFC 3279 / RFC 5480
663/// public key and signature algorithm parameters.
664///
665/// Provides OID constants for RSA, DSA, DH, KEA, ECDH, ECMQV and ECDSA
666/// algorithms, named curve OIDs, and the concrete structures:
667/// - `DsaParams` — DSA parameters `SEQUENCE { p INTEGER, q INTEGER, g INTEGER }`
668/// - `DomainParameters` — DH domain parameters
669/// - `ValidationParams` — DH validation parameters
670/// - `EcParameters` — `CHOICE { namedCurve OID, ... }`
671/// - `DsaSigValue` — DSA signature `SEQUENCE { r INTEGER, s INTEGER }`
672/// - `EcdsaSigValue` — ECDSA signature `SEQUENCE { r INTEGER, s INTEGER }`
673///
674/// Source: RFC 5912 §6 (cross-references RFC 3279 and RFC 5480).
675pub mod pkixalgs_2009_types {
676 include!(concat!(env!("OUT_DIR"), "/pkixalgs_2009_generated.rs"));
677}
678
679/// RFC 5912 PKIX1Explicit-2009 — 2009-syntax restatement of the RFC 5280
680/// explicit-tags module, using information object classes and parameterized types.
681///
682/// Provides OID constants for the PKIX hierarchy (`id-pkix`, `id-pe`, `id-qt`,
683/// `id-kp`, `id-ad`, etc.), attribute type OIDs (`id-at-*`), the DN attribute
684/// types (`X520name`, `X520CommonName`, …), and the core X.509 structures:
685/// - `Certificate2009` — `SIGNED { TBSCertificate }` (parameterized wrapper)
686/// - `TbsCertificate2009` — `SEQUENCE { version, serialNumber, … }` with 2009
687/// extension addition groups
688/// - `Name2009` — `CHOICE { rdnSequence RDNSequence }`
689/// - `SubjectPublicKeyInfo2009` — `SEQUENCE { algorithm, subjectPublicKey }`
690///
691/// Source: RFC 5912 §14.
692pub mod pkix1_explicit_types {
693 include!(concat!(env!("OUT_DIR"), "/pkix1_explicit_generated.rs"));
694}
695
696/// RFC 5912 PKIX1Implicit-2009 — 2009-syntax restatement of the RFC 5280
697/// implicit-tags module, defining X.509v3 certificate extension structures.
698///
699/// Provides OID constants for all standard X.509v3 extensions (`id-ce-*`)
700/// and the extension value types:
701/// - [`AuthorityKeyIdentifier`] — key identifier + optional issuer + serial
702/// - [`KeyUsage`] — BIT STRING with named bits (digitalSignature, …)
703/// - [`GeneralName`] — CHOICE of subject/issuer name forms
704/// - [`GeneralNames`] — `SEQUENCE OF GeneralName`
705/// - [`DistributionPoint`] — CRL distribution point
706/// - [`AccessDescription`] — authority / subject information access
707/// - and many more standard extension types
708///
709/// Source: RFC 5912 §14.
710pub mod pkix1_implicit_types {
711 include!(concat!(env!("OUT_DIR"), "/pkix1_implicit_generated.rs"));
712}
713
714/// RFC 5652 / RFC 6268 Cryptographic Message Syntax (CMS) 2010 types.
715///
716/// Generated from `asn1/CMS-2010.asn1`. Defines the subset of CMS types
717/// used by CMS-KEM (RFC 9629): [`CMSVersion`], [`RecipientIdentifier`],
718/// [`IssuerAndSerialNumber`], [`KeyDerivationAlgorithmIdentifier`],
719/// [`KeyEncryptionAlgorithmIdentifier`], [`EncryptedKey`], and
720/// [`UserKeyingMaterial`].
721///
722/// [`CMSVersion`]: cms_2010_types::CMSVersion
723/// [`RecipientIdentifier`]: cms_2010_types::RecipientIdentifier
724/// [`IssuerAndSerialNumber`]: cms_2010_types::IssuerAndSerialNumber
725/// [`KeyDerivationAlgorithmIdentifier`]: cms_2010_types::KeyDerivationAlgorithmIdentifier
726/// [`KeyEncryptionAlgorithmIdentifier`]: cms_2010_types::KeyEncryptionAlgorithmIdentifier
727/// [`EncryptedKey`]: cms_2010_types::EncryptedKey
728/// [`UserKeyingMaterial`]: cms_2010_types::UserKeyingMaterial
729pub mod cms_2010_types {
730 include!(concat!(env!("OUT_DIR"), "/cms_2010_generated.rs"));
731}
732
733/// RFC 9629 §6.1 KEM Algorithm Information Object Class.
734///
735/// Generated from `asn1/KEMAlgorithmInformation.asn1`. The module contains only
736/// an ASN.1 CLASS definition (`KEM-ALGORITHM`) which has no DER encoding; no
737/// Rust types are generated — the module is a documentation stub only.
738pub mod kem_alg_info_types {
739 include!(concat!(env!("OUT_DIR"), "/kem_alg_info_generated.rs"));
740}
741
742/// RFC 7229 test certificate policy OIDs.
743///
744/// Generated from `asn1/PKIXTestCertPolicies.asn1`. Defines eight PKIX test
745/// policy OIDs under the `id-TEST` arc (`1.3.6.1.5.5.7.13`):
746///
747/// | Constant | OID |
748/// |---------------------------|--------------------------|
749/// | `ID_TEST_CERT_POLICY_ONE` | `1.3.6.1.5.5.7.13.1` |
750/// | `ID_TEST_CERT_POLICY_TWO` | `1.3.6.1.5.5.7.13.2` |
751/// | `ID_TEST_CERT_POLICY_THREE` | `1.3.6.1.5.5.7.13.3` |
752/// | `ID_TEST_CERT_POLICY_FOUR` | `1.3.6.1.5.5.7.13.4` |
753/// | `ID_TEST_CERT_POLICY_FIVE` | `1.3.6.1.5.5.7.13.5` |
754/// | `ID_TEST_CERT_POLICY_SIX` | `1.3.6.1.5.5.7.13.6` |
755/// | `ID_TEST_CERT_POLICY_SEVEN` | `1.3.6.1.5.5.7.13.7` |
756/// | `ID_TEST_CERT_POLICY_EIGHT` | `1.3.6.1.5.5.7.13.8` |
757///
758/// These OIDs are intended **exclusively for testing** certificate policy
759/// processing implementations and MUST NOT appear in production certificates.
760pub mod pkix_test_cert_policies_types {
761 include!(concat!(
762 env!("OUT_DIR"),
763 "/pkix_test_cert_policies_generated.rs"
764 ));
765}
766
767/// RFC 9629 §6.2 CMS KEM Recipient Info types.
768///
769/// Generated from `asn1/CMS-KEM.asn1`. Defines [`KEMRecipientInfo`] and
770/// [`CMSORIforKEMOtherInfo`] for quantum-safe (KEM-based) key establishment
771/// in CMS `EnvelopedData`. `KEMRecipientInfo` is carried as an
772/// `OtherRecipientInfo` alternative identified by `id-ori-kem`.
773///
774/// [`KEMRecipientInfo`]: cms_kem_types::KEMRecipientInfo
775/// [`CMSORIforKEMOtherInfo`]: cms_kem_types::CMSORIforKEMOtherInfo
776pub mod cms_kem_types {
777 include!(concat!(env!("OUT_DIR"), "/cms_kem_generated.rs"));
778}
779
780/// RFC 5652 Cryptographic Message Syntax (CMS) full structure types.
781///
782/// Generated from `asn1/CMS-RFC5652.asn1`. Covers the complete set of CMS
783/// structures from RFC 5652: [`SignedData`], [`EnvelopedData`],
784/// [`DigestedData`], [`EncryptedData`], [`AuthenticatedData`] and all
785/// supporting types (SignerInfo, EncapsulatedContentInfo, OriginatorInfo,
786/// EncryptedContentInfo, RecipientEncryptedKey, KEKRecipientInfo, etc.).
787///
788/// CHOICE fields that cannot be decoded by the derive macro at compile time
789/// (CertificateSet, RevocationInfoChoices, SignerIdentifier, SignedAttributes,
790/// RecipientInfos, OriginatorIdentifierOrKey, etc.) are stored as
791/// [`RawDer<'a>`] for lazy decoding by the caller.
792///
793/// [`SignedData`]: cms_rfc5652_types::SignedData
794/// [`EnvelopedData`]: cms_rfc5652_types::EnvelopedData
795/// [`DigestedData`]: cms_rfc5652_types::DigestedData
796/// [`EncryptedData`]: cms_rfc5652_types::EncryptedData
797/// [`AuthenticatedData`]: cms_rfc5652_types::AuthenticatedData
798/// [`RawDer<'a>`]: synta::RawDer
799pub mod cms_rfc5652_types {
800 include!(concat!(env!("OUT_DIR"), "/cms_rfc5652_generated.rs"));
801}
802
803/// RFC 9399 §A.1 Certificate Image OID module.
804///
805/// Generated from `asn1/CERT-IMAGE-MODULE.asn1`. Defines the
806/// `id-logo-certImage` object identifier
807/// (`1.3.6.1.5.5.7.20.3`), which identifies the certificate image logotype
808/// type carried in `otherLogos` of a [`logotype_cert_extn_types::LogotypeExtn`].
809/// The certificate image is a complete visual representation of the certificate
810/// intended for human display (Section 4.4.3 of RFC 9399).
811///
812/// This module was previously published as RFC 6170, which is now obsoleted by
813/// RFC 9399.
814///
815/// [`logotype_cert_extn_types::LogotypeExtn`]: logotype_cert_extn_types::LogotypeExtn
816pub mod cert_image_module_types {
817 include!(concat!(env!("OUT_DIR"), "/cert_image_module_generated.rs"));
818}
819
820/// RFC 9399 §A.1 Logotype certificate extension types (1988 ASN.1 syntax).
821///
822/// Generated from `asn1/LogotypeCertExtn.asn1`. Defines the logotype
823/// certificate extension (OID `1.3.6.1.5.5.7.1.12`, `id-pe-logotype`) and
824/// all supporting structures for embedding or referencing logotype image and
825/// audio data in X.509 public key certificates and attribute certificates.
826///
827/// Key types:
828/// - [`LogotypeExtn`] — the top-level extension value (community, issuer,
829/// subject, and other logotypes).
830/// - [`LogotypeInfo`] — CHOICE between direct ([`LogotypeData`]) and indirect
831/// ([`LogotypeReference`]) addressing.
832/// - [`LogotypeDetails`] — media type, hash(es), and URI(s) for one logotype object.
833/// - [`HashAlgAndValue`] — one-way hash algorithm and computed value pair.
834/// - [`LogotypeImageInfo`] — recommended display dimensions and colour depth.
835/// - [`LogotypeAudioInfo`] — audio file metadata (size, duration, channels, language).
836/// - [`OtherLogotypeInfo`] — logotype identified by an arbitrary OID
837/// (loyalty, background, certificate image, or private extensions).
838///
839/// Other logotype OIDs defined here: `id-logo` (`1.3.6.1.5.5.7.20`),
840/// `id-logo-loyalty` (`…20.1`), `id-logo-background` (`…20.2`).
841/// The `id-logo-certImage` (`…20.3`) OID is in [`cert_image_module_types`].
842///
843/// [`LogotypeExtn`]: logotype_cert_extn_types::LogotypeExtn
844/// [`LogotypeInfo`]: logotype_cert_extn_types::LogotypeInfo
845/// [`LogotypeData`]: logotype_cert_extn_types::LogotypeData
846/// [`LogotypeReference`]: logotype_cert_extn_types::LogotypeReference
847/// [`LogotypeDetails`]: logotype_cert_extn_types::LogotypeDetails
848/// [`HashAlgAndValue`]: logotype_cert_extn_types::HashAlgAndValue
849/// [`LogotypeImageInfo`]: logotype_cert_extn_types::LogotypeImageInfo
850/// [`LogotypeAudioInfo`]: logotype_cert_extn_types::LogotypeAudioInfo
851/// [`OtherLogotypeInfo`]: logotype_cert_extn_types::OtherLogotypeInfo
852/// [`cert_image_module_types`]: cert_image_module_types
853pub mod logotype_cert_extn_types {
854 include!(concat!(env!("OUT_DIR"), "/logotype_cert_extn_generated.rs"));
855}
856
857/// RFC 2634 Extended Security Services (ESS) types.
858///
859/// Generated from `asn1/ESS.asn1`. ESS defines optional security service
860/// extensions for S/MIME: signed receipts, security labels, secure mailing
861/// lists, and signing certificates.
862///
863/// Key types:
864/// - [`ReceiptRequest`] — signed receipt request attribute
865/// - [`Receipt`] — signed receipt content type
866/// - [`ContentHints`] — content hints attribute
867/// - [`MsgSigDigest`] — message signature digest (alias for OCTET STRING)
868/// - [`ContentReference`] — content reference attribute
869/// - [`ESSSecurityLabel`] — security label attribute (SET)
870/// - [`ESSPrivacyMark`] — privacy mark CHOICE (PrintableString or UTF8String)
871/// - [`SecurityCategories`] / [`SecurityCategory`] — security category set
872/// - [`EquivalentLabels`] — equivalent labels attribute
873/// - [`MLExpansionHistory`] / [`MLData`] — mailing list expansion history
874/// - [`SigningCertificate`] — signing certificate attribute
875/// - [`ESSCertID`] — certificate hash + issuer/serial (SHA-1 based)
876/// - [`IssuerSerial`] — issuer GeneralNames + serial number
877///
878/// [`ReceiptRequest`]: ess_types::ReceiptRequest
879/// [`Receipt`]: ess_types::Receipt
880/// [`ContentHints`]: ess_types::ContentHints
881/// [`MsgSigDigest`]: ess_types::MsgSigDigest
882/// [`ContentReference`]: ess_types::ContentReference
883/// [`ESSSecurityLabel`]: ess_types::ESSSecurityLabel
884/// [`ESSPrivacyMark`]: ess_types::ESSPrivacyMark
885/// [`SecurityCategories`]: ess_types::SecurityCategories
886/// [`SecurityCategory`]: ess_types::SecurityCategory
887/// [`EquivalentLabels`]: ess_types::EquivalentLabels
888/// [`MLExpansionHistory`]: ess_types::MLExpansionHistory
889/// [`MLData`]: ess_types::MLData
890/// [`SigningCertificate`]: ess_types::SigningCertificate
891/// [`ESSCertID`]: ess_types::ESSCertID
892/// [`IssuerSerial`]: ess_types::IssuerSerial
893pub mod ess_types {
894 include!(concat!(env!("OUT_DIR"), "/ess_generated.rs"));
895}
896
897/// RFC 3161 Time-Stamp Protocol (TSP) types.
898///
899/// Generated from `asn1/PKIXTSP.asn1`. Defines the wire format for
900/// Time-Stamp Requests and Responses as specified in RFC 3161.
901///
902/// Key types:
903/// - [`TimeStampReq`] — time-stamp request (version, messageImprint, reqPolicy, nonce, certReq, extensions)
904/// - [`MessageImprint`] — hash algorithm OID + hash value of the data to be time-stamped
905/// - [`TimeStampResp`] — response containing a PKIStatusInfo and an optional TimeStampToken
906/// - [`PKIStatusInfo`] — status code, optional free text, optional failure info
907/// - [`TSTInfo`] — the TSTInfo structure encapsulated within a SignedData ContentInfo
908/// - [`Accuracy`] — optional time-stamp accuracy in seconds, milliseconds, microseconds
909///
910/// The `timeStampToken` field in [`TimeStampResp`] and the `TimeStampToken` type alias
911/// are both `ContentInfo` (from [`crate::pkcs7_types`]); the eContentType OID is
912/// `id-ct-TSTInfo` (1.2.840.113549.1.9.16.1.4), and the eContent is a DER-encoded
913/// `TSTInfo` structure wrapped in a `SignedData`.
914///
915/// [`TimeStampReq`]: tsp_types::TimeStampReq
916/// [`MessageImprint`]: tsp_types::MessageImprint
917/// [`TimeStampResp`]: tsp_types::TimeStampResp
918/// [`PKIStatusInfo`]: tsp_types::PKIStatusInfo
919/// [`TSTInfo`]: tsp_types::TSTInfo
920/// [`Accuracy`]: tsp_types::Accuracy
921pub mod tsp_types {
922 include!(concat!(env!("OUT_DIR"), "/pkixtsp_generated.rs"));
923}
924
925/// RFC 5911 CryptographicMessageSyntax-2009 OID constants and structural types.
926///
927/// Generated from `asn1/CryptographicMessageSyntax-2009.asn1`. The module
928/// uses 2009 IOC syntax; codegen emits OID constants and any plain
929/// SEQUENCE/CHOICE types it can resolve, skipping CLASS and VALUE assignments.
930pub mod cms_2009_types {
931 include!(concat!(env!("OUT_DIR"), "/cms_2009_generated.rs"));
932}
933
934/// RFC 5912 §8 PKIX1-PSS-OAEP-Algorithms-2009 — RSA-PSS and RSA-OAEP.
935///
936/// Generated from `asn1/PKIX1-PSS-OAEP-Algorithms-2009.asn1`. Defines
937/// `RSASSA-PSS-params`, `RSAES-OAEP-params`, `HashAlgorithm`,
938/// `MaskGenAlgorithm`, `PSourceAlgorithm`, and SHA-224/256/384/512 OID
939/// constants. IOC object-set values are skipped by codegen.
940pub mod pkix1_pss_oaep_alg_2009_types {
941 include!(concat!(
942 env!("OUT_DIR"),
943 "/pkix1_pss_oaep_alg_2009_generated.rs"
944 ));
945}
946
947/// RFC 9654 OCSP-2024-88 — updated OCSP module in 1988 ASN.1 syntax.
948///
949/// This is the normative 1988-syntax module from RFC 9654 (which obsoletes
950/// RFC 8954). It defines the same OCSP types as [`ocsp`] (generated from
951/// RFC 6960) but with the updated module OID `id-mod-ocsp-2024-88(111)` and
952/// includes the `PreferredSignatureAlgorithm` and extended-revoke OID from
953/// RFC 8954.
954///
955/// The types in this module (`OCSPRequest`, `OCSPResponse`, `BasicOCSPResponse`,
956/// etc.) are structurally identical to those in [`ocsp`]; this module exists to
957/// expose the RFC 9654 OID constants.
958///
959/// Source: RFC 9654 Appendix A.1.
960#[allow(unused_imports, dead_code)]
961pub mod ocsp_2024_88_types {
962 include!(concat!(env!("OUT_DIR"), "/ocsp_2024_88_generated.rs"));
963}
964
965/// RFC 9654 OCSP-2024-08 — updated OCSP module in 2008 ASN.1 syntax.
966///
967/// This is the 2008/2009-syntax module from RFC 9654 Appendix A.2, provided
968/// for compatibility with RFC 5912-style information object class (IOC)
969/// consumers. It defines the same OCSP wire types as [`ocsp_2024_88_types`]
970/// using parameterized `AlgorithmIdentifier` and `EXTENSION` constraints.
971///
972/// Because the 2009 IOC syntax (`RESPONSE ::= TYPE-IDENTIFIER`, parameterized
973/// `Extensions{{...}}`) is structural-only, the codegen emits the concrete
974/// types (`OCSPRequest`, `OCSPResponse`, etc.) without the IOC wrappers.
975///
976/// Source: RFC 9654 Appendix A.2.
977#[allow(unused_imports, dead_code)]
978pub mod ocsp_2024_08_types {
979 include!(concat!(env!("OUT_DIR"), "/ocsp_2024_08_generated.rs"));
980}
981
982/// RFC 9608 NoRevAvailExtn — `noRevAvail` certificate extension (OID 2.5.29.56).
983///
984/// The `noRevAvail` extension signals that no revocation information will ever
985/// be available for a certificate (e.g. short-lived certificates). Including
986/// this extension allows relying parties to skip revocation checking.
987///
988/// Key constants:
989/// - [`no_rev_avail_extn_types::ID_CE_NO_REV_AVAIL`] — OID 2.5.29.56
990///
991/// Source: RFC 9608 §5.
992#[allow(unused_imports, dead_code)]
993pub mod no_rev_avail_extn_types {
994 include!(concat!(env!("OUT_DIR"), "/no_rev_avail_extn_generated.rs"));
995}
996
997/// RFC 9345 DelegatedCredentialExtn — `DelegationUsage` certificate extension.
998///
999/// The `DelegationUsage` extension marks an end-entity certificate as suitable
1000/// for issuing TLS delegated credentials (RFC 9345). The extension value is
1001/// NULL; its presence alone is the signal.
1002///
1003/// Key constants:
1004/// - [`delegated_cred_extn_types::ID_PE_DELEGATION_USAGE`] — Cloudflare enterprise OID
1005///
1006/// Source: RFC 9345 §4.2.
1007#[allow(unused_imports, dead_code)]
1008pub mod delegated_cred_extn_types {
1009 include!(concat!(
1010 env!("OUT_DIR"),
1011 "/delegated_cred_extn_generated.rs"
1012 ));
1013}
1014
1015/// RFC 9310 NFTypeCertExtn — Network Function type certificate extension.
1016///
1017/// The `NFType` extension (`id-pe-nftype`, 1.3.6.1.5.5.7.1.34) carries a
1018/// sequence of IA5String network function type identifiers in 5G NF
1019/// certificates per 3GPP TS 33.310.
1020///
1021/// Key types:
1022/// - [`nf_type_cert_extn_types::NFTypes`] — `SEQUENCE SIZE (1..MAX) OF NFType`
1023/// - [`nf_type_cert_extn_types::NFType`] — `IA5String (SIZE (1..32))`
1024///
1025/// Source: RFC 9310 §4.
1026#[allow(unused_imports, dead_code)]
1027pub mod nf_type_cert_extn_types {
1028 include!(concat!(env!("OUT_DIR"), "/nf_type_cert_extn_generated.rs"));
1029}
1030
1031/// RFC 8479 PrivateKeyValidationAttrV1 — private key validation attribute.
1032///
1033/// Defines the `at-validation-parameters` ATTRIBUTE and the `ValidationParams`
1034/// SEQUENCE for carrying key validation parameters (hash algorithm OID + seed)
1035/// in PKCS#8 PrivateKeyInfo structures, as used by some HSMs and key-generation
1036/// procedures that need to prove a key was generated with a known seed.
1037///
1038/// Key types:
1039/// - [`pk_validation_attr_types::ValidationParams`] — `SEQUENCE { hashAlg OID, seed OCTET STRING }`
1040///
1041/// Source: RFC 8479 §3.
1042#[allow(unused_imports, dead_code)]
1043pub mod pk_validation_attr_types {
1044 include!(concat!(env!("OUT_DIR"), "/pk_validation_attr_generated.rs"));
1045}
1046
1047/// RFC 7633 TLS-Feature-Module-2015 — TLS features certificate extension.
1048///
1049/// The `TLSFeature` extension (`id-pe-tlsfeature`, 1.3.6.1.5.5.7.1.24) carries
1050/// a sequence of TLS extension identifiers (as integers) that a relying party
1051/// must request in the TLS handshake. The most common use is to require OCSP
1052/// stapling (`status_request`, value 5).
1053///
1054/// Key types:
1055/// - [`tls_feature_module_types::Features`] — `SEQUENCE OF INTEGER`
1056///
1057/// Key constants:
1058/// - `id-pe-tlsfeature` — OID 1.3.6.1.5.5.7.1.24
1059///
1060/// Source: RFC 7633 Appendix A.
1061#[allow(unused_imports, dead_code)]
1062pub mod tls_feature_module_types {
1063 include!(concat!(env!("OUT_DIR"), "/tls_feature_module_generated.rs"));
1064}
1065
1066/// RFC 9814 SLH-DSA (SPHINCS+) X.509 key-container types.
1067///
1068/// Generated from `asn1/SLH-DSA-Module-2024.asn1`. Defines the raw OCTET STRING
1069/// containers for SLH-DSA public and private keys as placed in
1070/// `SubjectPublicKeyInfo.subjectPublicKey` and `OneAsymmetricKey.privateKey`.
1071///
1072/// Key types:
1073/// - [`SlhDsaPublicKey`] — public key octet string (32, 48, or 64 bytes depending
1074/// on the SLH-DSA parameter set: sha2-128, sha2-192/shake-192, sha2-256/shake-256)
1075/// - [`SlhDsaPrivateKey`] — private key octet string (64, 96, or 128 bytes)
1076///
1077/// All SLH-DSA algorithm OIDs (`id-slh-dsa-sha2-128s` through `id-slh-dsa-shake-256f`)
1078/// are already present in [`crate::oids`] from the base X.509 schema.
1079///
1080/// [`SlhDsaPublicKey`]: slh_dsa_module_2024_types::SlhDsaPublicKey
1081/// [`SlhDsaPrivateKey`]: slh_dsa_module_2024_types::SlhDsaPrivateKey
1082pub mod slh_dsa_module_2024_types {
1083 include!(concat!(
1084 env!("OUT_DIR"),
1085 "/slh_dsa_module_2024_generated.rs"
1086 ));
1087}
1088
1089/// RFC 9881 X.509 ML-DSA (CRYSTALS-Dilithium) algorithm identifier module.
1090///
1091/// Generated from `asn1/X509-ML-DSA-2025.asn1`. All ML-DSA types and OID constants
1092/// from RFC 9881 are provided by the existing crate infrastructure:
1093///
1094/// - OID constants (`id-ml-dsa-44`, `id-ml-dsa-65`, `id-ml-dsa-87`) — in [`crate::oids`]
1095/// - Key structure types — in [`crate::mldsa_types`]
1096///
1097/// This module is a documentation stub confirming the RFC 9881 module identity
1098/// (`id-mod-x509-ml-dsa-2025`, OID 1.3.6.1.5.5.7.0.119). It generates no new
1099/// Rust types as all structural content is already covered.
1100pub mod x509_ml_dsa_2025_types {
1101 include!(concat!(env!("OUT_DIR"), "/x509_ml_dsa_2025_generated.rs"));
1102}
1103
1104/// RFC 9935 ML-KEM (CRYSTALS-Kyber) X.509 key-container types.
1105///
1106/// Generated from `asn1/X509-ML-KEM-2025.asn1`. Defines the private and public key
1107/// structure types for the three ML-KEM parameter sets as specified in RFC 9935.
1108///
1109/// ML-KEM OID constants (`id-ml-kem-512`, `id-ml-kem-768`, `id-ml-kem-1024`) are
1110/// already in [`crate::oids`] from the base X.509 schema.
1111/// RFC 9935 refers to the same OID values as `id-alg-ml-kem-512/768/1024`.
1112///
1113/// Key types:
1114/// - [`MlKem512PrivateKey`] / [`MlKem768PrivateKey`] / [`MlKem1024PrivateKey`] — CHOICE
1115/// of seed (64 B), expandedKey, or both; parallel structure to ML-DSA private keys
1116/// - [`MlKem512PublicKey`] / [`MlKem768PublicKey`] / [`MlKem1024PublicKey`] — raw public
1117/// key octet strings (800 / 1184 / 1568 bytes)
1118/// - `MlKem512PrivateKeyBoth` / `MlKem768PrivateKeyBoth` / `MlKem1024PrivateKeyBoth` —
1119/// helper SEQUENCE structs for the `both` arm of each private key CHOICE
1120///
1121/// Information Object Class assignments (PUBLIC-KEY, KEM-ALGORITHM instances) are not
1122/// representable in synta-codegen and are omitted.
1123///
1124/// [`MlKem512PrivateKey`]: x509_ml_kem_2025_types::MlKem512PrivateKey
1125/// [`MlKem768PrivateKey`]: x509_ml_kem_2025_types::MlKem768PrivateKey
1126/// [`MlKem1024PrivateKey`]: x509_ml_kem_2025_types::MlKem1024PrivateKey
1127/// [`MlKem512PublicKey`]: x509_ml_kem_2025_types::MlKem512PublicKey
1128/// [`MlKem768PublicKey`]: x509_ml_kem_2025_types::MlKem768PublicKey
1129/// [`MlKem1024PublicKey`]: x509_ml_kem_2025_types::MlKem1024PublicKey
1130pub mod x509_ml_kem_2025_types {
1131 include!(concat!(env!("OUT_DIR"), "/x509_ml_kem_2025_generated.rs"));
1132}
1133
1134// ── Decryption trait and no-op sentinel ──────────────────────────────────────
1135
1136pub mod crypto;
1137pub use crypto::{
1138 constant_time_eq, default_key_id_hasher, default_signature_verifier, BackendPrivateKey,
1139 BackendPublicKey, BlockCipherProvider, CertificateSigner, CmsDecryptor, CmsEncryptor,
1140 DataHasher, Encryptor, EnvelopedDataDecryptor, ErasedCertificateSigner, ErasedDataHasher,
1141 ErasedHmacProvider, ErasedKeyIdHasher, ErasedSignatureVerifier, ErasedStreamingHasher,
1142 ErasedStreamingHmacProvider, HashState, HmacProvider, HmacState, KeyDecryptor, KeyEncryptor,
1143 KeyIdHasher, KeyIdMethod, KeySpec, KeyWrapAlgorithm, NoCmsDecryptor, NoCrypto, NoCryptoError,
1144 NoEncryptor, NoEncryptorError, NoEnvelopedDataDecryptor, NoEnvelopedDataDecryptorError,
1145 NoKeyIdHasher, NoKeyIdHasherError, NoPkcs12Encryptor, NoSignatureVerifier,
1146 NoSignatureVerifierError, NoSigner, NoSignerError, NoSymmetricCrypto, Pbkdf2Provider,
1147 Pkcs12Decryptor, Pkcs12Encryptor, PrivateKey, PrivateKeyBuilder, PrivateKeyError,
1148 RsaPrivateComponents, SecureRandom, SignatureVerifier, StreamingHasher, StreamingHmacProvider,
1149 UnsignedCertificateSigner,
1150};
1151#[cfg(any(feature = "openssl", feature = "nss"))]
1152pub use crypto::{
1153 default_create_enveloped_data, default_prepare_enveloped_data, DefaultCrypto,
1154 DefaultCryptoError, DefaultEnvelopedDataDecryptor,
1155};
1156pub use crypto::{hkdf_expand, hkdf_extract, hmac_output_len};
1157
1158/// Return the default [`DataHasher`] for the active crypto backend.
1159///
1160/// When the `openssl` feature is enabled, returns an OpenSSL-backed hasher.
1161/// Returns a [`PrivateKeyError`]-yielding stub when no backend is available.
1162///
1163/// The returned [`Box<dyn ErasedDataHasher>`] implements [`DataHasher`] directly
1164/// via a blanket impl, so it can be stored in a struct field or passed to any
1165/// function accepting `impl DataHasher`, without naming the backend type.
1166pub fn default_data_hasher() -> Box<dyn crypto::ErasedDataHasher> {
1167 #[cfg(all(feature = "nss", not(feature = "openssl")))]
1168 {
1169 crate::nss_backend::nss_data_hasher()
1170 }
1171 #[cfg(feature = "openssl")]
1172 {
1173 crate::openssl_backend::openssl_data_hasher()
1174 }
1175 #[cfg(not(any(feature = "openssl", feature = "nss")))]
1176 {
1177 Box::new(crypto::NoSymmetricCrypto)
1178 }
1179}
1180
1181/// Return the default [`HmacProvider`] for the active crypto backend.
1182///
1183/// When the `openssl` feature is enabled, returns an OpenSSL-backed provider.
1184/// Returns a [`PrivateKeyError`]-yielding stub when no backend is available.
1185///
1186/// The returned [`Box<dyn ErasedHmacProvider>`] implements [`HmacProvider`]
1187/// directly via a blanket impl, so it can be stored in a struct field or passed
1188/// to any function accepting `impl HmacProvider`, without naming the backend
1189/// type.
1190pub fn default_hmac_provider() -> Box<dyn crypto::ErasedHmacProvider> {
1191 #[cfg(all(feature = "nss", not(feature = "openssl")))]
1192 {
1193 crate::nss_backend::nss_hmac_provider()
1194 }
1195 #[cfg(feature = "openssl")]
1196 {
1197 crate::openssl_backend::openssl_hmac_provider()
1198 }
1199 #[cfg(not(any(feature = "openssl", feature = "nss")))]
1200 {
1201 Box::new(crypto::NoSymmetricCrypto)
1202 }
1203}
1204
1205/// Return the default [`StreamingHasher`] for the active crypto backend.
1206///
1207/// When the `nss` feature is enabled, returns an NSS PKCS#11-backed hasher.
1208/// When the `openssl` feature is enabled (and `nss` is not), returns an
1209/// OpenSSL-backed hasher. Returns a [`PrivateKeyError`]-yielding stub when no
1210/// backend is available.
1211///
1212/// The returned [`Box<dyn ErasedStreamingHasher>`] implements [`StreamingHasher`]
1213/// directly via a blanket impl, so it can be stored in a struct field or passed
1214/// to any function accepting `impl StreamingHasher`, without naming the backend
1215/// type.
1216///
1217/// [`StreamingHasher`]: crypto::StreamingHasher
1218/// [`ErasedStreamingHasher`]: crypto::ErasedStreamingHasher
1219pub fn default_streaming_hasher() -> Box<dyn crypto::ErasedStreamingHasher> {
1220 #[cfg(all(feature = "nss", not(feature = "openssl")))]
1221 {
1222 crate::nss_backend::nss_streaming_hasher()
1223 }
1224 #[cfg(feature = "openssl")]
1225 {
1226 crate::openssl_backend::openssl_streaming_hasher()
1227 }
1228 #[cfg(not(any(feature = "openssl", feature = "nss")))]
1229 {
1230 Box::new(crypto::NoSymmetricCrypto)
1231 }
1232}
1233
1234/// Return the default [`StreamingHmacProvider`] for the active crypto backend.
1235///
1236/// The returned [`Box<dyn ErasedStreamingHmacProvider>`] implements
1237/// [`StreamingHmacProvider`] directly via a blanket impl.
1238pub fn default_streaming_hmac_provider() -> Box<dyn crypto::ErasedStreamingHmacProvider> {
1239 #[cfg(all(feature = "nss", not(feature = "openssl")))]
1240 {
1241 crate::nss_backend::nss_streaming_hmac_provider()
1242 }
1243 #[cfg(feature = "openssl")]
1244 {
1245 crate::openssl_backend::openssl_streaming_hmac_provider()
1246 }
1247 #[cfg(not(any(feature = "openssl", feature = "nss")))]
1248 {
1249 Box::new(crypto::NoSymmetricCrypto)
1250 }
1251}
1252
1253/// Return the default [`Pbkdf2Provider`] for the active crypto backend.
1254pub fn default_pbkdf2_provider() -> impl crypto::Pbkdf2Provider {
1255 #[cfg(all(feature = "nss", not(feature = "openssl")))]
1256 {
1257 crate::nss_backend::nss_pbkdf2_provider()
1258 }
1259 #[cfg(feature = "openssl")]
1260 {
1261 crate::openssl_backend::openssl_symmetric_crypto()
1262 }
1263 #[cfg(not(any(feature = "openssl", feature = "nss")))]
1264 {
1265 crypto::NoSymmetricCrypto
1266 }
1267}
1268
1269/// Return the default [`BlockCipherProvider`] for the active crypto backend.
1270pub fn default_block_cipher_provider() -> impl crypto::BlockCipherProvider {
1271 #[cfg(all(feature = "nss", not(feature = "openssl")))]
1272 {
1273 crate::nss_backend::nss_block_cipher_provider()
1274 }
1275 #[cfg(feature = "openssl")]
1276 {
1277 crate::openssl_backend::openssl_symmetric_crypto()
1278 }
1279 #[cfg(not(any(feature = "openssl", feature = "nss")))]
1280 {
1281 crypto::NoSymmetricCrypto
1282 }
1283}
1284
1285/// Return the default [`SecureRandom`] for the active crypto backend.
1286pub fn default_secure_random() -> impl crypto::SecureRandom {
1287 #[cfg(all(feature = "nss", not(feature = "openssl")))]
1288 {
1289 crate::nss_backend::nss_secure_random()
1290 }
1291 #[cfg(feature = "openssl")]
1292 {
1293 crate::openssl_backend::openssl_symmetric_crypto()
1294 }
1295 #[cfg(not(any(feature = "openssl", feature = "nss")))]
1296 {
1297 crypto::NoSymmetricCrypto
1298 }
1299}
1300
1301// ── PKCS#7 certificate extraction ────────────────────────────────────────────
1302
1303pub mod pkcs7;
1304pub use pkcs7::{certs_from_pkcs7, Pkcs7Error};
1305
1306// ── PKCS#12 certificate extraction ───────────────────────────────────────────
1307
1308pub mod pkcs12;
1309pub use pkcs12::{certs_from_pkcs12, keys_from_pkcs12, pki_from_pkcs12, Pkcs12Error, Pkcs12Pki};
1310
1311// ── PKCS#12 archive builder ───────────────────────────────────────────────────
1312
1313pub mod pkcs12_builder;
1314pub use pkcs12_builder::{Pkcs12Builder, Pkcs12BuilderError};
1315
1316// ── CMS EnvelopedData builder ─────────────────────────────────────────────────
1317
1318pub mod enveloped_data_builder;
1319pub use enveloped_data_builder::{EnvelopedDataBuilder, EnvelopedDataBuilderError};
1320
1321// ── NSS crypto backend (optional feature) ────────────────────────────────────
1322
1323#[cfg(all(feature = "nss", not(feature = "openssl")))]
1324pub mod nss_backend;
1325#[cfg(all(feature = "nss", not(feature = "openssl")))]
1326pub use nss_backend::{NssKeyIdHasher, NssSignatureVerifier, NssVerifierError};
1327
1328// ── OpenSSL crypto backend (optional feature) ─────────────────────────────────
1329
1330#[cfg(feature = "openssl")]
1331pub mod openssl_backend;
1332#[cfg(feature = "openssl")]
1333pub use openssl_backend::{
1334 create_enveloped_data, prepare_enveloped_data, OpensslCertificateSigner,
1335 OpensslCertificateSignerError, OpensslDecryptor, OpensslDecryptorError, OpensslEncryptor,
1336 OpensslEncryptorError, OpensslEnvelopedDataDecryptor, OpensslKeyError, OpensslKeyIdHasher,
1337 OpensslKeyIdHasherError, OpensslPkcs12Encryptor, OpensslPrivateKey, OpensslRsaOaepDecryptor,
1338 OpensslRsaOaepEncryptor, OpensslRsaPkcs1Decryptor, OpensslRsaPkcs1Encryptor,
1339 OpensslSignatureVerifier, OpensslSymmetricCrypto, OpensslSymmetricError, OpensslVerifierError,
1340 Pkcs12Cipher, Pkcs12Config, Pkcs12HmacAlgorithm,
1341};
1342
1343// ── X.509 extension value builders ───────────────────────────────────────────
1344
1345pub mod ext_builder;
1346pub use ext_builder::{
1347 encode_authority_key_identifier, encode_basic_constraints, encode_key_usage,
1348 encode_subject_key_identifier, AuthorityInformationAccessBuilder, CRLDistributionPointsBuilder,
1349 CertificatePoliciesBuilder, ExtendedKeyUsageBuilder, IssuerAlternativeNameBuilder,
1350 IssuingDistributionPointBuilder, NameConstraintsBuilder, SubjectAlternativeNameBuilder,
1351};
1352
1353// ── Certificate builder ───────────────────────────────────────────────────────
1354
1355pub mod builder;
1356pub use builder::{BuilderError, CertificateBuilder};
1357
1358pub mod csr_builder;
1359pub use csr_builder::{CsrBuilder, CsrBuilderError};
1360
1361/// Builder for RFC 5755 Attribute Certificate TBS encoding.
1362///
1363/// [`AttributeCertificateBuilder`] assembles the `AttributeCertificateInfo`
1364/// (TBS) SEQUENCE using generated [`attribute_cert_types`] and [`GeneralNameSpec`].
1365pub mod ac_builder;
1366pub use ac_builder::AttributeCertificateBuilder;
1367
1368/// Builder for RFC 5280 §5 Certificate Revocation List TBS encoding.
1369///
1370/// [`CertificateListBuilder`] assembles a `TBSCertList` DER blob suitable
1371/// for external signing. Call [`CertificateListBuilder::assemble`] to wrap
1372/// the signed TBS in the outer `CertificateList` SEQUENCE.
1373pub mod crl_builder;
1374pub use crl_builder::CertificateListBuilder;
1375
1376/// Builder for RFC 6960 OCSP response encoding.
1377///
1378/// [`OCSPResponseBuilder`] assembles a `ResponseData` DER blob suitable for
1379/// external signing. Call [`OCSPResponseBuilder::assemble`] to wrap the
1380/// signed TBS in the complete `OCSPResponse` envelope.
1381pub mod ocsp_builder;
1382pub use ocsp_builder::{OCSPResponseBuilder, SingleResponseSpec};
1383
1384/// Builder for RFC 6960 OCSP request encoding.
1385///
1386/// [`OCSPRequestBuilder`] assembles a `TBSRequest` DER blob suitable for
1387/// direct submission to an OCSP responder (unsigned) or for external signing.
1388/// Call [`OCSPRequestBuilder::assemble`] to wrap the signed TBS in the complete
1389/// `OCSPRequest` envelope when a signature is required.
1390pub mod ocsp_request_builder;
1391pub use ocsp_request_builder::{CertIDSpec, OCSPRequestBuilder};
1392
1393/// Builder for RFC 3161 Time-Stamp Protocol (TSP) request encoding.
1394///
1395/// [`TimeStampReqBuilder`] assembles a `TimeStampReq` DER blob suitable for
1396/// submission to a Time Stamping Authority (TSA).
1397pub mod tsp_builder;
1398pub use tsp_builder::TimeStampReqBuilder;
1399
1400/// Builders for RFC 2634 Extended Security Services (ESS) structures.
1401///
1402/// - [`SigningCertificateBuilder`] — assembles a `SigningCertificate` DER blob.
1403/// - [`ReceiptRequestBuilder`] — assembles a `ReceiptRequest` DER blob.
1404/// - [`ESSSecurityLabelBuilder`] — assembles an `ESSSecurityLabel` DER blob.
1405pub mod ess_builder;
1406pub use ess_builder::{ESSSecurityLabelBuilder, ReceiptRequestBuilder, SigningCertificateBuilder};
1407
1408/// Builders for PKCS #5 v2.1 (RFC 8018) parameter structures.
1409///
1410/// - [`Pbkdf2ParamsBuilder`] — assembles `Pkcs5Pbkdf2Params` DER.
1411/// - [`Pbes2ParamsBuilder`] — assembles `Pkcs5Pbes2Params` DER.
1412pub mod pkcs5_builder;
1413pub use pkcs5_builder::{Pbes2ParamsBuilder, Pbkdf2ParamsBuilder};
1414
1415/// Builder for RFC 9399 Logotype certificate extension (OID 1.3.6.1.5.5.7.1.12).
1416///
1417/// [`LogotypeExtnBuilder`] assembles a `LogotypeExtn` DER value for use as
1418/// the `id-pe-logotype` extension value.
1419pub mod logotype_builder;
1420pub use logotype_builder::{LogotypeDetailsSpec, LogotypeExtnBuilder};
1421
1422/// Builder for RFC 7773 Authentication Context certificate extension (ACE-88).
1423///
1424/// [`AuthenticationContextsBuilder`] assembles an `AuthenticationContexts`
1425/// DER SEQUENCE OF for the `id-ce-authContext` extension value.
1426pub mod ace88_builder;
1427pub use ace88_builder::AuthenticationContextsBuilder;
1428
1429// ── Format-agnostic PKI reader ────────────────────────────────────────────────
1430
1431pub mod reader;
1432pub use reader::{read_pki_blocks, PkiDecryptor, ReadAnyError};
1433
1434// ============================================================================
1435// Helper functions
1436// ============================================================================
1437
1438/// Return the canonical display name for a signature algorithm OID.
1439///
1440/// Recognizes specific PKCS #1 RSA variants (MD5, SHA-1, SHA-256, SHA-384,
1441/// SHA-512), ECDSA variants (SHA-1, SHA-256, SHA-384, SHA-512), DSA,
1442/// EdDSA (Ed25519/Ed448), and post-quantum ML-DSA (FIPS 204). Returns
1443/// [`names::OTHER`] for any OID not in those families. Return values are
1444/// [`names`] constants, so callers can compare with `==` or pattern-match
1445/// on them directly.
1446///
1447/// # Performance note
1448///
1449/// All families use two-level integer dispatch rather than `&[u32]` slice
1450/// equality: a length+prefix check selects the family, then a `u32` match
1451/// dispatches to the variant. All discriminant values are derived from the
1452/// generated `oids` constants via const-indexing, so they stay in sync with
1453/// the ASN.1 schema automatically. Unknown RSA/ECDSA/DSA variants fall
1454/// through to `starts_with` prefix checks as a final safety net.
1455pub fn identify_signature_algorithm(oid: &ObjectIdentifier) -> &'static str {
1456 let c = oid.components();
1457
1458 // ── EdDSA (RFC 8410): [1, 3, 101, sub] — 4 arcs ─────────────────────────
1459 const ED0: u32 = oids::ED25519[0]; // 1
1460 const ED1: u32 = oids::ED25519[1]; // 3
1461 const ED2: u32 = oids::ED25519[2]; // 101
1462 const ED25519_V: u32 = oids::ED25519[3]; // 112
1463 const ED448_V: u32 = oids::ED448[3]; // 113
1464
1465 // ── ML-DSA (FIPS 204): [2, 16, 840, 1, 101, 3, 4, 3, sub] — 9 arcs ─────
1466 // First 7 arcs are shared with ML-KEM; arc[7]=3 selects the ML-DSA family.
1467 const PQC0: u32 = oids::ML_DSA_44[0]; // 2
1468 const PQC1: u32 = oids::ML_DSA_44[1]; // 16
1469 const PQC2: u32 = oids::ML_DSA_44[2]; // 840
1470 const PQC3: u32 = oids::ML_DSA_44[3]; // 1
1471 const PQC4: u32 = oids::ML_DSA_44[4]; // 101
1472 const PQC5: u32 = oids::ML_DSA_44[5]; // 3
1473 const PQC6: u32 = oids::ML_DSA_44[6]; // 4
1474 const MLDSA_D: u32 = oids::ML_DSA_44[7]; // 3
1475 const MLDSA44_V: u32 = oids::ML_DSA_44[8]; // 17
1476 const MLDSA65_V: u32 = oids::ML_DSA_65[8]; // 18
1477 const MLDSA87_V: u32 = oids::ML_DSA_87[8]; // 19
1478
1479 // ── RSA (PKCS #1): [1, 2, 840, 113549, 1, 1, sub] — 7 arcs ─────────────
1480 const RSA0: u32 = oids::RSA[0]; // 1
1481 const RSA1: u32 = oids::RSA[1]; // 2
1482 const RSA2: u32 = oids::RSA[2]; // 840
1483 const RSA3: u32 = oids::RSA[3]; // 113549
1484 const RSA4: u32 = oids::RSA[4]; // 1
1485 const RSA5: u32 = oids::RSA[5]; // 1
1486 const MD5_RSA_V: u32 = oids::MD5_WITH_RSA[6]; // 4
1487 const SHA1_RSA_V: u32 = oids::SHA1_WITH_RSA[6]; // 5
1488 const SHA256_RSA_V: u32 = oids::SHA256_WITH_RSA[6]; // 11
1489 const SHA384_RSA_V: u32 = oids::SHA384_WITH_RSA[6]; // 12
1490 const SHA512_RSA_V: u32 = oids::SHA512_WITH_RSA[6]; // 13
1491
1492 // ── ECDSA (ANSI X9.62 / RFC 5758) ────────────────────────────────────────
1493 // SHA-1: [1, 2, 840, 10045, 4, 1] — 6 arcs.
1494 // SHA-2: [1, 2, 840, 10045, 4, 3, sub] — 7 arcs; arc[5]=3 is the sub-family.
1495 const EC0: u32 = oids::ECDSA_SIG[0]; // 1
1496 const EC1: u32 = oids::ECDSA_SIG[1]; // 2
1497 const EC2: u32 = oids::ECDSA_SIG[2]; // 840
1498 const EC3: u32 = oids::ECDSA_SIG[3]; // 10045
1499 const EC4: u32 = oids::ECDSA_SIG[4]; // 4
1500 const ECDSA_SHA1_V: u32 = oids::ECDSA_WITH_SHA1[5]; // 1
1501 const ECDSA_SUB: u32 = oids::ECDSA_WITH_SHA256[5]; // 3
1502 const ECDSA_SHA256_V: u32 = oids::ECDSA_WITH_SHA256[6]; // 2
1503 const ECDSA_SHA384_V: u32 = oids::ECDSA_WITH_SHA384[6]; // 3
1504 const ECDSA_SHA512_V: u32 = oids::ECDSA_WITH_SHA512[6]; // 4
1505
1506 // Length + prefix check, then integer dispatch on the sub-variant arc.
1507 if let &[ED0, ED1, ED2, sub] = c {
1508 return match sub {
1509 ED25519_V => names::ED25519,
1510 ED448_V => names::ED448,
1511 _ => names::OTHER,
1512 };
1513 }
1514 if let &[PQC0, PQC1, PQC2, PQC3, PQC4, PQC5, PQC6, MLDSA_D, sub] = c {
1515 match sub {
1516 MLDSA44_V => return names::ML_DSA_44,
1517 MLDSA65_V => return names::ML_DSA_65,
1518 MLDSA87_V => return names::ML_DSA_87,
1519 _ => {} // Unknown 9-arc NIST PQC OID with ML-DSA family arc.
1520 }
1521 }
1522 if let &[RSA0, RSA1, RSA2, RSA3, RSA4, RSA5, sub] = c {
1523 return match sub {
1524 MD5_RSA_V => names::MD5_WITH_RSA,
1525 SHA1_RSA_V => names::SHA1_WITH_RSA,
1526 SHA256_RSA_V => names::SHA256_WITH_RSA,
1527 SHA384_RSA_V => names::SHA384_WITH_RSA,
1528 SHA512_RSA_V => names::SHA512_WITH_RSA,
1529 _ => names::RSA, // Unknown PKCS #1 variant (e.g. rsaEncryption, PSS).
1530 };
1531 }
1532 if let &[EC0, EC1, EC2, EC3, EC4, ECDSA_SHA1_V] = c {
1533 return names::ECDSA_WITH_SHA1;
1534 }
1535 if let &[EC0, EC1, EC2, EC3, EC4, ECDSA_SUB, sub] = c {
1536 return match sub {
1537 ECDSA_SHA256_V => names::ECDSA_WITH_SHA256,
1538 ECDSA_SHA384_V => names::ECDSA_WITH_SHA384,
1539 ECDSA_SHA512_V => names::ECDSA_WITH_SHA512,
1540 _ => names::ECDSA, // Unknown 7-arc ECDSA variant.
1541 };
1542 }
1543
1544 // ── id-alg-unsigned (RFC 9925): [1, 3, 6, 1, 5, 5, 7, 6, 36] — 9 arcs ─────
1545 const UNSIGNED_OID: &[u32] = oids::ALG_UNSIGNED;
1546 if c == UNSIGNED_OID {
1547 return names::UNSIGNED;
1548 }
1549
1550 // ── Composite ML-DSA (draft-ietf-lamps-pq-composite-sigs): [1,3,6,1,5,5,7,6,sub] — 9 arcs ─
1551 // Arc prefix shared with id-alg-unsigned; sub-arc 37–54 selects the variant.
1552 const CA0: u32 = oids::COMPOSITE_MLDSA_ARC[0]; // 1
1553 const CA1: u32 = oids::COMPOSITE_MLDSA_ARC[1]; // 3
1554 const CA2: u32 = oids::COMPOSITE_MLDSA_ARC[2]; // 6
1555 const CA3: u32 = oids::COMPOSITE_MLDSA_ARC[3]; // 1
1556 const CA4: u32 = oids::COMPOSITE_MLDSA_ARC[4]; // 5
1557 const CA5: u32 = oids::COMPOSITE_MLDSA_ARC[5]; // 5
1558 const CA6: u32 = oids::COMPOSITE_MLDSA_ARC[6]; // 7
1559 const CA7: u32 = oids::COMPOSITE_MLDSA_ARC[7]; // 6
1560 if let &[CA0, CA1, CA2, CA3, CA4, CA5, CA6, CA7, sub] = c {
1561 match sub {
1562 37 => return names::MLDSA44_RSA2048_PSS_SHA256,
1563 38 => return names::MLDSA44_RSA2048_PKCS15_SHA256,
1564 39 => return names::MLDSA44_ED25519_SHA512,
1565 40 => return names::MLDSA44_ECDSA_P256_SHA256,
1566 41 => return names::MLDSA65_RSA3072_PSS_SHA512,
1567 42 => return names::MLDSA65_RSA3072_PKCS15_SHA512,
1568 43 => return names::MLDSA65_RSA4096_PSS_SHA512,
1569 44 => return names::MLDSA65_RSA4096_PKCS15_SHA512,
1570 45 => return names::MLDSA65_ECDSA_P256_SHA512,
1571 46 => return names::MLDSA65_ECDSA_P384_SHA512,
1572 47 => return names::MLDSA65_ECDSA_BRAINPOOL_P256R1_SHA512,
1573 48 => return names::MLDSA65_ED25519_SHA512,
1574 49 => return names::MLDSA87_ECDSA_P384_SHA512,
1575 50 => return names::MLDSA87_ECDSA_BRAINPOOL_P384R1_SHA512,
1576 51 => return names::MLDSA87_ED448_SHAKE256,
1577 52 => return names::MLDSA87_RSA3072_PSS_SHA512,
1578 53 => return names::MLDSA87_RSA4096_PSS_SHA512,
1579 54 => return names::MLDSA87_ECDSA_P521_SHA512,
1580 _ => {} // Unknown id-alg sub-arc.
1581 }
1582 }
1583
1584 // Safety net for RSA/ECDSA/DSA OIDs with arc counts not matched above.
1585 if c.starts_with(oids::RSA) {
1586 names::RSA
1587 } else if c.starts_with(oids::ECDSA_SIG) {
1588 names::ECDSA
1589 } else if c.starts_with(oids::DSA) {
1590 names::DSA
1591 } else {
1592 names::OTHER
1593 }
1594}
1595
1596/// Return the canonical display name for a public key algorithm OID, if known.
1597///
1598/// Recognizes RSA, EC/ECDSA, DSA, EdDSA (Ed25519/Ed448), post-quantum
1599/// ML-DSA (FIPS 204) and ML-KEM (FIPS 203), and all 18 composite ML-DSA
1600/// algorithms (draft-ietf-lamps-pq-composite-sigs). Returns `None` for
1601/// unrecognized OIDs. Return values are [`names`] constants.
1602///
1603/// # Performance note
1604///
1605/// Same two-level dispatch as [`identify_signature_algorithm`]. ML-DSA and
1606/// ML-KEM share a 7-arc NIST PQC prefix; arc\[7\] (3 vs 4) selects the family
1607/// and arc\[8\] selects the variant. Composite ML-DSA OIDs share the id-alg
1608/// arc prefix `[1,3,6,1,5,5,7,6]` and use sub-arcs 37–54.
1609/// All values derived from `oids` constants.
1610pub fn identify_public_key_algorithm(oid: &ObjectIdentifier) -> Option<&'static str> {
1611 let c = oid.components();
1612
1613 // ── EdDSA (RFC 8410): [1, 3, 101, sub] — 4 arcs ─────────────────────────
1614 const ED0: u32 = oids::ED25519[0]; // 1
1615 const ED1: u32 = oids::ED25519[1]; // 3
1616 const ED2: u32 = oids::ED25519[2]; // 101
1617 const ED25519_V: u32 = oids::ED25519[3]; // 112
1618 const ED448_V: u32 = oids::ED448[3]; // 113
1619
1620 // ── NIST PQC shared prefix: [2, 16, 840, 1, 101, 3, 4] — 7 arcs ─────────
1621 // arc[7]: 3 = ML-DSA family, 4 = ML-KEM family.
1622 const PQC0: u32 = oids::ML_DSA_44[0]; // 2
1623 const PQC1: u32 = oids::ML_DSA_44[1]; // 16
1624 const PQC2: u32 = oids::ML_DSA_44[2]; // 840
1625 const PQC3: u32 = oids::ML_DSA_44[3]; // 1
1626 const PQC4: u32 = oids::ML_DSA_44[4]; // 101
1627 const PQC5: u32 = oids::ML_DSA_44[5]; // 3
1628 const PQC6: u32 = oids::ML_DSA_44[6]; // 4
1629
1630 // ML-DSA (FIPS 204): arc[7] = 3, arc[8] = variant.
1631 const MLDSA_D: u32 = oids::ML_DSA_44[7]; // 3
1632 const MLDSA44_V: u32 = oids::ML_DSA_44[8]; // 17
1633 const MLDSA65_V: u32 = oids::ML_DSA_65[8]; // 18
1634 const MLDSA87_V: u32 = oids::ML_DSA_87[8]; // 19
1635
1636 // ML-KEM (FIPS 203): arc[7] = 4, arc[8] = variant.
1637 const MLKEM_D: u32 = oids::ML_KEM_512[7]; // 4
1638 const MLKEM512_V: u32 = oids::ML_KEM_512[8]; // 1
1639 const MLKEM768_V: u32 = oids::ML_KEM_768[8]; // 2
1640 const MLKEM1024_V: u32 = oids::ML_KEM_1024[8]; // 3
1641
1642 // EdDSA: length + prefix check, then integer dispatch on sub-variant arc.
1643 if let &[ED0, ED1, ED2, sub] = c {
1644 return match sub {
1645 ED25519_V => Some(names::ED25519),
1646 ED448_V => Some(names::ED448),
1647 _ => None,
1648 };
1649 }
1650
1651 // NIST PQC: length + shared-prefix check, then dispatch on family (arc[7])
1652 // and variant (arc[8]).
1653 if let &[PQC0, PQC1, PQC2, PQC3, PQC4, PQC5, PQC6, d, sub] = c {
1654 match d {
1655 MLDSA_D => {
1656 return match sub {
1657 MLDSA44_V => Some(names::ML_DSA_44),
1658 MLDSA65_V => Some(names::ML_DSA_65),
1659 MLDSA87_V => Some(names::ML_DSA_87),
1660 _ => None,
1661 }
1662 }
1663 MLKEM_D => {
1664 return match sub {
1665 MLKEM512_V => Some(names::ML_KEM_512),
1666 MLKEM768_V => Some(names::ML_KEM_768),
1667 MLKEM1024_V => Some(names::ML_KEM_1024),
1668 _ => None,
1669 }
1670 }
1671 _ => {} // Unknown 9-arc NIST PQC OID, fall through.
1672 }
1673 }
1674
1675 // ── Composite ML-DSA (draft-ietf-lamps-pq-composite-sigs): [1,3,6,1,5,5,7,6,sub] ─
1676 const CA0: u32 = oids::COMPOSITE_MLDSA_ARC[0]; // 1
1677 const CA1: u32 = oids::COMPOSITE_MLDSA_ARC[1]; // 3
1678 const CA2: u32 = oids::COMPOSITE_MLDSA_ARC[2]; // 6
1679 const CA3: u32 = oids::COMPOSITE_MLDSA_ARC[3]; // 1
1680 const CA4: u32 = oids::COMPOSITE_MLDSA_ARC[4]; // 5
1681 const CA5: u32 = oids::COMPOSITE_MLDSA_ARC[5]; // 5
1682 const CA6: u32 = oids::COMPOSITE_MLDSA_ARC[6]; // 7
1683 const CA7: u32 = oids::COMPOSITE_MLDSA_ARC[7]; // 6
1684 if let &[CA0, CA1, CA2, CA3, CA4, CA5, CA6, CA7, sub] = c {
1685 let name = match sub {
1686 37 => names::MLDSA44_RSA2048_PSS_SHA256,
1687 38 => names::MLDSA44_RSA2048_PKCS15_SHA256,
1688 39 => names::MLDSA44_ED25519_SHA512,
1689 40 => names::MLDSA44_ECDSA_P256_SHA256,
1690 41 => names::MLDSA65_RSA3072_PSS_SHA512,
1691 42 => names::MLDSA65_RSA3072_PKCS15_SHA512,
1692 43 => names::MLDSA65_RSA4096_PSS_SHA512,
1693 44 => names::MLDSA65_RSA4096_PKCS15_SHA512,
1694 45 => names::MLDSA65_ECDSA_P256_SHA512,
1695 46 => names::MLDSA65_ECDSA_P384_SHA512,
1696 47 => names::MLDSA65_ECDSA_BRAINPOOL_P256R1_SHA512,
1697 48 => names::MLDSA65_ED25519_SHA512,
1698 49 => names::MLDSA87_ECDSA_P384_SHA512,
1699 50 => names::MLDSA87_ECDSA_BRAINPOOL_P384R1_SHA512,
1700 51 => names::MLDSA87_ED448_SHAKE256,
1701 52 => names::MLDSA87_RSA3072_PSS_SHA512,
1702 53 => names::MLDSA87_RSA4096_PSS_SHA512,
1703 54 => names::MLDSA87_ECDSA_P521_SHA512,
1704 _ => return None, // Unknown id-alg sub-arc.
1705 };
1706 return Some(name);
1707 }
1708
1709 // RSA / ECDSA / DSA: variable-length OIDs, identified by arc prefix.
1710 if c.starts_with(oids::RSA) {
1711 Some(names::RSA)
1712 } else if c.starts_with(oids::ECDSA_KEY) {
1713 Some(names::ECDSA)
1714 } else if c.starts_with(oids::DSA) {
1715 Some(names::DSA)
1716 } else {
1717 None
1718 }
1719}
1720
1721/// Build and DER-encode a signing `AlgorithmIdentifier` from a key type OID and
1722/// a hash algorithm name.
1723///
1724/// This is the companion to [`identify_signature_algorithm`] and
1725/// [`identify_public_key_algorithm`]: given the key type OID (as found in the
1726/// `algorithm` field of a PKCS#8 `PrivateKeyInfo`) and a hash algorithm name,
1727/// returns the DER-encoded `AlgorithmIdentifier` for the corresponding signing
1728/// operation. RSA algorithms include a `NULL` parameters element (as required
1729/// by RFC 3279 §2.2.1); EdDSA and ML-DSA algorithms have no parameters.
1730///
1731/// # Supported mappings
1732///
1733/// | Key OID | `hash_algo` | Signing OID |
1734/// |---|---|---|
1735/// | `id-ecPublicKey` | `"sha256"` | `ecdsa-with-SHA256` |
1736/// | `id-ecPublicKey` | `"sha384"` | `ecdsa-with-SHA384` |
1737/// | `id-ecPublicKey` | `"sha512"` | `ecdsa-with-SHA512` |
1738/// | `rsaEncryption` | `"sha1"` | `sha1WithRSAEncryption` |
1739/// | `rsaEncryption` | `"sha256"` | `sha256WithRSAEncryption` |
1740/// | `rsaEncryption` | `"sha384"` | `sha384WithRSAEncryption` |
1741/// | `rsaEncryption` | `"sha512"` | `sha512WithRSAEncryption` |
1742/// | `id-Ed25519` | (ignored) | `id-Ed25519` |
1743/// | `id-Ed448` | (ignored) | `id-Ed448` |
1744/// | `id-ML-DSA-44` | (ignored) | `id-ML-DSA-44` |
1745/// | `id-ML-DSA-65` | (ignored) | `id-ML-DSA-65` |
1746/// | `id-ML-DSA-87` | (ignored) | `id-ML-DSA-87` |
1747///
1748/// Returns `None` if the key OID is unrecognised, the `hash_algo` is not valid
1749/// for the key type, or DER encoding fails.
1750pub fn signing_algorithm_der(key_oid: &ObjectIdentifier, hash_algo: &str) -> Option<Vec<u8>> {
1751 use synta::{Element, Null};
1752
1753 // ── Signing OID + RSA NULL-params flag ────────────────────────────────────
1754 let (sig_oid_comps, null_params): (&[u32], bool) = {
1755 let c = key_oid.components();
1756
1757 if c == oids::EC_PUBLIC_KEY {
1758 let sig = match hash_algo {
1759 "sha256" => oids::ECDSA_WITH_SHA256,
1760 "sha384" => oids::ECDSA_WITH_SHA384,
1761 "sha512" => oids::ECDSA_WITH_SHA512,
1762 _ => return None,
1763 };
1764 (sig, false)
1765 } else if c == oids::RSA_ENCRYPTION {
1766 let sig = match hash_algo {
1767 "sha1" => oids::SHA1_WITH_RSA,
1768 "sha256" => oids::SHA256_WITH_RSA,
1769 "sha384" => oids::SHA384_WITH_RSA,
1770 "sha512" => oids::SHA512_WITH_RSA,
1771 _ => return None,
1772 };
1773 (sig, true)
1774 } else if c == oids::ED25519 {
1775 (oids::ED25519, false)
1776 } else if c == oids::ED448 {
1777 (oids::ED448, false)
1778 } else if c == oids::ML_DSA_44 {
1779 (oids::ML_DSA_44, false)
1780 } else if c == oids::ML_DSA_65 {
1781 (oids::ML_DSA_65, false)
1782 } else if c == oids::ML_DSA_87 {
1783 (oids::ML_DSA_87, false)
1784 } else {
1785 return None;
1786 }
1787 };
1788
1789 // ── Build and DER-encode the signing AlgorithmIdentifier ──────────────────
1790 let sig_oid = ObjectIdentifier::new(sig_oid_comps).ok()?;
1791 let sig_alg = AlgorithmIdentifier {
1792 algorithm: sig_oid,
1793 parameters: if null_params {
1794 Some(Element::Null(Null))
1795 } else {
1796 None
1797 },
1798 };
1799 sig_alg.to_der().ok()
1800}
1801
1802/// Return the [`AlgorithmIdentifier`] for a named hash (digest) algorithm.
1803///
1804/// The returned value has a `'static` lifetime because the only borrowed field,
1805/// `parameters`, holds `Element::Null` which owns no data.
1806///
1807/// # Supported names
1808///
1809/// | `hash_algo` | OID |
1810/// |---|---|
1811/// | `"sha1"` | 1.3.14.3.2.26 (`id-sha1`) |
1812/// | `"sha256"` | 2.16.840.1.101.3.4.2.1 (`id-sha256`) |
1813/// | `"sha384"` | 2.16.840.1.101.3.4.2.2 (`id-sha384`) |
1814/// | `"sha512"` | 2.16.840.1.101.3.4.2.3 (`id-sha512`) |
1815///
1816/// Returns `None` for unknown names.
1817pub fn digest_alg_id(hash_algo: &str) -> Option<AlgorithmIdentifier<'static>> {
1818 use synta::{Element, Null};
1819 let oid_comps: &[u32] = match hash_algo {
1820 "sha1" => oids::ID_SHA1,
1821 "sha256" => oids::ID_SHA256,
1822 "sha384" => oids::ID_SHA384,
1823 "sha512" => oids::ID_SHA512,
1824 _ => return None,
1825 };
1826 let oid = ObjectIdentifier::new(oid_comps).ok()?;
1827 Some(AlgorithmIdentifier {
1828 algorithm: oid,
1829 parameters: Some(Element::Null(Null)),
1830 })
1831}
1832
1833/// Return the key size in bits for a well-known EC named curve OID.
1834///
1835/// Returns the field size (security parameter) of the named curve, which is
1836/// what tools like OpenSSL report as the "Public-Key" bit count for EC keys.
1837/// Returns `None` for unrecognized curves; callers should fall back to the
1838/// raw BIT STRING length.
1839///
1840/// # Example
1841///
1842/// ```rust
1843/// use synta_certificate::{oids, ec_curve_key_bits};
1844///
1845/// assert_eq!(ec_curve_key_bits(oids::EC_CURVE_P256), Some(256));
1846/// assert_eq!(ec_curve_key_bits(oids::EC_CURVE_P384), Some(384));
1847/// assert_eq!(ec_curve_key_bits(oids::EC_CURVE_P521), Some(521));
1848/// assert_eq!(ec_curve_key_bits(&[1, 2, 3]), None);
1849/// ```
1850pub fn ec_curve_key_bits(c: &[u32]) -> Option<usize> {
1851 match c {
1852 _ if c == oids::EC_CURVE_P256 => Some(256),
1853 _ if c == oids::EC_CURVE_P384 => Some(384),
1854 _ if c == oids::EC_CURVE_P521 => Some(521),
1855 _ if c == oids::EC_CURVE_SECP256K1 => Some(256),
1856 _ => None,
1857 }
1858}
1859
1860/// Return the short ASN.1 name for a well-known EC named curve OID.
1861///
1862/// Returns `None` for unrecognized curves; callers should fall back to the
1863/// dotted OID notation.
1864///
1865/// # Example
1866///
1867/// ```rust
1868/// use synta_certificate::{oids, ec_curve_short_name};
1869///
1870/// assert_eq!(ec_curve_short_name(oids::EC_CURVE_P256), Some("prime256v1"));
1871/// assert_eq!(ec_curve_short_name(oids::EC_CURVE_P384), Some("secp384r1"));
1872/// assert_eq!(ec_curve_short_name(&[1, 2, 3]), None);
1873/// ```
1874pub fn ec_curve_short_name(c: &[u32]) -> Option<&'static str> {
1875 match c {
1876 _ if c == oids::EC_CURVE_P256 => Some("prime256v1"),
1877 _ if c == oids::EC_CURVE_P384 => Some("secp384r1"),
1878 _ if c == oids::EC_CURVE_P521 => Some("secp521r1"),
1879 _ if c == oids::EC_CURVE_SECP256K1 => Some("secp256k1"),
1880 _ => None,
1881 }
1882}
1883
1884/// Return the NIST curve name for a well-known EC named curve OID.
1885///
1886/// Returns `None` for curves with no NIST name (e.g. `secp256k1`).
1887///
1888/// # Example
1889///
1890/// ```rust
1891/// use synta_certificate::{oids, ec_curve_nist_name};
1892///
1893/// assert_eq!(ec_curve_nist_name(oids::EC_CURVE_P256), Some("P-256"));
1894/// assert_eq!(ec_curve_nist_name(oids::EC_CURVE_SECP256K1), None);
1895/// ```
1896pub fn ec_curve_nist_name(c: &[u32]) -> Option<&'static str> {
1897 match c {
1898 _ if c == oids::EC_CURVE_P256 => Some("P-256"),
1899 _ if c == oids::EC_CURVE_P384 => Some("P-384"),
1900 _ if c == oids::EC_CURVE_P521 => Some("P-521"),
1901 _ => None,
1902 }
1903}
1904
1905/// Return the display name for a well-known X.509v3 extension OID.
1906///
1907/// Covers the standard RFC 5280 extensions and the Certificate Transparency
1908/// SCT extension. Unknown OIDs are returned as their dotted decimal notation.
1909///
1910/// # Example
1911///
1912/// ```rust
1913/// use synta_certificate::{oids, extension_oid_name};
1914/// use synta::ObjectIdentifier;
1915///
1916/// // Construct an OID from known components for testing
1917/// let key_usage = ObjectIdentifier::new(oids::KEY_USAGE).unwrap();
1918/// assert_eq!(extension_oid_name(&key_usage), "X509v3 Key Usage");
1919/// ```
1920pub fn extension_oid_name(oid: &synta::ObjectIdentifier) -> String {
1921 match oid.components() {
1922 c if c == oids::SUBJECT_KEY_IDENTIFIER => "X509v3 Subject Key Identifier".into(),
1923 c if c == oids::KEY_USAGE => "X509v3 Key Usage".into(),
1924 c if c == oids::SUBJECT_ALT_NAME => "X509v3 Subject Alternative Name".into(),
1925 c if c == oids::ISSUER_ALT_NAME => "X509v3 Issuer Alternative Name".into(),
1926 c if c == oids::BASIC_CONSTRAINTS => "X509v3 Basic Constraints".into(),
1927 c if c == oids::CRL_DISTRIBUTION_POINTS => "X509v3 CRL Distribution Points".into(),
1928 c if c == oids::CERTIFICATE_POLICIES => "X509v3 Certificate Policies".into(),
1929 c if c == oids::AUTHORITY_KEY_IDENTIFIER => "X509v3 Authority Key Identifier".into(),
1930 c if c == oids::EXTENDED_KEY_USAGE => "X509v3 Extended Key Usage".into(),
1931 c if c == oids::AUTHORITY_INFO_ACCESS => "Authority Information Access".into(),
1932 c if c == oids::CT_PRECERT_SCTS => "CT Precertificate SCTs".into(),
1933 _ => oid.to_string(),
1934 }
1935}
1936
1937/// Decode the raw DER bytes of an Extensions SEQUENCE OF into a `Vec<Extension>`.
1938///
1939/// `raw` must be the DER of `SEQUENCE OF Extension` — exactly what
1940/// `tbs_certificate.extensions.as_bytes()` returns after the outer `[3]
1941/// EXPLICIT` tag has been stripped by the parser. Returns an empty `Vec` on
1942/// decode error.
1943pub fn decode_extensions<'a>(raw: &'a [u8]) -> Vec<Extension<'a>> {
1944 use synta::Decode;
1945 let mut decoder = synta::Decoder::new(raw, synta::Encoding::Der);
1946 Vec::<Extension<'a>>::decode(&mut decoder).unwrap_or_default()
1947}
1948
1949/// Validate the outer Certificate SEQUENCE envelope without fully decoding.
1950///
1951/// Performs a shallow 4-operation structural scan:
1952/// 1. outer `Certificate` SEQUENCE tag
1953/// 2. outer `Certificate` SEQUENCE length
1954/// 3. `TBSCertificate` SEQUENCE tag
1955/// 4. `TBSCertificate` SEQUENCE length
1956///
1957/// Returns the byte range `tbs_start..tbs_end` of the complete
1958/// `TBSCertificate` TLV (tag + length + content) within `data`. The range
1959/// can be used to extract the exact bytes that must be covered by the
1960/// certificate's signature.
1961///
1962/// This is approximately 10× faster than [`Certificate::decode()`] and is
1963/// useful for:
1964/// - Pre-validation before committing to a full parse
1965/// - Extracting the TBS byte range for offline signature verification
1966/// - Benchmarking the minimum parse cost (envelope scan only)
1967///
1968/// Returns an error if `data` is not a structurally valid DER SEQUENCE (e.g.
1969/// truncated, wrong tag, or indefinite-length TBS).
1970///
1971/// # Example
1972///
1973/// ```rust
1974/// let der: &[u8] = &[
1975/// // Minimal Certificate: SEQUENCE { SEQUENCE {} SEQUENCE {} BIT STRING {} }
1976/// 0x30, 0x0a, // Certificate SEQUENCE, length 10
1977/// 0x30, 0x00, // TBSCertificate SEQUENCE, length 0
1978/// 0x30, 0x00, // signatureAlgorithm SEQUENCE, length 0
1979/// 0x03, 0x04, 0x00, 0xde, 0xad, 0xbe, // signature BIT STRING
1980/// ];
1981/// let range = synta_certificate::validate_envelope(der).unwrap();
1982/// // TBS starts at byte 2 (after the 2-byte outer header), ends at byte 4
1983/// assert_eq!(range, 2..4);
1984/// ```
1985pub fn validate_envelope(data: &[u8]) -> synta::Result<core::ops::Range<usize>> {
1986 use synta::{Decoder, Encoding};
1987 let mut d = Decoder::new(data, Encoding::Der);
1988 // Outer Certificate SEQUENCE tag + length.
1989 d.read_tag()?;
1990 d.read_length()?.definite()?;
1991 // TBSCertificate: record full TLV range (tag + length + content).
1992 let tbs_start = d.position();
1993 d.read_tag()?;
1994 let tbs_content_len = d.read_length()?.definite()?;
1995 Ok(tbs_start..(d.position() + tbs_content_len))
1996}
1997
1998/// Test one bit in a `KeyUsage` BIT STRING.
1999///
2000/// `n` is the named-bit index as defined in RFC 5280 §4.2.1.3 and the
2001/// `KEY_USAGE_*` constants exported from this crate. Returns `true` if the
2002/// bit is set, `false` if the bit is absent or if `n` is beyond the length of
2003/// the encoding.
2004///
2005/// # Example
2006///
2007/// ```rust,ignore
2008/// use synta_certificate::{key_usage_bit, KEY_USAGE_KEY_CERT_SIGN};
2009/// let is_ca = key_usage_bit(&ku, KEY_USAGE_KEY_CERT_SIGN);
2010/// ```
2011pub fn key_usage_bit(ku: &KeyUsage, n: usize) -> bool {
2012 let bytes = ku.as_bytes();
2013 bytes
2014 .get(n / 8)
2015 .is_some_and(|&b| (b >> (7 - (n % 8))) & 1 != 0)
2016}
2017
2018/// Byte ranges within a DER-encoded `Certificate` needed for signature
2019/// verification.
2020///
2021/// All ranges index into the same `cert_der` byte slice that was passed to
2022/// [`cert_byte_ranges`].
2023pub struct CertByteRanges {
2024 /// The complete `TBSCertificate` TLV (tag + length + content).
2025 pub tbs: core::ops::Range<usize>,
2026 /// The outer `signatureAlgorithm` TLV (tag + length + content).
2027 pub signature_algorithm: core::ops::Range<usize>,
2028 /// The `SubjectPublicKeyInfo` TLV (tag + length + content), found inside
2029 /// `TBSCertificate`.
2030 pub subject_public_key_info: core::ops::Range<usize>,
2031}
2032
2033/// Extract the byte ranges required for certificate signature verification
2034/// from a DER-encoded `Certificate`.
2035///
2036/// The returned ranges all index into `cert_der`. Typical usage:
2037///
2038/// ```rust,ignore
2039/// let subject_ranges = cert_byte_ranges(subject_der)?;
2040/// let issuer_ranges = cert_byte_ranges(issuer_der)?;
2041/// verifier.verify_certificate_signature(
2042/// &subject_der[subject_ranges.tbs],
2043/// &subject_der[subject_ranges.signature_algorithm],
2044/// signature_bits,
2045/// &issuer_der[issuer_ranges.subject_public_key_info],
2046/// )?;
2047/// ```
2048///
2049/// Returns `None` if `cert_der` is structurally malformed (truncated, wrong
2050/// tag, or indefinite length). The input is expected to have already passed
2051/// synta's strict DER decoder.
2052pub fn cert_byte_ranges(cert_der: &[u8]) -> Option<CertByteRanges> {
2053 use synta::{Decoder, Encoding, TagClass};
2054
2055 let mut d = Decoder::new(cert_der, Encoding::Der);
2056
2057 // Outer Certificate SEQUENCE header.
2058 d.read_tag().ok()?;
2059 d.read_length().ok()?.definite().ok()?;
2060
2061 // TBSCertificate: record start, read header, skip content.
2062 let tbs_start = d.position();
2063 d.read_tag().ok()?;
2064 let tbs_content_len = d.read_length().ok()?.definite().ok()?;
2065 let tbs_content_start = d.position();
2066 let tbs_end = tbs_content_start + tbs_content_len;
2067 d.read_bytes(tbs_content_len).ok()?;
2068
2069 // signatureAlgorithm: record start, read header, skip content.
2070 let sig_alg_start = d.position();
2071 d.read_tag().ok()?;
2072 let sig_alg_content_len = d.read_length().ok()?.definite().ok()?;
2073 let sig_alg_end = d.position() + sig_alg_content_len;
2074
2075 // SubjectPublicKeyInfo: walk into TBS content via a fresh decoder.
2076 let tbs_content = cert_der.get(tbs_content_start..tbs_end)?;
2077 let mut t = Decoder::new(tbs_content, Encoding::Der);
2078
2079 // Skip optional [0] EXPLICIT version.
2080 let first_tag = t.peek_tag().ok()?;
2081 if first_tag.class() == TagClass::ContextSpecific && first_tag.number() == 0 {
2082 t.read_tag().ok()?;
2083 let n = t.read_length().ok()?.definite().ok()?;
2084 t.read_bytes(n).ok()?;
2085 }
2086 // Skip serialNumber (INTEGER), signature (SEQUENCE), issuer (SEQUENCE),
2087 // validity (SEQUENCE), subject (SEQUENCE) — five fields, each a full TLV.
2088 for _ in 0..5 {
2089 t.read_tag().ok()?;
2090 let n = t.read_length().ok()?.definite().ok()?;
2091 t.read_bytes(n).ok()?;
2092 }
2093 // SubjectPublicKeyInfo starts here.
2094 let spki_start = tbs_content_start + t.position();
2095 t.read_tag().ok()?;
2096 let spki_content_len = t.read_length().ok()?.definite().ok()?;
2097 let spki_end = tbs_content_start + t.position() + spki_content_len;
2098
2099 Some(CertByteRanges {
2100 tbs: tbs_start..tbs_end,
2101 signature_algorithm: sig_alg_start..sig_alg_end,
2102 subject_public_key_info: spki_start..spki_end,
2103 })
2104}
2105
2106/// Format the human-readable content of a well-known X.509v3 extension value.
2107///
2108/// Returns `Some(content)` for recognized extensions (e.g. `"Digital Signature"` for Key
2109/// Usage, `"DNS:example.com"` for Subject Alternative Name). Returns `None` for
2110/// unrecognized extensions. The returned string does not include the extension name or
2111/// criticality; callers handle those separately.
2112///
2113/// # Performance note
2114///
2115/// All id-ce OIDs (2.5.29.N) share a 3-arc prefix; a single length+prefix check selects
2116/// the id-ce family, then a `u32` match dispatches to the specific extension decoder.
2117/// This avoids repeated `&[u32]` slice comparisons across known extensions.
2118pub fn format_extension_value<'a>(ext: &Extension<'a>) -> Option<String> {
2119 let c = ext.extn_id.components();
2120 let bytes = ext.extn_value.as_bytes();
2121
2122 // ── id-ce (2.5.29.N) — 4 arcs ────────────────────────────────────────────
2123 const CE0: u32 = oids::KEY_USAGE[0]; // 2
2124 const CE1: u32 = oids::KEY_USAGE[1]; // 5
2125 const CE2: u32 = oids::KEY_USAGE[2]; // 29
2126 const CE_SKI_V: u32 = oids::SUBJECT_KEY_IDENTIFIER[3]; // 14
2127 const CE_KU_V: u32 = oids::KEY_USAGE[3]; // 15
2128 const CE_SAN_V: u32 = oids::SUBJECT_ALT_NAME[3]; // 17
2129 const CE_IAN_V: u32 = oids::ISSUER_ALT_NAME[3]; // 18
2130 const CE_BC_V: u32 = oids::BASIC_CONSTRAINTS[3]; // 19
2131 const CE_CDP_V: u32 = oids::CRL_DISTRIBUTION_POINTS[3]; // 31
2132 const CE_CP_V: u32 = oids::CERTIFICATE_POLICIES[3]; // 32
2133 const CE_AKI_V: u32 = oids::AUTHORITY_KEY_IDENTIFIER[3]; // 35
2134 const CE_EKU_V: u32 = oids::EXTENDED_KEY_USAGE[3]; // 37
2135
2136 if let &[CE0, CE1, CE2, sub] = c {
2137 return match sub {
2138 CE_SKI_V => format_ski_ext(bytes),
2139 CE_KU_V => format_key_usage_ext(bytes),
2140 CE_SAN_V | CE_IAN_V => format_general_names_ext(bytes),
2141 CE_BC_V => format_basic_constraints_ext(bytes),
2142 CE_CDP_V => format_crl_dp_ext(bytes),
2143 CE_CP_V => format_cert_policies_ext(bytes),
2144 CE_AKI_V => format_aki_ext(bytes),
2145 CE_EKU_V => format_eku_ext(bytes),
2146 _ => None,
2147 };
2148 }
2149
2150 // Authority Information Access is in the id-pe arc (1.3.6.1.5.5.7.1.N),
2151 // not id-ce, so it falls outside the 4-arc dispatch above.
2152 if c == oids::AUTHORITY_INFO_ACCESS {
2153 return format_aia_ext(bytes);
2154 }
2155
2156 None
2157}
2158
2159fn format_key_usage_ext(bytes: &[u8]) -> Option<String> {
2160 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2161 let ku: KeyUsage = decoder.decode().ok()?;
2162 let ku_bytes = ku.as_bytes();
2163
2164 let bit_set = |n: usize| -> bool {
2165 ku_bytes
2166 .get(n / 8)
2167 .is_some_and(|&b| (b >> (7 - (n % 8))) & 1 != 0)
2168 };
2169
2170 let mut parts = Vec::new();
2171 if bit_set(KEY_USAGE_DIGITAL_SIGNATURE) {
2172 parts.push("Digital Signature");
2173 }
2174 if bit_set(KEY_USAGE_NON_REPUDIATION) {
2175 parts.push("Non Repudiation");
2176 }
2177 if bit_set(KEY_USAGE_KEY_ENCIPHERMENT) {
2178 parts.push("Key Encipherment");
2179 }
2180 if bit_set(KEY_USAGE_DATA_ENCIPHERMENT) {
2181 parts.push("Data Encipherment");
2182 }
2183 if bit_set(KEY_USAGE_KEY_AGREEMENT) {
2184 parts.push("Key Agreement");
2185 }
2186 if bit_set(KEY_USAGE_KEY_CERT_SIGN) {
2187 parts.push("Certificate Sign");
2188 }
2189 if bit_set(KEY_USAGE_C_RLSIGN) {
2190 parts.push("CRL Sign");
2191 }
2192 if bit_set(KEY_USAGE_ENCIPHER_ONLY) {
2193 parts.push("Encipher Only");
2194 }
2195 if bit_set(KEY_USAGE_DECIPHER_ONLY) {
2196 parts.push("Decipher Only");
2197 }
2198
2199 Some(parts.join(", "))
2200}
2201
2202fn format_eku_ext(bytes: &[u8]) -> Option<String> {
2203 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2204 let eku: Vec<ObjectIdentifier> = decoder.decode().ok()?;
2205
2206 // ── id-kp (1.3.6.1.5.5.7.3.N) — 9 arcs ──────────────────────────────────
2207 const KP0: u32 = ID_KP_SERVER_AUTH[0]; // 1
2208 const KP1: u32 = ID_KP_SERVER_AUTH[1]; // 3
2209 const KP2: u32 = ID_KP_SERVER_AUTH[2]; // 6
2210 const KP3: u32 = ID_KP_SERVER_AUTH[3]; // 1
2211 const KP4: u32 = ID_KP_SERVER_AUTH[4]; // 5
2212 const KP5: u32 = ID_KP_SERVER_AUTH[5]; // 5
2213 const KP6: u32 = ID_KP_SERVER_AUTH[6]; // 7
2214 const KP7: u32 = ID_KP_SERVER_AUTH[7]; // 3
2215 const KP_SERVER_V: u32 = ID_KP_SERVER_AUTH[8]; // 1
2216 const KP_CLIENT_V: u32 = ID_KP_CLIENT_AUTH[8]; // 2
2217 const KP_CODESIGN_V: u32 = ID_KP_CODE_SIGNING[8]; // 3
2218 const KP_EMAIL_V: u32 = ID_KP_EMAIL_PROTECTION[8]; // 4
2219 const KP_TIME_V: u32 = ID_KP_TIME_STAMPING[8]; // 8
2220 const KP_OCSP_V: u32 = ID_KP_OCSPSIGNING[8]; // 9
2221
2222 let names: Vec<&str> = eku
2223 .iter()
2224 .map(|oid| {
2225 if let &[KP0, KP1, KP2, KP3, KP4, KP5, KP6, KP7, sub] = oid.components() {
2226 match sub {
2227 KP_SERVER_V => "TLS Web Server Authentication",
2228 KP_CLIENT_V => "TLS Web Client Authentication",
2229 KP_CODESIGN_V => "Code Signing",
2230 KP_EMAIL_V => "E-mail Protection",
2231 KP_TIME_V => "Time Stamping",
2232 KP_OCSP_V => "OCSP Signing",
2233 _ => "Unknown Purpose",
2234 }
2235 } else {
2236 "Unknown Purpose"
2237 }
2238 })
2239 .collect();
2240
2241 Some(names.join(", "))
2242}
2243
2244/// Context-specific tag numbers for the `GeneralName` CHOICE type (RFC 5280 §4.2.1.6).
2245///
2246/// These constants correspond to the `u32` tag number values returned by
2247/// [`parse_general_names`] and [`Certificate::subject_alt_names`].
2248///
2249/// | Constant | Tag | `GeneralName` alternative |
2250/// |----------|-----|--------------------------|
2251/// | [`general_name::OTHER_NAME`] | 0 | `otherName` |
2252/// | [`general_name::RFC822_NAME`] | 1 | `rfc822Name` (email) |
2253/// | [`general_name::DNS_NAME`] | 2 | `dNSName` |
2254/// | [`general_name::X400_ADDRESS`] | 3 | `x400Address` |
2255/// | [`general_name::DIRECTORY_NAME`] | 4 | `directoryName` |
2256/// | [`general_name::EDI_PARTY_NAME`] | 5 | `ediPartyName` |
2257/// | [`general_name::URI`] | 6 | `uniformResourceIdentifier` |
2258/// | [`general_name::IP_ADDRESS`] | 7 | `iPAddress` |
2259/// | [`general_name::REGISTERED_ID`] | 8 | `registeredID` |
2260pub mod general_name {
2261 /// `otherName [0]` — content is the full `OtherNameValue` TLV (constructed).
2262 pub const OTHER_NAME: u32 = 0;
2263 /// `rfc822Name [1]` — content is raw IA5String bytes (e-mail address).
2264 pub const RFC822_NAME: u32 = 1;
2265 /// `dNSName [2]` — content is raw IA5String bytes (DNS host name).
2266 pub const DNS_NAME: u32 = 2;
2267 /// `x400Address [3]`.
2268 pub const X400_ADDRESS: u32 = 3;
2269 /// `directoryName [4]` — content is the complete Name SEQUENCE TLV.
2270 pub const DIRECTORY_NAME: u32 = 4;
2271 /// `ediPartyName [5]`.
2272 pub const EDI_PARTY_NAME: u32 = 5;
2273 /// `uniformResourceIdentifier [6]` — content is raw IA5String bytes (URI).
2274 pub const URI: u32 = 6;
2275 /// `iPAddress [7]` — content is 4 raw bytes (IPv4) or 16 raw bytes (IPv6).
2276 pub const IP_ADDRESS: u32 = 7;
2277 /// `registeredID [8]` — content is raw OID value bytes (no tag/length).
2278 pub const REGISTERED_ID: u32 = 8;
2279
2280 // ── GeneralNameSpec ───────────────────────────────────────────────────────
2281
2282 /// A `GeneralName` specification used by builders (e.g. [`crate::CMPMessageBuilder`]).
2283 ///
2284 /// Holds the data needed to construct a [`crate::GeneralName`] at build
2285 /// time without going through an intermediate DER encode/decode round-trip.
2286 /// Use the associated constructors rather than matching on variants directly.
2287 ///
2288 /// # Example
2289 ///
2290 /// ```rust,ignore
2291 /// use synta_certificate::{CMPMessageBuilder, general_name::GeneralNameSpec};
2292 ///
2293 /// let der = CMPMessageBuilder::new()
2294 /// .sender(GeneralNameSpec::rfc822("ca@example.com"))
2295 /// .recipient(GeneralNameSpec::directory_name(&name_der))
2296 /// .body_pkiconf()
2297 /// .build()?;
2298 /// ```
2299 #[derive(Clone)]
2300 pub enum GeneralNameSpec {
2301 /// `rfc822Name [1]` — e-mail address (ASCII).
2302 Rfc822(String),
2303 /// `dNSName [2]` — DNS host name (ASCII).
2304 Dns(String),
2305 /// `uniformResourceIdentifier [6]` — URI (ASCII).
2306 Uri(String),
2307 /// `directoryName [4]` — pre-encoded `Name` DER SEQUENCE TLV.
2308 DirectoryName(Vec<u8>),
2309 /// `iPAddress [7]` — 4 raw bytes (IPv4) or 16 raw bytes (IPv6).
2310 IpAddress(Vec<u8>),
2311 /// `registeredID [8]` — an object identifier.
2312 RegisteredId(synta::ObjectIdentifier),
2313 }
2314
2315 impl GeneralNameSpec {
2316 /// Create a `rfc822Name` spec from an e-mail address string.
2317 pub fn rfc822(email: &str) -> Self {
2318 Self::Rfc822(email.to_string())
2319 }
2320
2321 /// Create a `dNSName` spec from a DNS host name string.
2322 pub fn dns(host: &str) -> Self {
2323 Self::Dns(host.to_string())
2324 }
2325
2326 /// Create a `uniformResourceIdentifier` spec from a URI string.
2327 pub fn uri(uri: &str) -> Self {
2328 Self::Uri(uri.to_string())
2329 }
2330
2331 /// Create a `directoryName` spec from a pre-encoded `Name` DER SEQUENCE TLV.
2332 ///
2333 /// Pass the output of [`crate::NameBuilder::build`] or
2334 /// `Certificate::subject_raw_der()` directly.
2335 pub fn directory_name(name_der: &[u8]) -> Self {
2336 Self::DirectoryName(name_der.to_vec())
2337 }
2338
2339 /// Create an `iPAddress` spec from raw address bytes.
2340 ///
2341 /// Pass 4 bytes for IPv4 or 16 bytes for IPv6.
2342 pub fn ip_address(addr: &[u8]) -> Self {
2343 Self::IpAddress(addr.to_vec())
2344 }
2345
2346 /// Create a `registeredID` spec from an object identifier.
2347 pub fn registered_id(oid: synta::ObjectIdentifier) -> Self {
2348 Self::RegisteredId(oid)
2349 }
2350
2351 /// Construct a [`crate::GeneralName`] from `self`.
2352 ///
2353 /// For string-based variants the string data is cloned into the result.
2354 /// For `DirectoryName` the result borrows from the stored `Vec<u8>`.
2355 pub fn to_general_name(&self) -> synta::Result<crate::GeneralName<'_>> {
2356 match self {
2357 Self::Rfc822(s) => {
2358 let ia5 = synta::IA5String::new(s.clone())?;
2359 Ok(crate::GeneralName::Rfc822Name(ia5))
2360 }
2361 Self::Dns(s) => {
2362 let ia5 = synta::IA5String::new(s.clone())?;
2363 Ok(crate::GeneralName::DNSName(ia5))
2364 }
2365 Self::Uri(s) => {
2366 let ia5 = synta::IA5String::new(s.clone())?;
2367 Ok(crate::GeneralName::UniformResourceIdentifier(ia5))
2368 }
2369 Self::DirectoryName(bytes) => {
2370 let name = synta::Decoder::new(bytes, synta::Encoding::Der)
2371 .decode::<crate::Name<'_>>()?;
2372 Ok(crate::GeneralName::DirectoryName(name))
2373 }
2374 Self::IpAddress(bytes) => Ok(crate::GeneralName::IPAddress(
2375 synta::OctetString::new(bytes.clone()),
2376 )),
2377 Self::RegisteredId(oid) => Ok(crate::GeneralName::RegisteredID(oid.clone())),
2378 }
2379 }
2380 }
2381}
2382
2383/// Re-export [`general_name::GeneralNameSpec`] at the crate root for convenience.
2384pub use general_name::GeneralNameSpec;
2385
2386/// Parse a DER-encoded `SEQUENCE OF GeneralName` into raw `(tag_number, content)` pairs.
2387///
2388/// `raw` must be the **complete DER bytes** of the `SEQUENCE OF GeneralName` value —
2389/// exactly the bytes you get from an extension's `extn_value` octet-string content for
2390/// SAN (2.5.29.17) or IAN (2.5.29.18).
2391///
2392/// Returns one `(tag_number, content_bytes)` tuple per `GeneralName` alternative.
2393/// Tag numbers follow RFC 5280:
2394///
2395/// | Tag | GeneralName alternative |
2396/// |-----|------------------------|
2397/// | 0 | otherName (constructed; content is the full `OtherNameValue` TLV) |
2398/// | 1 | rfc822Name — raw IA5String bytes |
2399/// | 2 | dNSName — raw IA5String bytes |
2400/// | 3 | x400Address |
2401/// | 4 | directoryName — the Name SEQUENCE TLV |
2402/// | 5 | ediPartyName |
2403/// | 6 | uniformResourceIdentifier — raw IA5String bytes |
2404/// | 7 | iPAddress — 4 (IPv4) or 16 (IPv6) raw bytes |
2405/// | 8 | registeredID — raw OID value bytes |
2406///
2407/// Returns an empty `Vec` if `raw` is not a valid DER SEQUENCE.
2408///
2409/// This is the Rust counterpart to `synta.parse_general_names()` in the Python binding.
2410///
2411/// # Example
2412///
2413/// ```
2414/// use synta_certificate::{general_name, parse_general_names};
2415///
2416/// // SEQUENCE { [2] IMPLICIT IA5String "a.b" } (dNSName)
2417/// let der: &[u8] = &[0x30, 0x05, 0x82, 0x03, b'a', b'.', b'b'];
2418/// let entries = parse_general_names(der);
2419/// assert_eq!(entries.len(), 1);
2420/// let (tag, content) = &entries[0];
2421/// assert_eq!(*tag, general_name::DNS_NAME);
2422/// assert_eq!(content, b"a.b");
2423/// ```
2424pub fn parse_general_names(raw: &[u8]) -> Vec<(u32, Vec<u8>)> {
2425 parse_general_names_inner(raw).unwrap_or_default()
2426}
2427
2428/// Encode a list of ``(tag_number, content_bytes)`` pairs as a DER
2429/// ``SEQUENCE OF GeneralName``.
2430///
2431/// This is the Rust-level inverse of [`parse_general_names`]: call it with the
2432/// ``(u32, &[u8])`` pairs that `parse_general_names` returns (possibly after
2433/// modification) to get back the DER-encoded `SEQUENCE OF GeneralName` bytes.
2434///
2435/// Each entry is `(tag_number, value_bytes)` where `value_bytes` is the raw
2436/// **value** of the context-specific TLV (without tag/length bytes), exactly
2437/// as returned by `parse_general_names`. Use the [`general_name`] constants
2438/// for tag numbers.
2439///
2440/// Returns `None` if any entry cannot be constructed (e.g. invalid UTF-8 for
2441/// an IA5String variant or an invalid OID value encoding).
2442pub fn encode_general_names(entries: &[(u32, &[u8])]) -> Option<Vec<u8>> {
2443 use synta::traits::Decode;
2444
2445 // Append a DER length to `out`.
2446 fn push_der_len(out: &mut Vec<u8>, len: usize) {
2447 if len < 0x80 {
2448 out.push(len as u8);
2449 } else if len <= 0xFF {
2450 out.extend_from_slice(&[0x81, len as u8]);
2451 } else {
2452 out.extend_from_slice(&[0x82, (len >> 8) as u8, len as u8]);
2453 }
2454 }
2455
2456 // Emit a context-specific CONSTRUCTED TLV: (0x80 | 0x20 | tag) + len + value.
2457 //
2458 // Used for IMPLICIT SEQUENCE alternatives (otherName [0], x400Address [3],
2459 // ediPartyName [5]) where parse_general_names has already stripped the 0x30
2460 // SEQUENCE tag, returning only the SEQUENCE body in `value`. Re-wrapping
2461 // with the original context-specific constructed tag reconstructs the exact
2462 // wire bytes without needing to decode the inner structure.
2463 fn context_constructed_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
2464 let mut tlv = Vec::with_capacity(3 + value.len());
2465 tlv.push(0xA0 | tag); // context-specific (0x80) | constructed (0x20) | tag
2466 push_der_len(&mut tlv, value.len());
2467 tlv.extend_from_slice(value);
2468 tlv
2469 }
2470
2471 // Encode one GeneralName entry as raw DER bytes.
2472 fn encode_one(tag_num: u32, content: &[u8]) -> Option<Vec<u8>> {
2473 use general_name as gn;
2474 match tag_num {
2475 gn::OTHER_NAME | gn::X400_ADDRESS | gn::EDI_PARTY_NAME => {
2476 // [0]/[3]/[5] IMPLICIT SEQUENCE: parse_general_names returns the
2477 // SEQUENCE body without the 0x30 wrapper (IMPLICIT tagging replaced
2478 // the SEQUENCE tag with the context-specific constructed tag).
2479 // Re-wrap directly — no decode/re-encode of the inner structure needed.
2480 Some(context_constructed_tlv(tag_num as u8, content))
2481 }
2482 gn::RFC822_NAME => {
2483 // IMPLICIT [1]: content = raw IA5String bytes
2484 let s = std::str::from_utf8(content).ok()?;
2485 GeneralName::Rfc822Name(synta::IA5String::new(s.to_string()).ok()?)
2486 .to_der()
2487 .ok()
2488 }
2489 gn::DNS_NAME => {
2490 // IMPLICIT [2]: content = raw IA5String bytes
2491 let s = std::str::from_utf8(content).ok()?;
2492 GeneralName::DNSName(synta::IA5String::new(s.to_string()).ok()?)
2493 .to_der()
2494 .ok()
2495 }
2496 gn::DIRECTORY_NAME => {
2497 // EXPLICIT [4]: content = full Name SEQUENCE TLV
2498 let mut dec = synta::Decoder::new(content, synta::Encoding::Der);
2499 GeneralName::DirectoryName(Name::decode(&mut dec).ok()?)
2500 .to_der()
2501 .ok()
2502 }
2503 gn::URI => {
2504 // IMPLICIT [6]: content = raw IA5String bytes
2505 let s = std::str::from_utf8(content).ok()?;
2506 GeneralName::UniformResourceIdentifier(synta::IA5String::new(s.to_string()).ok()?)
2507 .to_der()
2508 .ok()
2509 }
2510 gn::IP_ADDRESS => {
2511 // IMPLICIT [7]: content = 4 (IPv4) or 16 (IPv6) raw bytes
2512 GeneralName::IPAddress(synta::OctetString::new(content.to_vec()))
2513 .to_der()
2514 .ok()
2515 }
2516 gn::REGISTERED_ID => {
2517 // IMPLICIT [8]: content = raw OID value bytes (no tag/length)
2518 let oid = synta::ObjectIdentifier::from_content_bytes(content).ok()?;
2519 GeneralName::RegisteredID(oid).to_der().ok()
2520 }
2521 _ => None,
2522 }
2523 }
2524
2525 // Accumulate encoded GeneralName TLVs, then wrap in an outer SEQUENCE.
2526 let mut body: Vec<u8> = Vec::new();
2527 for &(tag_num, content) in entries {
2528 body.extend_from_slice(&encode_one(tag_num, content)?);
2529 }
2530 let mut out = Vec::with_capacity(4 + body.len());
2531 out.push(0x30); // SEQUENCE tag
2532 push_der_len(&mut out, body.len());
2533 out.extend_from_slice(&body);
2534 Some(out)
2535}
2536
2537/// Convenience methods for [`crl::CertificateList`].
2538///
2539/// Adds high-level accessors for commonly-needed CRL fields that are buried
2540/// inside the extension list.
2541impl<'a> crl::CertificateList<'a> {
2542 /// Return the CRL sequence number from the ``cRLNumber`` extension
2543 /// (OID `2.5.29.20`), if present.
2544 ///
2545 /// The integer is decoded from the DER-encoded ``extnValue`` and returned
2546 /// as an owned [`synta::Integer`] (arbitrary precision, heap-allocated only
2547 /// for values larger than 16 bytes).
2548 ///
2549 /// Returns `None` when the CRL carries no ``cRLExtensions``, no
2550 /// ``cRLNumber`` extension, or the extension value fails to decode as an
2551 /// INTEGER.
2552 pub fn crl_number(&self) -> Option<synta::Integer> {
2553 let exts = self.tbs_cert_list.crl_extensions.as_ref()?;
2554 for ext in exts {
2555 if ext.extn_id.components() == oids::CRL_NUMBER {
2556 use synta::Decode;
2557 let mut dec = synta::Decoder::new(ext.extn_value.as_bytes(), synta::Encoding::Der);
2558 return synta::Integer::decode(&mut dec).ok();
2559 }
2560 }
2561 None
2562 }
2563}
2564
2565/// Convenience method for accessing Subject Alternative Names.
2566///
2567/// This `impl` block adds [`subject_alt_names`][Certificate::subject_alt_names]
2568/// directly on the generated [`Certificate`] type.
2569impl<'a> Certificate<'a> {
2570 /// Return the Subject Alternative Names carried by this certificate.
2571 ///
2572 /// Locates the SAN extension (OID `2.5.29.17`) in the certificate's
2573 /// extension list and returns its `GeneralName` entries as
2574 /// `(tag_number, content_bytes)` pairs, exactly as
2575 /// [`parse_general_names`] does. Tag numbers follow RFC 5280 §4.2.1.6:
2576 ///
2577 /// | Tag | `GeneralName` alternative |
2578 /// |-----|--------------------------|
2579 /// | 1 | `rfc822Name` — raw IA5String bytes (email) |
2580 /// | 2 | `dNSName` — raw IA5String bytes |
2581 /// | 4 | `directoryName` — complete Name SEQUENCE TLV |
2582 /// | 6 | `uniformResourceIdentifier` — raw IA5String bytes |
2583 /// | 7 | `iPAddress` — 4 bytes (IPv4) or 16 bytes (IPv6) |
2584 /// | 8 | `registeredID` — raw OID value bytes |
2585 ///
2586 /// Returns an empty `Vec` when the certificate has no SAN extension or
2587 /// the extension value cannot be parsed.
2588 ///
2589 /// # Example
2590 ///
2591 /// ```rust,ignore
2592 /// use synta_certificate::Certificate;
2593 /// use synta::{Decoder, Encoding};
2594 ///
2595 /// let mut dec = Decoder::new(der, Encoding::Der);
2596 /// let cert: Certificate = dec.decode().unwrap();
2597 ///
2598 /// for (tag, content) in cert.subject_alt_names() {
2599 /// match tag {
2600 /// 2 => println!("DNS: {}", std::str::from_utf8(&content).unwrap_or("?")),
2601 /// 7 if content.len() == 4 => println!("IPv4: {}.{}.{}.{}", content[0], content[1], content[2], content[3]),
2602 /// 7 => println!("IPv6: {} bytes", content.len()),
2603 /// 1 => println!("email: {}", std::str::from_utf8(&content).unwrap_or("?")),
2604 /// _ => {}
2605 /// }
2606 /// }
2607 /// ```
2608 pub fn subject_alt_names(&self) -> Vec<(u32, Vec<u8>)> {
2609 let raw = match self.tbs_certificate.extensions.as_ref() {
2610 Some(r) => r,
2611 None => return Vec::new(),
2612 };
2613 find_extension_value(raw.as_bytes(), oids::SUBJECT_ALT_NAME)
2614 .map(parse_general_names)
2615 .unwrap_or_default()
2616 }
2617}
2618
2619/// Single-pass scan of a DER-encoded `SEQUENCE OF Extension`, returning the
2620/// `extnValue` content bytes of the first extension whose `extnId` matches
2621/// `oid`. Stops as soon as the matching extension is found without decoding
2622/// the remainder of the sequence.
2623///
2624/// `raw` must be the complete TLV of the `Extensions` SEQUENCE (i.e. the
2625/// bytes captured by the `RawDer<'a>` extensions field, which already has
2626/// the `[3] EXPLICIT` wrapper stripped by the derive macro).
2627///
2628/// Returns `None` on parse error or if no matching extension is found.
2629pub fn find_extension_value<'a>(raw: &'a [u8], oid: &[u32]) -> Option<&'a [u8]> {
2630 use synta::tag::TAG_SEQUENCE;
2631 use synta::Tag;
2632
2633 let mut dec = synta::Decoder::new(raw, synta::Encoding::Der);
2634 let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
2635 let mut outer = dec.enter_constructed(seq_tag).ok()?;
2636
2637 while !outer.is_empty() {
2638 let ext: Extension<'_> = outer.decode().ok()?;
2639 if ext.extn_id.components() == oid {
2640 return Some(ext.extn_value.as_bytes());
2641 }
2642 }
2643 None
2644}
2645
2646fn parse_general_names_inner(raw: &[u8]) -> Option<Vec<(u32, Vec<u8>)>> {
2647 use synta::tag::TAG_SEQUENCE;
2648 use synta::{Tag, TagClass};
2649
2650 let mut decoder = synta::Decoder::new(raw, synta::Encoding::Der);
2651 let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
2652 let mut inner = decoder.enter_constructed(seq_tag).ok()?;
2653
2654 let mut result = Vec::new();
2655 while !inner.is_empty() {
2656 let tag = inner.read_tag().ok()?;
2657 let len = inner.read_length().ok()?.definite().ok()?;
2658 let content = inner.read_bytes(len).ok()?.to_vec();
2659 // GeneralName alternatives always use context-specific IMPLICIT tags [0]–[8].
2660 // Silently skip any non-context-specific element (malformed input defence),
2661 // consistent with the class check in format_general_names_ext.
2662 if tag.class() != TagClass::ContextSpecific {
2663 continue;
2664 }
2665 result.push((tag.number(), content));
2666 }
2667 Some(result)
2668}
2669
2670fn format_general_names_ext(bytes: &[u8]) -> Option<String> {
2671 // GeneralName alternatives use IMPLICIT context-specific tags per RFC 5280
2672 // (the module uses IMPLICIT TAGS default). We parse raw TLVs directly:
2673 // [1] IMPLICIT IA5String → email address
2674 // [2] IMPLICIT IA5String → DNS name
2675 // [6] IMPLICIT IA5String → URI
2676 // [7] IMPLICIT OCTET STRING → IP address (4 or 16 bytes)
2677 // All others (OtherName, directoryName, …) are silently skipped.
2678 use synta::tag::TAG_SEQUENCE;
2679 use synta::TagClass;
2680
2681 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2682 let seq_tag = synta::Tag::universal_constructed(TAG_SEQUENCE);
2683 let mut inner = decoder.enter_constructed(seq_tag).ok()?;
2684
2685 let mut parts = Vec::new();
2686 while !inner.is_empty() {
2687 let tag = inner.read_tag().ok()?;
2688 let len = inner.read_length().ok()?.definite().ok()?;
2689 let content = inner.read_bytes(len).ok()?;
2690
2691 if tag.class() != TagClass::ContextSpecific {
2692 continue;
2693 }
2694 let s = match tag.number() {
2695 1 => format!("email:{}", core::str::from_utf8(content).unwrap_or("?")),
2696 2 => format!("DNS:{}", core::str::from_utf8(content).unwrap_or("?")),
2697 6 => format!("URI:{}", core::str::from_utf8(content).unwrap_or("?")),
2698 7 => format_ip_address(content),
2699 _ => continue,
2700 };
2701 parts.push(s);
2702 }
2703
2704 if parts.is_empty() {
2705 None
2706 } else {
2707 Some(parts.join(", "))
2708 }
2709}
2710
2711fn format_ip_address(bytes: &[u8]) -> String {
2712 match bytes.len() {
2713 4 => format!(
2714 "IP Address:{}.{}.{}.{}",
2715 bytes[0], bytes[1], bytes[2], bytes[3]
2716 ),
2717 16 => {
2718 let parts: Vec<String> = bytes
2719 .chunks(2)
2720 .map(|c| format!("{:02X}{:02X}", c[0], c[1]))
2721 .collect();
2722 format!("IP Address:{}", parts.join(":"))
2723 }
2724 _ => format!(
2725 "IP Address:{}",
2726 bytes
2727 .iter()
2728 .map(|b| format!("{:02x}", b))
2729 .collect::<Vec<_>>()
2730 .join(":")
2731 ),
2732 }
2733}
2734
2735fn format_basic_constraints_ext(bytes: &[u8]) -> Option<String> {
2736 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2737 let bc: BasicConstraints = decoder.decode().ok()?;
2738
2739 let mut parts = Vec::new();
2740 let is_ca = bc.c_a.map(|b| b.0).unwrap_or(false);
2741 parts.push(if is_ca { "CA:TRUE" } else { "CA:FALSE" }.to_string());
2742 if let Some(path_len) = &bc.path_len_constraint {
2743 parts.push(format!("pathlen:{}", path_len.as_i64().unwrap_or(0)));
2744 }
2745 Some(parts.join(", "))
2746}
2747
2748fn format_ski_ext<'a>(bytes: &'a [u8]) -> Option<String> {
2749 use synta::OctetStringRef;
2750 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2751 let ski: OctetStringRef<'a> = decoder.decode().ok()?;
2752 let hex: String = ski
2753 .as_bytes()
2754 .iter()
2755 .map(|b| format!("{:02X}", b))
2756 .collect::<Vec<_>>()
2757 .join(":");
2758 Some(hex)
2759}
2760
2761fn format_crl_dp_ext(bytes: &[u8]) -> Option<String> {
2762 // CRLDistributionPoints ::= SEQUENCE OF DistributionPoint
2763 // DistributionPoint ::= SEQUENCE {
2764 // distributionPoint [0] DistributionPointName OPTIONAL, -- CHOICE
2765 // fullName [0] GeneralNames, -- IMPLICIT
2766 // nameRelative... [1] RelativeDistinguishedName -- IMPLICIT
2767 // reasons [1] OPTIONAL, cRLIssuer [2] OPTIONAL }
2768 //
2769 // In practice nearly all certificates use fullName with a single URI.
2770 use synta::tag::TAG_SEQUENCE;
2771 use synta::{Tag, TagClass};
2772
2773 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2774 let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
2775 let mut outer = decoder.enter_constructed(seq_tag).ok()?;
2776
2777 let mut parts = Vec::new();
2778 while !outer.is_empty() {
2779 // Each element is a DistributionPoint SEQUENCE.
2780 let mut dp = outer.enter_constructed(seq_tag).ok()?;
2781 while !dp.is_empty() {
2782 let dp_tag = dp.read_tag().ok()?;
2783 let dp_len = dp.read_length().ok()?.definite().ok()?;
2784 let dp_content = dp.read_bytes(dp_len).ok()?;
2785
2786 // distributionPoint is [0] — a DistributionPointName CHOICE.
2787 if dp_tag.class() == TagClass::ContextSpecific && dp_tag.number() == 0 {
2788 // fullName [0] IMPLICIT GeneralNames — walk the GeneralNames entries.
2789 let mut gn_dec = synta::Decoder::new(dp_content, synta::Encoding::Der);
2790 while !gn_dec.is_empty() {
2791 let gn_tag = gn_dec.read_tag().ok()?;
2792 let gn_len = gn_dec.read_length().ok()?.definite().ok()?;
2793 let gn_content = gn_dec.read_bytes(gn_len).ok()?;
2794 if gn_tag.class() == TagClass::ContextSpecific && gn_tag.number() == 6 {
2795 // uniformResourceIdentifier [6] IMPLICIT IA5String
2796 let uri = core::str::from_utf8(gn_content).unwrap_or("?");
2797 parts.push(format!("URI:{}", uri));
2798 }
2799 }
2800 }
2801 // reasons [1] and cRLIssuer [2] are skipped — rarely needed for display.
2802 }
2803 }
2804
2805 if parts.is_empty() {
2806 None
2807 } else {
2808 Some(parts.join(", "))
2809 }
2810}
2811
2812fn format_cert_policies_ext(bytes: &[u8]) -> Option<String> {
2813 // CertificatePolicies ::= SEQUENCE OF PolicyInformation
2814 // Decode using the code-generated PolicyInformation<'a> type.
2815 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2816 let infos: Vec<PolicyInformation<'_>> = decoder.decode().ok()?;
2817 if infos.is_empty() {
2818 return None;
2819 }
2820 Some(
2821 infos
2822 .iter()
2823 .map(|pi| format!("Policy: {}", pi.policy_identifier))
2824 .collect::<Vec<_>>()
2825 .join(", "),
2826 )
2827}
2828
2829fn format_aia_ext(bytes: &[u8]) -> Option<String> {
2830 // AuthorityInfoAccessSyntax ::= SEQUENCE OF AccessDescription
2831 // AccessDescription ::= SEQUENCE { accessMethod OID, accessLocation GeneralName }
2832 use synta::tag::TAG_SEQUENCE;
2833 use synta::{Tag, TagClass};
2834
2835 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2836 let seq_tag = Tag::universal_constructed(TAG_SEQUENCE);
2837 let mut outer = decoder.enter_constructed(seq_tag).ok()?;
2838
2839 // Prefix and sub-arc constants derived from the autogenerated OID arrays so
2840 // they stay in sync with the ASN.1 schema if the OID values ever change.
2841 const AD0: u32 = ID_AD[0];
2842 const AD1: u32 = ID_AD[1];
2843 const AD2: u32 = ID_AD[2];
2844 const AD3: u32 = ID_AD[3];
2845 const AD4: u32 = ID_AD[4];
2846 const AD5: u32 = ID_AD[5];
2847 const AD6: u32 = ID_AD[6];
2848 const AD7: u32 = ID_AD[7];
2849 const OCSP_V: u32 = oids::AD_OCSP[oids::AD_OCSP.len() - 1];
2850 const CA_ISSUERS_V: u32 = oids::AD_CA_ISSUERS[oids::AD_CA_ISSUERS.len() - 1];
2851
2852 let mut parts = Vec::new();
2853 while !outer.is_empty() {
2854 let mut ad = outer.enter_constructed(seq_tag).ok()?;
2855 let oid = synta::ObjectIdentifier::decode(&mut ad).ok()?;
2856 let gn_tag = ad.read_tag().ok()?;
2857 let gn_len = ad.read_length().ok()?.definite().ok()?;
2858 let gn_content = ad.read_bytes(gn_len).ok()?;
2859
2860 // accessLocation: [6] IMPLICIT IA5String for URI (most common).
2861 let location_str = if gn_tag.class() == TagClass::ContextSpecific && gn_tag.number() == 6 {
2862 match core::str::from_utf8(gn_content) {
2863 Ok(uri) => format!("URI:{}", uri),
2864 Err(_) => format!("URI:<invalid UTF-8: {} bytes>", gn_content.len()),
2865 }
2866 } else {
2867 format!("[tag {}]", gn_tag.number())
2868 };
2869
2870 let label = if let &[AD0, AD1, AD2, AD3, AD4, AD5, AD6, AD7, sub] = oid.components() {
2871 match sub {
2872 OCSP_V => "OCSP",
2873 CA_ISSUERS_V => "CA Issuers",
2874 _ => "Unknown",
2875 }
2876 } else {
2877 "Unknown"
2878 };
2879 parts.push(format!("{} - {}", label, location_str));
2880 }
2881
2882 if parts.is_empty() {
2883 None
2884 } else {
2885 Some(parts.join("\n"))
2886 }
2887}
2888
2889fn format_aki_ext(bytes: &[u8]) -> Option<String> {
2890 // AuthorityKeyIdentifier ::= SEQUENCE {
2891 // keyIdentifier [0] IMPLICIT OCTET STRING OPTIONAL,
2892 // authorityCertIssuer [1] IMPLICIT GeneralNames OPTIONAL,
2893 // authorityCertSerialNumber [2] IMPLICIT INTEGER OPTIONAL }
2894 // Tag bytes: 0x80 = primitive ctx 0, 0xA1 = constructed ctx 1, 0x82 = primitive ctx 2
2895 use synta::tag::TAG_SEQUENCE;
2896 use synta::TagClass;
2897
2898 let mut decoder = synta::Decoder::new(bytes, synta::Encoding::Der);
2899 let seq_tag = synta::Tag::universal_constructed(TAG_SEQUENCE);
2900 let mut inner = decoder.enter_constructed(seq_tag).ok()?;
2901
2902 let mut parts = Vec::new();
2903 while !inner.is_empty() {
2904 let tag = inner.read_tag().ok()?;
2905 let len = inner.read_length().ok()?.definite().ok()?;
2906 let content = inner.read_bytes(len).ok()?;
2907
2908 if tag.class() != TagClass::ContextSpecific {
2909 continue;
2910 }
2911 match tag.number() {
2912 0 => {
2913 // keyIdentifier [0] IMPLICIT OCTET STRING
2914 let hex = content
2915 .iter()
2916 .map(|b| format!("{:02X}", b))
2917 .collect::<Vec<_>>()
2918 .join(":");
2919 parts.push(format!("keyid:{}", hex));
2920 }
2921 2 => {
2922 // authorityCertSerialNumber [2] IMPLICIT INTEGER
2923 let hex = content
2924 .iter()
2925 .map(|b| format!("{:02X}", b))
2926 .collect::<Vec<_>>()
2927 .join(":");
2928 parts.push(format!("serial:{}", hex));
2929 }
2930 1 => {
2931 // authorityCertIssuer [1] IMPLICIT GeneralNames
2932 // Content is SEQUENCE OF GeneralName; directoryName uses [4] EXPLICIT Name.
2933 let mut gn_dec = synta::Decoder::new(content, synta::Encoding::Der);
2934 while !gn_dec.is_empty() {
2935 let gn_tag = gn_dec.read_tag().ok()?;
2936 let gn_len = gn_dec.read_length().ok()?.definite().ok()?;
2937 let gn_content = gn_dec.read_bytes(gn_len).ok()?;
2938 // directoryName [4] — EXPLICIT: gn_content starts with the Name TLV (0x30)
2939 if gn_tag.class() == synta::TagClass::ContextSpecific && gn_tag.number() == 4 {
2940 parts.push(format!("DirName:{}", format_dn_slash(gn_content)));
2941 }
2942 }
2943 }
2944 _ => {} // skip unknown fields
2945 }
2946 }
2947
2948 if parts.is_empty() {
2949 None
2950 } else {
2951 Some(parts.join("\n "))
2952 }
2953}
2954
2955#[cfg(test)]
2956mod tests {
2957 use super::*;
2958
2959 // ── parse_general_names ───────────────────────────────────────────────────
2960
2961 /// A Universal-class tag (here UTF8String, tag 0x0C) inside a GeneralNames
2962 /// SEQUENCE must be silently skipped; only context-specific tags make it
2963 /// into the result.
2964 #[test]
2965 fn parse_general_names_skips_universal_class_tags() {
2966 // GeneralNames SEQUENCE (0x30) containing:
2967 // [2] PRIMITIVE "a.com" — dNSName (keep)
2968 // UTF8String "skip" — Universal 0x0C (skip)
2969 // [6] PRIMITIVE "http://b.com" — URI (keep)
2970 //
2971 // Byte layout:
2972 // 30 1b SEQUENCE (27 bytes)
2973 // 82 05 ... [2] dNSName "a.com" (7 bytes)
2974 // 0c 04 ... UTF8String "skip" (6 bytes)
2975 // 86 0c ... [6] URI "http://b.com" (14 bytes)
2976 let gn_der: &[u8] = &[
2977 0x30, 0x1b, 0x82, 0x05, b'a', b'.', b'c', b'o', b'm', 0x0c, 0x04, b's', b'k', b'i',
2978 b'p', 0x86, 0x0c, b'h', b't', b't', b'p', b':', b'/', b'/', b'b', b'.', b'c', b'o',
2979 b'm',
2980 ];
2981 let result = parse_general_names(gn_der);
2982 assert_eq!(
2983 result.len(),
2984 2,
2985 "Universal tag must be skipped; got {result:?}"
2986 );
2987 assert_eq!(result[0], (2, b"a.com".to_vec()));
2988 assert_eq!(result[1], (6, b"http://b.com".to_vec()));
2989 }
2990
2991 // ── encode_general_names ──────────────────────────────────────────────────
2992
2993 /// `encode_general_names` must round-trip an otherName entry.
2994 ///
2995 /// Wire layout of the input SEQUENCE OF GeneralName:
2996 ///
2997 /// 30 10 SEQUENCE (16 bytes)
2998 /// A0 0E [0] CONSTRUCTED — otherName
2999 /// 06 02 2A 03 OID 1.2.3 (type-id)
3000 /// A0 08 [0] EXPLICIT (value)
3001 /// 0C 06 ... UTF8String "foobar"
3002 ///
3003 /// parse_general_names returns tag=0, content=the 14 bytes inside A0 0E.
3004 /// encode_general_names must reconstruct the identical 18-byte sequence.
3005 #[test]
3006 fn encode_general_names_roundtrip_othername() {
3007 // Build the input: SEQUENCE { [0] CONSTRUCTED { OID 1.2.3, [0] UTF8String "foobar" } }
3008 // OID 1.2.3 value bytes: 2A 03 (1*40+2=42=0x2A, then 3)
3009 // UTF8String "foobar": 0C 06 66 6F 6F 62 61 72
3010 // [0] EXPLICIT value: A0 08 0C 06 66 6F 6F 62 61 72
3011 // OtherName body (inside A0 0E): 06 02 2A 03 A0 08 0C 06 66 6F 6F 62 61 72 (14 bytes)
3012 // GeneralName [0]: A0 0E <14 bytes> (16 bytes)
3013 // SEQUENCE OF: 30 10 <16 bytes> (18 bytes)
3014 #[rustfmt::skip]
3015 let gn_seq: &[u8] = &[
3016 0x30, 0x10, // SEQUENCE (16)
3017 0xA0, 0x0E, // [0] CONSTRUCTED (14)
3018 0x06, 0x02, 0x2A, 0x03, // OID 1.2.3
3019 0xA0, 0x08, // [0] EXPLICIT
3020 0x0C, 0x06, // UTF8String (6)
3021 b'f', b'o', b'o', b'b', b'a', b'r',
3022 ];
3023
3024 let parsed = parse_general_names(gn_seq);
3025 assert_eq!(parsed.len(), 1);
3026 assert_eq!(parsed[0].0, general_name::OTHER_NAME);
3027
3028 let entries: Vec<(u32, &[u8])> = parsed.iter().map(|(t, c)| (*t, c.as_slice())).collect();
3029 let re_encoded = encode_general_names(&entries).expect("encode_general_names failed");
3030 assert_eq!(
3031 re_encoded, gn_seq,
3032 "OtherName roundtrip mismatch:\n got: {re_encoded:02X?}\n expected: {gn_seq:02X?}"
3033 );
3034 }
3035
3036 /// `encode_general_names` must round-trip an ediPartyName entry.
3037 ///
3038 /// 30 0C SEQUENCE (12 bytes)
3039 /// A5 0A [5] CONSTRUCTED — ediPartyName
3040 /// 81 08 ... [1] IMPLICIT UTF8String "testname" (partyName)
3041 #[test]
3042 fn encode_general_names_roundtrip_edipartyname() {
3043 #[rustfmt::skip]
3044 let gn_seq: &[u8] = &[
3045 0x30, 0x0C, // SEQUENCE (12)
3046 0xA5, 0x0A, // [5] CONSTRUCTED (10)
3047 0x81, 0x08, // [1] IMPLICIT (partyName, 8 bytes)
3048 b't', b'e', b's', b't', b'n', b'a', b'm', b'e',
3049 ];
3050
3051 let parsed = parse_general_names(gn_seq);
3052 assert_eq!(parsed.len(), 1);
3053 assert_eq!(parsed[0].0, general_name::EDI_PARTY_NAME);
3054
3055 let entries: Vec<(u32, &[u8])> = parsed.iter().map(|(t, c)| (*t, c.as_slice())).collect();
3056 let re_encoded = encode_general_names(&entries).expect("encode_general_names failed");
3057 assert_eq!(
3058 re_encoded, gn_seq,
3059 "EDIPartyName roundtrip mismatch:\n got: {re_encoded:02X?}\n expected: {gn_seq:02X?}"
3060 );
3061 }
3062
3063 /// `encode_general_names` must round-trip dNSName and iPAddress entries.
3064 #[test]
3065 fn encode_general_names_roundtrips_simple_types() {
3066 // GeneralNames: dNSName "x.y" (5 bytes TLV) + iPAddress 1.2.3.4 (6 bytes TLV)
3067 // Inner total = 11 = 0x0B.
3068 let gn_der: &[u8] = &[
3069 0x30, 0x0b, // SEQUENCE 11 bytes
3070 0x82, 0x03, b'x', b'.', b'y', // [2] dNSName "x.y"
3071 0x87, 0x04, 1, 2, 3, 4, // [7] iPAddress 1.2.3.4
3072 ];
3073 let tuples = parse_general_names(gn_der);
3074 assert_eq!(tuples.len(), 2);
3075 let encoded = encode_general_names(
3076 &tuples
3077 .iter()
3078 .map(|(t, v)| (*t, v.as_slice()))
3079 .collect::<Vec<_>>(),
3080 );
3081 assert_eq!(encoded.as_deref(), Some(gn_der));
3082 }
3083
3084 // ── format_extension_value — new formatters ───────────────────────────────
3085
3086 /// Build a raw `SEQUENCE OF Extension` byte vector containing a single
3087 /// Extension with the given OID content bytes and extnValue DER.
3088 ///
3089 /// Used to drive `decode_extensions` + `format_extension_value` without
3090 /// needing real certificate files.
3091 #[cfg(feature = "derive")]
3092 fn make_extensions_der(oid_content: &[u8], ext_value_der: &[u8]) -> Vec<u8> {
3093 assert!(oid_content.len() <= 127);
3094 assert!(ext_value_der.len() <= 127);
3095
3096 let oid_tlv: Vec<u8> = {
3097 let mut v = vec![0x06u8, oid_content.len() as u8];
3098 v.extend_from_slice(oid_content);
3099 v
3100 };
3101 let octet_tlv: Vec<u8> = {
3102 let mut v = vec![0x04u8, ext_value_der.len() as u8];
3103 v.extend_from_slice(ext_value_der);
3104 v
3105 };
3106 let ext_body: Vec<u8> = [oid_tlv.as_slice(), octet_tlv.as_slice()].concat();
3107 assert!(ext_body.len() <= 127);
3108 let ext_tlv: Vec<u8> = {
3109 let mut v = vec![0x30u8, ext_body.len() as u8];
3110 v.extend_from_slice(&ext_body);
3111 v
3112 };
3113 assert!(ext_tlv.len() <= 127);
3114 let mut out = vec![0x30u8, ext_tlv.len() as u8];
3115 out.extend_from_slice(&ext_tlv);
3116 out
3117 }
3118
3119 /// CRL Distribution Points (2.5.29.31) — single DistributionPoint with a
3120 /// fullName URI.
3121 ///
3122 /// DER for the extension value:
3123 /// 30 19 SEQUENCE OF DistributionPoint
3124 /// 30 17 DistributionPoint SEQUENCE
3125 /// A0 15 [0] CONSTRUCTED (distributionPoint / fullName)
3126 /// 86 13 [6] PRIMITIVE URI "http://crl.test/crl" (19 bytes)
3127 #[cfg(feature = "derive")]
3128 #[test]
3129 fn format_extension_value_crl_distribution_points() {
3130 let uri = b"http://crl.test/crl"; // 19 bytes
3131 let ext_val: Vec<u8> = {
3132 let mut v = vec![
3133 0x30,
3134 0x19, // SEQUENCE OF DistributionPoint
3135 0x30,
3136 0x17, // DistributionPoint
3137 0xA0,
3138 0x15, // [0] CONSTRUCTED
3139 0x86,
3140 uri.len() as u8,
3141 ];
3142 v.extend_from_slice(uri);
3143 v
3144 };
3145 // OID 2.5.29.31 content bytes
3146 let raw = make_extensions_der(&[0x55, 0x1D, 0x1F], &ext_val);
3147 let exts = decode_extensions(&raw);
3148 assert_eq!(exts.len(), 1);
3149 let result = format_extension_value(&exts[0]);
3150 assert_eq!(result.as_deref(), Some("URI:http://crl.test/crl"));
3151 }
3152
3153 /// Certificate Policies (2.5.29.32) — single PolicyInformation with OID
3154 /// 2.5.29.32.0 (anyPolicy).
3155 ///
3156 /// DER for the extension value:
3157 /// 30 08 SEQUENCE OF PolicyInformation
3158 /// 30 06 PolicyInformation
3159 /// 06 04 55 1D 20 00 OID 2.5.29.32.0
3160 #[cfg(feature = "derive")]
3161 #[test]
3162 fn format_extension_value_certificate_policies() {
3163 // OID 2.5.29.32.0 (anyPolicy): 55 1D 20 00
3164 let ext_val: &[u8] = &[
3165 0x30, 0x08, // SEQUENCE OF PolicyInformation
3166 0x30, 0x06, // PolicyInformation
3167 0x06, 0x04, 0x55, 0x1D, 0x20, 0x00, // OID 2.5.29.32.0
3168 ];
3169 // OID 2.5.29.32 content bytes
3170 let raw = make_extensions_der(&[0x55, 0x1D, 0x20], ext_val);
3171 let exts = decode_extensions(&raw);
3172 assert_eq!(exts.len(), 1);
3173 let result = format_extension_value(&exts[0]);
3174 assert_eq!(result.as_deref(), Some("Policy: 2.5.29.32.0"));
3175 }
3176
3177 /// Authority Information Access (1.3.6.1.5.5.7.1.1) — single OCSP
3178 /// AccessDescription with URI "http://ocsp.test".
3179 ///
3180 /// DER for the extension value:
3181 /// 30 1E SEQUENCE OF AccessDescription
3182 /// 30 1C AccessDescription
3183 /// 06 08 2B... OID 1.3.6.1.5.5.7.48.1 (id-ad-ocsp)
3184 /// 86 10 [6] PRIMITIVE URI "http://ocsp.test" (16 bytes)
3185 #[cfg(feature = "derive")]
3186 #[test]
3187 fn format_extension_value_authority_info_access() {
3188 let uri = b"http://ocsp.test"; // 16 bytes
3189 let ext_val: Vec<u8> = {
3190 let mut v = vec![
3191 0x30,
3192 0x1E, // SEQUENCE OF AccessDescription
3193 0x30,
3194 0x1C, // AccessDescription
3195 // OID 1.3.6.1.5.5.7.48.1 (id-ad-ocsp)
3196 0x06,
3197 0x08,
3198 0x2B,
3199 0x06,
3200 0x01,
3201 0x05,
3202 0x05,
3203 0x07,
3204 0x30,
3205 0x01,
3206 0x86,
3207 uri.len() as u8,
3208 ];
3209 v.extend_from_slice(uri);
3210 v
3211 };
3212 // OID 1.3.6.1.5.5.7.1.1 (id-pe-authorityInfoAccess) content bytes
3213 let raw = make_extensions_der(&[0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01], &ext_val);
3214 let exts = decode_extensions(&raw);
3215 assert_eq!(exts.len(), 1);
3216 let result = format_extension_value(&exts[0]);
3217 assert_eq!(result.as_deref(), Some("OCSP - URI:http://ocsp.test"));
3218 }
3219}