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 }