darwincode 1.9.69

The open source terminal AI coding agent
use aes_gcm::{
    Aes256Gcm, Nonce,
    aead::{Aead, KeyInit},
};
use anyhow::{Result, bail};
use sha2::{Digest, Sha256};

/// Derive a secure, hardware-bound 256-bit symmetric key by hashing machine-id and user details.
pub fn derive_hardware_key() -> Result<[u8; 32]> {
    static KEY_CACHE: std::sync::OnceLock<[u8; 32]> = std::sync::OnceLock::new();
    if let Some(key) = KEY_CACHE.get() {
        return Ok(*key);
    }

    let mut hasher = Sha256::new();

    // Read local machine-id in a robust cross-platform manner
    let mut machine_id = String::new();

    #[cfg(target_os = "linux")]
    {
        if let Ok(id) = std::fs::read_to_string("/etc/machine-id") {
            machine_id = id.trim().to_owned();
        } else if let Ok(id) = std::fs::read_to_string("/var/lib/dbus/machine-id") {
            machine_id = id.trim().to_owned();
        }
    }

    #[cfg(target_os = "windows")]
    {
        if let Ok(output) = std::process::Command::new("reg")
            .args(&[
                "query",
                "HKLM\\SOFTWARE\\Microsoft\\Cryptography",
                "/v",
                "MachineGuid",
            ])
            .output()
        {
            let stdout = String::from_utf8_lossy(&output.stdout);
            if let Some(guid_line) = stdout.lines().find(|l| l.contains("MachineGuid")) {
                if let Some(guid) = guid_line.split_whitespace().last() {
                    machine_id = guid.trim().to_owned();
                }
            }
        }
    }

    #[cfg(target_os = "macos")]
    {
        if let Ok(output) = std::process::Command::new("ioreg")
            .args(&["-rd1", "-c", "IOPlatformExpertDevice"])
            .output()
        {
            let stdout = String::from_utf8_lossy(&output.stdout);
            if let Some(uuid_line) = stdout.lines().find(|l| l.contains("IOPlatformUUID")) {
                if let Some(uuid) = uuid_line.split('=').last() {
                    machine_id = uuid.replace('"', "").trim().to_owned();
                }
            }
        }
    }

    if machine_id.is_empty() {
        // Retrieve config directory to store/load a persistent unique machine-id
        let base_dir = std::env::var_os("XDG_CONFIG_HOME")
            .map(std::path::PathBuf::from)
            .or_else(|| std::env::var_os("APPDATA").map(std::path::PathBuf::from))
            .or_else(|| {
                std::env::var_os("HOME").map(|home| std::path::PathBuf::from(home).join(".config"))
            })
            .or_else(|| {
                std::env::var_os("USERPROFILE")
                    .map(|home| std::path::PathBuf::from(home).join(".config"))
            });

        if let Some(base) = base_dir {
            let darwincode_dir = base.join("darwincode");
            let _ = std::fs::create_dir_all(&darwincode_dir);
            let machine_id_path = darwincode_dir.join("machine-id");
            if let Ok(id) = std::fs::read_to_string(&machine_id_path) {
                machine_id = id.trim().to_owned();
            } else {
                // Generate a random stable key
                let mut bytes = [0u8; 32];
                rand::fill(&mut bytes);
                let hex_id = bytes
                    .iter()
                    .map(|b| format!("{:02x}", b))
                    .collect::<String>();

                // Write with secure permissions on unix
                #[cfg(unix)]
                {
                    use std::os::unix::fs::OpenOptionsExt;
                    if let Ok(mut file) = std::fs::OpenOptions::new()
                        .create(true)
                        .write(true)
                        .truncate(true)
                        .mode(0o600)
                        .open(&machine_id_path)
                    {
                        use std::io::Write;
                        let _ = write!(file, "{}", hex_id);
                    }
                }
                #[cfg(not(unix))]
                {
                    let _ = std::fs::write(&machine_id_path, &hex_id);
                }
                machine_id = hex_id;
            }
        } else {
            // Extreme fallback if home path cannot be determined at all
            machine_id = "ultimate-emergency-fallback-key-998".to_owned();
        }
    }

    let username = std::env::var("USER")
        .or_else(|_| std::env::var("USERNAME"))
        .unwrap_or_else(|_| "default_user".to_owned());
    let home = std::env::var("HOME")
        .or_else(|_| std::env::var("USERPROFILE"))
        .unwrap_or_else(|_| "/".to_owned());

    hasher.update(machine_id.as_bytes());
    hasher.update(username.as_bytes());
    hasher.update(home.as_bytes());

    let hash = hasher.finalize();
    let mut key = [0u8; 32];
    key.copy_from_slice(&hash);
    let _ = KEY_CACHE.set(key);
    Ok(key)
}

/// Encrypt data using AES-256-GCM
pub fn encrypt_data(data: &[u8], key: &[u8; 32]) -> Result<Vec<u8>> {
    let cipher = Aes256Gcm::new_from_slice(key)
        .map_err(|e| anyhow::anyhow!("failed to create cipher: {}", e))?;

    // Generate a secure 96-bit (12-byte) nonce/IV
    let mut nonce_bytes = [0u8; 12];
    rand::fill(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);

    let ciphertext = cipher
        .encrypt(nonce, data)
        .map_err(|e| anyhow::anyhow!("encryption error: {}", e))?;

    // Output is nonce + ciphertext
    let mut output = Vec::with_capacity(12 + ciphertext.len());
    output.extend_from_slice(&nonce_bytes);
    output.extend_from_slice(&ciphertext);
    Ok(output)
}

/// Decrypt data using AES-256-GCM
pub fn decrypt_data(data: &[u8], key: &[u8; 32]) -> Result<Vec<u8>> {
    if data.len() < 12 {
        bail!("Invalid ciphertext (too short)");
    }

    let cipher = Aes256Gcm::new_from_slice(key)
        .map_err(|e| anyhow::anyhow!("failed to create cipher: {}", e))?;

    let (nonce_bytes, ciphertext) = data.split_at(12);
    let nonce = Nonce::from_slice(nonce_bytes);

    let plaintext = cipher
        .decrypt(nonce, ciphertext)
        .map_err(|e| anyhow::anyhow!("decryption error: {}", e))?;

    Ok(plaintext)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encryption_roundtrip() {
        let key = derive_hardware_key().expect("failed to derive key");
        let plaintext = b"Hello, secure world! This is a highly confidential chat log entry.";

        let ciphertext = encrypt_data(plaintext, &key).expect("encryption failed");
        assert_ne!(plaintext.to_vec(), ciphertext);
        assert!(ciphertext.len() > 12);

        let decrypted = decrypt_data(&ciphertext, &key).expect("decryption failed");
        assert_eq!(plaintext.to_vec(), decrypted);
    }

    #[test]
    fn test_decryption_with_wrong_key() {
        let key1 = [1u8; 32];
        let key2 = [2u8; 32];
        let plaintext = b"Confidential info";

        let ciphertext = encrypt_data(plaintext, &key1).expect("encryption failed");
        let decrypt_result = decrypt_data(&ciphertext, &key2);
        assert!(decrypt_result.is_err());
    }

    #[test]
    fn test_decryption_invalid_length() {
        let key = [1u8; 32];
        let too_short = vec![0u8; 11];
        let decrypt_result = decrypt_data(&too_short, &key);
        assert!(decrypt_result.is_err());
    }
}