diff --git a/linux-hello-daemon/Cargo.toml b/linux-hello-daemon/Cargo.toml index bd3a32d..2b4da70 100644 --- a/linux-hello-daemon/Cargo.toml +++ b/linux-hello-daemon/Cargo.toml @@ -14,7 +14,7 @@ name = "linux-hello-daemon" path = "src/main.rs" [features] -default = [] +default = ["tpm"] tpm = ["tss-esapi"] onnx = ["ort", "ndarray"] diff --git a/linux-hello-daemon/examples/tpm_test.rs b/linux-hello-daemon/examples/tpm_test.rs new file mode 100644 index 0000000..712e4ce --- /dev/null +++ b/linux-hello-daemon/examples/tpm_test.rs @@ -0,0 +1,114 @@ +//! TPM Test Example +//! +//! Tests the TPM storage functionality +//! +//! Run with: cargo run --example tpm_test +//! Or with sudo: sudo -E cargo run --example tpm_test + +use linux_hello_daemon::tpm::{get_tpm_storage, TpmStorage}; + +fn main() { + // Initialize logging + tracing_subscriber::fmt() + .with_env_filter("info") + .init(); + + println!("=== Linux Hello TPM Test ===\n"); + + // Get TPM storage + println!("1. Getting TPM storage..."); + let mut storage = get_tpm_storage(); + + // Check availability + println!("2. Checking TPM availability..."); + let available = storage.is_available(); + println!(" TPM available: {}", available); + + if !available { + println!("\n⚠️ TPM not available, will use software fallback"); + } + + // Initialize + println!("\n3. Initializing storage..."); + match storage.initialize() { + Ok(()) => println!(" ✓ Storage initialized successfully"), + Err(e) => { + println!(" ✗ Failed to initialize: {}", e); + return; + } + } + + // Test data + let test_user = "test_user"; + let test_data = b"This is a test face embedding with 512 bytes of data. \ + In a real scenario, this would be a 128 or 512 dimensional float vector \ + representing the facial features extracted by a neural network. The data \ + needs to be encrypted securely so that biometric information cannot be \ + extracted even if the storage is compromised."; + + println!("\n4. Creating user key for '{}'...", test_user); + match storage.create_user_key(test_user) { + Ok(()) => println!(" ✓ User key created"), + Err(e) => println!(" Note: {}", e), // May already exist + } + + // Encrypt + println!("\n5. Encrypting {} bytes of test data...", test_data.len()); + let encrypted = match storage.encrypt(test_user, test_data) { + Ok(enc) => { + println!(" ✓ Encryption successful"); + println!(" - Ciphertext length: {} bytes", enc.ciphertext.len()); + println!(" - IV length: {} bytes", enc.iv.len()); + println!(" - TPM encrypted: {}", enc.tpm_encrypted); + enc + } + Err(e) => { + println!(" ✗ Encryption failed: {}", e); + return; + } + }; + + // Decrypt + println!("\n6. Decrypting data..."); + let decrypted = match storage.decrypt(test_user, &encrypted) { + Ok(dec) => { + println!(" ✓ Decryption successful"); + println!(" - Decrypted length: {} bytes", dec.len()); + dec + } + Err(e) => { + println!(" ✗ Decryption failed: {}", e); + return; + } + }; + + // Verify + println!("\n7. Verifying data integrity..."); + if decrypted == test_data { + println!(" ✓ Data matches! Roundtrip successful."); + } else { + println!(" ✗ Data mismatch! Something went wrong."); + println!(" Expected: {:?}", String::from_utf8_lossy(test_data)); + println!(" Got: {:?}", String::from_utf8_lossy(&decrypted)); + return; + } + + // Cleanup + println!("\n8. Cleaning up test user key..."); + match storage.remove_user_key(test_user) { + Ok(()) => println!(" ✓ User key removed"), + Err(e) => println!(" Note: {}", e), + } + + println!("\n=== All tests passed! ==="); + + if encrypted.tpm_encrypted { + println!("\n🔐 Data was encrypted using hardware TPM!"); + } else { + println!("\n🔑 Data was encrypted using software fallback (AES-256-GCM)."); + println!(" For hardware TPM encryption, ensure:"); + println!(" - TPM device exists (/dev/tpm0 or /dev/tpmrm0)"); + println!(" - User has permission (member of 'tss' group)"); + println!(" - Or run with sudo"); + } +} diff --git a/linux-hello-daemon/src/secure_template_store.rs b/linux-hello-daemon/src/secure_template_store.rs index b55bcbe..780245b 100644 --- a/linux-hello-daemon/src/secure_template_store.rs +++ b/linux-hello-daemon/src/secure_template_store.rs @@ -102,7 +102,7 @@ impl SecureTemplateStore { } /// Store a template securely - pub fn store(&self, template: &FaceTemplate) -> Result<()> { + pub fn store(&mut self, template: &FaceTemplate) -> Result<()> { if self.encryption_enabled { self.store_encrypted(template) } else { @@ -111,7 +111,7 @@ impl SecureTemplateStore { } /// Store an encrypted template - fn store_encrypted(&self, template: &FaceTemplate) -> Result<()> { + fn store_encrypted(&mut self, template: &FaceTemplate) -> Result<()> { let user_dir = self.base_path.join(&template.user); fs::create_dir_all(&user_dir)?; @@ -143,7 +143,7 @@ impl SecureTemplateStore { } /// Load a template - pub fn load(&self, user: &str, label: &str) -> Result { + pub fn load(&mut self, user: &str, label: &str) -> Result { // Try encrypted version first let secure_path = self.secure_template_path(user, label); if secure_path.exists() && self.encryption_enabled { @@ -155,7 +155,7 @@ impl SecureTemplateStore { } /// Load and decrypt a template - fn load_encrypted(&self, user: &str, label: &str) -> Result { + fn load_encrypted(&mut self, user: &str, label: &str) -> Result { let path = self.secure_template_path(user, label); let content = fs::read_to_string(&path)?; @@ -184,13 +184,13 @@ impl SecureTemplateStore { } /// Load a template as SecureEmbedding (for secure comparisons) - pub fn load_secure(&self, user: &str, label: &str) -> Result { + pub fn load_secure(&mut self, user: &str, label: &str) -> Result { let template = self.load(user, label)?; Ok(SecureEmbedding::new(template.embedding)) } /// Load all templates for a user - pub fn load_all(&self, user: &str) -> Result> { + pub fn load_all(&mut self, user: &str) -> Result> { let user_dir = self.base_path.join(user); if !user_dir.exists() { @@ -262,12 +262,10 @@ impl SecureTemplateStore { } /// Remove all templates for a user - pub fn remove_all(&self, user: &str) -> Result<()> { + pub fn remove_all(&mut self, user: &str) -> Result<()> { // Also remove user's TPM key if self.encryption_enabled { - let _ = self.tpm.as_ref(); - // Note: We'd call remove_user_key here but tpm is immutable - // In production, we'd use interior mutability + let _ = self.tpm.remove_user_key(user); } self.fallback_store.remove_all(user) diff --git a/linux-hello-daemon/src/tpm.rs b/linux-hello-daemon/src/tpm.rs index 48edad2..b758b7f 100644 --- a/linux-hello-daemon/src/tpm.rs +++ b/linux-hello-daemon/src/tpm.rs @@ -16,8 +16,8 @@ //! //! ```text //! TPM Storage Root Key (SRK) -//! └── Linux Hello Primary Key (sealed to PCRs) -//! └── User Template Encryption Key (per-user) +//! └── Linux Hello Primary Key (RSA, for key wrapping) +//! └── User Symmetric Key (AES-256-CFB, per-user) //! └── Encrypted face template //! ``` //! @@ -68,11 +68,11 @@ pub const PRIMARY_KEY_HANDLE: u32 = 0x81000001; /// Encrypted template data structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EncryptedTemplate { - /// Encrypted embedding data (includes AES-GCM authentication tag) + /// Encrypted embedding data (includes AES-GCM authentication tag for software, raw for TPM) pub ciphertext: Vec, - /// Initialization vector (nonce) - 12 bytes for AES-GCM + /// Initialization vector (nonce) - 16 bytes for AES-CFB (TPM) or 12 bytes for AES-GCM (software) pub iv: Vec, - /// Salt used for key derivation - 32 bytes + /// Salt used for key derivation (software only) - 32 bytes pub salt: Vec, /// Key handle used for encryption pub key_handle: u32, @@ -81,22 +81,22 @@ pub struct EncryptedTemplate { } /// TPM2 storage provider trait -pub trait TpmStorage { +pub trait TpmStorage: Send + Sync { /// Check if TPM is available fn is_available(&self) -> bool; - + /// Initialize TPM storage (create primary key if needed) fn initialize(&mut self) -> Result<()>; - + /// Encrypt data using TPM-bound key - fn encrypt(&self, user: &str, plaintext: &[u8]) -> Result; - + fn encrypt(&mut self, user: &str, plaintext: &[u8]) -> Result; + /// Decrypt data using TPM-bound key - fn decrypt(&self, user: &str, encrypted: &EncryptedTemplate) -> Result>; - + fn decrypt(&mut self, user: &str, encrypted: &EncryptedTemplate) -> Result>; + /// Create a user-specific encryption key fn create_user_key(&mut self, user: &str) -> Result<()>; - + /// Remove user key fn remove_user_key(&mut self, user: &str) -> Result<()>; } @@ -275,7 +275,7 @@ impl TpmStorage for SoftwareTpmFallback { Ok(()) } - fn encrypt(&self, user: &str, plaintext: &[u8]) -> Result { + fn encrypt(&mut self, user: &str, plaintext: &[u8]) -> Result { // Generate cryptographically secure random salt and nonce let salt: [u8; SALT_SIZE] = Self::generate_random_bytes(); let nonce: [u8; NONCE_SIZE] = Self::generate_random_bytes(); @@ -295,7 +295,7 @@ impl TpmStorage for SoftwareTpmFallback { }) } - fn decrypt(&self, user: &str, encrypted: &EncryptedTemplate) -> Result> { + fn decrypt(&mut self, user: &str, encrypted: &EncryptedTemplate) -> Result> { if encrypted.tpm_encrypted { return Err(Error::Tpm( "Cannot decrypt TPM-encrypted template without TPM".to_string(), @@ -364,82 +364,381 @@ impl TpmStorage for SoftwareTpmFallback { #[cfg(feature = "tpm")] pub mod real_tpm { use super::*; + use std::convert::TryFrom; use tss_esapi::{ Context, TctiNameConf, + abstraction::cipher::Cipher, + attributes::ObjectAttributesBuilder, handles::KeyHandle, interface_types::{ - algorithm::{HashingAlgorithm, SymmetricAlgorithm}, - key_bits::AesKeyBits, + algorithm::{HashingAlgorithm, PublicAlgorithm, SymmetricMode}, + key_bits::RsaKeyBits, resource_handles::Hierarchy, + session_handles::AuthSession, }, structures::{ - Public, PublicBuilder, SymmetricDefinitionObject, - CreatePrimaryKeyResult, + Auth, InitialValue, MaxBuffer, Private, Public, PublicBuffer, PublicBuilder, + RsaExponent, SymmetricCipherParameters, }, }; + /// AES-256-CFB IV size (128 bits = 16 bytes) + const TPM_IV_SIZE: usize = 16; + + /// Stored user key data (persisted to disk) + #[derive(Debug, Clone, Serialize, Deserialize)] + struct StoredUserKey { + /// TPM Private blob (encrypted by TPM) + private_blob: Vec, + /// TPM Public blob + public_blob: Vec, + /// Key auth value (encrypted with master secret) + encrypted_auth: Vec, + /// IV for auth encryption + auth_iv: Vec, + /// Salt for auth encryption + auth_salt: Vec, + } + /// TPM2 storage implementation pub struct Tpm2Storage { context: Option, primary_key: Option, + primary_auth: Option, key_path: PathBuf, + /// Software fallback for encrypting auth values stored on disk + software_fallback: SoftwareTpmFallback, } impl Tpm2Storage { pub fn new>(key_path: P) -> Self { + let key_path = key_path.as_ref().to_path_buf(); Self { context: None, primary_key: None, - key_path: key_path.as_ref().to_path_buf(), + primary_auth: None, + key_path: key_path.clone(), + software_fallback: SoftwareTpmFallback::new(key_path), } } /// Try to connect to TPM fn connect(&mut self) -> Result<()> { + // Try environment variable first, then default device let tcti = TctiNameConf::from_environment_variable() - .or_else(|_| TctiNameConf::Device(Default::default())) - .map_err(|e| Error::Tpm(format!("Failed to configure TCTI: {}", e)))?; - + .unwrap_or_else(|_| TctiNameConf::Device(Default::default())); + let context = Context::new(tcti) .map_err(|e| Error::Tpm(format!("Failed to create TPM context: {}", e)))?; - + self.context = Some(context); Ok(()) } - /// Create or load primary key + /// Get path to user's TPM key file + fn user_tpm_key_path(&self, user: &str) -> PathBuf { + self.key_path.join(format!("{}.tpmkey", user)) + } + + /// Get path to primary key auth file + fn primary_auth_path(&self) -> PathBuf { + self.key_path.join("primary.auth") + } + + /// Create the RSA primary key for wrapping user keys fn setup_primary_key(&mut self) -> Result<()> { + use rand::RngCore; + + // Get auth path before borrowing context mutably + let auth_path = self.primary_auth_path(); + let ctx = self.context.as_mut() .ok_or_else(|| Error::Tpm("TPM not connected".to_string()))?; - // Try to load existing primary key - // If not found, create a new one - - let primary_public = PublicBuilder::new() - .with_public_algorithm(tss_esapi::interface_types::algorithm::PublicAlgorithm::SymCipher) - .with_name_hashing_algorithm(HashingAlgorithm::Sha256) - .with_symmetric_definition(SymmetricDefinitionObject::Aes { - key_bits: AesKeyBits::Aes256, - mode: tss_esapi::interface_types::algorithm::SymmetricMode::Cfb, - }) - .build() - .map_err(|e| Error::Tpm(format!("Failed to build key template: {}", e)))?; + // Set owner auth to empty (default) + ctx.tr_set_auth(Hierarchy::Owner.into(), Auth::default()) + .map_err(|e| Error::Tpm(format!("Failed to set owner auth: {}", e)))?; - let result = ctx.create_primary( - Hierarchy::Owner, - primary_public, - None, // auth value - None, // initial data - None, // outside info - None, // creation PCR - ).map_err(|e| Error::Tpm(format!("Failed to create primary key: {}", e)))?; + // Generate or load primary key auth + let primary_auth = if auth_path.exists() { + let auth_bytes = std::fs::read(&auth_path)?; + Auth::try_from(auth_bytes) + .map_err(|e| Error::Tpm(format!("Failed to load primary auth: {}", e)))? + } else { + // Generate new auth value + let mut auth_bytes = vec![0u8; 32]; + rand::thread_rng().fill_bytes(&mut auth_bytes); + + // Save auth value + std::fs::write(&auth_path, &auth_bytes)?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&auth_path, std::fs::Permissions::from_mode(0o600))?; + } + + Auth::try_from(auth_bytes) + .map_err(|e| Error::Tpm(format!("Failed to create primary auth: {}", e)))? + }; + + // Create RSA primary key for wrapping symmetric keys + // Using AES-256-CFB as the symmetric algorithm for child keys + let cipher = Cipher::aes_256_cfb(); + + let primary_public = tss_esapi::utils::create_restricted_decryption_rsa_public( + cipher.try_into() + .map_err(|e: tss_esapi::Error| Error::Tpm(format!("Failed to convert cipher: {}", e)))?, + RsaKeyBits::Rsa2048, + RsaExponent::default(), + ).map_err(|e| Error::Tpm(format!("Failed to create primary public: {}", e)))?; + + let result = ctx.execute_with_session(Some(AuthSession::Password), |ctx| { + ctx.create_primary( + Hierarchy::Owner, + primary_public, + Some(primary_auth.clone()), + None, // initial data + None, // outside info + None, // creation PCR + ) + }).map_err(|e| Error::Tpm(format!("Failed to create primary key: {}", e)))?; + + // Set auth on the primary key handle + ctx.tr_set_auth(result.key_handle.into(), primary_auth.clone()) + .map_err(|e| Error::Tpm(format!("Failed to set primary key auth: {}", e)))?; self.primary_key = Some(result.key_handle); - info!("TPM2 primary key created"); - + self.primary_auth = Some(primary_auth); + + info!("TPM2 primary key created successfully"); Ok(()) } + + /// Create a symmetric key for a user under the primary key + fn create_symmetric_key(&mut self, user: &str) -> Result { + use rand::RngCore; + + let ctx = self.context.as_mut() + .ok_or_else(|| Error::Tpm("TPM not connected".to_string()))?; + + let primary_key = self.primary_key + .ok_or_else(|| Error::Tpm("Primary key not initialized".to_string()))?; + + // Generate auth for the symmetric key + let mut auth_bytes = vec![0u8; 16]; + rand::thread_rng().fill_bytes(&mut auth_bytes); + let sym_key_auth = Auth::try_from(auth_bytes.clone()) + .map_err(|e| Error::Tpm(format!("Failed to create symmetric key auth: {}", e)))?; + + // Build symmetric key attributes + let object_attributes = ObjectAttributesBuilder::new() + .with_user_with_auth(true) + .with_sign_encrypt(true) + .with_decrypt(true) + .with_sensitive_data_origin(true) + .build() + .map_err(|e| Error::Tpm(format!("Failed to build object attributes: {}", e)))?; + + // Create symmetric key public template (AES-256-CFB) + let cipher = Cipher::aes_256_cfb(); + let sym_key_public = PublicBuilder::new() + .with_public_algorithm(PublicAlgorithm::SymCipher) + .with_name_hashing_algorithm(HashingAlgorithm::Sha256) + .with_object_attributes(object_attributes) + .with_symmetric_cipher_parameters(SymmetricCipherParameters::new( + cipher.try_into() + .map_err(|e: tss_esapi::Error| Error::Tpm(format!("Failed to convert cipher: {}", e)))?, + )) + .with_symmetric_cipher_unique_identifier(Default::default()) + .build() + .map_err(|e| Error::Tpm(format!("Failed to build symmetric key public: {}", e)))?; + + // Create the symmetric key under the primary key + let creation_data = ctx.execute_with_session(Some(AuthSession::Password), |ctx| { + ctx.create( + primary_key, + sym_key_public, + Some(sym_key_auth), + None, // No initial sensitive data - TPM generates the key + None, // outside info + None, // creation PCR + ) + }).map_err(|e| Error::Tpm(format!("Failed to create symmetric key: {}", e)))?; + + // Serialize private and public blobs + // Private implements Deref>, so we can clone the inner Vec + let private_blob: Vec = (*creation_data.out_private).clone(); + // Public needs to go through PublicBuffer for serialization + let public_buffer = PublicBuffer::try_from(creation_data.out_public) + .map_err(|e| Error::Tpm(format!("Failed to serialize public: {}", e)))?; + let public_blob: Vec = (*public_buffer).clone(); + + // Encrypt the auth value for storage using software encryption + // (We need to store it to load the key later) + let encrypted = self.software_fallback.encrypt(user, &auth_bytes)?; + + let stored_key = StoredUserKey { + private_blob, + public_blob, + encrypted_auth: encrypted.ciphertext, + auth_iv: encrypted.iv, + auth_salt: encrypted.salt, + }; + + // Save to disk + let key_path = self.user_tpm_key_path(user); + let json = serde_json::to_string(&stored_key) + .map_err(|e| Error::Tpm(format!("Failed to serialize key: {}", e)))?; + std::fs::write(&key_path, json)?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))?; + } + + info!("Created TPM symmetric key for user: {}", user); + Ok(stored_key) + } + + /// Load a user's symmetric key from disk and into TPM + fn load_user_key(&mut self, user: &str) -> Result<(KeyHandle, Auth)> { + // Get all values that need self before getting mutable context + let key_path = self.user_tpm_key_path(user); + let primary_key = self.primary_key + .ok_or_else(|| Error::Tpm("Primary key not initialized".to_string()))?; + + // Load stored key data + if !key_path.exists() { + return Err(Error::Tpm(format!("No TPM key found for user: {}", user))); + } + + let json = std::fs::read_to_string(&key_path)?; + let stored_key: StoredUserKey = serde_json::from_str(&json) + .map_err(|e| Error::Tpm(format!("Failed to parse stored key: {}", e)))?; + + // Decrypt the auth value using the stored salt + let encrypted_template = EncryptedTemplate { + ciphertext: stored_key.encrypted_auth, + iv: stored_key.auth_iv, + salt: stored_key.auth_salt, + key_handle: 0, + tpm_encrypted: false, + }; + + let auth_bytes = self.software_fallback.decrypt(user, &encrypted_template)?; + let sym_key_auth = Auth::try_from(auth_bytes) + .map_err(|e| Error::Tpm(format!("Failed to create auth from decrypted value: {}", e)))?; + + // Reconstruct Private and Public from stored blobs + let private = Private::try_from(stored_key.private_blob) + .map_err(|e| Error::Tpm(format!("Failed to parse private blob: {}", e)))?; + // Public needs to go through PublicBuffer for deserialization + let public_buffer = PublicBuffer::try_from(stored_key.public_blob) + .map_err(|e| Error::Tpm(format!("Failed to parse public buffer: {}", e)))?; + let public = Public::try_from(public_buffer) + .map_err(|e| Error::Tpm(format!("Failed to parse public: {}", e)))?; + + // Now get context for TPM operations + let ctx = self.context.as_mut() + .ok_or_else(|| Error::Tpm("TPM not connected".to_string()))?; + + // Load the key into TPM + let key_handle = ctx.execute_with_session(Some(AuthSession::Password), |ctx| { + ctx.load(primary_key, private, public) + }).map_err(|e| Error::Tpm(format!("Failed to load symmetric key: {}", e)))?; + + // Set auth on the loaded key + ctx.tr_set_auth(key_handle.into(), sym_key_auth.clone()) + .map_err(|e| Error::Tpm(format!("Failed to set key auth: {}", e)))?; + + Ok((key_handle, sym_key_auth)) + } + + /// Encrypt data using TPM + fn tpm_encrypt(&mut self, user: &str, plaintext: &[u8]) -> Result<(Vec, Vec)> { + use rand::RngCore; + + // Load user's symmetric key + let (key_handle, _auth) = self.load_user_key(user)?; + + let ctx = self.context.as_mut() + .ok_or_else(|| Error::Tpm("TPM not connected".to_string()))?; + + // Generate random IV + let mut iv_bytes = vec![0u8; TPM_IV_SIZE]; + rand::thread_rng().fill_bytes(&mut iv_bytes); + let initial_value = InitialValue::try_from(iv_bytes.clone()) + .map_err(|e| Error::Tpm(format!("Failed to create IV: {}", e)))?; + + // TPM has a maximum buffer size, so we may need to encrypt in chunks + // MaxBuffer is typically 1024 bytes + let max_chunk_size = 1024; + let mut ciphertext = Vec::new(); + + for chunk in plaintext.chunks(max_chunk_size) { + let data = MaxBuffer::try_from(chunk.to_vec()) + .map_err(|e| Error::Tpm(format!("Failed to create buffer: {}", e)))?; + + let (encrypted_chunk, _) = ctx.execute_with_session(Some(AuthSession::Password), |ctx| { + ctx.encrypt_decrypt_2( + key_handle, + false, // encrypt + SymmetricMode::Cfb, + data, + initial_value.clone(), + ) + }).map_err(|e| Error::Tpm(format!("TPM encryption failed: {}", e)))?; + + ciphertext.extend_from_slice(&encrypted_chunk); + } + + // Flush the key from TPM to free resources + ctx.flush_context(key_handle.into()) + .map_err(|e| Error::Tpm(format!("Failed to flush key: {}", e)))?; + + Ok((ciphertext, iv_bytes)) + } + + /// Decrypt data using TPM + fn tpm_decrypt(&mut self, user: &str, ciphertext: &[u8], iv: &[u8]) -> Result> { + // Load user's symmetric key + let (key_handle, _auth) = self.load_user_key(user)?; + + let ctx = self.context.as_mut() + .ok_or_else(|| Error::Tpm("TPM not connected".to_string()))?; + + let initial_value = InitialValue::try_from(iv.to_vec()) + .map_err(|e| Error::Tpm(format!("Failed to create IV: {}", e)))?; + + // Decrypt in chunks + let max_chunk_size = 1024; + let mut plaintext = Vec::new(); + + for chunk in ciphertext.chunks(max_chunk_size) { + let data = MaxBuffer::try_from(chunk.to_vec()) + .map_err(|e| Error::Tpm(format!("Failed to create buffer: {}", e)))?; + + let (decrypted_chunk, _) = ctx.execute_with_session(Some(AuthSession::Password), |ctx| { + ctx.encrypt_decrypt_2( + key_handle, + true, // decrypt + SymmetricMode::Cfb, + data, + initial_value.clone(), + ) + }).map_err(|e| Error::Tpm(format!("TPM decryption failed: {}", e)))?; + + plaintext.extend_from_slice(&decrypted_chunk); + } + + // Flush the key from TPM + ctx.flush_context(key_handle.into()) + .map_err(|e| Error::Tpm(format!("Failed to flush key: {}", e)))?; + + Ok(plaintext) + } } impl TpmStorage for Tpm2Storage { @@ -454,63 +753,91 @@ pub mod real_tpm { return Err(Error::Tpm("TPM device not found".to_string())); } - self.connect()?; - self.setup_primary_key()?; + // Create key directory std::fs::create_dir_all(&self.key_path)?; - - info!("TPM2 storage initialized"); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&self.key_path, std::fs::Permissions::from_mode(0o700))?; + } + + // Initialize software fallback (for auth encryption) + self.software_fallback.initialize()?; + + // Connect to TPM + self.connect()?; + + // Setup primary key + self.setup_primary_key()?; + + info!("TPM2 storage initialized successfully"); Ok(()) } - fn encrypt(&self, user: &str, plaintext: &[u8]) -> Result { - let ctx = self.context.as_ref() - .ok_or_else(|| Error::Tpm("TPM not initialized".to_string()))?; - - // For now, use software encryption with TPM-derived key - // Full TPM encryption would use ctx.encrypt_decrypt() - - // Placeholder: In production, use TPM2_EncryptDecrypt - let fallback = SoftwareTpmFallback::new(&self.key_path); - let mut result = fallback.encrypt(user, plaintext)?; - result.tpm_encrypted = true; - result.key_handle = PRIMARY_KEY_HANDLE; - - Ok(result) + fn encrypt(&mut self, user: &str, plaintext: &[u8]) -> Result { + // Ensure user has a key + let key_path = self.user_tpm_key_path(user); + if !key_path.exists() { + self.create_user_key(user)?; + } + + // Perform TPM encryption + let (ciphertext, iv) = self.tpm_encrypt(user, plaintext)?; + + Ok(EncryptedTemplate { + ciphertext, + iv, + salt: Vec::new(), // Not used for TPM encryption + key_handle: PRIMARY_KEY_HANDLE, + tpm_encrypted: true, + }) } - fn decrypt(&self, user: &str, encrypted: &EncryptedTemplate) -> Result> { - let _ctx = self.context.as_ref() - .ok_or_else(|| Error::Tpm("TPM not initialized".to_string()))?; - - // Placeholder: In production, use TPM2_EncryptDecrypt - let fallback = SoftwareTpmFallback::new(&self.key_path); - fallback.decrypt(user, encrypted) + fn decrypt(&mut self, user: &str, encrypted: &EncryptedTemplate) -> Result> { + if !encrypted.tpm_encrypted { + // Fall back to software decryption for non-TPM encrypted data + return self.software_fallback.decrypt(user, encrypted); + } + + // Perform TPM decryption + self.tpm_decrypt(user, &encrypted.ciphertext, &encrypted.iv) } fn create_user_key(&mut self, user: &str) -> Result<()> { - // In production, create a child key under the primary key - let fallback = SoftwareTpmFallback::new(&self.key_path); - fallback.create_user_key(user) + self.create_symmetric_key(user)?; + Ok(()) } fn remove_user_key(&mut self, user: &str) -> Result<()> { - let fallback = SoftwareTpmFallback::new(&self.key_path); - fallback.remove_user_key(user) + let key_path = self.user_tpm_key_path(user); + if key_path.exists() { + // Securely delete by overwriting first + let file_size = std::fs::metadata(&key_path)?.len() as usize; + let zeros = vec![0u8; file_size]; + std::fs::write(&key_path, &zeros)?; + std::fs::remove_file(&key_path)?; + info!("Removed TPM key for user: {}", user); + } + Ok(()) } } } /// Get the appropriate TPM storage implementation -pub fn get_tpm_storage() -> Box { - #[cfg(feature = "tpm")] - { - let tpm = real_tpm::Tpm2Storage::new(SoftwareTpmFallback::default_key_path()); - if tpm.is_available() { - return Box::new(tpm); - } +#[cfg(feature = "tpm")] +pub fn get_tpm_storage() -> Box { + let tpm = real_tpm::Tpm2Storage::new(SoftwareTpmFallback::default_key_path()); + if tpm.is_available() { + return Box::new(tpm); } - + // Fall back to software implementation + warn!("TPM not available, using software fallback"); + Box::new(SoftwareTpmFallback::new(SoftwareTpmFallback::default_key_path())) +} + +#[cfg(not(feature = "tpm"))] +pub fn get_tpm_storage() -> Box { Box::new(SoftwareTpmFallback::new(SoftwareTpmFallback::default_key_path())) } @@ -522,14 +849,14 @@ mod tests { fn test_software_fallback_encrypt_decrypt() { let mut storage = SoftwareTpmFallback::new("/tmp/linux-hello-test-keys"); storage.initialize().unwrap(); - + let plaintext = b"test embedding data 12345"; let encrypted = storage.encrypt("testuser", plaintext).unwrap(); - + assert!(!encrypted.tpm_encrypted); assert!(!encrypted.ciphertext.is_empty()); assert_ne!(&encrypted.ciphertext, plaintext); - + let decrypted = storage.decrypt("testuser", &encrypted).unwrap(); assert_eq!(decrypted, plaintext); } @@ -538,10 +865,10 @@ mod tests { fn test_software_fallback_user_keys() { let mut storage = SoftwareTpmFallback::new("/tmp/linux-hello-test-keys"); storage.initialize().unwrap(); - + storage.create_user_key("testuser2").unwrap(); assert!(storage.user_key_path("testuser2").exists()); - + storage.remove_user_key("testuser2").unwrap(); assert!(!storage.user_key_path("testuser2").exists()); } @@ -564,4 +891,46 @@ mod tests { assert_eq!(restored.salt, template.salt); assert_eq!(restored.key_handle, template.key_handle); } + + #[test] + fn test_software_fallback_different_users() { + let mut storage = SoftwareTpmFallback::new("/tmp/linux-hello-test-keys-users"); + storage.initialize().unwrap(); + + let plaintext = b"shared secret data"; + + // Encrypt for user1 + let encrypted1 = storage.encrypt("user1", plaintext).unwrap(); + + // Encrypt for user2 + let encrypted2 = storage.encrypt("user2", plaintext).unwrap(); + + // Ciphertexts should be different (different keys, different IVs) + assert_ne!(encrypted1.ciphertext, encrypted2.ciphertext); + + // Each user can decrypt their own data + let decrypted1 = storage.decrypt("user1", &encrypted1).unwrap(); + let decrypted2 = storage.decrypt("user2", &encrypted2).unwrap(); + + assert_eq!(decrypted1, plaintext); + assert_eq!(decrypted2, plaintext); + + // User cannot decrypt other user's data + let result = storage.decrypt("user1", &encrypted2); + assert!(result.is_err()); + } + + #[test] + fn test_software_fallback_large_data() { + let mut storage = SoftwareTpmFallback::new("/tmp/linux-hello-test-keys-large"); + storage.initialize().unwrap(); + + // Test with larger data (like a face embedding) + let plaintext: Vec = (0..512).map(|i| (i % 256) as u8).collect(); + + let encrypted = storage.encrypt("testuser", &plaintext).unwrap(); + let decrypted = storage.decrypt("testuser", &encrypted).unwrap(); + + assert_eq!(decrypted, plaintext); + } }