Skip to main content

sigstore_tsa/
asn1.rs

1//! ASN.1 types for RFC 3161 Time-Stamp Protocol
2//!
3//! This module defines the ASN.1 structures used in the Time-Stamp Protocol
4//! as specified in RFC 3161.
5
6use const_oid::ObjectIdentifier;
7use der::{
8    asn1::{BitString, GeneralizedTime, Int, OctetString, Uint},
9    Decode, Encode, Sequence,
10};
11use rand::RngExt;
12use sigstore_types::HashAlgorithm;
13use x509_cert::{ext::pkix::name::GeneralName, ext::Extensions};
14
15/// OID for SHA-256: 2.16.840.1.101.3.4.2.1
16pub const OID_SHA256: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_256;
17
18/// OID for SHA-384: 2.16.840.1.101.3.4.2.2
19pub const OID_SHA384: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_384;
20
21/// OID for SHA-512: 2.16.840.1.101.3.4.2.3
22pub const OID_SHA512: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_512;
23
24/// OID for id-ct-TSTInfo: 1.2.840.113549.1.9.16.1.4
25pub const OID_TST_INFO: ObjectIdentifier =
26    ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.16.1.4");
27
28/// Generates a random nonce for RFC 3161 timestamp requests.
29///
30/// Uses `der::asn1::Uint` to guarantee correct minimal positive DER INTEGER
31/// encoding. Raw random bytes are passed through `Uint::new` which strips
32/// leading zeros and adds sign-bit padding as required by DER.
33pub fn generate_nonce() -> Int {
34    let nonce: u64 = rand::rng().random();
35    let uint = Uint::new(&nonce.to_be_bytes()).expect("valid uint from random u64");
36    Int::from(uint)
37}
38
39/// Algorithm identifier with optional parameters
40#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
41pub struct AlgorithmIdentifier {
42    /// Algorithm OID
43    pub algorithm: ObjectIdentifier,
44    /// Optional parameters (usually NULL for hash algorithms)
45    #[asn1(optional = "true")]
46    pub parameters: Option<der::Any>,
47}
48
49impl AlgorithmIdentifier {
50    /// Create a SHA-256 algorithm identifier
51    pub fn sha256() -> Self {
52        Self {
53            algorithm: OID_SHA256,
54            parameters: None,
55        }
56    }
57
58    /// Create a SHA-384 algorithm identifier
59    pub fn sha384() -> Self {
60        Self {
61            algorithm: OID_SHA384,
62            parameters: None,
63        }
64    }
65
66    /// Create a SHA-512 algorithm identifier
67    pub fn sha512() -> Self {
68        Self {
69            algorithm: OID_SHA512,
70            parameters: None,
71        }
72    }
73
74    /// Try to convert to a HashAlgorithm enum
75    pub fn to_hash_algorithm(&self) -> Option<HashAlgorithm> {
76        match self.algorithm {
77            OID_SHA256 => Some(HashAlgorithm::Sha2256),
78            OID_SHA384 => Some(HashAlgorithm::Sha2384),
79            OID_SHA512 => Some(HashAlgorithm::Sha2512),
80            _ => None,
81        }
82    }
83}
84
85impl From<HashAlgorithm> for AlgorithmIdentifier {
86    fn from(algo: HashAlgorithm) -> Self {
87        match algo {
88            HashAlgorithm::Sha2256 => Self::sha256(),
89            HashAlgorithm::Sha2384 => Self::sha384(),
90            HashAlgorithm::Sha2512 => Self::sha512(),
91        }
92    }
93}
94
95/// Message imprint containing hash algorithm and hashed message (ASN.1/DER format).
96///
97/// RFC 3161 Section 2.4.1
98///
99/// Note: This is different from `sigstore_types::MessageImprint` which is the
100/// JSON/serde representation used in bundles.
101#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
102pub struct Asn1MessageImprint {
103    /// Hash algorithm used
104    pub hash_algorithm: AlgorithmIdentifier,
105    /// Hashed message
106    pub hashed_message: OctetString,
107}
108
109impl Asn1MessageImprint {
110    /// Create a new message imprint
111    pub fn new(algorithm: AlgorithmIdentifier, digest: Vec<u8>) -> Self {
112        Self {
113            hash_algorithm: algorithm,
114            hashed_message: OctetString::new(digest).expect("valid octet string"),
115        }
116    }
117}
118
119/// Time-stamp request
120/// RFC 3161 Section 2.4.1
121#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
122pub struct TimeStampReq {
123    /// Version (must be 1)
124    pub version: u8,
125    /// Message imprint to be timestamped
126    pub message_imprint: Asn1MessageImprint,
127    /// Optional policy OID
128    #[asn1(optional = "true")]
129    pub req_policy: Option<ObjectIdentifier>,
130    /// Optional nonce
131    #[asn1(optional = "true")]
132    pub nonce: Option<Int>,
133    /// Whether to include certificates in response
134    #[asn1(default = "default_false")]
135    pub cert_req: bool,
136    // Extensions omitted for simplicity
137}
138
139fn default_false() -> bool {
140    false
141}
142
143impl TimeStampReq {
144    /// Create a new timestamp request with an automatically generated nonce
145    pub fn new(message_imprint: Asn1MessageImprint) -> Self {
146        Self {
147            version: 1,
148            message_imprint,
149            req_policy: None,
150            nonce: Some(generate_nonce()),
151            cert_req: true,
152        }
153    }
154
155    /// Create a new timestamp request without a nonce (not recommended)
156    pub fn new_without_nonce(message_imprint: Asn1MessageImprint) -> Self {
157        Self {
158            version: 1,
159            message_imprint,
160            req_policy: None,
161            nonce: None,
162            cert_req: true,
163        }
164    }
165
166    /// Set the nonce manually (overrides auto-generated nonce).
167    pub fn with_nonce(mut self, nonce: u64) -> Self {
168        let uint = Uint::new(&nonce.to_be_bytes()).expect("valid unsigned integer");
169        self.nonce = Some(Int::from(uint));
170        self
171    }
172
173    /// Set whether to request certificates
174    pub fn with_cert_req(mut self, cert_req: bool) -> Self {
175        self.cert_req = cert_req;
176        self
177    }
178
179    /// Encode to DER
180    pub fn to_der(&self) -> Result<Vec<u8>, der::Error> {
181        Encode::to_der(self)
182    }
183}
184
185/// PKI status values
186/// RFC 3161 Section 2.4.2
187#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188#[repr(u8)]
189pub enum PkiStatus {
190    /// Granted
191    Granted = 0,
192    /// Granted with modifications
193    GrantedWithMods = 1,
194    /// Rejection
195    Rejection = 2,
196    /// Waiting
197    Waiting = 3,
198    /// Revocation warning
199    RevocationWarning = 4,
200    /// Revocation notification
201    RevocationNotification = 5,
202}
203
204impl TryFrom<u8> for PkiStatus {
205    type Error = ();
206
207    fn try_from(value: u8) -> Result<Self, Self::Error> {
208        match value {
209            0 => Ok(PkiStatus::Granted),
210            1 => Ok(PkiStatus::GrantedWithMods),
211            2 => Ok(PkiStatus::Rejection),
212            3 => Ok(PkiStatus::Waiting),
213            4 => Ok(PkiStatus::RevocationWarning),
214            5 => Ok(PkiStatus::RevocationNotification),
215            _ => Err(()),
216        }
217    }
218}
219
220/// PKI status info
221/// RFC 3161 Section 2.4.2
222#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
223pub struct PkiStatusInfo {
224    /// Status value
225    pub status: u8,
226    /// Optional failure info
227    #[asn1(optional = "true")]
228    pub fail_info: Option<BitString>,
229}
230
231impl PkiStatusInfo {
232    /// Check if the status indicates success
233    pub fn is_success(&self) -> bool {
234        self.status == PkiStatus::Granted as u8 || self.status == PkiStatus::GrantedWithMods as u8
235    }
236
237    /// Get the status as an enum
238    pub fn status_enum(&self) -> Option<PkiStatus> {
239        PkiStatus::try_from(self.status).ok()
240    }
241}
242
243/// Accuracy of the timestamp
244/// RFC 3161 Section 2.4.2
245#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
246pub struct Accuracy {
247    /// Seconds
248    #[asn1(optional = "true")]
249    pub seconds: Option<u64>,
250    /// Milliseconds (1-999)
251    #[asn1(context_specific = "0", optional = "true")]
252    pub millis: Option<u16>,
253    /// Microseconds (1-999)
254    #[asn1(context_specific = "1", optional = "true")]
255    pub micros: Option<u16>,
256}
257
258/// TSTInfo - the actual timestamp token info
259/// RFC 3161 Section 2.4.2
260#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
261pub struct TstInfo {
262    /// Version (must be 1)
263    pub version: u8,
264    /// Policy OID
265    pub policy: ObjectIdentifier,
266    /// Message imprint
267    pub message_imprint: Asn1MessageImprint,
268    /// Serial number
269    pub serial_number: Int,
270    /// Generation time
271    pub gen_time: GeneralizedTime,
272    /// Accuracy
273    #[asn1(optional = "true")]
274    pub accuracy: Option<Accuracy>,
275    /// Ordering
276    #[asn1(default = "default_false")]
277    pub ordering: bool,
278    /// Nonce
279    #[asn1(optional = "true")]
280    pub nonce: Option<Int>,
281    /// TSA name
282    #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
283    pub tsa: Option<GeneralName>,
284    /// Extensions
285    #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
286    pub extensions: Option<Extensions>,
287}
288
289impl TstInfo {
290    /// Decode from DER bytes
291    pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
292        Self::from_der(bytes)
293    }
294}
295
296/// Time-stamp response
297/// RFC 3161 Section 2.4.2
298#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
299pub struct TimeStampResp {
300    /// Status information
301    pub status: PkiStatusInfo,
302    /// Time-stamp token (CMS ContentInfo)
303    #[asn1(optional = "true")]
304    pub time_stamp_token: Option<der::Any>,
305}
306
307impl TimeStampResp {
308    /// Decode from DER bytes
309    pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
310        Self::from_der(bytes)
311    }
312
313    /// Check if the response indicates success
314    pub fn is_success(&self) -> bool {
315        self.status.is_success() && self.time_stamp_token.is_some()
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_message_imprint_encode() {
325        let digest = vec![0u8; 32]; // SHA-256 produces 32 bytes
326        let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
327        let der = Encode::to_der(&imprint).unwrap();
328        assert!(!der.is_empty());
329    }
330
331    #[test]
332    fn test_timestamp_req_encode() {
333        let digest = vec![0u8; 32];
334        let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
335        let req = TimeStampReq::new(imprint);
336        let der = req.to_der().unwrap();
337        assert!(!der.is_empty());
338    }
339
340    #[test]
341    fn test_timestamp_req_has_nonce() {
342        let digest = vec![0u8; 32];
343        let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
344        let req = TimeStampReq::new(imprint);
345
346        // Verify that the request has a nonce
347        assert!(
348            req.nonce.is_some(),
349            "Nonce should be automatically generated"
350        );
351    }
352
353    #[test]
354    fn test_generate_nonce_roundtrips_as_canonical_der() {
355        // Encode → decode round-trip. The der crate's Int decoder calls
356        // `validate_canonical` internally, which rejects non-minimal
357        // encodings (e.g. 0x00 0x35 — the same check Go's encoding/asn1
358        // performs). If our nonce encoding is ever non-minimal, the
359        // decode step will fail with a non-canonical error.
360        for _ in 0..1000 {
361            let nonce = generate_nonce();
362            let encoded = Encode::to_der(&nonce).expect("DER encoding must succeed");
363            let decoded = Int::from_der(&encoded);
364            assert!(
365                decoded.is_ok(),
366                "nonce failed canonical DER round-trip: {:02x?} → {:?}",
367                encoded,
368                decoded.err()
369            );
370        }
371    }
372
373    #[test]
374    fn test_uint_produces_canonical_der_for_problematic_patterns() {
375        // These are the exact byte patterns that caused HTTP 400 with the
376        // old code. Encode via Uint→Int, then decode to trigger
377        // validate_canonical.
378
379        let cases: &[&[u8]] = &[
380            &[0x00, 0x35],             // leading zero unnecessary (0x35 high bit clear)
381            &[0x00, 0xFF],             // leading zero IS needed (0xFF high bit set)
382            &[0x00, 0x00, 0x42],       // two leading zeros, both unnecessary
383            &[0x00, 0x00, 0x00, 0x01], // many leading zeros
384        ];
385
386        for input in cases {
387            let uint = Uint::new(input).unwrap();
388            let int = Int::from(uint);
389            let encoded = Encode::to_der(&int).unwrap();
390            let decoded = Int::from_der(&encoded);
391            assert!(
392                decoded.is_ok(),
393                "input {:02x?} produced non-canonical DER: {:02x?} → {:?}",
394                input,
395                encoded,
396                decoded.err()
397            );
398        }
399    }
400
401    #[test]
402    fn test_pki_status() {
403        assert!(PkiStatus::try_from(0).is_ok());
404        assert!(PkiStatus::try_from(5).is_ok());
405        assert!(PkiStatus::try_from(6).is_err());
406    }
407}