1use crate::binary::{self, BinaryXml, Location};
4use crate::binding::Bound;
5use crate::container::DS;
6use crate::properties::{Error, PropertyResult};
7use crate::xml::{Access, AccessError, Name, Snapshot, Xml};
8use structured_asn1::memory::{MemoryError, OwnedVec, Work};
9use structured_pkix::Certificate;
10
11#[derive(Debug)]
13pub struct Header<N, D, G> {
14 snapshot: Snapshot<D, G>,
15 signature: N,
16 key_info: N,
17 x509_data: N,
18 method: N,
19 certificates: OwnedVec<N>,
20}
21
22impl<N: Copy, D: Copy, G: Copy> Header<N, D, G> {
23 #[must_use]
25 pub fn snapshot(&self) -> Snapshot<D, G> {
26 self.snapshot
27 }
28 #[must_use]
30 pub fn signature(&self) -> N {
31 self.signature
32 }
33 #[must_use]
35 pub fn key_info(&self) -> N {
36 self.key_info
37 }
38 #[must_use]
40 pub fn x509_data(&self) -> N {
41 self.x509_data
42 }
43 #[must_use]
45 pub fn method(&self) -> N {
46 self.method
47 }
48 #[must_use]
50 pub fn certificates(&self) -> &[N] {
51 &self.certificates
52 }
53}
54
55pub type Reading<X> = Header<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
57
58#[derive(Clone, Copy, PartialEq, Eq)]
60pub(crate) enum ValueKind {
61 Certificate,
62 Crl,
63}
64impl ValueKind {
65 pub(crate) fn local(self) -> &'static str {
66 match self {
67 Self::Certificate => "X509Certificate",
68 Self::Crl => "X509CRL",
69 }
70 }
71}
72
73type ValueVisitResult<X, E> =
76 Result<(), crate::reference::VisitError<Error<<X as Xml>::Error, <X as Xml>::Id>, E>>;
77
78pub(crate) fn visit_values<X: Xml, W: Work, VE>(
79 access: &Access<'_, X, W>,
80 data: X::Id,
81 mut visitor: impl FnMut(X::Id, ValueKind) -> Result<(), VE>,
82) -> ValueVisitResult<X, VE> {
83 use crate::reference::VisitError;
84 for child in access.children(data) {
85 let node = child.map_err(|cause| VisitError::Processing(Error::Child(cause)))?;
86 for kind in [ValueKind::Certificate, ValueKind::Crl] {
87 if access
88 .has_name(
89 node,
90 Name {
91 namespace: DS,
92 local: kind.local(),
93 },
94 )
95 .map_err(|cause| VisitError::Processing(Error::Access(cause)))?
96 {
97 visitor(node, kind).map_err(VisitError::Visitor)?;
98 break;
99 }
100 }
101 }
102 Ok(())
103}
104
105pub fn read<X: Xml, W: Work>(
113 access: &Access<'_, X, W>,
114 bound: &Bound<X::Id, X::Document, X::Generation>,
115) -> PropertyResult<Reading<X>, X> {
116 let layout = bound.containers();
117 if access.snapshot() != layout.snapshot() {
118 return Err(Error::Access(AccessError::Changed {
119 node: layout.signature(),
120 }));
121 }
122 let signature = layout.signature();
123 let key_info = required(access, signature, "KeyInfo")?;
124 let x509_data = required(access, key_info, "X509Data")?;
125 let signed_info = required(access, signature, "SignedInfo")?;
126 let method = required(access, signed_info, "CanonicalizationMethod")?;
127 let allowed = access
128 .attribute(
129 method,
130 Name {
131 namespace: "",
132 local: "Algorithm",
133 },
134 |value| {
135 let Some(value) = value else {
136 return Ok(false);
137 };
138 let value = access.trim_xml_whitespace(value)?;
139 for algorithm in [
140 "http://www.w3.org/2006/12/xml-c14n11",
141 "http://www.w3.org/2001/10/xml-exc-c14n#",
142 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
143 "http://www.w3.org/2006/12/xml-c14n11#WithComments",
144 "http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
145 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments",
146 ] {
147 if access.equal(value, algorithm)? {
148 return Ok(true);
149 }
150 }
151 Ok(false)
152 },
153 )
154 .map_err(Error::Access)?
155 .map_err(|cause| Error::Resource {
156 node: method,
157 cause,
158 })?;
159 if !allowed {
160 return Err(Error::Unexpected { node: method });
161 }
162 let mut certificates = access
163 .query(x509_data, |_, _, work| Ok(work.vector()))
164 .map_err(Error::Access)?;
165 visit_values(access, x509_data, |node, kind| {
166 if kind == ValueKind::Certificate {
167 certificates
168 .try_push(node)
169 .map_err(|cause| Error::Resource { node, cause })?;
170 }
171 Ok(())
172 })
173 .map_err(|cause| match cause {
174 crate::reference::VisitError::Processing(cause)
175 | crate::reference::VisitError::Visitor(cause) => cause,
176 })?;
177 if certificates.is_empty() {
178 return Err(Error::Missing {
179 parent: x509_data,
180 local: "X509Certificate",
181 });
182 }
183 Ok(Header {
184 snapshot: layout.snapshot(),
185 signature,
186 key_info,
187 x509_data,
188 method,
189 certificates: certificates.into_owned(),
190 })
191}
192
193fn required<X: Xml, W: Work>(
194 access: &Access<'_, X, W>,
195 parent: X::Id,
196 local: &'static str,
197) -> PropertyResult<X::Id, X> {
198 let mut found = None;
199 for child in access.children(parent) {
200 let node = child.map_err(Error::Child)?;
201 if access
202 .has_name(
203 node,
204 Name {
205 namespace: DS,
206 local,
207 },
208 )
209 .map_err(Error::Access)?
210 {
211 if found.is_some() {
212 return Err(Error::Duplicate { node });
213 }
214 found = Some(node);
215 }
216 }
217 found.ok_or(Error::Missing { parent, local })
218}
219
220#[derive(Debug)]
222pub struct InclusionError(pub MemoryError);
223
224pub type Inclusion<X> =
226 Option<Location<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>>;
227
228pub fn included<X: BinaryXml, W: Work>(
233 xml: &X,
234 header: &Reading<X>,
235 certificate: &Certificate<'_>,
236 execution: &W,
237) -> binary::BinaryResult<Inclusion<X>, X, InclusionError> {
238 let access = Access::new(xml, header.snapshot, execution);
239 for index in 0..header.certificates.len() {
240 let location = Location {
241 snapshot: header.snapshot,
242 node: header.key_info,
243 };
244 access
245 .query(header.key_info, |_, _, work| Ok(work.charge_work(1)))
246 .map_err(|cause| binary::Error::Access { location, cause })?
247 .map_err(|cause| binary::Error::Callback {
248 location,
249 cause: InclusionError(cause),
250 })?;
251 let location = Location {
252 snapshot: header.snapshot,
253 node: header.certificates[index],
254 };
255 let matches = binary::with_octets(xml, location, execution, |bytes| {
256 if bytes.len() != certificate.as_bytes().len() {
257 return Ok(false);
258 }
259 let length = u64::try_from(bytes.len())
260 .map_err(|_| InclusionError(MemoryError::WorkExceeded))?;
261 execution.charge_work(length).map_err(InclusionError)?;
262 Ok(bytes == certificate.as_bytes())
263 })?;
264 if matches {
265 return Ok(Some(location));
266 }
267 }
268 Ok(None)
269}