structured_pkix/algorithm.rs
1//! Which algorithm, and the parameters that go with it.
2//!
3//! The identifier is kept as it was encoded and is not interpreted here. What
4//! may be verified with a key, and how, belongs to the algorithm registry; this
5//! layer's job is to hand that registry exactly what the certificate said,
6//! including parameters it does not understand.
7
8use crate::error::{Error, ErrorCode, Located, Result};
9use structured_asn1::memory::Memory;
10use structured_asn1::{Element, Oid, Reader, Tag, universal};
11
12/// An algorithm and its parameters, as the certificate wrote them.
13#[derive(Debug, Clone, Copy)]
14pub struct AlgorithmIdentifier<'a> {
15 algorithm: Oid<'a>,
16 parameters: Option<&'a [u8]>,
17 der: &'a [u8],
18 offset: u64,
19 field: &'static str,
20}
21
22impl<'a> AlgorithmIdentifier<'a> {
23 /// Which algorithm.
24 #[must_use]
25 pub fn algorithm(self) -> Oid<'a> {
26 self.algorithm
27 }
28
29 /// The parameters, as the whole encoded element, or `None` where the
30 /// certificate wrote none.
31 ///
32 /// Kept encoded because what they mean depends on the algorithm: the same
33 /// bytes are a curve for one and a hash function and salt length for
34 /// another.
35 #[must_use]
36 pub fn parameters(self) -> Option<&'a [u8]> {
37 self.parameters
38 }
39
40 /// The whole identifier as it was encoded.
41 #[must_use]
42 pub fn as_bytes(self) -> &'a [u8] {
43 self.der
44 }
45
46 /// Whether the parameters are absent, or the null that means the same
47 /// thing for the algorithms that spell it that way.
48 #[must_use]
49 pub fn has_no_parameters(self) -> bool {
50 // A null is two bytes: the tag and a zero length.
51 self.parameters
52 .is_none_or(|parameters| parameters == [0x05, 0x00])
53 }
54
55 /// Whether two identifiers name the same algorithm with the same
56 /// parameters.
57 ///
58 /// An absent parameter and an explicit null are treated as the same, and
59 /// nothing else is. Issuers disagree about which of the two to write for
60 /// the algorithms that take no parameters, and both say the same thing;
61 /// any other difference changes what is being verified.
62 ///
63 /// The equivalence is deliberately not algorithm-specific. Which
64 /// parameter encodings an algorithm permits at all, including that the
65 /// Edwards-curve identifiers forbid the null form, is the algorithm
66 /// registry's decision, made against the identifier inside the signed
67 /// bytes. This comparison only asks whether the unsigned outer identifier
68 /// agrees with that signed one, and absent versus null cannot change
69 /// which algorithm that is: an outer null beside a signed absent is a
70 /// malformed spelling of the same identity, not a different algorithm
71 /// escaping the mismatch check.
72 ///
73 /// Deferred comparisons charge the supplied current operation. Their
74 /// resource diagnostics identify `self`'s original field and offset; they
75 /// do not assert that the two identifiers share a source. Parser-local
76 /// limits apply only when comparing through the producing reader.
77 ///
78 /// # Errors
79 /// Returns a resource refusal before an unfunded byte comparison.
80 ///
81 /// # Examples
82 /// ```
83 /// # extern crate alloc;
84 /// # use alloc::sync::Arc;
85 /// # use structured_asn1::{Parser, Tag, universal};
86 /// # use structured_asn1::memory::{Memory, MemoryAccount};
87 /// # let execution = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
88 /// # let parser = Parser::der(execution.clone());
89 /// # let mut reader = parser.read(b"\x30\x04\x06\x02\x2a\x03", 0).unwrap();
90 /// # let element = reader.expect(Tag::constructed(universal::SEQUENCE)).unwrap();
91 /// let identifier = structured_pkix::algorithm::read(&reader, &element, "algorithm")?;
92 /// assert!(identifier.matches(&identifier, &execution)?);
93 /// # Ok::<(), structured_pkix::Error>(())
94 /// ```
95 pub fn matches(self, other: &AlgorithmIdentifier<'_>, execution: &Memory) -> Result<bool> {
96 self.matches_with(other, |work| {
97 execution
98 .charge_work(work)
99 .map_err(|reason| Error::resource(reason, self.field, self.offset))
100 })
101 }
102
103 pub(crate) fn matches_in(
104 self,
105 other: &AlgorithmIdentifier<'_>,
106 parent: &Reader<'_>,
107 ) -> Result<bool> {
108 self.matches_with(other, |work| {
109 parent.charge(work).map_err(|error| {
110 Error::new(ErrorCode::Encoding(error.code()), self.field, self.offset)
111 })
112 })
113 }
114
115 fn matches_with(
116 self,
117 other: &AlgorithmIdentifier<'_>,
118 mut charge: impl FnMut(u64) -> Result<()>,
119 ) -> Result<bool> {
120 charge(1)?;
121 if self.algorithm.as_bytes().len() != other.algorithm.as_bytes().len() {
122 return Ok(false);
123 }
124 charge(self.algorithm.as_bytes().len() as u64)?;
125 if self.algorithm != other.algorithm {
126 return Ok(false);
127 }
128 if self.has_no_parameters() && other.has_no_parameters() {
129 return Ok(true);
130 }
131 let (Some(left), Some(right)) = (self.parameters, other.parameters) else {
132 return Ok(false);
133 };
134 if left.len() != right.len() {
135 return Ok(false);
136 }
137 charge(left.len() as u64)?;
138 Ok(left == right)
139 }
140}
141
142/// Reads an algorithm identifier from an element a schema said is one.
143///
144/// It is public because certificates are not the only place this structure
145/// appears: a signed message names a digest and a signature algorithm the same
146/// way, and reading them a second time somewhere else would be a second set of
147/// rules about what parameters are allowed to look like.
148///
149/// # Errors
150///
151/// Reports the encoding fault and the field it was in.
152pub fn read<'a>(
153 parent: &Reader<'a>,
154 element: &Element<'a>,
155 field: &'static str,
156) -> Result<AlgorithmIdentifier<'a>> {
157 let fail = |code| Error::new(code, field, element.span().start);
158
159 if element.tag() != Tag::constructed(universal::SEQUENCE) {
160 return Err(fail(ErrorCode::Encoding(
161 structured_asn1::ErrorCode::UnexpectedTag,
162 )));
163 }
164
165 let mut inside = parent.enter(element).field(field)?;
166 let algorithm = inside.read_oid().field(field)?;
167 let parameter = inside.next_element().field(field)?;
168
169 // What the parameter says is the registry's business: a curve, a hash,
170 // the salt length of a padding scheme. It is handed the bytes on the
171 // promise that they are the encoding the document claims, so the whole
172 // value is held to that and not only its outer header.
173 if let Some(parameter) = ¶meter {
174 crate::name::walk_der(&inside, parameter, field, crate::name::Strings::Checked)?;
175 }
176 let parameters = parameter.map(Element::as_bytes);
177
178 // The schema has room for one parameter and no more, and a second would
179 // leave which of them the algorithm takes with no answer.
180 if !inside.is_empty() {
181 return Err(fail(ErrorCode::TrailingData));
182 }
183
184 Ok(AlgorithmIdentifier {
185 algorithm,
186 parameters,
187 der: element.as_bytes(),
188 offset: element.span().start,
189 field,
190 })
191}
192
193#[cfg(test)]
194mod tests;