diff --git a/crates/sign/src/timestamp.rs b/crates/sign/src/timestamp.rs index 58eda4f049..0eba6bb72c 100644 --- a/crates/sign/src/timestamp.rs +++ b/crates/sign/src/timestamp.rs @@ -62,6 +62,8 @@ pub struct TimestampToken { pub policy: String, pub serial: Vec, pub gen_time: Time, + /// The TSTInfo nonce, when it has one that fits a `u64` (the size requests use). + pub nonce: Option, } impl TimestampToken { @@ -140,6 +142,9 @@ pub fn parse_token(raw: &[u8]) -> Result { .to_vec(); let serial = fields.get(3).ok_or_else(|| bad("TSTInfo has no serial number"))?.uint_bytes().to_vec(); let gen_time = fields.get(4).ok_or_else(|| bad("TSTInfo has no generation time"))?.time()?; + // The nonce is the only INTEGER after genTime (RFC 3161 §2.4.2): accuracy is a SEQUENCE and + // ordering a BOOLEAN. + let nonce = fields.iter().skip(5).find(|f| f.tag == tag::INTEGER).and_then(|f| f.u64().ok()); // The signed messageDigest uses the SignerInfo's digest algorithm, which need not be the // imprint's (RFC 5652 §5.4). let content_digest = token.signer.digest.digest(&[info]); @@ -151,7 +156,7 @@ pub fn parse_token(raw: &[u8]) -> Result { if !token.verify_signature(cert, &content_digest)? { return Err(bad("TSA signature is invalid")); } - Ok(TimestampToken { raw: raw.to_vec(), digest, imprint, policy, serial, gen_time }) + Ok(TimestampToken { raw: raw.to_vec(), digest, imprint, policy, serial, gen_time, nonce }) } /// Parse and validate a `TimeStampResp`, including the CMS signature and message imprint. @@ -170,6 +175,12 @@ pub fn parse_response(bytes: &[u8], query: &TimestampQuery) -> Result, policy: Option<&str>| { + let mut q = TimestampQuery::new(alg, alg.digest(&[b"document"])).unwrap(); + (q.nonce, q.policy) = (nonce, policy.map(str::to_string)); + q + }; + let answer = |q: &TimestampQuery| respond(&tsa.key, &tsa.certificate, &tsa.chain, alg, q, "1.2.3.4", time, 9).unwrap(); + let with_nonce = answer(&query(Some(42), None)); + assert!(parse_response(&with_nonce, &query(Some(42), Some("1.2.3.4"))).is_ok()); + assert!(parse_response(&with_nonce, &query(None, None)).is_ok()); + assert!(parse_response(&with_nonce, &query(Some(43), None)).is_err(), "another request's nonce"); + assert!(parse_response(&with_nonce, &query(Some(42), Some("1.2.3.5"))).is_err(), "another policy"); + let without_nonce = answer(&query(None, None)); + assert!(parse_response(&without_nonce, &query(Some(42), None)).is_err(), "no nonce in the token"); +} + #[cfg(windows)] #[test] fn windows_store_enumeration_and_missing_identity() {