c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

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 }