Skip to main content

uselesskey_core_x509_negative/
lib.rs

1#![forbid(unsafe_code)]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4//! X.509 negative-fixture policy helpers.
5//!
6//! # Examples
7//!
8//! Apply a negative policy to a base X.509 spec:
9//!
10//! ```
11//! use uselesskey_core_x509_negative::X509Negative;
12//! use uselesskey_core_x509_spec::{NotBeforeOffset, X509Spec};
13//!
14//! let base = X509Spec::self_signed("test.example.com");
15//! let expired = X509Negative::Expired.apply_to_spec(&base);
16//! assert_eq!(expired.not_before_offset, NotBeforeOffset::DaysAgo(395));
17//! ```
18
19use uselesskey_core_x509_spec::{KeyUsage, NotBeforeOffset, X509Spec};
20
21pub use uselesskey_core_x509_chain_negative::ChainNegative;
22
23/// Types of invalid X.509 certificates for negative testing.
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
25pub enum X509Negative {
26    /// Certificate with not_after in the past (expired).
27    Expired,
28    /// Certificate with not_before in the future (not yet valid).
29    NotYetValid,
30    /// Certificate marked as CA but without proper key usage flags.
31    WrongKeyUsage,
32    /// Self-signed certificate that claims to be a CA but has conflicting extensions.
33    SelfSignedButClaimsCA,
34}
35
36impl X509Negative {
37    /// Modify a spec to produce the negative fixture variant.
38    pub fn apply_to_spec(&self, base_spec: &X509Spec) -> X509Spec {
39        let mut spec = base_spec.clone();
40
41        match self {
42            X509Negative::Expired => {
43                // Certificate expired 30 days ago.
44                // not_before was 395 days ago, valid for 365 days = expired 30 days ago.
45                spec.not_before_offset = NotBeforeOffset::DaysAgo(395);
46                spec.validity_days = 365;
47            }
48            X509Negative::NotYetValid => {
49                // Certificate valid starting 30 days from now.
50                spec.not_before_offset = NotBeforeOffset::DaysFromNow(30);
51                spec.validity_days = 365;
52            }
53            X509Negative::WrongKeyUsage => {
54                // Marked as CA but without key_cert_sign.
55                spec.is_ca = true;
56                spec.key_usage = KeyUsage {
57                    key_cert_sign: false, // Wrong! CA should have this.
58                    crl_sign: false,
59                    digital_signature: true,
60                    key_encipherment: true,
61                };
62            }
63            X509Negative::SelfSignedButClaimsCA => {
64                // Self-signed but claims to be CA with no real chain context.
65                spec.is_ca = true;
66                spec.key_usage = KeyUsage::ca();
67            }
68        }
69
70        spec
71    }
72
73    /// Human-readable description of this negative fixture.
74    pub fn description(&self) -> &'static str {
75        match self {
76            X509Negative::Expired => "Certificate with not_after in the past (expired)",
77            X509Negative::NotYetValid => {
78                "Certificate with not_before in the future (not yet valid)"
79            }
80            X509Negative::WrongKeyUsage => {
81                "Certificate marked as CA but without keyCertSign key usage"
82            }
83            X509Negative::SelfSignedButClaimsCA => "Self-signed certificate that claims to be a CA",
84        }
85    }
86
87    /// Variant name for cache keys.
88    pub fn variant_name(&self) -> &'static str {
89        match self {
90            X509Negative::Expired => "expired",
91            X509Negative::NotYetValid => "not_yet_valid",
92            X509Negative::WrongKeyUsage => "wrong_key_usage",
93            X509Negative::SelfSignedButClaimsCA => "self_signed_ca",
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn x509_negative_expired_exact_values() {
104        let base = X509Spec::self_signed("test");
105        let modified = X509Negative::Expired.apply_to_spec(&base);
106
107        assert_eq!(modified.not_before_offset, NotBeforeOffset::DaysAgo(395));
108        assert_eq!(modified.validity_days, 365);
109        assert!(!modified.is_ca);
110        assert_eq!(modified.key_usage, KeyUsage::leaf());
111    }
112
113    #[test]
114    fn x509_negative_not_yet_valid_exact_values() {
115        let base = X509Spec::self_signed("test");
116        let modified = X509Negative::NotYetValid.apply_to_spec(&base);
117
118        assert_eq!(modified.not_before_offset, NotBeforeOffset::DaysFromNow(30));
119        assert_eq!(modified.validity_days, 365);
120    }
121
122    #[test]
123    fn x509_negative_wrong_key_usage_exact_values() {
124        let base = X509Spec::self_signed("test");
125        let modified = X509Negative::WrongKeyUsage.apply_to_spec(&base);
126
127        assert!(modified.is_ca);
128        assert_eq!(
129            modified.key_usage,
130            KeyUsage {
131                key_cert_sign: false,
132                crl_sign: false,
133                digital_signature: true,
134                key_encipherment: true,
135            }
136        );
137    }
138
139    #[test]
140    fn x509_negative_self_signed_ca_exact_values() {
141        let base = X509Spec::self_signed("test");
142        let modified = X509Negative::SelfSignedButClaimsCA.apply_to_spec(&base);
143
144        assert!(modified.is_ca);
145        assert_eq!(modified.key_usage, KeyUsage::ca());
146    }
147
148    #[test]
149    fn x509_negative_variant_names_are_stable() {
150        assert_eq!(X509Negative::Expired.variant_name(), "expired");
151        assert_eq!(X509Negative::NotYetValid.variant_name(), "not_yet_valid");
152        assert_eq!(
153            X509Negative::WrongKeyUsage.variant_name(),
154            "wrong_key_usage"
155        );
156        assert_eq!(
157            X509Negative::SelfSignedButClaimsCA.variant_name(),
158            "self_signed_ca"
159        );
160    }
161
162    #[test]
163    fn x509_negative_descriptions_are_stable() {
164        assert_eq!(
165            X509Negative::Expired.description(),
166            "Certificate with not_after in the past (expired)"
167        );
168        assert_eq!(
169            X509Negative::NotYetValid.description(),
170            "Certificate with not_before in the future (not yet valid)"
171        );
172        assert_eq!(
173            X509Negative::WrongKeyUsage.description(),
174            "Certificate marked as CA but without keyCertSign key usage"
175        );
176        assert_eq!(
177            X509Negative::SelfSignedButClaimsCA.description(),
178            "Self-signed certificate that claims to be a CA"
179        );
180    }
181}