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}