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: absent and NULL both mean none, NULL is
6//! recognised under BER, and any other value of a known identifier is
7//! malformed. An identifier this table does not know is unsupported. Execution
8//! then goes through the provider's [`Digest`] implementation.
9
10use super::AlgorithmIdentifier;
11use crate::error::{Error, Result};
12use crate::oid;
13use structured_asn1::Oid;
14use structured_asn1::digest::{Digest, Resolution};
15use structured_asn1::memory::Work;
16
17/// A digest algorithm named by a standard ASN.1 identifier.
18#[non_exhaustive]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum Algorithm {
21    /// SHA-1, FIPS 180-4.
22    Sha1,
23    /// SHA-224, FIPS 180-4.
24    Sha224,
25    /// SHA-256, FIPS 180-4.
26    Sha256,
27    /// SHA-384, FIPS 180-4.
28    Sha384,
29    /// SHA-512, FIPS 180-4.
30    Sha512,
31    /// SHA-512/224, FIPS 180-4.
32    Sha512_224,
33    /// SHA-512/256, FIPS 180-4.
34    Sha512_256,
35}
36
37impl Algorithm {
38    /// Output width in octets.
39    #[must_use]
40    pub const fn width(self) -> usize {
41        match self {
42            Self::Sha1 => 20,
43            Self::Sha224 | Self::Sha512_224 => 28,
44            Self::Sha256 | Self::Sha512_256 => 32,
45            Self::Sha384 => 48,
46            Self::Sha512 => 64,
47        }
48    }
49
50    /// The identifier this algorithm is written under.
51    #[must_use]
52    pub const fn identifier(self) -> Oid<'static> {
53        match self {
54            Self::Sha1 => oid::SHA1,
55            Self::Sha224 => oid::SHA224,
56            Self::Sha256 => oid::SHA256,
57            Self::Sha384 => oid::SHA384,
58            Self::Sha512 => oid::SHA512,
59            Self::Sha512_224 => oid::SHA512_224,
60            Self::Sha512_256 => oid::SHA512_256,
61        }
62    }
63}
64
65/// A [`Digest`] provider's identities for standard ASN.1 digest algorithms.
66pub trait AsnDigest<'a>: Digest<'a> {
67    /// This provider's identity for `algorithm`, or `None` when it does not
68    /// implement it or its policy refuses it. O(1); no work is spent.
69    fn identity(&self, algorithm: Algorithm) -> Option<Self::Identity>;
70}
71
72/// Reads which standard digest `identifier` names.
73///
74/// Every algorithm here takes no parameters: the field "should generally be
75/// omitted, but if present, it shall have a value of type NULL"
76/// (RFC 8017 Appendix B.1; RFC 5754 §2 and RFC 3370 §2.1 require receivers to
77/// accept both). NULL is the primitive NULL the producing reader validated
78/// under the document's own encoding, so a BER long-form length is NULL too
79/// (RFC 5652 §1: a signed message is BER).
80///
81/// # Errors
82///
83/// A located resource refusal when `execution` cannot pay for the comparison;
84/// never `Malformed` or `Unsupported`.
85///
86/// # Examples
87///
88/// ```
89/// # extern crate alloc;
90/// # use alloc::sync::Arc;
91/// # use structured_asn1::{Parser, Tag, universal, digest::Resolution};
92/// # use structured_asn1::memory::{Memory, MemoryAccount};
93/// use structured_pkix::algorithm::{self, digest};
94/// # let execution = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
95/// # let parser = Parser::der(execution.clone());
96/// // SEQUENCE { OID 2.16.840.1.101.3.4.2.1, NULL }
97/// let der = [0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00];
98/// # let mut reader = parser.read(&der, 0).unwrap();
99/// # let element = reader.expect(Tag::constructed(universal::SEQUENCE)).unwrap();
100/// let identifier = algorithm::read(&reader, &element, "digestAlgorithm")?;
101/// assert_eq!(
102///     digest::algorithm(&identifier, &execution)?,
103///     Resolution::Supported(digest::Algorithm::Sha256),
104/// );
105/// # Ok::<(), structured_pkix::Error>(())
106/// ```
107pub fn algorithm<W: Work>(
108    identifier: &AlgorithmIdentifier<'_>,
109    execution: &W,
110) -> Result<Resolution<Algorithm>> {
111    let written = identifier.algorithm().as_bytes();
112    // One length dispatch and at most one look at each written byte, which is
113    // exactly what this charge pays for; the OID is a slice of the parsed
114    // input, so its length plus one fits. The patterns are the bytes of the
115    // `oid` constants that `Algorithm::identifier` names.
116    execution
117        .charge_work(written.len() as u64 + 1)
118        .map_err(|reason| Error::resource(reason, identifier.field, identifier.offset))?;
119    let algorithm = match written {
120        [0x2b, 0x0e, 0x03, 0x02, 0x1a] => Algorithm::Sha1,
121        [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, last] => match last {
122            0x01 => Algorithm::Sha256,
123            0x02 => Algorithm::Sha384,
124            0x03 => Algorithm::Sha512,
125            0x04 => Algorithm::Sha224,
126            0x05 => Algorithm::Sha512_224,
127            0x06 => Algorithm::Sha512_256,
128            _ => return Ok(Resolution::Unsupported),
129        },
130        _ => return Ok(Resolution::Unsupported),
131    };
132    if identifier.parameters().is_some() && !identifier.has_null_parameter() {
133        return Ok(Resolution::Malformed);
134    }
135    Ok(Resolution::Supported(algorithm))
136}
137
138/// Resolves `identifier` to `digest`'s own identity: [`algorithm`], then the
139/// provider's [`AsnDigest::identity`].
140///
141/// # Errors
142///
143/// As [`algorithm`].
144pub fn resolve<'a, D: AsnDigest<'a> + ?Sized, W: Work>(
145    digest: &D,
146    identifier: &AlgorithmIdentifier<'_>,
147    execution: &W,
148) -> Result<Resolution<D::Identity>> {
149    Ok(match algorithm(identifier, execution)? {
150        Resolution::Supported(algorithm) => supported(digest, algorithm),
151        Resolution::Unsupported => Resolution::Unsupported,
152        Resolution::Malformed => Resolution::Malformed,
153    })
154}
155
156/// The provider's identity for an algorithm a schema imposes without writing
157/// an identifier, such as SHA-1 for an OCSP `ResponderID` byKey.
158pub fn supported<'a, D: AsnDigest<'a> + ?Sized>(
159    digest: &D,
160    algorithm: Algorithm,
161) -> Resolution<D::Identity> {
162    digest
163        .identity(algorithm)
164        .map_or(Resolution::Unsupported, Resolution::Supported)
165}