Skip to main content

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