structured_pkix/algorithm/digest.rs
1//! Which digest an ASN.1 `AlgorithmIdentifier` names, resolved once for OCSP,
2//! CMS and every other ASN.1 consumer.
3//!
4//! The identifier is matched exactly. Parameters are read under the encoding
5//! of the document they came from. SHA-1 and SHA-2 accept absent or primitive
6//! NULL parameters, including BER NULL lengths; SHA3 requires absence. Other
7//! parameters of a known identifier are malformed. An identifier this table
8//! does not know is unsupported. Execution
9//! then goes through the provider's [`Digest`] implementation.
10
11use super::AlgorithmIdentifier;
12use crate::error::{Error, Result};
13use crate::oid;
14use structured_asn1::Oid;
15use structured_asn1::digest::{Digest, Resolution};
16use structured_asn1::memory::Work;
17
18/// A digest algorithm named by a standard ASN.1 identifier.
19#[non_exhaustive]
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub enum Algorithm {
22 /// SHA-1, FIPS 180-4.
23 Sha1,
24 /// SHA-224, FIPS 180-4.
25 Sha224,
26 /// SHA-256, FIPS 180-4.
27 Sha256,
28 /// SHA-384, FIPS 180-4.
29 Sha384,
30 /// SHA-512, FIPS 180-4.
31 Sha512,
32 /// SHA-512/224, FIPS 180-4.
33 Sha512_224,
34 /// SHA-512/256, FIPS 180-4.
35 Sha512_256,
36 /// SHA3-224, FIPS 202.
37 Sha3_224,
38 /// SHA3-256, FIPS 202.
39 Sha3_256,
40 /// SHA3-384, FIPS 202.
41 Sha3_384,
42 /// SHA3-512, FIPS 202.
43 Sha3_512,
44}
45
46impl Algorithm {
47 /// Output width in octets.
48 #[must_use]
49 pub const fn width(self) -> usize {
50 match self {
51 Self::Sha1 => 20,
52 Self::Sha224 | Self::Sha512_224 | Self::Sha3_224 => 28,
53 Self::Sha256 | Self::Sha512_256 | Self::Sha3_256 => 32,
54 Self::Sha384 | Self::Sha3_384 => 48,
55 Self::Sha512 | Self::Sha3_512 => 64,
56 }
57 }
58
59 /// The identifier this algorithm is written under.
60 #[must_use]
61 pub const fn identifier(self) -> Oid<'static> {
62 match self {
63 Self::Sha1 => oid::SHA1,
64 Self::Sha224 => oid::SHA224,
65 Self::Sha256 => oid::SHA256,
66 Self::Sha384 => oid::SHA384,
67 Self::Sha512 => oid::SHA512,
68 Self::Sha512_224 => oid::SHA512_224,
69 Self::Sha512_256 => oid::SHA512_256,
70 Self::Sha3_224 => oid::SHA3_224,
71 Self::Sha3_256 => oid::SHA3_256,
72 Self::Sha3_384 => oid::SHA3_384,
73 Self::Sha3_512 => oid::SHA3_512,
74 }
75 }
76}
77
78/// A [`Digest`] provider's identities for standard ASN.1 digest algorithms.
79pub trait AsnDigest<'a>: Digest<'a> {
80 /// This provider's identity for `algorithm`, or `None` when it does not
81 /// implement it or its policy refuses it. O(1); no work is spent.
82 fn identity(&self, algorithm: Algorithm) -> Option<Self::Identity>;
83}
84
85/// Reads which standard digest `identifier` names.
86///
87/// SHA-1 and SHA-2 accept absent or primitive NULL parameters (RFC 8017
88/// Appendix B.1, RFC 5754 §2 and RFC 3370 §2.1). The producing reader validates
89/// NULL under the document's encoding, including BER long-form lengths.
90/// SHA3-224/256/384/512 require absent parameters, even in BER
91/// ([RFC 9688 §2](https://www.rfc-editor.org/rfc/rfc9688.html#section-2)).
92///
93/// # Errors
94///
95/// A located resource refusal when either the original parser's cumulative
96/// ceiling or the current execution cannot pay for the comparison; never
97/// `Malformed` or `Unsupported`. A retained identifier preserves its source
98/// ceiling even when `execution` uses a different account. Same-session work
99/// is admitted once, without opening a cursor or allocating storage.
100///
101/// # Examples
102///
103/// ```
104/// # extern crate alloc;
105/// # use alloc::sync::Arc;
106/// # use structured_asn1::{Parser, Tag, universal, digest::Resolution};
107/// # use structured_asn1::memory::{Memory, MemoryAccount};
108/// use structured_pkix::algorithm::{self, digest};
109/// # let execution = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
110/// # let parser = Parser::der(execution.clone());
111/// // SEQUENCE { OID 2.16.840.1.101.3.4.2.1, NULL }
112/// let der = [0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00];
113/// # let mut reader = parser.read(&der, 0).unwrap();
114/// # let element = reader.expect(Tag::constructed(universal::SEQUENCE)).unwrap();
115/// let identifier = algorithm::read(&reader, &element, "digestAlgorithm")?;
116/// assert_eq!(
117/// digest::algorithm(&identifier, &execution)?,
118/// Resolution::Supported(digest::Algorithm::Sha256),
119/// );
120/// # Ok::<(), structured_pkix::Error>(())
121/// ```
122pub fn algorithm<W: Work>(
123 identifier: &AlgorithmIdentifier<'_>,
124 execution: &W,
125) -> Result<Resolution<Algorithm>> {
126 let written = identifier.algorithm().as_bytes();
127 // One length dispatch and at most one look at each written byte, which is
128 // exactly what this charge pays for; the OID is a slice of the parsed
129 // input, so its length plus one fits. The patterns are the bytes of the
130 // `oid` constants that `Algorithm::identifier` names.
131 identifier
132 .query_in(execution)
133 .charge_work(written.len() as u64 + 1)
134 .map_err(|reason| Error::resource(reason, identifier.field, identifier.offset))?;
135 let algorithm = match written {
136 [0x2b, 0x0e, 0x03, 0x02, 0x1a] => Algorithm::Sha1,
137 [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, last] => match last {
138 0x01 => Algorithm::Sha256,
139 0x02 => Algorithm::Sha384,
140 0x03 => Algorithm::Sha512,
141 0x04 => Algorithm::Sha224,
142 0x05 => Algorithm::Sha512_224,
143 0x06 => Algorithm::Sha512_256,
144 0x07 => Algorithm::Sha3_224,
145 0x08 => Algorithm::Sha3_256,
146 0x09 => Algorithm::Sha3_384,
147 0x0a => Algorithm::Sha3_512,
148 _ => return Ok(Resolution::Unsupported),
149 },
150 _ => return Ok(Resolution::Unsupported),
151 };
152 if identifier.parameters().is_some() {
153 // RFC 9688 §2: SHA3 digest parameters MUST be absent, not NULL.
154 // https://www.rfc-editor.org/rfc/rfc9688.html#section-2
155 if matches!(
156 algorithm,
157 Algorithm::Sha3_224 | Algorithm::Sha3_256 | Algorithm::Sha3_384 | Algorithm::Sha3_512
158 ) || !identifier.has_null_parameter()
159 {
160 return Ok(Resolution::Malformed);
161 }
162 }
163 Ok(Resolution::Supported(algorithm))
164}
165
166/// Resolves `identifier` to `digest`'s own identity: [`algorithm`], then the
167/// provider's [`AsnDigest::identity`].
168///
169/// # Errors
170///
171/// As [`algorithm`].
172pub fn resolve<'a, D: AsnDigest<'a> + ?Sized, W: Work>(
173 digest: &D,
174 identifier: &AlgorithmIdentifier<'_>,
175 execution: &W,
176) -> Result<Resolution<D::Identity>> {
177 Ok(match algorithm(identifier, execution)? {
178 Resolution::Supported(algorithm) => supported(digest, algorithm),
179 Resolution::Unsupported => Resolution::Unsupported,
180 Resolution::Malformed => Resolution::Malformed,
181 })
182}
183
184/// The provider's identity for an algorithm a schema imposes without writing
185/// an identifier, such as SHA-1 for an OCSP `ResponderID` byKey.
186pub fn supported<'a, D: AsnDigest<'a> + ?Sized>(
187 digest: &D,
188 algorithm: Algorithm,
189) -> Resolution<D::Identity> {
190 digest
191 .identity(algorithm)
192 .map_or(Resolution::Unsupported, Resolution::Supported)
193}