Skip to main content

pkix_path/
serde_der.rs

1//! Helper functions for serde-serializing DER-encodable types in a
2//! format-adaptive wire form.
3//!
4//! # Wire form
5//!
6//! Human-readable serializers (JSON, TOML, YAML) receive a **base64**
7//! string of the type's DER encoding. Binary serializers (postcard,
8//! bincode, MessagePack, CBOR-without-readability-hint) receive the **raw
9//! DER bytes**. The selection is driven by
10//! [`serde::Serializer::is_human_readable`] /
11//! [`serde::Deserializer::is_human_readable`].
12//!
13//! # Round-trip
14//!
15//! DER is a canonical encoding for the types this helper is used on
16//! (`x509_cert::name::Name`, `x509_cert::serial_number::SerialNumber`,
17//! `spki::SubjectPublicKeyInfoOwned`,
18//! `x509_cert::ext::pkix::certpolicy::PolicyQualifierInfo`,
19//! `der::asn1::ObjectIdentifier`, …). Serialize-then-deserialize produces
20//! a value whose DER re-encoding is byte-identical to the original.
21//!
22//! # Usage
23//!
24//! ```ignore
25//! #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26//! struct Anchor {
27//!     #[cfg_attr(
28//!         feature = "serde",
29//!         serde(with = "crate::serde_der")
30//!     )]
31//!     subject: x509_cert::name::Name,
32//! }
33//! ```
34//!
35//! For `Option<T>` fields, use [`crate::serde_der::option`]. For
36//! `Vec<T>` fields, use [`crate::serde_der::vec`].
37//!
38//! # Why not `serde_with`?
39//!
40//! [`serde_with`] would express these helpers more concisely via
41//! `#[serde_as(as = "DerBase64")]`. We avoid the dependency because the
42//! workspace MSRV is 1.73 and the latest `serde_with` 3.x releases
43//! (3.18+) require rustc 1.88. Pinning to 3.12 was a viable alternative,
44//! but a small hand-written helper module is simpler and keeps the
45//! dependency footprint smaller.
46
47#[cfg(not(feature = "std"))]
48use alloc::{string::String, vec::Vec};
49
50use base64ct::{Base64, Encoding};
51use der::{Decode, Encode};
52use serde::{Deserialize, Deserializer, Serialize, Serializer};
53
54/// Serialize a single DER-encodable value.
55///
56/// Emits a base64 string in human-readable serializers and raw bytes in
57/// binary serializers.
58///
59/// # Errors
60///
61/// Returns the serializer's error if the underlying serializer fails or
62/// if DER encoding of `value` fails.
63pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
64where
65    S: Serializer,
66    T: Encode,
67{
68    let der_bytes = value.to_der().map_err(serde::ser::Error::custom)?;
69    if serializer.is_human_readable() {
70        let encoded = Base64::encode_string(&der_bytes);
71        serializer.serialize_str(&encoded)
72    } else {
73        serializer.serialize_bytes(&der_bytes)
74    }
75}
76
77/// Deserialize a single DER-encodable value.
78///
79/// Accepts a base64 string from human-readable deserializers and raw
80/// bytes from binary deserializers.
81///
82/// # Errors
83///
84/// Returns the deserializer's error if the underlying deserializer
85/// fails, if base64 decoding fails (human-readable mode), or if DER
86/// decoding of the recovered bytes fails.
87pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
88where
89    D: Deserializer<'de>,
90    T: for<'a> Decode<'a>,
91{
92    let der_bytes = if deserializer.is_human_readable() {
93        let s = String::deserialize(deserializer)?;
94        Base64::decode_vec(&s).map_err(serde::de::Error::custom)?
95    } else {
96        Vec::<u8>::deserialize(deserializer)?
97    };
98    T::from_der(&der_bytes).map_err(serde::de::Error::custom)
99}
100
101/// Serde helper for `Option<T>` fields where `T` is DER-encodable.
102///
103/// Use as `#[serde(with = "crate::serde_der::option")]` on the field.
104pub mod option {
105    use super::{Base64, Decode, Deserialize, Deserializer, Encode, Encoding, Serializer};
106    #[cfg(not(feature = "std"))]
107    use alloc::{string::String, vec::Vec};
108
109    /// Serialize an optional DER-encodable value.
110    ///
111    /// `None` serializes as `null` / unit; `Some(value)` serializes as
112    /// `value` per the top-level format rule.
113    ///
114    /// # Errors
115    ///
116    /// Returns the serializer's error if the underlying serializer fails
117    /// or if DER encoding fails.
118    pub fn serialize<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
119    where
120        S: Serializer,
121        T: Encode,
122    {
123        match value {
124            Some(v) => {
125                let der_bytes = v.to_der().map_err(serde::ser::Error::custom)?;
126                if serializer.is_human_readable() {
127                    let encoded = Base64::encode_string(&der_bytes);
128                    serializer.serialize_some(&encoded)
129                } else {
130                    serializer.serialize_some(&der_bytes)
131                }
132            }
133            None => serializer.serialize_none(),
134        }
135    }
136
137    /// Deserialize an optional DER-encodable value.
138    ///
139    /// # Errors
140    ///
141    /// Returns the deserializer's error if the underlying deserializer
142    /// fails, if base64 decoding fails, or if DER decoding fails.
143    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
144    where
145        D: Deserializer<'de>,
146        T: for<'a> Decode<'a>,
147    {
148        if deserializer.is_human_readable() {
149            let opt = Option::<String>::deserialize(deserializer)?;
150            match opt {
151                Some(s) => {
152                    let der_bytes = Base64::decode_vec(&s).map_err(serde::de::Error::custom)?;
153                    let v = T::from_der(&der_bytes).map_err(serde::de::Error::custom)?;
154                    Ok(Some(v))
155                }
156                None => Ok(None),
157            }
158        } else {
159            let opt = Option::<Vec<u8>>::deserialize(deserializer)?;
160            match opt {
161                Some(bytes) => {
162                    let v = T::from_der(&bytes).map_err(serde::de::Error::custom)?;
163                    Ok(Some(v))
164                }
165                None => Ok(None),
166            }
167        }
168    }
169}
170
171/// Serde helper for `Vec<T>` fields where `T` is DER-encodable.
172///
173/// Use as `#[serde(with = "crate::serde_der::vec")]` on the field. Each
174/// element is encoded independently per the top-level format rule.
175pub mod vec {
176    use super::{Base64, Decode, Deserialize, Deserializer, Encode, Encoding, Serializer};
177    use serde::ser::SerializeSeq;
178    #[cfg(not(feature = "std"))]
179    use alloc::{string::String, vec::Vec};
180
181    /// Serialize a `Vec` of DER-encodable values as a sequence.
182    ///
183    /// # Errors
184    ///
185    /// Returns the serializer's error if the underlying serializer fails
186    /// or if DER encoding of any element fails.
187    pub fn serialize<S, T>(values: &[T], serializer: S) -> Result<S::Ok, S::Error>
188    where
189        S: Serializer,
190        T: Encode,
191    {
192        let is_hr = serializer.is_human_readable();
193        let mut seq = serializer.serialize_seq(Some(values.len()))?;
194        for v in values {
195            let der_bytes = v.to_der().map_err(serde::ser::Error::custom)?;
196            if is_hr {
197                let encoded = Base64::encode_string(&der_bytes);
198                seq.serialize_element(&encoded)?;
199            } else {
200                seq.serialize_element(der_bytes.as_slice())?;
201            }
202        }
203        seq.end()
204    }
205
206    /// Deserialize a `Vec` of DER-encodable values from a sequence.
207    ///
208    /// # Errors
209    ///
210    /// Returns the deserializer's error if the underlying deserializer
211    /// fails, if base64 decoding fails, or if DER decoding of any
212    /// element fails.
213    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
214    where
215        D: Deserializer<'de>,
216        T: for<'a> Decode<'a>,
217    {
218        if deserializer.is_human_readable() {
219            let strs = Vec::<String>::deserialize(deserializer)?;
220            let mut out = Vec::with_capacity(strs.len());
221            for s in strs {
222                let der_bytes = Base64::decode_vec(&s).map_err(serde::de::Error::custom)?;
223                let v = T::from_der(&der_bytes).map_err(serde::de::Error::custom)?;
224                out.push(v);
225            }
226            Ok(out)
227        } else {
228            let byte_vecs = Vec::<Vec<u8>>::deserialize(deserializer)?;
229            let mut out = Vec::with_capacity(byte_vecs.len());
230            for bytes in byte_vecs {
231                let v = T::from_der(&bytes).map_err(serde::de::Error::custom)?;
232                out.push(v);
233            }
234            Ok(out)
235        }
236    }
237}
238
239/// Serde helper for `Option<Vec<T>>` fields where `T` is DER-encodable.
240///
241/// Use as `#[serde(with = "crate::serde_der::option_vec")]` on the
242/// field. `None` serializes as `null` / unit; `Some(vec)` serializes as
243/// a sequence per [`vec`].
244pub mod option_vec {
245    use super::{Base64, Decode, Deserialize, Deserializer, Encode, Encoding, Serialize, Serializer};
246    use serde::ser::SerializeSeq;
247    #[cfg(not(feature = "std"))]
248    use alloc::{string::String, vec::Vec};
249
250    /// Serialize an optional `Vec` of DER-encodable values.
251    ///
252    /// # Errors
253    ///
254    /// Returns the serializer's error if the underlying serializer fails
255    /// or if DER encoding fails.
256    pub fn serialize<S, T>(value: &Option<Vec<T>>, serializer: S) -> Result<S::Ok, S::Error>
257    where
258        S: Serializer,
259        T: Encode,
260    {
261        match value {
262            Some(values) => {
263                let is_hr = serializer.is_human_readable();
264                let mut bytes_buf: Vec<Vec<u8>> = Vec::with_capacity(values.len());
265                let mut str_buf: Vec<String> = Vec::with_capacity(values.len());
266                for v in values {
267                    let der_bytes = v.to_der().map_err(serde::ser::Error::custom)?;
268                    if is_hr {
269                        str_buf.push(Base64::encode_string(&der_bytes));
270                    } else {
271                        bytes_buf.push(der_bytes);
272                    }
273                }
274                if is_hr {
275                    serializer.serialize_some(&str_buf)
276                } else {
277                    // Serialize the outer Some(Vec<Vec<u8>>) via a custom
278                    // wrapper so each element is emitted as a byte string
279                    // rather than a generic sequence-of-u8.
280                    struct Bytes<'a>(&'a [Vec<u8>]);
281                    impl<'a> Serialize for Bytes<'a> {
282                        fn serialize<S2: Serializer>(&self, s: S2) -> Result<S2::Ok, S2::Error> {
283                            let mut seq = s.serialize_seq(Some(self.0.len()))?;
284                            for b in self.0 {
285                                seq.serialize_element(b.as_slice())?;
286                            }
287                            seq.end()
288                        }
289                    }
290                    serializer.serialize_some(&Bytes(&bytes_buf))
291                }
292            }
293            None => serializer.serialize_none(),
294        }
295    }
296
297    /// Deserialize an optional `Vec` of DER-encodable values.
298    ///
299    /// # Errors
300    ///
301    /// Returns the deserializer's error if the underlying deserializer
302    /// fails, if base64 decoding fails, or if DER decoding fails.
303    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
304    where
305        D: Deserializer<'de>,
306        T: for<'a> Decode<'a>,
307    {
308        if deserializer.is_human_readable() {
309            let opt = Option::<Vec<String>>::deserialize(deserializer)?;
310            match opt {
311                Some(strs) => {
312                    let mut out = Vec::with_capacity(strs.len());
313                    for s in strs {
314                        let der_bytes =
315                            Base64::decode_vec(&s).map_err(serde::de::Error::custom)?;
316                        let v = T::from_der(&der_bytes).map_err(serde::de::Error::custom)?;
317                        out.push(v);
318                    }
319                    Ok(Some(out))
320                }
321                None => Ok(None),
322            }
323        } else {
324            let opt = Option::<Vec<Vec<u8>>>::deserialize(deserializer)?;
325            match opt {
326                Some(byte_vecs) => {
327                    let mut out = Vec::with_capacity(byte_vecs.len());
328                    for bytes in byte_vecs {
329                        let v = T::from_der(&bytes).map_err(serde::de::Error::custom)?;
330                        out.push(v);
331                    }
332                    Ok(Some(out))
333                }
334                None => Ok(None),
335            }
336        }
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    //! Unit tests for the DER serde helpers.
343    //!
344    //! These tests use the `der::asn1::ObjectIdentifier` type as the
345    //! DER-encodable subject because it is small, has a stable encoding,
346    //! and is independent of the path-validation code under test in the
347    //! rest of `pkix-path`. The OID `1.2.840.10045.4.3.2`
348    //! (`ecdsa-with-SHA256`) encodes to the DER bytes
349    //! `06 08 2A 86 48 CE 3D 04 03 02` per the published ASN.1 encoding
350    //! rules — this is the *external* oracle: a hand-computed expected
351    //! encoding, not anything produced by this helper.
352
353    use super::*;
354    use der::asn1::ObjectIdentifier;
355
356    const ECDSA_WITH_SHA256_OID: &str = "1.2.840.10045.4.3.2";
357    // Hand-computed DER encoding of ECDSA_WITH_SHA256_OID. Tag 0x06 (OID),
358    // length 0x08, then the OID body bytes per X.690 §8.19.
359    const ECDSA_WITH_SHA256_DER: &[u8] =
360        &[0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02];
361    // Standard base64 (RFC 4648 §4) of the DER bytes, with required
362    // `=` padding for a 10-byte input. Padding is mandatory in the
363    // standard alphabet that base64ct's `Base64` type emits and
364    // requires on decode.
365    const ECDSA_WITH_SHA256_BASE64: &str = "BggqhkjOPQQDAg==";
366
367    /// Serialize a known OID to JSON; assert the emitted JSON matches a
368    /// hand-computed base64 string. JSON is the human-readable oracle.
369    #[test]
370    fn json_serialize_matches_hand_computed_base64() {
371        #[derive(Serialize)]
372        struct Wrapper {
373            #[serde(serialize_with = "super::serialize")]
374            oid: ObjectIdentifier,
375        }
376        let oid: ObjectIdentifier = ECDSA_WITH_SHA256_OID.parse().unwrap();
377        let w = Wrapper { oid };
378        let json = serde_json::to_string(&w).unwrap();
379        let expected = format!(r#"{{"oid":"{}"}}"#, ECDSA_WITH_SHA256_BASE64);
380        assert_eq!(json, expected);
381    }
382
383    /// Deserialize a hand-written JSON document; assert the recovered OID
384    /// equals the known reference value.
385    #[test]
386    fn json_deserialize_matches_hand_constructed_value() {
387        #[derive(Deserialize)]
388        struct Wrapper {
389            #[serde(deserialize_with = "super::deserialize")]
390            oid: ObjectIdentifier,
391        }
392        let json = format!(r#"{{"oid":"{}"}}"#, ECDSA_WITH_SHA256_BASE64);
393        let w: Wrapper = serde_json::from_str(&json).unwrap();
394        let expected: ObjectIdentifier = ECDSA_WITH_SHA256_OID.parse().unwrap();
395        assert_eq!(w.oid, expected);
396    }
397
398    /// Round-trip OID through JSON; the recovered OID must DER-encode to
399    /// the same canonical bytes. This is the cache-key requirement from
400    /// AGENTS.md non-negotiable #6.
401    #[test]
402    fn json_round_trip_preserves_der_canonical_form() {
403        #[derive(Serialize, Deserialize)]
404        struct Wrapper {
405            #[serde(with = "super::super::serde_der")]
406            oid: ObjectIdentifier,
407        }
408        let oid: ObjectIdentifier = ECDSA_WITH_SHA256_OID.parse().unwrap();
409        let original_der = oid.to_der().unwrap();
410        assert_eq!(original_der.as_slice(), ECDSA_WITH_SHA256_DER);
411
412        let w = Wrapper { oid };
413        let json = serde_json::to_string(&w).unwrap();
414        let back: Wrapper = serde_json::from_str(&json).unwrap();
415        let recovered_der = back.oid.to_der().unwrap();
416        assert_eq!(recovered_der, original_der);
417    }
418
419    /// Serialize via a binary format (postcard-equivalent custom serializer
420    /// that reports is_human_readable=false). The helper must emit raw
421    /// DER bytes, NOT base64. Verified by asserting the serialized
422    /// `Vec<u8>` payload contains the canonical DER bytes verbatim.
423    #[test]
424    fn binary_serializer_emits_raw_der_bytes_not_base64() {
425        use serde::ser::Impossible;
426
427        /// Minimal binary serializer that records the bytes written
428        /// without any encoding transformation. Reports
429        /// `is_human_readable() = false` so the helper takes the binary
430        /// branch. Only implements the serializer surface the helper
431        /// uses (`serialize_bytes`); everything else panics, which
432        /// suffices for this targeted assertion.
433        struct BytesCapture {
434            captured: Vec<u8>,
435        }
436        impl Serializer for &mut BytesCapture {
437            type Ok = ();
438            type Error = serde::de::value::Error;
439            type SerializeSeq = Impossible<(), Self::Error>;
440            type SerializeTuple = Impossible<(), Self::Error>;
441            type SerializeTupleStruct = Impossible<(), Self::Error>;
442            type SerializeTupleVariant = Impossible<(), Self::Error>;
443            type SerializeMap = Impossible<(), Self::Error>;
444            type SerializeStruct = Impossible<(), Self::Error>;
445            type SerializeStructVariant = Impossible<(), Self::Error>;
446
447            fn is_human_readable(&self) -> bool {
448                false
449            }
450            fn serialize_bytes(self, v: &[u8]) -> Result<(), Self::Error> {
451                self.captured.extend_from_slice(v);
452                Ok(())
453            }
454            fn serialize_bool(self, _: bool) -> Result<(), Self::Error> {
455                unreachable!()
456            }
457            fn serialize_i8(self, _: i8) -> Result<(), Self::Error> {
458                unreachable!()
459            }
460            fn serialize_i16(self, _: i16) -> Result<(), Self::Error> {
461                unreachable!()
462            }
463            fn serialize_i32(self, _: i32) -> Result<(), Self::Error> {
464                unreachable!()
465            }
466            fn serialize_i64(self, _: i64) -> Result<(), Self::Error> {
467                unreachable!()
468            }
469            fn serialize_u8(self, _: u8) -> Result<(), Self::Error> {
470                unreachable!()
471            }
472            fn serialize_u16(self, _: u16) -> Result<(), Self::Error> {
473                unreachable!()
474            }
475            fn serialize_u32(self, _: u32) -> Result<(), Self::Error> {
476                unreachable!()
477            }
478            fn serialize_u64(self, _: u64) -> Result<(), Self::Error> {
479                unreachable!()
480            }
481            fn serialize_f32(self, _: f32) -> Result<(), Self::Error> {
482                unreachable!()
483            }
484            fn serialize_f64(self, _: f64) -> Result<(), Self::Error> {
485                unreachable!()
486            }
487            fn serialize_char(self, _: char) -> Result<(), Self::Error> {
488                unreachable!()
489            }
490            fn serialize_str(self, _: &str) -> Result<(), Self::Error> {
491                unreachable!()
492            }
493            fn serialize_none(self) -> Result<(), Self::Error> {
494                unreachable!()
495            }
496            fn serialize_some<T: ?Sized + Serialize>(self, _: &T) -> Result<(), Self::Error> {
497                unreachable!()
498            }
499            fn serialize_unit(self) -> Result<(), Self::Error> {
500                unreachable!()
501            }
502            fn serialize_unit_struct(self, _: &'static str) -> Result<(), Self::Error> {
503                unreachable!()
504            }
505            fn serialize_unit_variant(
506                self,
507                _: &'static str,
508                _: u32,
509                _: &'static str,
510            ) -> Result<(), Self::Error> {
511                unreachable!()
512            }
513            fn serialize_newtype_struct<T: ?Sized + Serialize>(
514                self,
515                _: &'static str,
516                _: &T,
517            ) -> Result<(), Self::Error> {
518                unreachable!()
519            }
520            fn serialize_newtype_variant<T: ?Sized + Serialize>(
521                self,
522                _: &'static str,
523                _: u32,
524                _: &'static str,
525                _: &T,
526            ) -> Result<(), Self::Error> {
527                unreachable!()
528            }
529            fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
530                unreachable!()
531            }
532            fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
533                unreachable!()
534            }
535            fn serialize_tuple_struct(
536                self,
537                _: &'static str,
538                _: usize,
539            ) -> Result<Self::SerializeTupleStruct, Self::Error> {
540                unreachable!()
541            }
542            fn serialize_tuple_variant(
543                self,
544                _: &'static str,
545                _: u32,
546                _: &'static str,
547                _: usize,
548            ) -> Result<Self::SerializeTupleVariant, Self::Error> {
549                unreachable!()
550            }
551            fn serialize_map(self, _: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
552                unreachable!()
553            }
554            fn serialize_struct(
555                self,
556                _: &'static str,
557                _: usize,
558            ) -> Result<Self::SerializeStruct, Self::Error> {
559                unreachable!()
560            }
561            fn serialize_struct_variant(
562                self,
563                _: &'static str,
564                _: u32,
565                _: &'static str,
566                _: usize,
567            ) -> Result<Self::SerializeStructVariant, Self::Error> {
568                unreachable!()
569            }
570        }
571
572        let oid: ObjectIdentifier = ECDSA_WITH_SHA256_OID.parse().unwrap();
573        let mut cap = BytesCapture {
574            captured: Vec::new(),
575        };
576        super::serialize(&oid, &mut cap).unwrap();
577        assert_eq!(cap.captured, ECDSA_WITH_SHA256_DER);
578    }
579}