upstream-rs 1.11.1

Fetch package updates directly from the source.
Documentation
use crate::{
    models::{
        common::enums::{Provider, TrustMode},
        provider::Release,
    },
    providers::provider_manager::ProviderManager,
};
use anyhow::{Result, anyhow};
use std::path::Path;

use super::{
    checksum_verifier::ChecksumVerifier,
    signature_verifier::{MinisignPublicKey, SignatureVerificationStatus, SignatureVerifier},
};

pub enum ChecksumVerificationStatus {
    Verified,
    Missing,
}

pub enum TrustVerificationStatus {
    Skipped,
    Verified {
        checksum: ChecksumVerificationStatus,
        signature: SignatureVerificationStatus,
    },
}

pub struct TrustVerifier<'a> {
    checksum_verifier: ChecksumVerifier<'a>,
    signature_verifier: SignatureVerifier<'a>,
    trust_mode: TrustMode,
    trusted_keys: &'a [MinisignPublicKey],
}

impl<'a> TrustVerifier<'a> {
    pub fn new(
        provider_manager: &'a ProviderManager,
        download_cache: &'a Path,
        trust_mode: TrustMode,
        trusted_keys: &'a [MinisignPublicKey],
    ) -> Self {
        Self {
            checksum_verifier: ChecksumVerifier::new(provider_manager, download_cache),
            signature_verifier: SignatureVerifier::new(provider_manager, download_cache),
            trust_mode,
            trusted_keys,
        }
    }

    pub async fn verify_file<F>(
        &self,
        asset_path: &Path,
        release: &Release,
        provider: &Provider,
        dl_progress: &mut Option<F>,
    ) -> Result<TrustVerificationStatus>
    where
        F: FnMut(u64, u64),
    {
        if self.trust_mode == TrustMode::None {
            return Ok(TrustVerificationStatus::Skipped);
        }

        let checksum_status = match self
            .checksum_verifier
            .try_verify_file(asset_path, release, provider, dl_progress)
            .await?
        {
            true => ChecksumVerificationStatus::Verified,
            false => ChecksumVerificationStatus::Missing,
        };

        let signature_status = self
            .signature_verifier
            .try_verify_file(
                asset_path,
                release,
                provider,
                self.trusted_keys,
                dl_progress,
            )
            .await?;

        self.enforce_policy(&checksum_status, &signature_status)?;

        Ok(TrustVerificationStatus::Verified {
            checksum: checksum_status,
            signature: signature_status,
        })
    }

    fn enforce_policy(
        &self,
        checksum: &ChecksumVerificationStatus,
        signature: &SignatureVerificationStatus,
    ) -> Result<()> {
        match self.trust_mode {
            TrustMode::None => Ok(()),
            TrustMode::BestEffort => {
                self.enforce_signature_attempt_result(signature)?;
                Ok(())
            }
            TrustMode::Checksum => {
                if matches!(checksum, ChecksumVerificationStatus::Missing) {
                    return Err(anyhow!(
                        "Checksum is required but no checksum asset was found"
                    ));
                }
                self.enforce_signature_attempt_result(signature)?;
                Ok(())
            }
            TrustMode::Signature => {
                if matches!(signature, SignatureVerificationStatus::MissingSignature) {
                    return Err(anyhow!(
                        "Signature is required but no signature asset was found"
                    ));
                }
                self.enforce_signature_attempt_result(signature)?;
                Ok(())
            }
            TrustMode::All => {
                if matches!(checksum, ChecksumVerificationStatus::Missing) {
                    return Err(anyhow!(
                        "Checksum is required but no checksum asset was found"
                    ));
                }
                if matches!(signature, SignatureVerificationStatus::MissingSignature) {
                    return Err(anyhow!(
                        "Signature is required but no signature asset was found"
                    ));
                }
                self.enforce_signature_attempt_result(signature)?;
                Ok(())
            }
        }
    }

    fn enforce_signature_attempt_result(
        &self,
        signature: &SignatureVerificationStatus,
    ) -> Result<()> {
        match signature {
            SignatureVerificationStatus::Verified { .. }
            | SignatureVerificationStatus::MissingSignature => Ok(()),
            SignatureVerificationStatus::InvalidSignature => Err(anyhow!(
                "Signature verification failed: signature is invalid"
            )),
            SignatureVerificationStatus::NoTrustedKeyMatched => Err(anyhow!(
                "Signature verification failed: no configured trusted key matched"
            )),
        }
    }
}