// SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2023 use core::slice; use std::path::Path; use helper::{download_first_crl_from_x509, StoreSetupMode}; use log::{debug, trace}; use openssl::error::ErrorStack; use openssl::stack::Stack; use openssl::x509::store::X509Store; use openssl::x509::{CrlStatus, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, X509}; use crate::error::bail_hkd_verify; use crate::misc::{read_certs, read_file}; use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension}; use crate::Result; mod helper; mod test; /// A `HkdVerifier` verifies that a host-key document(HKD) can be trusted. /// /// If the verification fails the HKD should not be used to create requests. pub trait HkdVerifier { /// Checks if the given host-key document can be trusted. /// /// # Errors /// /// This function will return an error if the host-key document cannot be /// trusted. Refer to the concrete Error type for the specific reason. fn verify(&self, hkd: &X509Ref) -> Result<()>; } /// A verifier that does not verify and accepts all given host-keys as valid. #[derive(Debug)] pub struct NoVerifyHkd; impl HkdVerifier for NoVerifyHkd { fn verify(&self, _hkd: &X509Ref) -> Result<()> { Ok(()) } } /// A verifier that checks the host-key document against a chain of trust. pub struct CertVerifier { store: X509Store, ibm_z_sign_key: X509, offline: bool, } impl std::fmt::Debug for CertVerifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("CertVerifier") } } impl HkdVerifier for CertVerifier { /// This function verifies a host-key /// document. To do so multiple steps are required: /// /// 1. `issuer(host_key`) == `subject(ibm_z_sign_key`) /// 2. Signature verification /// 3. @hkd must not be expired /// 4. @hkd must not be revoked fn verify(&self, hkd: &X509Ref) -> Result<()> { helper::verify_hkd_options(hkd, &self.ibm_z_sign_key)?; // verify that the HKD was signed with the key of the IBM signing key if !hkd.verify(self.ibm_z_sign_key.public_key()?.as_ref())? { bail_hkd_verify!(Signature); } // Find matching CRL for sign key in the store or download them let crls = self.hkd_crls(hkd)?; // Verify that the CRLs are still valid let mut verified_crls = Vec::with_capacity(crls.len()); for crl in &crls { if helper::verify_crl(crl, &self.ibm_z_sign_key).is_some() { verified_crls.push(crl.to_owned()); } } // Test if HKD was revoked (min1 required) if verified_crls.is_empty() { bail_hkd_verify!(NoCrl); } for crl in verified_crls { match crl.get_by_serial(hkd.serial_number()) { CrlStatus::NotRevoked => (), _ => bail_hkd_verify!(HkdRevoked), } } debug!("HKD: verified"); Ok(()) } } impl CertVerifier { fn quirk_crls( ctx: &mut X509StoreContextRef, subject: &X509NameRef, ) -> Result, ErrorStack> { match ctx.crls(subject) { Ok(ret) if !ret.is_empty() => return Ok(ret), _ => (), } // Armonk/Poughkeepsie fixup trace!("quirk_crls: Try Locality"); if let Some(locality_subject) = helper::armonk_locality_fixup(subject) { match ctx.crls(&locality_subject) { Ok(ret) if !ret.is_empty() => return Ok(ret), _ => (), } // reorder trace!("quirk_crls: Try Locality+Reorder"); if let Ok(locality_ordered_subject) = helper::reorder_x509_names(&locality_subject) { match ctx.crls(&locality_ordered_subject) { Ok(ret) if !ret.is_empty() => return Ok(ret), _ => (), } } } // reorder unchanged locality subject trace!("quirk_crls: Try Reorder"); if let Ok(ordered_subject) = helper::reorder_x509_names(subject) { match ctx.crls(&ordered_subject) { Ok(ret) if !ret.is_empty() => return Ok(ret), _ => (), } } // nothing found, return empty stack Stack::new() } /// Download the CRLs that a HKD refers to. pub fn hkd_crls(&self, hkd: &X509Ref) -> Result> { let mut ctx = X509StoreContext::new()?; // Unfortunately we cannot use a dedicated function here and have to use a closure (E0434) // Otherwise, we cannot refer to self // Search for local CRLs let mut crls = ctx.init_opt(&self.store, None, None, |ctx| { let subject = self.ibm_z_sign_key.subject_name(); Self::quirk_crls(ctx, subject) })?; if !self.offline { // Try to download a CRL if defined in the HKD if let Some(crl) = download_first_crl_from_x509(hkd)? { crl.into_iter().try_for_each(|c| crls.push(c.into()))?; } } Ok(crls) } } impl CertVerifier { /// Create a `CertVerifier`. /// /// * `cert_paths` - Paths to certificates for the chain of trust /// * `crl_paths` - Paths to certificate revocation lists for the chain of trust /// * `root_ca_path` - Path to the root of trust /// * `offline` - if set to true the verification process will not try to download CRLs from the /// internet. /// /// # Errors /// /// This function will return an error if the chain of trust could not be established. pub fn new( cert_paths: &[P], crl_paths: &[Q], root_ca_path: Option, offline: bool, ) -> Result where P: AsRef, Q: AsRef, R: AsRef, { let mut untr_certs = Vec::with_capacity(cert_paths.len()); for path in cert_paths { let mut crt = read_certs(&read_file(path, "certificate")?)?; untr_certs.append(&mut crt); } let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs.clone())?; let root_ca_verification = match root_ca_path { Some(_) => helper::RootCaVerification::SkipPinning, None => helper::RootCaVerification::RootCaOrganizationPinning("DigiCert"), }; // Two-round verification: // // Round 1: Verify chain without CRL checks before downloading files // from URLs from (yet) untrusted certificates. let store_builder = helper::store_setup( root_ca_path.as_ref(), crl_paths, cert_paths, StoreSetupMode::WithoutCrlCheck, )?; helper::verify_chain( &store_builder.build(), &chain, slice::from_ref(&ibm_z_sign_key), &root_ca_verification, )?; // Round 2: Download CRLs and verify again, but this time with CRL checks let mut store_builder = helper::store_setup( root_ca_path, crl_paths, cert_paths, StoreSetupMode::WithCrlCheck, )?; if !offline { for cert in &untr_certs { if let Some(crls) = download_first_crl_from_x509(cert)? { crls.iter().try_for_each(|c| store_builder.add_crl(c))?; } } } let store = store_builder.build(); helper::verify_chain( &store, &chain, slice::from_ref(&ibm_z_sign_key), &root_ca_verification, )?; Ok(Self { store, ibm_z_sign_key, offline, }) } }