Skip to main content

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}