Skip to main content

seal/security/
tempfile.rs

1//! Secure temporary file handling with automatic cleanup
2
3use std::fs::File;
4use std::io::{self, Write};
5use std::path::{Path, PathBuf};
6use tempfile::NamedTempFile;
7use zeroize::Zeroize;
8
9/// Secure temporary file wrapper that:
10/// 1. Creates files with 0600 permissions (owner read/write only)
11/// 2. Overwrites content with zeros before deletion
12/// 3. Automatically deletes on drop
13pub struct SecureTempFile {
14    file: NamedTempFile,
15    path: PathBuf,
16}
17
18impl SecureTempFile {
19    /// Create a new secure temporary file
20    pub fn new() -> io::Result<Self> {
21        let file = tempfile::Builder::new()
22            .prefix("seal-secret-")
23            .suffix(".yaml")
24            .rand_bytes(16)
25            .tempfile()?;
26
27        let path = file.path().to_path_buf();
28
29        // Set restrictive permissions (Unix only)
30        #[cfg(unix)]
31        {
32            use std::os::unix::fs::PermissionsExt;
33            let mut perms = std::fs::metadata(&path)?.permissions();
34            perms.set_mode(0o600); // Owner read/write only
35            std::fs::set_permissions(&path, perms)?;
36        }
37
38        Ok(Self { file, path })
39    }
40
41    /// Write data to the temporary file
42    pub fn write_all(&mut self, data: &[u8]) -> io::Result<()> {
43        self.file.write_all(data)?;
44        self.file.flush()?;
45        Ok(())
46    }
47
48    /// Get the path to the temporary file
49    pub fn path(&self) -> &Path {
50        &self.path
51    }
52
53    /// Explicitly delete the file (called automatically on drop)
54    pub fn delete(self) -> io::Result<()> {
55        // Drop will handle cleanup
56        Ok(())
57    }
58}
59
60impl Drop for SecureTempFile {
61    fn drop(&mut self) {
62        // Attempt to overwrite file contents with zeros before deletion
63        if let Ok(metadata) = std::fs::metadata(&self.path) {
64            let file_size = metadata.len() as usize;
65            if file_size > 0 && file_size < 10 * 1024 * 1024 {
66                // Only overwrite if < 10MB
67                let mut zeros = vec![0u8; file_size];
68                if let Ok(mut file) = File::create(&self.path) {
69                    let _ = file.write_all(&zeros);
70                    let _ = file.flush();
71                }
72                zeros.zeroize();
73            }
74        }
75        // NamedTempFile will automatically delete on drop
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_secure_temp_file_creation() -> io::Result<()> {
85        let temp_file = SecureTempFile::new()?;
86        assert!(temp_file.path().exists());
87        Ok(())
88    }
89
90    #[test]
91    fn test_secure_temp_file_write() -> io::Result<()> {
92        let mut temp_file = SecureTempFile::new()?;
93        temp_file.write_all(b"test data")?;
94        assert!(temp_file.path().exists());
95        Ok(())
96    }
97
98    #[test]
99    fn test_secure_temp_file_cleanup() -> io::Result<()> {
100        let path;
101        {
102            let temp_file = SecureTempFile::new()?;
103            path = temp_file.path().to_path_buf();
104            assert!(path.exists());
105        }
106        // File should be deleted after drop
107        assert!(!path.exists());
108        Ok(())
109    }
110}