Skip to main content

structured_pkix/algorithm/
dstu4145.rs

1//! DSTU 4145 wire profiles, without field arithmetic or a hash engine.
2//!
3//! The pinned Ukrainian OID table distinguishes basis, byte order and digest.
4//! Its historical encoding profile remains useful for reading signed objects;
5//! the 2012 regulatory act was repealed in 2020, so recognition is not a claim
6//! of current legal compliance. SPKI's identifier does not restrict the digest
7//! of a signature made with that key (ยง3.11.1.2.2).
8//! <https://zakon.rada.gov.ua/laws/show/z1399-12>
9//! <https://zakon.rada.gov.ua/laws/show/z1398-12>
10
11use super::{AlgorithmIdentifier, digest};
12use crate::{Error, Result};
13use structured_asn1::memory::Work;
14
15pub mod parameters;
16pub use parameters::{Curve, ExplicitCurve, NamedCurve, Parameters, Reduction, read_parameters};
17
18/// Representation of a binary field, not proof that a backend implements it.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Basis {
21    /// Polynomial basis.
22    Polynomial,
23    /// Optimal normal basis; this module performs no basis conversion.
24    OptimalNormal,
25}
26
27/// Byte order of field elements and the complete signature bit string.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ByteOrder {
30    /// Least significant octet first.
31    LittleEndian,
32    /// Most significant octet first.
33    BigEndian,
34}
35
36/// Exactly recognized identifier facts, independent of key and math validity.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct Profile {
39    digest: digest::Algorithm,
40    basis: Basis,
41    byte_order: ByteOrder,
42}
43
44impl Profile {
45    /// The signature OID's digest. A key OID imposes no signature digest.
46    #[must_use]
47    pub const fn digest(self) -> digest::Algorithm {
48        self.digest
49    }
50
51    /// Field representation selected by this identifier.
52    #[must_use]
53    pub const fn basis(self) -> Basis {
54        self.basis
55    }
56
57    /// Octet representation selected by this identifier.
58    #[must_use]
59    pub const fn byte_order(self) -> ByteOrder {
60        self.byte_order
61    }
62}
63
64/// Identifies the exact OID under its original and current execution ceilings.
65///
66/// Parameters remain role-specific: signatures require absence through
67/// [`super::signature::with_algorithm`]; SPKI uses [`read_parameters`]. Unknown
68/// OIDs, including a recognized prefix with extra arcs, return `None`.
69///
70/// # Errors
71/// Located terminal resource refusal before classification, never `None`.
72pub fn identify<W: Work>(
73    identifier: AlgorithmIdentifier<'_>,
74    execution: &W,
75) -> Result<Option<Profile>> {
76    let bytes = identifier.algorithm().as_bytes();
77    identifier
78        .query_in(execution)
79        .charge_work(bytes.len() as u64 + 1)
80        .map_err(|reason| Error::resource(reason, identifier.field, identifier.offset()))?;
81    Ok(from_octets(bytes))
82}
83
84/// The original OID traversal was already admitted by the caller.
85pub(super) fn from_octets(bytes: &[u8]) -> Option<Profile> {
86    use digest::Algorithm::{Gost34311, Kupyna256, Kupyna384, Kupyna512};
87    let (digest, basis, tail) = match bytes {
88        [0x2a, 0x86, 0x24, 2, 1, 1, 1, 1, 3, 1, basis, tail @ ..] => (Gost34311, basis, tail),
89        [
90            0x2a,
91            0x86,
92            0x24,
93            2,
94            1,
95            1,
96            1,
97            1,
98            3,
99            6,
100            hash,
101            basis,
102            tail @ ..,
103        ] => {
104            let digest = match hash {
105                1 => Kupyna256,
106                2 => Kupyna384,
107                3 => Kupyna512,
108                _ => return None,
109            };
110            (digest, basis, tail)
111        }
112        _ => return None,
113    };
114    let basis = match basis {
115        1 => Basis::Polynomial,
116        2 => Basis::OptimalNormal,
117        _ => return None,
118    };
119    let byte_order = match tail {
120        [] => ByteOrder::LittleEndian,
121        [1, 1] => ByteOrder::BigEndian,
122        _ => return None,
123    };
124    Some(Profile {
125        digest,
126        basis,
127        byte_order,
128    })
129}
130
131/// Original fixed-width unsigned octets, without copying or normalization.
132///
133/// Padding and zero remain visible. This is not a validated group element or
134/// a scalar range check. As with ASN.1 raw views, callers fund their own byte
135/// iteration; constructing an iterator performs no scan or allocation.
136#[derive(Debug, Clone, Copy)]
137pub struct Octets<'a> {
138    bytes: &'a [u8],
139    byte_order: ByteOrder,
140    offset: u64,
141}
142
143impl<'a> Octets<'a> {
144    pub(crate) const fn new(bytes: &'a [u8], byte_order: ByteOrder, offset: u64) -> Self {
145        Self {
146            bytes,
147            byte_order,
148            offset,
149        }
150    }
151
152    /// Exact original octets, including all width padding.
153    #[must_use]
154    pub const fn as_bytes(self) -> &'a [u8] {
155        self.bytes
156    }
157
158    /// The original byte order, not a host-endian interpretation.
159    #[must_use]
160    pub const fn byte_order(self) -> ByteOrder {
161        self.byte_order
162    }
163
164    /// First original payload octet in the producing source coordinates.
165    #[must_use]
166    pub const fn offset(self) -> u64 {
167        self.offset
168    }
169
170    /// Unsigned magnitude, most significant octet first, retaining width.
171    pub fn big_endian(self) -> impl ExactSizeIterator<Item = u8> + DoubleEndedIterator + 'a {
172        let bytes = self.bytes;
173        (0..bytes.len()).map(move |index| match self.byte_order {
174            ByteOrder::BigEndian => bytes[index],
175            ByteOrder::LittleEndian => bytes[bytes.len() - 1 - index],
176        })
177    }
178}