mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Perform certificate verification in two stages. First, verify the
certificate chain without CRL checks. Once the chain has been validated,
download the referenced CRLs and repeat the verification with CRL
checking enabled.
Fixes: c6f621d0dc ("rust: Add library for pv tools")
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
232 lines
7.4 KiB
Rust
232 lines
7.4 KiB
Rust
// 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<Stack<StackableX509Crl>, 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<Stack<StackableX509Crl>> {
|
|
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<P, Q, R>(
|
|
cert_paths: &[P],
|
|
crl_paths: &[Q],
|
|
root_ca_path: Option<R>,
|
|
offline: bool,
|
|
) -> Result<Self>
|
|
where
|
|
P: AsRef<Path>,
|
|
Q: AsRef<Path>,
|
|
R: AsRef<Path>,
|
|
{
|
|
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())?;
|
|
|
|
// 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),
|
|
)?;
|
|
|
|
// 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))?;
|
|
|
|
Ok(Self {
|
|
store,
|
|
ibm_z_sign_key,
|
|
offline,
|
|
})
|
|
}
|
|
}
|