time_stamp.rs (12840B)
1 // Copyright 2022 Adobe. All rights reserved. 2 // This file is licensed to you under the Apache License, 3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) 4 // or the MIT license (http://opensource.org/licenses/MIT), 5 // at your option. 6 7 // Unless required by applicable law or agreed to in writing, 8 // this software is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or 10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the 11 // specific language governing permissions and limitations under 12 // each license. 13 14 use async_generic::async_generic; 15 use bcder::decode::Constructed; 16 use coset::{sig_structure_data, ProtectedHeader}; 17 use serde::{Deserialize, Serialize}; 18 use x509_certificate::DigestAlgorithm::{self}; 19 20 /// Generate TimeStamp signature according to https://datatracker.ietf.org/doc/html/rfc3161 21 /// using the specified Time Authority 22 use crate::error::{Error, Result}; 23 use crate::{ 24 asn1::{ 25 rfc3161::{TimeStampResp, TstInfo, OID_CONTENT_TYPE_TST_INFO}, 26 rfc5652::{CertificateChoices::Certificate, SignedData, OID_ID_SIGNED_DATA}, 27 }, 28 hash_utils::vec_compare, 29 AsyncSigner, Signer, 30 }; 31 32 #[allow(dead_code)] 33 pub(crate) fn cose_countersign_data(data: &[u8], p_header: &ProtectedHeader) -> Vec<u8> { 34 let aad: Vec<u8> = Vec::new(); 35 36 // create sig_structure_data to be signed 37 sig_structure_data( 38 coset::SignatureContext::CounterSignature, 39 p_header.clone(), 40 None, 41 &aad, 42 data, 43 ) 44 } 45 46 #[async_generic( 47 async_signature( 48 signer: &dyn AsyncSigner, 49 data: &[u8], 50 p_header: &ProtectedHeader, 51 ))] 52 pub(crate) fn cose_timestamp_countersign( 53 signer: &dyn Signer, 54 data: &[u8], 55 p_header: &ProtectedHeader, 56 ) -> Option<Result<Vec<u8>>> { 57 // create countersignature with TimeStampReq parameters 58 // payload: data 59 // context "CounterSigner" 60 // certReq true 61 // algorithm sha256 62 63 // create sig data structure to be time stamped 64 let sd = cose_countersign_data(data, p_header); 65 66 if _sync { 67 timestamp_data(signer, &sd) 68 } else { 69 timestamp_data_async(signer, &sd).await 70 } 71 } 72 73 #[allow(dead_code)] 74 pub(crate) fn cose_sigtst_to_tstinfos( 75 sigtst_cbor: &[u8], 76 data: &[u8], 77 p_header: &ProtectedHeader, 78 ) -> Result<Vec<TstInfo>> { 79 let tst_container: TstContainer = 80 serde_cbor::from_slice(sigtst_cbor).map_err(|_err| Error::CoseTimeStampGeneration)?; 81 82 let mut tstinfos: Vec<TstInfo> = Vec::new(); 83 84 for token in &tst_container.tst_tokens { 85 let tbs = cose_countersign_data(data, p_header); 86 let tst_info = verify_timestamp(&token.val, &tbs)?; 87 tstinfos.push(tst_info); 88 } 89 90 if tstinfos.is_empty() { 91 Err(Error::NotFound) 92 } else { 93 Ok(tstinfos) 94 } 95 } 96 97 /// internal only function to work around bug in serialization of TimeStampResponse 98 /// so we just return the data directly 99 #[cfg(not(target_arch = "wasm32"))] 100 fn time_stamp_request_http( 101 url: &str, 102 headers: Option<Vec<(String, String)>>, 103 request: &crate::asn1::rfc3161::TimeStampReq, 104 ) -> Result<Vec<u8>> { 105 use std::io::Read; 106 107 use bcder::encode::Values; 108 109 const HTTP_CONTENT_TYPE_REQUEST: &str = "application/timestamp-query"; 110 const HTTP_CONTENT_TYPE_RESPONSE: &str = "application/timestamp-reply"; 111 112 let mut body = Vec::<u8>::new(); 113 request 114 .encode_ref() 115 .write_encoded(bcder::Mode::Der, &mut body)?; 116 117 let body_reader = std::io::Cursor::new(body); 118 119 let mut req = ureq::post(url); 120 121 if let Some(headers) = headers { 122 for (ref name, ref value) in headers { 123 req = req.set(name.as_str(), value.as_str()); 124 } 125 } 126 127 let response = req 128 .set("Content-Type", HTTP_CONTENT_TYPE_REQUEST) 129 .send(body_reader) 130 .map_err(|_err| Error::CoseTimeStampGeneration)?; 131 132 if response.status() == 200 && response.content_type() == HTTP_CONTENT_TYPE_RESPONSE { 133 let len = response 134 .header("Content-Length") 135 .and_then(|s| s.parse::<usize>().ok()) 136 .unwrap_or(20000); 137 138 let mut response_bytes: Vec<u8> = Vec::with_capacity(len); 139 140 response 141 .into_reader() 142 .take(1000000) 143 .read_to_end(&mut response_bytes) 144 .map_err(|_err| Error::CoseTimeStampGeneration)?; 145 146 let res = TimeStampResponse( 147 Constructed::decode(response_bytes.as_ref(), bcder::Mode::Der, |cons| { 148 TimeStampResp::take_from(cons) 149 }) 150 .map_err(|_err| Error::CoseTimeStampGeneration)?, 151 ); 152 153 // Verify nonce was reflected, if present. 154 if res.is_success() { 155 if let Some(tst_info) = res 156 .tst_info() 157 .map_err(|_err| Error::CoseTimeStampGeneration)? 158 { 159 if tst_info.nonce != request.nonce { 160 return Err(Error::CoseTimeStampGeneration); 161 } 162 } 163 } 164 165 Ok(response_bytes) 166 } else { 167 Err(Error::CoseTimeStampGeneration) 168 } 169 } 170 171 /// Send a Time-Stamp request for a given message to an HTTP URL. 172 /// 173 /// This is a wrapper around [time_stamp_request_http] that constructs the low-level 174 /// ASN.1 request object with reasonable defaults. 175 176 pub(crate) fn time_stamp_message_http( 177 message: &[u8], 178 digest_algorithm: DigestAlgorithm, 179 ) -> Result<crate::asn1::rfc3161::TimeStampReq> { 180 use rand::{thread_rng, Rng}; 181 182 let mut h = digest_algorithm.digester(); 183 h.update(message); 184 let digest = h.finish(); 185 186 let mut random = [0u8; 8]; 187 thread_rng() 188 .try_fill(&mut random) 189 .map_err(|_| Error::CoseTimeStampGeneration)?; 190 191 let request = crate::asn1::rfc3161::TimeStampReq { 192 version: bcder::Integer::from(1_u8), 193 message_imprint: crate::asn1::rfc3161::MessageImprint { 194 hash_algorithm: digest_algorithm.into(), 195 hashed_message: bcder::OctetString::new(bytes::Bytes::copy_from_slice(digest.as_ref())), 196 }, 197 req_policy: None, 198 nonce: Some(bcder::Integer::from(u64::from_le_bytes(random))), 199 cert_req: Some(true), 200 extensions: None, 201 }; 202 203 Ok(request) 204 } 205 206 pub struct TimeStampResponse(TimeStampResp); 207 208 impl std::ops::Deref for TimeStampResponse { 209 type Target = TimeStampResp; 210 211 fn deref(&self) -> &Self::Target { 212 &self.0 213 } 214 } 215 216 impl TimeStampResponse { 217 /// Whether the time stamp request was successful. 218 #[cfg(not(target_arch = "wasm32"))] 219 pub const fn is_success(&self) -> bool { 220 matches!( 221 self.0.status.status, 222 crate::asn1::rfc3161::PkiStatus::Granted 223 | crate::asn1::rfc3161::PkiStatus::GrantedWithMods 224 ) 225 } 226 227 fn signed_data(&self) -> Result<Option<SignedData>> { 228 if let Some(token) = &self.0.time_stamp_token { 229 if token.content_type == OID_ID_SIGNED_DATA { 230 Ok(Some( 231 token 232 .content 233 .clone() 234 .decode(SignedData::take_from) 235 .map_err(|_err| Error::CoseTimeStampGeneration)?, 236 )) 237 } else { 238 Err(Error::CoseTimeStampGeneration) 239 } 240 } else { 241 Ok(None) 242 } 243 } 244 245 fn tst_info(&self) -> Result<Option<TstInfo>> { 246 if let Some(signed_data) = self.signed_data()? { 247 if signed_data.content_info.content_type == OID_CONTENT_TYPE_TST_INFO { 248 if let Some(content) = signed_data.content_info.content { 249 Ok(Some( 250 Constructed::decode(content.to_bytes(), bcder::Mode::Der, |cons| { 251 TstInfo::take_from(cons) 252 }) 253 .map_err(|_err| Error::CoseTimeStampGeneration)?, 254 )) 255 } else { 256 Ok(None) 257 } 258 } else { 259 Ok(None) 260 } 261 } else { 262 Ok(None) 263 } 264 } 265 } 266 267 /// Generate TimeStamp based on rfc3161 using "data" as MessageImprint and return raw TimeStampRsp bytes 268 #[async_generic(async_signature(signer: &dyn AsyncSigner, data: &[u8]))] 269 pub fn timestamp_data(signer: &dyn Signer, data: &[u8]) -> Option<Result<Vec<u8>>> { 270 if _sync { 271 signer.send_timestamp_request(data) 272 } else { 273 signer.send_timestamp_request(data).await 274 // TO DO: Fix bug in async_generic. This .await 275 // should be automatically removed. 276 } 277 } 278 279 #[cfg(not(target_arch = "wasm32"))] 280 pub fn default_rfc3161_request( 281 url: &str, 282 headers: Option<Vec<(String, String)>>, 283 data: &[u8], 284 message: &[u8], 285 ) -> Result<Vec<u8>> { 286 use crate::asn1::rfc3161::TimeStampReq; 287 let request = Constructed::decode( 288 bcder::decode::SliceSource::new(data), 289 bcder::Mode::Der, 290 TimeStampReq::take_from, 291 ) 292 .map_err(|_err| Error::CoseTimeStampGeneration)?; 293 294 let ts = time_stamp_request_http(url, headers, &request)?; 295 296 // sanity check 297 verify_timestamp(&ts, message)?; 298 299 Ok(ts) 300 } 301 302 #[allow(unused_variables)] 303 pub fn default_rfc3161_message(data: &[u8]) -> Result<Vec<u8>> { 304 use bcder::encode::Values; 305 let request = time_stamp_message_http(data, x509_certificate::DigestAlgorithm::Sha256)?; 306 307 let mut body = Vec::<u8>::new(); 308 request 309 .encode_ref() 310 .write_encoded(bcder::Mode::Der, &mut body)?; 311 Ok(body) 312 } 313 314 pub fn gt_to_datetime( 315 gt: x509_certificate::asn1time::GeneralizedTime, 316 ) -> chrono::DateTime<chrono::Utc> { 317 gt.into() 318 } 319 fn time_to_datetime(t: x509_certificate::asn1time::Time) -> chrono::DateTime<chrono::Utc> { 320 match t { 321 x509_certificate::asn1time::Time::UtcTime(u) => *u, 322 x509_certificate::asn1time::Time::GeneralTime(gt) => gt_to_datetime(gt), 323 } 324 } 325 /// Returns TimeStamp token info if ts verifies against supplied data 326 pub fn verify_timestamp(ts: &[u8], data: &[u8]) -> Result<TstInfo> { 327 let ts_resp = get_timestamp_response(ts)?; 328 329 // make sure this signature matches the expected data 330 let tst_opt = ts_resp.tst_info()?; 331 let tst = tst_opt.ok_or(Error::CoseInvalidTimeStamp)?; 332 let mi = &tst.message_imprint; 333 334 let digest_algorithm = DigestAlgorithm::try_from(&mi.hash_algorithm.algorithm) 335 .map_err(|_e| Error::UnsupportedType)?; 336 337 let mut h = digest_algorithm.digester(); 338 h.update(data); 339 let digest = h.finish(); 340 341 if !vec_compare(digest.as_ref(), &mi.hashed_message.to_bytes()) { 342 return Err(Error::CoseTimeStampMismatch); 343 } 344 345 // check for timestamp expiration during stamping 346 if let Ok(Some(sd)) = ts_resp.signed_data() { 347 if let Some(cs) = sd.certificates { 348 if !cs.is_empty() { 349 let cert = match &cs[0] { 350 Certificate(c) => c, 351 _ => return Err(Error::CoseTimeStampValidity), 352 }; 353 354 let signing_time = gt_to_datetime(tst.gen_time.clone()).timestamp(); 355 let not_before = 356 time_to_datetime(cert.tbs_certificate.validity.not_before.clone()).timestamp(); 357 358 let not_after = 359 time_to_datetime(cert.tbs_certificate.validity.not_after.clone()).timestamp(); 360 361 if !(signing_time >= not_before && signing_time <= not_after) { 362 return Err(Error::CoseTimeStampValidity); 363 } 364 } 365 } 366 } 367 368 Ok(tst) 369 } 370 371 /// Get TimeStampResponse from DER TimeStampResp bytes 372 pub fn get_timestamp_response(tsresp: &[u8]) -> Result<TimeStampResponse> { 373 let ts = TimeStampResponse( 374 Constructed::decode(tsresp, bcder::Mode::Der, |cons| { 375 TimeStampResp::take_from(cons) 376 }) 377 .map_err(|_e| Error::CoseInvalidTimeStamp)?, 378 ); 379 380 Ok(ts) 381 } 382 383 #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] 384 pub struct TstToken { 385 #[serde(with = "serde_bytes")] 386 pub val: Vec<u8>, 387 } 388 389 #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] 390 pub struct TstContainer { 391 #[serde(rename = "tstTokens")] 392 pub tst_tokens: Vec<TstToken>, 393 } 394 395 impl TstContainer { 396 pub const fn new() -> Self { 397 TstContainer { 398 tst_tokens: Vec::new(), 399 } 400 } 401 402 pub fn add_token(&mut self, token: TstToken) { 403 self.tst_tokens.push(token); 404 } 405 } 406 407 impl Default for TstContainer { 408 fn default() -> Self { 409 Self::new() 410 } 411 } 412 413 /// Wrap rfc3161 TimeStampRsp in COSE sigTst object 414 pub fn make_cose_timestamp(ts_data: &[u8]) -> TstContainer { 415 let token = TstToken { 416 val: ts_data.to_vec(), 417 }; 418 419 let mut container = TstContainer::new(); 420 container.add_token(token); 421 422 container 423 }