Skip to main content

x509_validator/
policy_builder.rs

1use crate::PolicyFailureReason;
2use crate::der_parser::Oid;
3use crate::policy::{PolicyEvaluationResult, ValidationPolicy};
4use crate::unverified_chain::UnverifiedCertificateChain;
5
6/// Combines two [`ValidationPolicy`] values so that both must be met for the combination to be met.
7/// Built by the [`policy!`] macro when composing a flat sequence of policies; can also be constructed
8/// directly for manual, non-macro composition.
9///
10/// [`policy!`]: crate::policy!
11pub struct Tuple2<A, B> {
12    first: A,
13    second: B,
14}
15
16impl<A, B> Tuple2<A, B> {
17    pub fn new(first: A, second: B) -> Self {
18        Self { first, second }
19    }
20}
21
22impl<A: ValidationPolicy, B: ValidationPolicy> ValidationPolicy for Tuple2<A, B> {
23    fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
24        let mut exts = self
25            .first
26            .verifying_critical_extensions();
27        exts.extend(
28            self.second
29                .verifying_critical_extensions(),
30        );
31        exts
32    }
33
34    fn chain_meets_policy_requirements(
35        &self,
36        chain: &UnverifiedCertificateChain<'_>,
37    ) -> PolicyEvaluationResult {
38        self.first
39            .chain_meets_policy_requirements(chain)?;
40        self.second
41            .chain_meets_policy_requirements(chain)
42    }
43}
44
45/// Chooses between two [`ValidationPolicy`] values at construction time; only the active variant is
46/// evaluated. Built by the [`policy!`] macro when composing an `if`/`else` block.
47///
48/// [`policy!`]: crate::policy!
49pub enum Either<A, B> {
50    First(A),
51    Second(B),
52}
53
54impl<A: ValidationPolicy, B: ValidationPolicy> ValidationPolicy for Either<A, B> {
55    fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
56        match self {
57            Self::First(a) => a.verifying_critical_extensions(),
58            Self::Second(b) => b.verifying_critical_extensions(),
59        }
60    }
61
62    fn chain_meets_policy_requirements(
63        &self,
64        chain: &UnverifiedCertificateChain<'_>,
65    ) -> PolicyEvaluationResult {
66        match self {
67            Self::First(a) => a.chain_meets_policy_requirements(chain),
68            Self::Second(b) => b.chain_meets_policy_requirements(chain),
69        }
70    }
71}
72
73/// Wraps an optional [`ValidationPolicy`]; a `None` policy always meets the requirements. Built by the
74/// [`policy!`] macro when composing a bare `if` block (no `else`).
75///
76/// [`policy!`]: crate::policy!
77pub struct WrappedOptional<P> {
78    wrapped: Option<P>,
79}
80
81impl<P> WrappedOptional<P> {
82    pub fn new(wrapped: Option<P>) -> Self {
83        Self { wrapped }
84    }
85}
86
87impl<P: ValidationPolicy> ValidationPolicy for WrappedOptional<P> {
88    fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
89        self.wrapped
90            .as_ref()
91            .map(|p| p.verifying_critical_extensions())
92            .unwrap_or_default()
93    }
94
95    fn chain_meets_policy_requirements(
96        &self,
97        chain: &UnverifiedCertificateChain<'_>,
98    ) -> PolicyEvaluationResult {
99        match &self.wrapped {
100            Some(p) => p.chain_meets_policy_requirements(chain),
101            None => Ok(()),
102        }
103    }
104}
105
106/// A DSL for constructing a [`ValidationPolicy`] out of other [`ValidationPolicy`] values, without type
107/// erasure.
108///
109/// A flat, semicolon-separated sequence of policy expressions builds an AND-chain (every listed policy
110/// must be met), using nested [`Tuple2`] values:
111///
112/// ```
113/// use x509_validator::policy;
114/// use x509_validator::rfc5280::{RFC5280Policy, VersionPolicy};
115/// # let validation_time: x509_validator::rfc5280::Timestamp = 0;
116///
117/// let built = policy! {
118///     RFC5280Policy::new(validation_time);
119///     VersionPolicy
120/// };
121/// ```
122///
123/// [`ValidationPolicy`]: crate::policy::ValidationPolicy
124#[macro_export]
125macro_rules! policy {
126    // `if`/`else`, followed by more items. Must come before the bare-`if` multi-item arm below,
127    // since `macro_rules!` tries arms top-to-bottom and a bare-`if` pattern would otherwise
128    // greedily match the `if (cond) { .. }` prefix of an `if`/`else` item and leave a dangling
129    // `else { .. }` in `$rest`, which then fails to parse recursively.
130    (if ($cond:expr) { $then:expr } else { $else_:expr }; $($rest:tt)+) => {
131        $crate::policy_builder::Tuple2::new(
132            if $cond {
133                $crate::policy_builder::Either::First($then)
134            } else {
135                $crate::policy_builder::Either::Second($else_)
136            },
137            $crate::policy!($($rest)+),
138        )
139    };
140    // `if`/`else`, sole item.
141    (if ($cond:expr) { $then:expr } else { $else_:expr }) => {
142        if $cond {
143            $crate::policy_builder::Either::First($then)
144        } else {
145            $crate::policy_builder::Either::Second($else_)
146        }
147    };
148    // Bare `if`, followed by more items.
149    (if ($cond:expr) { $body:expr }; $($rest:tt)+) => {
150        $crate::policy_builder::Tuple2::new(
151            $crate::policy_builder::WrappedOptional::new(if $cond { Some($body) } else { None }),
152            $crate::policy!($($rest)+),
153        )
154    };
155    // Bare `if`, sole item.
156    (if ($cond:expr) { $body:expr }) => {
157        $crate::policy_builder::WrappedOptional::new(if $cond { Some($body) } else { None })
158    };
159    // Plain expression, followed by more items.
160    ($first:expr; $($rest:tt)+) => {
161        $crate::policy_builder::Tuple2::new($first, $crate::policy!($($rest)+))
162    };
163    // Plain expression, sole item.
164    ($only:expr) => {
165        $only
166    };
167}
168
169/// Tries `first`; only if it fails, tries `second`. The overall extensions claimed
170/// are the intersection of both sub-policies' claims (a critical extension is only
171/// "handled" here if BOTH sub-policies would have handled it), deliberately
172/// asymmetric with [`Tuple2`], which unions its extensions. Intersection is required
173/// here because an extension is only safely ignorable if every alternative would
174/// have handled it.
175pub struct OneOfTuple2<A, B> {
176    first: A,
177    second: B,
178}
179
180impl<A, B> OneOfTuple2<A, B> {
181    pub fn new(first: A, second: B) -> Self {
182        Self { first, second }
183    }
184}
185
186impl<A: ValidationPolicy, B: ValidationPolicy> ValidationPolicy for OneOfTuple2<A, B> {
187    fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
188        let first = self
189            .first
190            .verifying_critical_extensions();
191        let second = self
192            .second
193            .verifying_critical_extensions();
194        first
195            .into_iter()
196            .filter(|oid| second.contains(oid))
197            .collect()
198    }
199
200    fn chain_meets_policy_requirements(
201        &self,
202        chain: &UnverifiedCertificateChain<'_>,
203    ) -> PolicyEvaluationResult {
204        match self
205            .first
206            .chain_meets_policy_requirements(chain)
207        {
208            Ok(()) => Ok(()),
209            Err(first_reason) => match self
210                .second
211                .chain_meets_policy_requirements(chain)
212            {
213                Ok(()) => Ok(()),
214                Err(second_reason) => Err(PolicyFailureReason::new(format!(
215                    "{first_reason} and {second_reason}"
216                ))),
217            },
218        }
219    }
220}
221
222/// Like [`WrappedOptional`], but a `None` policy FAILS instead of auto-passing:
223/// a disabled alternative inside a `one_of!` block should not count as "the one
224/// that succeeded."
225pub struct OneOfWrappedOptional<P> {
226    wrapped: Option<P>,
227}
228
229impl<P> OneOfWrappedOptional<P> {
230    pub fn new(wrapped: Option<P>) -> Self {
231        Self { wrapped }
232    }
233}
234
235impl<P: ValidationPolicy> ValidationPolicy for OneOfWrappedOptional<P> {
236    fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
237        self.wrapped
238            .as_ref()
239            .map(|p| p.verifying_critical_extensions())
240            .unwrap_or_default()
241    }
242
243    fn chain_meets_policy_requirements(
244        &self,
245        chain: &UnverifiedCertificateChain<'_>,
246    ) -> PolicyEvaluationResult {
247        match &self.wrapped {
248            Some(p) => p.chain_meets_policy_requirements(chain),
249            None => Err(PolicyFailureReason::new("alternative is disabled")),
250        }
251    }
252}
253
254/// A DSL for constructing a [`ValidationPolicy`] out of alternatives, without type erasure.
255///
256/// A flat, semicolon-separated sequence of policy expressions builds a try-each-until-one-succeeds
257/// chain (the first alternative that meets the requirements wins; if every alternative fails, the
258/// reported failure reason joins every alternative's reason), using nested [`OneOfTuple2`] values —
259/// this is the `one_of!` counterpart to [`policy!`]'s AND-chain.
260///
261/// ```
262/// use x509_validator::one_of;
263/// use x509_validator::rfc5280::{RFC5280Policy, VersionPolicy};
264/// # let validation_time: x509_validator::rfc5280::Timestamp = 0;
265///
266/// let built = one_of! {
267///     RFC5280Policy::new(validation_time);
268///     VersionPolicy
269/// };
270/// ```
271///
272/// [`ValidationPolicy`]: crate::policy::ValidationPolicy
273#[macro_export]
274macro_rules! one_of {
275    // `if`/`else`, followed by more items. Must come before the bare-`if` multi-item arm below,
276    // since `macro_rules!` tries arms top-to-bottom and a bare-`if` pattern would otherwise
277    // greedily match the `if (cond) { .. }` prefix of an `if`/`else` item and leave a dangling
278    // `else { .. }` in `$rest`, which then fails to parse recursively.
279    (if ($cond:expr) { $then:expr } else { $else_:expr }; $($rest:tt)+) => {
280        $crate::policy_builder::OneOfTuple2::new(
281            if $cond {
282                $crate::policy_builder::Either::First($then)
283            } else {
284                $crate::policy_builder::Either::Second($else_)
285            },
286            $crate::one_of!($($rest)+),
287        )
288    };
289    // `if`/`else`, sole item.
290    (if ($cond:expr) { $then:expr } else { $else_:expr }) => {
291        if $cond {
292            $crate::policy_builder::Either::First($then)
293        } else {
294            $crate::policy_builder::Either::Second($else_)
295        }
296    };
297    // Bare `if`, followed by more items.
298    (if ($cond:expr) { $body:expr }; $($rest:tt)+) => {
299        $crate::policy_builder::OneOfTuple2::new(
300            $crate::policy_builder::OneOfWrappedOptional::new(if $cond { Some($body) } else { None }),
301            $crate::one_of!($($rest)+),
302        )
303    };
304    // Bare `if`, sole item.
305    (if ($cond:expr) { $body:expr }) => {
306        $crate::policy_builder::OneOfWrappedOptional::new(if $cond { Some($body) } else { None })
307    };
308    // Plain expression, followed by more items.
309    ($first:expr; $($rest:tt)+) => {
310        $crate::policy_builder::OneOfTuple2::new($first, $crate::one_of!($($rest)+))
311    };
312    // Plain expression, sole item.
313    ($only:expr) => {
314        $only
315    };
316}