Skip to main content

structured_pkix/key/
effective.rs

1//! Streaming DER framing of an effective key, not key or algorithm acceptance.
2//!
3//! RFC5755 ยง7.3 hashes a whole DER `SubjectPublicKeyInfo`, including inherited
4//! parameters. Hashing only the original SPKI is wrong when DSA parameters
5//! came from an issuer. No joined buffer or parser over synthesized data is
6//! needed: only tag/length framing changes, all payload remains borrowed.
7//! <https://www.rfc-editor.org/rfc/rfc5755#section-7.3>
8
9use crate::algorithm::Nested;
10use crate::error::{Error, ErrorCode, Located, Result};
11use crate::path::WorkingKey;
12use structured_asn1::memory::{Execution, MemoryError, Work};
13use structured_asn1::{Encoding, Span};
14
15const FIELD: &str = "subjectPublicKeyInfo.effectiveDER";
16
17/// Which part failed to reach a streaming consumer. Framing is synthesized;
18/// it has no invented contiguous source span. Other parts retain their source.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Part {
21    /// Canonical tag/length or the zero unused-bits octet.
22    Framing,
23    /// Original algorithm OID contents.
24    Algorithm,
25    /// Original effective parameter TLV, possibly from an issuer.
26    Parameters,
27    /// Original public-key octets, without BIT STRING framing.
28    PublicKey,
29}
30
31/// Source/current processing refusal or the original visitor failure.
32#[derive(Debug)]
33pub enum VisitError<E> {
34    /// Original located PKIX/resource failure.
35    Processing {
36        /// Part whose metadata could not be admitted.
37        part: Part,
38        /// Real enclosing source, not a physical range of synthesized framing.
39        context: Span,
40        /// Original source/current failure.
41        source: Error,
42    },
43    /// The consumer refused this part; no subsequent part is emitted.
44    Callback {
45        /// Part the visitor refused.
46        part: Part,
47        /// Real enclosing input context; framing itself has no original range.
48        context: Span,
49        /// Original visitor cause.
50        source: E,
51    },
52}
53
54#[derive(Debug)]
55struct Header {
56    bytes: [u8; 11],
57    len: usize,
58}
59impl Header {
60    fn new(tag: u8, length: u64) -> Self {
61        let mut bytes = [0; 11];
62        bytes[0] = tag;
63        let encoded = structured_asn1::length::DerLength::new(length);
64        let encoded = encoded.as_bytes();
65        bytes[1..=encoded.len()].copy_from_slice(encoded);
66        Self {
67            bytes,
68            len: 1 + encoded.len(),
69        }
70    }
71    fn bytes(&self) -> &[u8] {
72        &self.bytes[..self.len]
73    }
74}
75
76/// Immutable DER chunks over the original key and effective parameter sources.
77///
78/// This proves canonical outer framing and parameter DER, not that a key's
79/// algorithm-specific payload is valid. A selected provider must check the
80/// original `WorkingKey` against its algorithm rules before using this stream.
81/// In particular, inherited parameters cannot repair an invalid written NULL.
82/// The type owns no allocation, account, parser or operation borrow.
83#[derive(Debug)]
84pub struct EffectiveSpki<'a> {
85    key: WorkingKey<'a>,
86    parameters: Option<Nested<'a>>,
87    sequence: Header,
88    algorithm: Header,
89    oid: Header,
90    bits: Header,
91    length: u64,
92}
93
94fn overflow(at: u64) -> Error {
95    Error::resource(MemoryError::WorkExceeded, FIELD, at)
96}
97fn add(left: u64, right: u64, at: u64) -> Result<u64> {
98    left.checked_add(right).ok_or_else(|| overflow(at))
99}
100
101impl<'a> EffectiveSpki<'a> {
102    /// Prepare canonical framing without copying any original payload.
103    /// DER parameters already validated by their original identifier need no
104    /// new scan. BER sources are held to DER in their original nested context;
105    /// noncanonical parameter TLVs are rejected rather than silently rewritten.
106    /// Written NULL is retained when there is no effective inherited value.
107    pub fn read<W: Work>(key: WorkingKey<'a>, work: &W) -> Result<Self> {
108        let own = key.key().algorithm();
109        let execution = own.query_in(work);
110        // Four length headers have at most eight significant length octets.
111        execution
112            .charge_work(64)
113            .map_err(|e| Error::resource(e, FIELD, own.offset()))?;
114        let bytes = key.key().whole_octets(FIELD)?;
115        let parameters = key.parameter_source().or_else(|| own.parameters_nested());
116        if let Some(source) = parameters.filter(|source| !source.encoding().is_der()) {
117            // Source encoding is known from the immutable, validated identifier.
118            // Opening is required only for BER: checking DER includes its header.
119            let (reader, element) = source.open(work, FIELD)?;
120            if !reader.encoding().is_der() {
121                let mut strict = reader.reread(&element, Encoding::Der).field(FIELD)?;
122                let original = strict
123                    .next_element()
124                    .field(FIELD)?
125                    .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, source.offset()))?;
126                crate::der::conforms(&strict, &original, FIELD)?;
127            }
128        }
129        let at = own.offset();
130        let oid = Header::new(6, own.algorithm().as_bytes().len() as u64);
131        let algorithm_len = add(oid.len as u64, own.algorithm().as_bytes().len() as u64, at)?;
132        let algorithm_len = add(
133            algorithm_len,
134            parameters.map_or(0, |p| p.as_bytes().len() as u64),
135            at,
136        )?;
137        let algorithm = Header::new(48, algorithm_len);
138        let mut bits = Header::new(3, add(bytes.len() as u64, 1, at)?);
139        bits.bytes[bits.len] = 0;
140        bits.len += 1;
141        let content = add(
142            add(algorithm.len as u64, algorithm_len, at)?,
143            add(bits.len as u64, bytes.len() as u64, at)?,
144            at,
145        )?;
146        let sequence = Header::new(48, content);
147        let length = add(sequence.len as u64, content, at)?;
148        Ok(Self {
149            key,
150            parameters,
151            sequence,
152            algorithm,
153            oid,
154            bits,
155            length,
156        })
157    }
158
159    /// Total encoded width, including all synthesized framing.
160    #[must_use]
161    pub fn encoded_len(&self) -> u64 {
162        self.length
163    }
164
165    /// Original effective key, including its parameter provenance.
166    #[must_use]
167    pub fn key(&self) -> WorkingKey<'a> {
168        self.key
169    }
170
171    /// Original key-source ceiling with the current operation's work/account.
172    /// Construction scans nothing; the selected key validator admits its own
173    /// work and uses shared readers for any independently sourced parameters.
174    pub fn execution_in<'q, W: Work>(&'q self, work: &'q W) -> Execution<'q> {
175        self.key.key().algorithm().query_in(work)
176    }
177
178    /// Emit every part once in DER order with its source/current execution.
179    /// The visitor admits its byte scan, output copies and transitive storage.
180    /// Metadata admission precedes each callback. A failure stops immediately;
181    /// successful partial completion is never returned, but external visitor
182    /// side effects cannot be rolled back by this method. Header borrows cannot
183    /// escape a callback, and no complete SPKI buffer is allocated.
184    ///
185    /// The synthesized header borrow cannot be retained as document data:
186    ///
187    /// ```compile_fail
188    /// use structured_asn1::memory::Memory;
189    /// use structured_pkix::key::effective::EffectiveSpki;
190    /// fn escape(view: &EffectiveSpki<'static>, work: &Memory) -> &'static [u8] {
191    ///     let mut saved: &'static [u8] = &[];
192    ///     let _ = view.visit(work, |_, bytes, _| {
193    ///         saved = bytes;
194    ///         Ok::<_, core::convert::Infallible>(())
195    ///     });
196    ///     saved
197    /// }
198    /// ```
199    pub fn visit<W: Work, E>(
200        &self,
201        work: &W,
202        mut visitor: impl FnMut(Part, &[u8], &Execution<'_>) -> core::result::Result<(), E>,
203    ) -> core::result::Result<(), VisitError<E>> {
204        let own = self.key.key().algorithm();
205        let execution = own.query_in(work);
206        let context = Span {
207            start: own.offset(),
208            length: own.as_bytes().len() as u64,
209        };
210        for bytes in [
211            self.sequence.bytes(),
212            self.algorithm.bytes(),
213            self.oid.bytes(),
214        ] {
215            emit(Part::Framing, bytes, &execution, context, &mut visitor)?;
216        }
217        emit(
218            Part::Algorithm,
219            own.algorithm().as_bytes(),
220            &execution,
221            context,
222            &mut visitor,
223        )?;
224        if let Some(source) = self.parameters {
225            emit(
226                Part::Parameters,
227                source.as_bytes(),
228                &source.execution_in(work),
229                Span {
230                    start: source.offset(),
231                    length: source.as_bytes().len() as u64,
232                },
233                &mut visitor,
234            )?;
235        }
236        emit(
237            Part::Framing,
238            self.bits.bytes(),
239            &execution,
240            context,
241            &mut visitor,
242        )?;
243        emit(
244            Part::PublicKey,
245            self.key.key().key().as_bytes(),
246            &execution,
247            Span {
248                start: self.key.key().key_offset(),
249                length: self.key.key().key().as_bytes().len() as u64,
250            },
251            &mut visitor,
252        )
253    }
254}
255
256fn emit<E>(
257    part: Part,
258    bytes: &[u8],
259    work: &Execution<'_>,
260    context: Span,
261    visitor: &mut impl FnMut(Part, &[u8], &Execution<'_>) -> core::result::Result<(), E>,
262) -> core::result::Result<(), VisitError<E>> {
263    work.charge_work(1).map_err(|e| VisitError::Processing {
264        part,
265        context,
266        source: Error::resource(e, FIELD, context.start),
267    })?;
268    visitor(part, bytes, work).map_err(|source| VisitError::Callback {
269        part,
270        context,
271        source,
272    })
273}