structured_pkix/ocsp/
delegation.rs1use crate::error::{Located, Result};
4use crate::extension::{Extension, Recognizer};
5use crate::path::{
6 self, Anchor, BuildResult, EffectiveAnchor, Validation, ValidationContext, Verify, Watch,
7 WorkingKey,
8};
9use crate::{Certificate, oid};
10use structured_asn1::memory::Work;
11use structured_asn1::{Tag, universal};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ResponderRevocation {
16 NoCheck,
18 Unresolved,
20}
21
22pub(crate) fn read_no_check<W: Work>(extension: &Extension<'_>, execution: &W) -> Result<()> {
23 const FIELD: &str = "extensions.ocspNoCheck";
24 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
25 let element = reader
26 .expect(Tag::primitive(universal::NULL))
27 .field(FIELD)?;
28 reader.null(&element).field(FIELD)?;
29 super::end(&reader, FIELD)
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum DelegationFailure {
37 IssuerKeyBinding,
39 SigningPurpose,
41 KeyUsage,
43}
44
45#[derive(Debug, Clone, Copy)]
48pub struct DelegateFacts<'a> {
49 responder: &'a Certificate<'a>,
50 issuer: &'a Certificate<'a>,
51 issuing_key: WorkingKey<'a>,
52 key: WorkingKey<'a>,
53 revocation: ResponderRevocation,
54}
55
56impl<'a> DelegateFacts<'a> {
57 #[must_use]
59 pub fn responder(self) -> &'a Certificate<'a> {
60 self.responder
61 }
62 #[must_use]
64 pub fn issuer(self) -> &'a Certificate<'a> {
65 self.issuer
66 }
67 #[must_use]
71 pub fn issuing_key(self) -> WorkingKey<'a> {
72 self.issuing_key
73 }
74 #[must_use]
76 pub fn key(self) -> WorkingKey<'a> {
77 self.key
78 }
79 #[must_use]
81 pub fn revocation(self) -> ResponderRevocation {
82 self.revocation
83 }
84}
85
86#[derive(Debug)]
88#[expect(
89 clippy::large_enum_variant,
90 reason = "fixed-size local facts retain both original issuer and responder parameter sources; avoid boxing solely for enum layout"
91)]
92pub enum Delegation<'a> {
93 Accepted(DelegateFacts<'a>),
95 Rejected(DelegationFailure),
97 PathRejected(path::Rejection<'a>),
99 Incomplete(path::Truncation),
101}
102
103pub fn check_delegate<'a, V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work>(
114 responder: &'a Certificate<'a>,
115 issuer: &'a Certificate<'a>,
116 issuing_key: WorkingKey<'a>,
117 context: &ValidationContext<'a, '_, V, W, R, E>,
118) -> BuildResult<Delegation<'a>, V::Error, R::Error> {
119 let execution = context.execution;
120 let Some(anchor) =
121 EffectiveAnchor::bind(Anchor::from_certificate(issuer), issuing_key, execution)?
122 else {
123 return Ok(Delegation::Rejected(DelegationFailure::IssuerKeyBinding));
124 };
125 let extensions = responder.tbs().extensions();
126 let Some(purposes) = extensions.extended_key_usage(execution)? else {
127 return Ok(Delegation::Rejected(DelegationFailure::SigningPurpose));
128 };
129 if !purposes.has(oid::OCSP_SIGNING, execution)? {
130 return Ok(Delegation::Rejected(DelegationFailure::SigningPurpose));
131 }
132 if !super::authority::signing_usage(responder, execution)? {
133 return Ok(Delegation::Rejected(DelegationFailure::KeyUsage));
134 }
135 let revocation = match extensions.get(oid::OCSP_NO_CHECK, execution)? {
136 Some(extension) => {
137 read_no_check(extension, execution)?;
138 ResponderRevocation::NoCheck
139 }
140 None => ResponderRevocation::Unresolved,
141 };
142 Ok(
143 match path::validate_effective(&[responder], anchor, context)? {
144 Validation::Accepted(facts) => Delegation::Accepted(DelegateFacts {
145 responder,
146 issuer,
147 issuing_key,
148 key: facts.key,
149 revocation,
150 }),
151 Validation::Rejected(reason) => Delegation::PathRejected(reason),
152 Validation::Incomplete(reason) => Delegation::Incomplete(reason),
153 },
154 )
155}
156
157#[cfg(test)]
158mod tests;