fixing some issues
This commit is contained in:
@@ -14,7 +14,7 @@ name = "linux-hello-daemon"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["tpm"]
|
||||
tpm = ["tss-esapi"]
|
||||
onnx = ["ort", "ndarray"]
|
||||
|
||||
|
||||
114
linux-hello-daemon/examples/tpm_test.rs
Normal file
114
linux-hello-daemon/examples/tpm_test.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user