keyhog 0.5.73

GPU-accelerated secret scanner for code, Git history, cloud, containers, browser assets, and live credential verification
//! Exact identity of the running executable used by persisted calibration.

use serde::Deserialize;
use sha2::{Digest, Sha256};
use std::collections::BTreeSet;
use std::io::Read;
use std::path::{Component, Path};
use std::sync::OnceLock;

const INSTALLED_MANIFEST_BYTES: u64 = 1024 * 1024;
const INSTALLED_ARTIFACT_BYTES: u64 = 256 * 1024 * 1024;
const INSTALLED_ARTIFACT_COUNT: usize = 64;

fn read_bounded(path: &Path, limit: u64) -> Option<Vec<u8>> {
    let mut file = std::fs::File::open(path).ok()?;
    let declared_len = file.metadata().ok()?.len();
    if declared_len > limit {
        return None;
    }
    let mut bytes = Vec::with_capacity(usize::try_from(declared_len).ok()?);
    file.by_ref()
        .take(limit.saturating_add(1))
        .read_to_end(&mut bytes)
        .ok()?;
    (u64::try_from(bytes.len()).ok()? <= limit).then_some(bytes)
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct InstalledGpuArtifactManifest {
    version: u32,
    artifacts: Vec<InstalledGpuArtifactEntry>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct InstalledGpuArtifactEntry {
    file_name: String,
    sha256: String,
}

pub(crate) fn installed_gpu_sidecar_digest(cache_dir: &Path) -> Option<String> {
    let manifest_path = cache_dir.join(".installed_manifest.json");
    let manifest_metadata = std::fs::symlink_metadata(&manifest_path).ok()?;
    if !manifest_metadata.file_type().is_file()
        || manifest_metadata.len() > INSTALLED_MANIFEST_BYTES
    {
        return None;
    }
    let manifest: InstalledGpuArtifactManifest =
        serde_json::from_slice(&read_bounded(&manifest_path, INSTALLED_MANIFEST_BYTES)?).ok()?;
    if manifest.version != 1
        || manifest.artifacts.is_empty()
        || manifest.artifacts.len() > INSTALLED_ARTIFACT_COUNT
    {
        return None;
    }

    let mut entries = manifest.artifacts;
    entries.sort_by(|left, right| left.file_name.cmp(&right.file_name));
    let mut names = BTreeSet::new();
    let mut identity = Sha256::new();
    identity.update(b"gpu_installed_manifest_v1:");
    let mut buffer = [0u8; 64 * 1024];
    for entry in entries {
        let name = Path::new(&entry.file_name);
        if name.components().count() != 1
            || !matches!(name.components().next(), Some(Component::Normal(_)))
            || !entry.file_name.ends_with(".bin")
            || !names.insert(entry.file_name.clone())
            || entry.sha256.len() != 64
        {
            return None;
        }
        let path = cache_dir.join(&entry.file_name);
        let metadata = std::fs::symlink_metadata(&path).ok()?;
        if !metadata.file_type().is_file() || metadata.len() > INSTALLED_ARTIFACT_BYTES {
            return None;
        }
        let mut file = std::fs::File::open(path).ok()?;
        let mut artifact = Sha256::new();
        let mut artifact_bytes = 0u64;
        loop {
            let read = file.read(&mut buffer).ok()?;
            if read == 0 {
                break;
            }
            artifact.update(&buffer[..read]);
            artifact_bytes = artifact_bytes.saturating_add(read as u64);
            if artifact_bytes > INSTALLED_ARTIFACT_BYTES {
                return None;
            }
        }
        let actual = artifact.finalize();
        let actual_hex = format!("{actual:x}");
        if !actual_hex.eq_ignore_ascii_case(&entry.sha256) {
            return None;
        }
        identity.update((entry.file_name.len() as u64).to_le_bytes());
        identity.update(entry.file_name.as_bytes());
        identity.update(actual);
    }
    Some(format!("{:x}", identity.finalize()))
}

pub(super) fn current_executable_sha256(
) -> Result<&'static str, Box<dyn std::error::Error + Send + Sync>> {
    static DIGEST: OnceLock<Result<String, String>> = OnceLock::new();
    DIGEST
        .get_or_init(keyhog_core::current_executable_sha256)
        .as_deref()
        .map_err(|error| error.clone().into())
}
#[cfg(not(test))]
mod gpu_sidecar_identity {
    pub(crate) fn current_gpu_sidecar_sha256() -> Option<String> {
        let cache_dir = keyhog_scanner::gpu_literal_artifact_cache_dir().ok()?;
        super::installed_gpu_sidecar_digest(&cache_dir)
    }
}

#[cfg(test)]
#[path = "../../../../../tests/unit/autoroute_artifact_identity.rs"]
mod gpu_sidecar_identity;

pub(super) use gpu_sidecar_identity::current_gpu_sidecar_sha256;