1use 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
15pub const OID_SHA256: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_256;
17
18pub const OID_SHA384: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_384;
20
21pub const OID_SHA512: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_512;
23
24pub const OID_TST_INFO: ObjectIdentifier =
26 ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.16.1.4");
27
28pub 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#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
41pub struct AlgorithmIdentifier {
42 pub algorithm: ObjectIdentifier,
44 #[asn1(optional = "true")]
46 pub parameters: Option<der::Any>,
47}
48
49impl AlgorithmIdentifier {
50 pub fn sha256() -> Self {
52 Self {
53 algorithm: OID_SHA256,
54 parameters: None,
55 }
56 }
57
58 pub fn sha384() -> Self {
60 Self {
61 algorithm: OID_SHA384,
62 parameters: None,
63 }
64 }
65
66 pub fn sha512() -> Self {
68 Self {
69 algorithm: OID_SHA512,
70 parameters: None,
71 }
72 }
73
74 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#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
102pub struct Asn1MessageImprint {
103 pub hash_algorithm: AlgorithmIdentifier,
105 pub hashed_message: OctetString,
107}
108
109impl Asn1MessageImprint {
110 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#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
122pub struct TimeStampReq {
123 pub version: u8,
125 pub message_imprint: Asn1MessageImprint,
127 #[asn1(optional = "true")]
129 pub req_policy: Option<ObjectIdentifier>,
130 #[asn1(optional = "true")]
132 pub nonce: Option<Int>,
133 #[asn1(default = "default_false")]
135 pub cert_req: bool,
136 }
138
139fn default_false() -> bool {
140 false
141}
142
143impl TimeStampReq {
144 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 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 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 pub fn with_cert_req(mut self, cert_req: bool) -> Self {
175 self.cert_req = cert_req;
176 self
177 }
178
179 pub fn to_der(&self) -> Result<Vec<u8>, der::Error> {
181 Encode::to_der(self)
182 }
183}
184
185#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188#[repr(u8)]
189pub enum PkiStatus {
190 Granted = 0,
192 GrantedWithMods = 1,
194 Rejection = 2,
196 Waiting = 3,
198 RevocationWarning = 4,
200 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#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
223pub struct PkiStatusInfo {
224 pub status: u8,
226 #[asn1(optional = "true")]
228 pub fail_info: Option<BitString>,
229}
230
231impl PkiStatusInfo {
232 pub fn is_success(&self) -> bool {
234 self.status == PkiStatus::Granted as u8 || self.status == PkiStatus::GrantedWithMods as u8
235 }
236
237 pub fn status_enum(&self) -> Option<PkiStatus> {
239 PkiStatus::try_from(self.status).ok()
240 }
241}
242
243#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
246pub struct Accuracy {
247 #[asn1(optional = "true")]
249 pub seconds: Option<u64>,
250 #[asn1(context_specific = "0", optional = "true")]
252 pub millis: Option<u16>,
253 #[asn1(context_specific = "1", optional = "true")]
255 pub micros: Option<u16>,
256}
257
258#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
261pub struct TstInfo {
262 pub version: u8,
264 pub policy: ObjectIdentifier,
266 pub message_imprint: Asn1MessageImprint,
268 pub serial_number: Int,
270 pub gen_time: GeneralizedTime,
272 #[asn1(optional = "true")]
274 pub accuracy: Option<Accuracy>,
275 #[asn1(default = "default_false")]
277 pub ordering: bool,
278 #[asn1(optional = "true")]
280 pub nonce: Option<Int>,
281 #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
283 pub tsa: Option<GeneralName>,
284 #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
286 pub extensions: Option<Extensions>,
287}
288
289impl TstInfo {
290 pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
292 Self::from_der(bytes)
293 }
294}
295
296#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
299pub struct TimeStampResp {
300 pub status: PkiStatusInfo,
302 #[asn1(optional = "true")]
304 pub time_stamp_token: Option<der::Any>,
305}
306
307impl TimeStampResp {
308 pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
310 Self::from_der(bytes)
311 }
312
313 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]; 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 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 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 let cases: &[&[u8]] = &[
380 &[0x00, 0x35], &[0x00, 0xFF], &[0x00, 0x00, 0x42], &[0x00, 0x00, 0x00, 0x01], ];
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}