Skip to main content

uselesskey_core_x509_chain_negative/
lib.rs

1//! X.509 chain-level negative-fixture policy helpers.
2//!
3//! Defines [`ChainNegative`] variants (hostname mismatch, unknown CA,
4//! expired leaf/intermediate, revoked leaf) and provides
5//! [`ChainNegative::apply_to_spec`] to derive a modified [`ChainSpec`]
6//! for each scenario. Used by `uselesskey-x509` to produce invalid
7//! certificate chains for TLS error-handling tests.
8
9#![forbid(unsafe_code)]
10#![warn(missing_docs)]
11#![cfg_attr(not(feature = "std"), no_std)]
12
13extern crate alloc;
14
15use alloc::string::{String, ToString};
16use uselesskey_core_x509_spec::{ChainSpec, KeyUsage, NotBeforeOffset};
17
18/// Types of invalid certificate chains for negative testing.
19#[derive(Clone, Debug, Eq, PartialEq, Hash)]
20pub enum ChainNegative {
21    /// Leaf cert has a SAN that doesn't match the expected hostname.
22    HostnameMismatch {
23        /// The wrong hostname to put in the leaf SAN.
24        wrong_hostname: String,
25    },
26    /// Chain is anchored to a different (unknown) root certificate identity.
27    /// This variant intentionally reuses the same underlying RSA key material
28    /// and changes certificate-level identity fields for the root certificate.
29    UnknownCa,
30    /// Leaf certificate is expired.
31    ExpiredLeaf,
32    /// Leaf certificate is not yet valid.
33    NotYetValidLeaf,
34    /// Intermediate certificate is expired.
35    ExpiredIntermediate,
36    /// Intermediate certificate is not yet valid.
37    NotYetValidIntermediate,
38    /// Intermediate certificate no longer claims CA status.
39    IntermediateNotCa,
40    /// Intermediate certificate claims CA status but lacks CA signing usage.
41    IntermediateWrongKeyUsage,
42    /// Leaf certificate is listed as revoked in a CRL signed by the intermediate CA.
43    RevokedLeaf,
44}
45
46impl ChainNegative {
47    /// Variant name for cache keys.
48    pub fn variant_name(&self) -> String {
49        match self {
50            ChainNegative::HostnameMismatch { wrong_hostname } => {
51                format!("hostname_mismatch:{wrong_hostname}")
52            }
53            ChainNegative::UnknownCa => "unknown_ca".to_string(),
54            ChainNegative::ExpiredLeaf => "expired_leaf".to_string(),
55            ChainNegative::NotYetValidLeaf => "not_yet_valid_leaf".to_string(),
56            ChainNegative::ExpiredIntermediate => "expired_intermediate".to_string(),
57            ChainNegative::NotYetValidIntermediate => "not_yet_valid_intermediate".to_string(),
58            ChainNegative::IntermediateNotCa => "intermediate_not_ca".to_string(),
59            ChainNegative::IntermediateWrongKeyUsage => "intermediate_wrong_key_usage".to_string(),
60            ChainNegative::RevokedLeaf => "revoked_leaf".to_string(),
61        }
62    }
63
64    /// Apply this negative variant to a chain spec.
65    pub fn apply_to_spec(&self, base_spec: &ChainSpec) -> ChainSpec {
66        let mut spec = base_spec.clone();
67        match self {
68            ChainNegative::HostnameMismatch { wrong_hostname } => {
69                spec.leaf_cn = wrong_hostname.clone();
70                spec.leaf_sans = vec![wrong_hostname.clone()];
71            }
72            ChainNegative::UnknownCa => {
73                // Use a different root CA CN so the chain anchors to a different root.
74                spec.root_cn = format!("{} Unknown Root CA", spec.leaf_cn);
75            }
76            ChainNegative::ExpiredLeaf => {
77                // Push not_before 730 days into the past with 1-day validity,
78                // so not_after = base_time - 729 days - unambiguously expired.
79                spec.leaf_validity_days = 1;
80                spec.leaf_not_before = Some(NotBeforeOffset::DaysAgo(730));
81            }
82            ChainNegative::NotYetValidLeaf => {
83                spec.leaf_not_before = Some(NotBeforeOffset::DaysFromNow(730));
84            }
85            ChainNegative::ExpiredIntermediate => {
86                spec.intermediate_validity_days = 1;
87                spec.intermediate_not_before = Some(NotBeforeOffset::DaysAgo(730));
88            }
89            ChainNegative::NotYetValidIntermediate => {
90                spec.intermediate_not_before = Some(NotBeforeOffset::DaysFromNow(730));
91            }
92            ChainNegative::IntermediateNotCa => {
93                spec.intermediate_is_ca = Some(false);
94            }
95            ChainNegative::IntermediateWrongKeyUsage => {
96                spec.intermediate_is_ca = Some(true);
97                spec.intermediate_key_usage = Some(KeyUsage {
98                    key_cert_sign: false,
99                    crl_sign: false,
100                    digital_signature: true,
101                    key_encipherment: false,
102                });
103            }
104            ChainNegative::RevokedLeaf => {
105                // No spec changes needed. The chain is structurally valid;
106                // the CRL listing the leaf as revoked is generated as a side-effect
107                // by the X.509 fixture producer for this variant.
108            }
109        }
110        spec
111    }
112}