isideload_cryptographic_message_syntax/
time_stamp_protocol.rs1use {
8 crate::asn1::{
9 rfc3161::{
10 MessageImprint, OID_CONTENT_TYPE_TST_INFO, PkiStatus, TimeStampReq, TimeStampResp,
11 TstInfo,
12 },
13 rfc5652::{OID_ID_SIGNED_DATA, SignedData},
14 },
15 aws_lc_rs::rand::SecureRandom,
16 bcder::{
17 Integer, OctetString,
18 decode::{Constructed, DecodeError, IntoSource, Source},
19 encode::Values,
20 },
21 reqwest::IntoUrl,
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 match self.signed_data()? {
130 Some(signed_data) => {
131 if signed_data.content_info.content_type == OID_CONTENT_TYPE_TST_INFO {
132 if let Some(content) = signed_data.content_info.content {
133 Ok(Some(Constructed::decode(
134 content.to_bytes(),
135 bcder::Mode::Der,
136 TstInfo::take_from,
137 )?))
138 } else {
139 Ok(None)
140 }
141 } else {
142 Ok(None)
143 }
144 }
145 _ => Ok(None),
146 }
147 }
148}
149
150impl From<TimeStampResp> for TimeStampResponse {
151 fn from(resp: TimeStampResp) -> Self {
152 Self(resp)
153 }
154}
155
156pub fn time_stamp_request_http(
158 url: impl IntoUrl,
159 request: &TimeStampReq,
160) -> Result<TimeStampResponse, TimeStampError> {
161 let client = reqwest::blocking::Client::new();
162
163 let mut body = Vec::<u8>::new();
164 request
165 .encode_ref()
166 .write_encoded(bcder::Mode::Der, &mut body)?;
167
168 let response = client
169 .post(url)
170 .header("Content-Type", HTTP_CONTENT_TYPE_REQUEST)
171 .body(body)
172 .send()?;
173
174 if response.status().is_success()
175 && response.headers().get("Content-Type")
176 == Some(&reqwest::header::HeaderValue::from_static(
177 HTTP_CONTENT_TYPE_RESPONSE,
178 ))
179 {
180 let response_bytes = response.bytes()?;
181
182 let res = TimeStampResponse(Constructed::decode(
183 response_bytes.as_ref(),
184 bcder::Mode::Der,
185 TimeStampResp::take_from,
186 )?);
187
188 if res.is_success() {
190 if let Some(tst_info) = res.tst_info()? {
191 if tst_info.nonce != request.nonce {
192 return Err(TimeStampError::NonceMismatch);
193 }
194 }
195 }
196
197 Ok(res)
198 } else {
199 Err(TimeStampError::Http("bad HTTP response"))
200 }
201}
202
203pub fn time_stamp_message_http(
208 url: impl IntoUrl,
209 message: &[u8],
210 digest_algorithm: DigestAlgorithm,
211) -> Result<TimeStampResponse, TimeStampError> {
212 let mut h = digest_algorithm.digester();
213 h.update(message);
214 let digest = h.finish();
215
216 let mut random = [0u8; 8];
217 aws_lc_rs::rand::SystemRandom::new()
218 .fill(&mut random)
219 .map_err(|_| TimeStampError::Random)?;
220
221 let request = TimeStampReq {
222 version: Integer::from(1),
223 message_imprint: MessageImprint {
224 hash_algorithm: digest_algorithm.into(),
225 hashed_message: OctetString::new(bytes::Bytes::copy_from_slice(digest.as_ref())),
226 },
227 req_policy: None,
228 nonce: Some(Integer::from(u64::from_le_bytes(random))),
229 cert_req: Some(true),
230 extensions: None,
231 };
232
233 time_stamp_request_http(url, &request)
234}
235
236#[cfg(test)]
237mod test {
238 use super::*;
239
240 const DIGICERT_TIMESTAMP_URL: &str = "http://timestamp.digicert.com";
241
242 #[test]
243 fn verify_static() {
244 let signed_data =
245 crate::SignedData::parse_ber(include_bytes!("testdata/tsp-signed-data.der")).unwrap();
246
247 for signer in signed_data.signers() {
248 signer
249 .verify_message_digest_with_signed_data(&signed_data)
250 .unwrap();
251 signer
252 .verify_signature_with_signed_data(&signed_data)
253 .unwrap();
254 }
255 }
256
257 #[test]
258 fn simple_request() {
259 let message = b"hello, world";
260
261 let res = time_stamp_message_http(DIGICERT_TIMESTAMP_URL, message, DigestAlgorithm::Sha256)
262 .unwrap();
263
264 let signed_data = res.signed_data().unwrap().unwrap();
265 assert_eq!(
266 signed_data.content_info.content_type,
267 OID_CONTENT_TYPE_TST_INFO
268 );
269 let tst_info = res.tst_info().unwrap().unwrap();
270 assert_eq!(tst_info.version, Integer::from(1));
271
272 let parsed = crate::SignedData::try_from(&signed_data).unwrap();
273 for signer in parsed.signers() {
274 signer
275 .verify_message_digest_with_signed_data(&parsed)
276 .unwrap();
277 signer.verify_signature_with_signed_data(&parsed).unwrap();
278 }
279 }
280}