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::Rng;
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 human-readable status text (PKIFreeText: SEQUENCE OF UTF8String).
227    #[asn1(optional = "true")]
228    pub status_string: Option<Vec<String>>,
229    /// Optional failure info
230    #[asn1(optional = "true")]
231    pub fail_info: Option<BitString>,
232}
233
234impl PkiStatusInfo {
235    /// Check if the status indicates success
236    pub fn is_success(&self) -> bool {
237        self.status == PkiStatus::Granted as u8 || self.status == PkiStatus::GrantedWithMods as u8
238    }
239
240    /// Get the status as an enum
241    pub fn status_enum(&self) -> Option<PkiStatus> {
242        PkiStatus::try_from(self.status).ok()
243    }
244}
245
246/// Accuracy of the timestamp
247/// RFC 3161 Section 2.4.2
248#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
249pub struct Accuracy {
250    /// Seconds
251    #[asn1(optional = "true")]
252    pub seconds: Option<u64>,
253    /// Milliseconds (1-999)
254    #[asn1(context_specific = "0", optional = "true")]
255    pub millis: Option<u16>,
256    /// Microseconds (1-999)
257    #[asn1(context_specific = "1", optional = "true")]
258    pub micros: Option<u16>,
259}
260
261/// TSTInfo - the actual timestamp token info
262/// RFC 3161 Section 2.4.2
263#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
264pub struct TstInfo {
265    /// Version (must be 1)
266    pub version: u8,
267    /// Policy OID
268    pub policy: ObjectIdentifier,
269    /// Message imprint
270    pub message_imprint: Asn1MessageImprint,
271    /// Serial number
272    pub serial_number: Int,
273    /// Generation time
274    pub gen_time: GeneralizedTime,
275    /// Accuracy
276    #[asn1(optional = "true")]
277    pub accuracy: Option<Accuracy>,
278    /// Ordering
279    #[asn1(default = "default_false")]
280    pub ordering: bool,
281    /// Nonce
282    #[asn1(optional = "true")]
283    pub nonce: Option<Int>,
284    /// TSA name
285    #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
286    pub tsa: Option<GeneralName>,
287    /// Extensions
288    #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
289    pub extensions: Option<Extensions>,
290}
291
292impl TstInfo {
293    /// Decode from DER bytes
294    pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
295        Self::from_der(bytes)
296    }
297}
298
299/// Time-stamp response
300/// RFC 3161 Section 2.4.2
301#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
302pub struct TimeStampResp {
303    /// Status information
304    pub status: PkiStatusInfo,
305    /// Time-stamp token (CMS ContentInfo)
306    #[asn1(optional = "true")]
307    pub time_stamp_token: Option<der::Any>,
308}
309
310impl TimeStampResp {
311    /// Decode from DER bytes
312    pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
313        Self::from_der(bytes)
314    }
315
316    /// Check if the response indicates success
317    pub fn is_success(&self) -> bool {
318        self.status.is_success() && self.time_stamp_token.is_some()
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn test_message_imprint_encode() {
328        let digest = vec![0u8; 32]; // SHA-256 produces 32 bytes
329        let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
330        let der = Encode::to_der(&imprint).unwrap();
331        assert!(!der.is_empty());
332    }
333
334    #[test]
335    fn test_timestamp_req_encode() {
336        let digest = vec![0u8; 32];
337        let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
338        let req = TimeStampReq::new(imprint);
339        let der = req.to_der().unwrap();
340        assert!(!der.is_empty());
341    }
342
343    #[test]
344    fn test_timestamp_req_has_nonce() {
345        let digest = vec![0u8; 32];
346        let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
347        let req = TimeStampReq::new(imprint);
348
349        // Verify that the request has a nonce
350        assert!(
351            req.nonce.is_some(),
352            "Nonce should be automatically generated"
353        );
354    }
355
356    #[test]
357    fn test_generate_nonce_roundtrips_as_canonical_der() {
358        // Encode → decode round-trip. The der crate's Int decoder calls
359        // `validate_canonical` internally, which rejects non-minimal
360        // encodings (e.g. 0x00 0x35 — the same check Go's encoding/asn1
361        // performs). If our nonce encoding is ever non-minimal, the
362        // decode step will fail with a non-canonical error.
363        for _ in 0..1000 {
364            let nonce = generate_nonce();
365            let encoded = Encode::to_der(&nonce).expect("DER encoding must succeed");
366            let decoded = Int::from_der(&encoded);
367            assert!(
368                decoded.is_ok(),
369                "nonce failed canonical DER round-trip: {:02x?} → {:?}",
370                encoded,
371                decoded.err()
372            );
373        }
374    }
375
376    #[test]
377    fn test_uint_produces_canonical_der_for_problematic_patterns() {
378        // These are the exact byte patterns that caused HTTP 400 with the
379        // old code. Encode via Uint→Int, then decode to trigger
380        // validate_canonical.
381
382        let cases: &[&[u8]] = &[
383            &[0x00, 0x35],             // leading zero unnecessary (0x35 high bit clear)
384            &[0x00, 0xFF],             // leading zero IS needed (0xFF high bit set)
385            &[0x00, 0x00, 0x42],       // two leading zeros, both unnecessary
386            &[0x00, 0x00, 0x00, 0x01], // many leading zeros
387        ];
388
389        for input in cases {
390            let uint = Uint::new(input).unwrap();
391            let int = Int::from(uint);
392            let encoded = Encode::to_der(&int).unwrap();
393            let decoded = Int::from_der(&encoded);
394            assert!(
395                decoded.is_ok(),
396                "input {:02x?} produced non-canonical DER: {:02x?} → {:?}",
397                input,
398                encoded,
399                decoded.err()
400            );
401        }
402    }
403
404    #[test]
405    fn test_pki_status() {
406        assert!(PkiStatus::try_from(0).is_ok());
407        assert!(PkiStatus::try_from(5).is_ok());
408        assert!(PkiStatus::try_from(6).is_err());
409    }
410}