cryptographic_message_syntax/
time_stamp_protocol.rs1use {
8 crate::asn1::{
9 rfc3161::{
10 MessageImprint, PkiStatus, TimeStampReq, TimeStampResp, TstInfo,
11 OID_CONTENT_TYPE_TST_INFO,
12 },
13 rfc5652::{SignedData, OID_ID_SIGNED_DATA},
14 },
15 bcder::{
16 decode::{Constructed, DecodeError, IntoSource, Source},
17 encode::Values,
18 Integer, OctetString,
19 },
20 reqwest::IntoUrl,
21 ring::rand::SecureRandom,
22 std::{convert::Infallible, ops::Deref},
23 x509_certificate::DigestAlgorithm,
24};
25
26pub const HTTP_CONTENT_TYPE_REQUEST: &str = "application/timestamp-query";
27
28pub const HTTP_CONTENT_TYPE_RESPONSE: &str = "application/timestamp-reply";
29
30#[derive(Debug)]
31pub enum TimeStampError {
32 Io(std::io::Error),
33 Reqwest(reqwest::Error),
34 Asn1Decode(DecodeError<Infallible>),
35 Http(&'static str),
36 Random,
37 NonceMismatch,
38 Unsuccessful(TimeStampResp),
39 BadResponse,
40}
41
42impl std::fmt::Display for TimeStampError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 Self::Io(e) => f.write_fmt(format_args!("I/O error: {}", e)),
46 Self::Reqwest(e) => f.write_fmt(format_args!("HTTP error: {}", e)),
47 Self::Asn1Decode(e) => f.write_fmt(format_args!("ASN.1 decode error: {}", e)),
48 Self::Http(msg) => f.write_str(msg),
49 Self::Random => f.write_str("error generating random nonce"),
50 Self::NonceMismatch => f.write_str("nonce mismatch"),
51 Self::Unsuccessful(r) => f.write_fmt(format_args!(
52 "unsuccessful Time-Stamp Protocol response: {:?}: {:?}",
53 r.status.status, r.status.status_string
54 )),
55 Self::BadResponse => f.write_str("bad server response"),
56 }
57 }
58}
59
60impl std::error::Error for TimeStampError {}
61
62impl From<std::io::Error> for TimeStampError {
63 fn from(e: std::io::Error) -> Self {
64 Self::Io(e)
65 }
66}
67
68impl From<reqwest::Error> for TimeStampError {
69 fn from(e: reqwest::Error) -> Self {
70 Self::Reqwest(e)
71 }
72}
73
74impl From<DecodeError<Infallible>> for TimeStampError {
75 fn from(e: DecodeError<Infallible>) -> Self {
76 Self::Asn1Decode(e)
77 }
78}
79
80pub struct TimeStampResponse(TimeStampResp);
85
86impl Deref for TimeStampResponse {
87 type Target = TimeStampResp;
88
89 fn deref(&self) -> &Self::Target {
90 &self.0
91 }
92}
93
94impl TimeStampResponse {
95 pub fn is_success(&self) -> bool {
97 matches!(
98 self.0.status.status,
99 PkiStatus::Granted | PkiStatus::GrantedWithMods
100 )
101 }
102
103 pub fn token_content_size(&self) -> Option<usize> {
105 self.0
106 .time_stamp_token
107 .as_ref()
108 .map(|token| token.content.len())
109 }
110
111 pub fn signed_data(&self) -> Result<Option<SignedData>, DecodeError<Infallible>> {
113 if let Some(token) = &self.0.time_stamp_token {
114 let source = token.content.clone();
115
116 if token.content_type == OID_ID_SIGNED_DATA {
117 Ok(Some(source.decode(SignedData::take_from)?))
118 } else {
119 Err(source
120 .into_source()
121 .content_err("invalid OID on signed data"))
122 }
123 } else {
124 Ok(None)
125 }
126 }
127
128 pub fn tst_info(&self) -> Result<Option<TstInfo>, DecodeError<Infallible>> {
129 if let Some(signed_data) = self.signed_data()? {
130 if signed_data.content_info.content_type == OID_CONTENT_TYPE_TST_INFO {
131 if let Some(content) = signed_data.content_info.content {
132 Ok(Some(Constructed::decode(
133 content.to_bytes(),
134 bcder::Mode::Der,
135 TstInfo::take_from,
136 )?))
137 } else {
138 Ok(None)
139 }
140 } else {
141 Ok(None)
142 }
143 } else {
144 Ok(None)
145 }
146 }
147}
148
149impl From<TimeStampResp> for TimeStampResponse {
150 fn from(resp: TimeStampResp) -> Self {
151 Self(resp)
152 }
153}
154
155pub fn time_stamp_request_http(
157 url: impl IntoUrl,
158 request: &TimeStampReq,
159) -> Result<TimeStampResponse, TimeStampError> {
160 let client = reqwest::blocking::Client::new();
161
162 let mut body = Vec::<u8>::new();
163 request
164 .encode_ref()
165 .write_encoded(bcder::Mode::Der, &mut body)?;
166
167 let response = client
168 .post(url)
169 .header("Content-Type", HTTP_CONTENT_TYPE_REQUEST)
170 .body(body)
171 .send()?;
172
173 if response.status().is_success()
174 && response.headers().get("Content-Type")
175 == Some(&reqwest::header::HeaderValue::from_static(
176 HTTP_CONTENT_TYPE_RESPONSE,
177 ))
178 {
179 let response_bytes = response.bytes()?;
180
181 let res = TimeStampResponse(Constructed::decode(
182 response_bytes.as_ref(),
183 bcder::Mode::Der,
184 TimeStampResp::take_from,
185 )?);
186
187 if res.is_success() {
189 if let Some(tst_info) = res.tst_info()? {
190 if tst_info.nonce != request.nonce {
191 return Err(TimeStampError::NonceMismatch);
192 }
193 }
194 }
195
196 Ok(res)
197 } else {
198 Err(TimeStampError::Http("bad HTTP response"))
199 }
200}
201
202pub fn time_stamp_message_http(
207 url: impl IntoUrl,
208 message: &[u8],
209 digest_algorithm: DigestAlgorithm,
210) -> Result<TimeStampResponse, TimeStampError> {
211 let mut h = digest_algorithm.digester();
212 h.update(message);
213 let digest = h.finish();
214
215 let mut random = [0u8; 8];
216 ring::rand::SystemRandom::new()
217 .fill(&mut random)
218 .map_err(|_| TimeStampError::Random)?;
219
220 let request = TimeStampReq {
221 version: Integer::from(1),
222 message_imprint: MessageImprint {
223 hash_algorithm: digest_algorithm.into(),
224 hashed_message: OctetString::new(bytes::Bytes::copy_from_slice(digest.as_ref())),
225 },
226 req_policy: None,
227 nonce: Some(Integer::from(u64::from_le_bytes(random))),
228 cert_req: Some(true),
229 extensions: None,
230 };
231
232 time_stamp_request_http(url, &request)
233}
234
235#[cfg(test)]
236mod test {
237 use super::*;
238
239 const DIGICERT_TIMESTAMP_URL: &str = "http://timestamp.digicert.com";
240
241 #[test]
242 fn verify_static() {
243 let signed_data =
244 crate::SignedData::parse_ber(include_bytes!("testdata/tsp-signed-data.der")).unwrap();
245
246 for signer in signed_data.signers() {
247 signer
248 .verify_message_digest_with_signed_data(&signed_data)
249 .unwrap();
250 signer
251 .verify_signature_with_signed_data(&signed_data)
252 .unwrap();
253 }
254 }
255
256 #[test]
257 fn simple_request() {
258 let message = b"hello, world";
259
260 let res = time_stamp_message_http(DIGICERT_TIMESTAMP_URL, message, DigestAlgorithm::Sha256)
261 .unwrap();
262
263 let signed_data = res.signed_data().unwrap().unwrap();
264 assert_eq!(
265 signed_data.content_info.content_type,
266 OID_CONTENT_TYPE_TST_INFO
267 );
268 let tst_info = res.tst_info().unwrap().unwrap();
269 assert_eq!(tst_info.version, Integer::from(1));
270
271 let parsed = crate::SignedData::try_from(&signed_data).unwrap();
272 for signer in parsed.signers() {
273 signer
274 .verify_message_digest_with_signed_data(&parsed)
275 .unwrap();
276 signer.verify_signature_with_signed_data(&parsed).unwrap();
277 }
278 }
279}