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

ocsp_utils.rs (20582B)


      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 chrono::{DateTime, NaiveDateTime, Utc};
     15 use conv::ConvUtil;
     16 use rasn_ocsp::{BasicOcspResponse, CertStatus, OcspResponse, OcspResponseStatus};
     17 use rasn_pkix::CrlReason;
     18 
     19 use crate::{
     20     status_tracker::{log_item, DetailedStatusTracker, StatusTracker},
     21     validation_status, Error, Result,
     22 };
     23 
     24 /// OcspData - struct to contain the OCSPResponse DER and the time
     25 /// for the next OCSP check
     26 pub(crate) struct OcspData {
     27     pub ocsp_der: Vec<u8>,
     28     pub next_update: DateTime<Utc>,
     29     pub revoked_at: Option<DateTime<Utc>>,
     30     pub ocsp_certs: Option<Vec<Vec<u8>>>,
     31 }
     32 
     33 impl OcspData {
     34     pub fn new() -> Self {
     35         OcspData {
     36             ocsp_der: Vec::new(),
     37             next_update: Utc::now(),
     38             revoked_at: None,
     39             ocsp_certs: None,
     40         }
     41     }
     42 }
     43 
     44 impl Default for OcspData {
     45     fn default() -> Self {
     46         Self {
     47             ocsp_der: Vec::new(),
     48             next_update: Utc::now(),
     49             revoked_at: None,
     50             ocsp_certs: None,
     51         }
     52     }
     53 }
     54 
     55 #[cfg(not(target_arch = "wasm32"))]
     56 fn extract_aia_responders(cert: &x509_parser::certificate::X509Certificate) -> Option<Vec<String>> {
     57     use x509_parser::der_parser::{oid, Oid};
     58 
     59     const AD_OCSP_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .48 .1);
     60     const AUTHORITY_INFO_ACCESS_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .1 .1);
     61 
     62     let em = cert.extensions_map().ok()?;
     63 
     64     let aia_extension = em.get(&AUTHORITY_INFO_ACCESS_OID)?;
     65 
     66     match aia_extension.parsed_extension() {
     67         x509_parser::extensions::ParsedExtension::AuthorityInfoAccess(aia) => {
     68             let mut output = Vec::new();
     69 
     70             for ad in &aia.accessdescs {
     71                 if let x509_parser::extensions::GeneralName::URI(uri) = ad.access_location {
     72                     if ad.access_method == AD_OCSP_OID {
     73                         output.push(uri.to_string())
     74                     }
     75                 }
     76             }
     77             Some(output)
     78         }
     79         _ => None,
     80     }
     81 }
     82 
     83 /// Check the supplied cert chain for an OCSP responder in the end-entity cert.  If found it will attempt to
     84 /// retrieve the OCSPResponse.  If successful returns OcspData containing the DER encoded OCSPResponse and
     85 /// the DateTime for when this cached response should be refreshed, and the OCSP signer certificate chain.  
     86 /// None otherwise.
     87 #[cfg(not(target_arch = "wasm32"))]
     88 pub(crate) fn fetch_ocsp_response(certs: &[Vec<u8>]) -> Option<Vec<u8>> {
     89     use std::io::Read;
     90 
     91     use rasn::prelude::*;
     92     use rasn_pkix::Certificate;
     93     use x509_parser::prelude::*;
     94 
     95     // must have minimal chain in hierarchical order
     96     if certs.len() < 2 {
     97         return None;
     98     }
     99 
    100     let (_rem, cert) = X509Certificate::from_der(&certs[0]).ok()?;
    101 
    102     if let Some(responders) = extract_aia_responders(&cert) {
    103         let sha1_oid = rasn::types::Oid::new(&[1, 3, 14, 3, 2, 26])?; // Sha1 Oid
    104         let alg = rasn::types::ObjectIdentifier::from(sha1_oid);
    105 
    106         let sha1_ai = rasn_pkix::AlgorithmIdentifier {
    107             algorithm: alg,
    108             parameters: Some(Any::new(rasn::der::encode(&()).ok()?)), /* many OCSP responders expect this to be NULL not None */
    109         };
    110 
    111         for r in responders {
    112             let url = url::Url::parse(&r).ok()?;
    113             let subject: Certificate = rasn::der::decode(&certs[0]).ok()?;
    114             let issuer: Certificate = rasn::der::decode(&certs[1]).ok()?;
    115 
    116             let issuer_name_raw = rasn::der::encode(&issuer.tbs_certificate.subject).ok()?;
    117             let issuer_key_raw = &issuer
    118                 .tbs_certificate
    119                 .subject_public_key_info
    120                 .subject_public_key
    121                 .as_raw_slice();
    122 
    123             let issuer_name_hash =
    124                 OctetString::from(crate::hash_utils::hash_sha1(&issuer_name_raw));
    125             let issuer_key_hash = OctetString::from(crate::hash_utils::hash_sha1(issuer_key_raw));
    126             let serial_number = subject.tbs_certificate.serial_number;
    127 
    128             // build request structures
    129 
    130             let req_cert = rasn_ocsp::CertId {
    131                 hash_algorithm: sha1_ai.clone(),
    132                 issuer_name_hash,
    133                 issuer_key_hash,
    134                 serial_number,
    135             };
    136 
    137             let ocsp_req = rasn_ocsp::Request {
    138                 req_cert,
    139                 single_request_extensions: None,
    140             };
    141 
    142             let request_list = vec![ocsp_req];
    143 
    144             let tbs_request = rasn_ocsp::TbsRequest {
    145                 version: rasn_ocsp::Version::parse_bytes(b"0", 16)?,
    146                 requestor_name: None,
    147                 request_list,
    148                 request_extensions: None,
    149             };
    150 
    151             let ocsp_request = rasn_ocsp::OcspRequest {
    152                 tbs_request,
    153                 optional_signature: None,
    154             };
    155 
    156             // build query param
    157             let request_der = rasn::der::encode(&ocsp_request).ok()?;
    158             let request_str = crate::utils::base64::encode(&request_der);
    159 
    160             let req_url = url.join(&request_str).ok()?;
    161 
    162             // fetch OCSP response
    163             let request = ureq::get(req_url.as_str());
    164             let response = if let Some(host) = url.host() {
    165                 request.set("Host", &host.to_string()).call().ok()? // for responders that don't support http 1.0
    166             } else {
    167                 request.call().ok()?
    168             };
    169 
    170             if response.status() == 200 {
    171                 let len = response
    172                     .header("Content-Length")
    173                     .and_then(|s| s.parse::<usize>().ok())
    174                     .unwrap_or(10000);
    175 
    176                 let mut ocsp_rsp: Vec<u8> = Vec::with_capacity(len);
    177 
    178                 response
    179                     .into_reader()
    180                     .take(1000000)
    181                     .read_to_end(&mut ocsp_rsp)
    182                     .ok()?;
    183 
    184                 return Some(ocsp_rsp);
    185             }
    186         }
    187     }
    188     None
    189 }
    190 // check to OCSP response with optional signing time (if available)
    191 // Returns - returns OcspData unless their is a structural error in the response.
    192 pub(crate) fn check_ocsp_response(
    193     ocsp_response_der: &[u8],
    194     signing_time: Option<DateTime<Utc>>,
    195     validation_log_out: &mut impl StatusTracker,
    196 ) -> Result<OcspData> {
    197     const DATE_FMT: &str = "%Y-%m-%d %H:%M:%S %Z";
    198 
    199     let mut validation_log = DetailedStatusTracker::default();
    200 
    201     let mut output = OcspData::new();
    202     output.ocsp_der = ocsp_response_der.to_vec();
    203     let mut found_good = false;
    204 
    205     if let Ok(ocsp_response) = rasn::der::decode::<OcspResponse>(ocsp_response_der) {
    206         if ocsp_response.status == OcspResponseStatus::Successful {
    207             if let Some(response_bytes) = ocsp_response.bytes {
    208                 if let Ok(basic_response) =
    209                     rasn::der::decode::<BasicOcspResponse>(&response_bytes.response)
    210                 {
    211                     let response_data = &basic_response.tbs_response_data;
    212 
    213                     // get OCSP cert chain if available
    214                     if let Some(ocsp_certs) = &basic_response.certs {
    215                         let mut cert_der_vec = Vec::new();
    216 
    217                         for ocsp_cert in ocsp_certs {
    218                             let cert_der = rasn::der::encode(ocsp_cert)
    219                                 .map_err(|_e| Error::CoseInvalidCert)?;
    220                             cert_der_vec.push(cert_der);
    221                         }
    222 
    223                         if output.ocsp_certs.is_none() {
    224                             output.ocsp_certs = Some(cert_der_vec);
    225                         }
    226                     }
    227 
    228                     for single_response in &response_data.responses {
    229                         let cert_status = &single_response.cert_status;
    230 
    231                         match cert_status {
    232                             CertStatus::Good => {
    233                                 // check cert range against signing time
    234                                 let this_update = NaiveDateTime::parse_from_str(
    235                                     &single_response.this_update.to_string(),
    236                                     DATE_FMT,
    237                                 )
    238                                 .map_err(|_e| Error::CoseInvalidCert)?
    239                                 .and_utc()
    240                                 .timestamp();
    241 
    242                                 let next_update = if let Some(nu) = &single_response.next_update {
    243                                     NaiveDateTime::parse_from_str(&nu.to_string(), DATE_FMT)
    244                                         .map_err(|_e| Error::CoseInvalidCert)?
    245                                         .and_utc()
    246                                         .timestamp()
    247                                 } else {
    248                                     this_update
    249                                 };
    250 
    251                                 // check to see if we are within range or current time within range
    252                                 let in_range = if let Some(st) = signing_time {
    253                                     st.timestamp() < this_update
    254                                         || (st.timestamp() >= this_update
    255                                             && st.timestamp() <= next_update)
    256                                 } else {
    257                                     // no timestamp so check against current time
    258                                     // use instant to avoid wasm issues
    259                                     let now_f64 = instant::now() / 1000.0;
    260                                     let now: i64 = now_f64.approx_as().map_err(|_e| {
    261                                         Error::BadParam("system time invalid".to_string())
    262                                     })?;
    263 
    264                                     now >= this_update && now <= next_update
    265                                 };
    266 
    267                                 if let Some(nu) = &single_response.next_update {
    268                                     let nu_utc = nu.naive_utc();
    269                                     output.next_update =
    270                                         DateTime::from_naive_utc_and_offset(nu_utc, Utc);
    271                                 }
    272 
    273                                 if !in_range {
    274                                     let log_item = log_item!(
    275                                         "OCSP_RESPONSE",
    276                                         "certificate revoked",
    277                                         "check_ocsp_response"
    278                                     )
    279                                     .error(Error::CoseCertRevoked)
    280                                     .validation_status(
    281                                         validation_status::SIGNING_CREDENTIAL_REVOKED,
    282                                     );
    283                                     validation_log.log_silent(log_item);
    284                                 } else {
    285                                     found_good = true;
    286                                     break; // found good match so break
    287                                 }
    288                             }
    289                             CertStatus::Revoked(revoked_info) => {
    290                                 if let Some(reason) = revoked_info.revocation_reason {
    291                                     if reason == CrlReason::RemoveFromCRL {
    292                                         // if it was revoked check if was revoked after signing time
    293                                         let revocation_time = &revoked_info.revocation_time;
    294                                         // check cert range against signing time
    295                                         let revoked_at = NaiveDateTime::parse_from_str(
    296                                             &revocation_time.to_string(),
    297                                             DATE_FMT,
    298                                         )
    299                                         .map_err(|_e| Error::CoseInvalidCert)?
    300                                         .and_utc()
    301                                         .timestamp();
    302 
    303                                         // check to see if we are within range or current time within range
    304                                         let in_range = if let Some(st) = signing_time {
    305                                             revoked_at > st.timestamp()
    306                                         } else {
    307                                             // no timestamp so check against current time
    308                                             // use instant to avoid wasm issues
    309                                             let now_f64 = instant::now() / 1000.0;
    310                                             let now: i64 = now_f64.approx_as().map_err(|_e| {
    311                                                 Error::BadParam("system time invalid".to_string())
    312                                             })?;
    313 
    314                                             revoked_at > now
    315                                         };
    316 
    317                                         if !in_range {
    318                                             let revoked_at_native = NaiveDateTime::parse_from_str(
    319                                                 &revocation_time.to_string(),
    320                                                 DATE_FMT,
    321                                             )
    322                                             .map_err(|_e| Error::CoseInvalidCert)?;
    323 
    324                                             let utc_with_offset: DateTime<Utc> =
    325                                                 DateTime::from_naive_utc_and_offset(
    326                                                     revoked_at_native,
    327                                                     Utc,
    328                                                 );
    329 
    330                                             let msg = format!(
    331                                                 "certificate revoked at: {}",
    332                                                 utc_with_offset
    333                                             );
    334                                             let log_item = log_item!(
    335                                                 "OCSP_RESPONSE",
    336                                                 &msg,
    337                                                 "check_ocsp_response"
    338                                             )
    339                                             .error(Error::CoseCertRevoked)
    340                                             .validation_status(
    341                                                 validation_status::SIGNING_CREDENTIAL_REVOKED,
    342                                             );
    343                                             validation_log.log_silent(log_item);
    344 
    345                                             output.revoked_at =
    346                                                 Some(DateTime::from_naive_utc_and_offset(
    347                                                     revoked_at_native,
    348                                                     Utc,
    349                                                 ));
    350                                         }
    351                                     } else {
    352                                         let revoked_at_native = NaiveDateTime::parse_from_str(
    353                                             &revoked_info.revocation_time.to_string(),
    354                                             DATE_FMT,
    355                                         )
    356                                         .map_err(|_e| Error::CoseInvalidCert)?;
    357 
    358                                         let utc_with_offset: DateTime<Utc> =
    359                                             DateTime::from_naive_utc_and_offset(
    360                                                 revoked_at_native,
    361                                                 Utc,
    362                                             );
    363 
    364                                         // check to see if cert was signed before revocation
    365                                         let in_range = if let Some(st) = signing_time {
    366                                             st.timestamp() < utc_with_offset.timestamp()
    367                                         } else {
    368                                             false
    369                                         };
    370 
    371                                         if !in_range {
    372                                             let msg = format!(
    373                                                 "certificate revoked at: {}",
    374                                                 utc_with_offset
    375                                             );
    376                                             let log_item = log_item!(
    377                                                 "OCSP_RESPONSE",
    378                                                 &msg,
    379                                                 "check_ocsp_response"
    380                                             )
    381                                             .error(Error::CoseCertRevoked)
    382                                             .validation_status(
    383                                                 validation_status::SIGNING_CREDENTIAL_REVOKED,
    384                                             );
    385                                             validation_log.log_silent(log_item);
    386 
    387                                             output.revoked_at =
    388                                                 Some(DateTime::from_naive_utc_and_offset(
    389                                                     revoked_at_native,
    390                                                     Utc,
    391                                                 ));
    392                                         } else {
    393                                             found_good = true;
    394                                             break; // found good match so break
    395                                         }
    396                                     }
    397                                 } else {
    398                                     let log_item = log_item!(
    399                                         "OCSP_RESPONSE",
    400                                         "certificate revoked",
    401                                         "check_ocsp_response"
    402                                     )
    403                                     .error(Error::CoseCertRevoked)
    404                                     .validation_status(
    405                                         validation_status::SIGNING_CREDENTIAL_REVOKED,
    406                                     );
    407                                     validation_log.log_silent(log_item);
    408                                 }
    409                             }
    410                             CertStatus::Unknown(_) => return Err(Error::UnsupportedType), /* noop for this case */
    411                         }
    412                     }
    413                 }
    414             }
    415         }
    416     }
    417     // Per the spec if we cannot interpret the OCSP data treat it as if it did not exist
    418     if !found_good {
    419         validation_log_out
    420             .get_log_mut()
    421             .append(validation_log.get_log_mut());
    422     }
    423 
    424     Ok(output)
    425 }
    426 
    427 #[cfg(test)]
    428 pub mod tests {
    429     #![allow(clippy::panic)]
    430     #![allow(clippy::unwrap_used)]
    431 
    432     use chrono::TimeZone;
    433 
    434     use super::*;
    435     use crate::status_tracker::report_split_errors;
    436     #[test]
    437     fn test_good_response() {
    438         let rsp_data = include_bytes!("../tests/fixtures/ocsp_good.data");
    439 
    440         let mut validation_log = DetailedStatusTracker::default();
    441 
    442         let test_time = Utc.with_ymd_and_hms(2023, 2, 1, 8, 0, 0).unwrap();
    443 
    444         let ocsp_data =
    445             check_ocsp_response(rsp_data, Some(test_time), &mut validation_log).unwrap();
    446 
    447         assert!(ocsp_data.revoked_at.is_none());
    448         assert!(ocsp_data.ocsp_certs.is_some());
    449     }
    450 
    451     #[test]
    452     fn test_revoked_response() {
    453         let rsp_data = include_bytes!("../tests/fixtures/ocsp_revoked.data");
    454 
    455         let mut validation_log = DetailedStatusTracker::default();
    456 
    457         let test_time = Utc.with_ymd_and_hms(2024, 2, 1, 8, 0, 0).unwrap();
    458 
    459         let ocsp_data =
    460             check_ocsp_response(rsp_data, Some(test_time), &mut validation_log).unwrap();
    461 
    462         let errors = report_split_errors(validation_log.get_log_mut());
    463 
    464         assert!(ocsp_data.revoked_at.is_some());
    465         assert!(!errors.is_empty());
    466     }
    467 }