Skip to main content

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) = &parameter {
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;