fixing some issues

This commit is contained in:
2026-01-15 23:16:15 +01:00
parent 23230cb745
commit 75be95fdf7
4 changed files with 582 additions and 101 deletions

View File

@@ -14,7 +14,7 @@ name = "linux-hello-daemon"
path = "src/main.rs"
[features]
default = []
default = ["tpm"]
tpm = ["tss-esapi"]
onnx = ["ort", "ndarray"]

View File

@@ -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");
}
}

View File

@@ -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<FaceTemplate> {
pub fn load(&mut self, user: &str, label: &str) -> Result<FaceTemplate> {
// 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<FaceTemplate> {
fn load_encrypted(&mut self, user: &str, label: &str) -> Result<FaceTemplate> {
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<SecureEmbedding> {
pub fn load_secure(&mut self, user: &str, label: &str) -> Result<SecureEmbedding> {
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<Vec<FaceTemplate>> {
pub fn load_all(&mut self, user: &str) -> Result<Vec<FaceTemplate>> {
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)

View File

@@ -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<u8>,
/// 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<u8>,
/// Salt used for key derivation - 32 bytes
/// Salt used for key derivation (software only) - 32 bytes
pub salt: Vec<u8>,
/// 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<EncryptedTemplate>;
fn encrypt(&mut self, user: &str, plaintext: &[u8]) -> Result<EncryptedTemplate>;
/// Decrypt data using TPM-bound key
fn decrypt(&self, user: &str, encrypted: &EncryptedTemplate) -> Result<Vec<u8>>;
fn decrypt(&mut self, user: &str, encrypted: &EncryptedTemplate) -> Result<Vec<u8>>;
/// 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<EncryptedTemplate> {
fn encrypt(&mut self, user: &str, plaintext: &[u8]) -> Result<EncryptedTemplate> {
// 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<Vec<u8>> {
fn decrypt(&mut self, user: &str, encrypted: &EncryptedTemplate) -> Result<Vec<u8>> {
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<u8>,
/// TPM Public blob
public_blob: Vec<u8>,
/// Key auth value (encrypted with master secret)
encrypted_auth: Vec<u8>,
/// IV for auth encryption
auth_iv: Vec<u8>,
/// Salt for auth encryption
auth_salt: Vec<u8>,
}
/// TPM2 storage implementation
pub struct Tpm2Storage {
context: Option<Context>,
primary_key: Option<KeyHandle>,
primary_auth: Option<Auth>,
key_path: PathBuf,
/// Software fallback for encrypting auth values stored on disk
software_fallback: SoftwareTpmFallback,
}
impl Tpm2Storage {
pub fn new<P: AsRef<Path>>(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<StoredUserKey> {
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<Target=Vec<u8>>, so we can clone the inner Vec
let private_blob: Vec<u8> = (*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<u8> = (*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<u8>, Vec<u8>)> {
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<Vec<u8>> {
// 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<EncryptedTemplate> {
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<EncryptedTemplate> {
// 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<Vec<u8>> {
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<Vec<u8>> {
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<dyn TpmStorage + Send + Sync> {
#[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<dyn TpmStorage> {
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<dyn TpmStorage> {
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<u8> = (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);
}
}