Create vault files owner-only from the first byte
This commit is contained in:
parent
848e29589c
commit
4461307162
1 changed files with 48 additions and 2 deletions
50
src/vault.rs
50
src/vault.rs
|
|
@ -1,7 +1,7 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
|
@ -250,10 +250,15 @@ fn write_restricted(path: &Path, content: &str) -> Result<(), AppError> {
|
||||||
|
|
||||||
let tmp_path = path.with_extension("json.tmp");
|
let tmp_path = path.with_extension("json.tmp");
|
||||||
{
|
{
|
||||||
|
// `mode` only applies when the file is freshly created, so the tmp
|
||||||
|
// file is owner-only from its very first byte. The explicit
|
||||||
|
// `set_permissions` below covers a tmp file left over by a crashed
|
||||||
|
// earlier run, which would be opened without applying `mode`.
|
||||||
let mut file = fs::OpenOptions::new()
|
let mut file = fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
|
.mode(0o600)
|
||||||
.open(&tmp_path)
|
.open(&tmp_path)
|
||||||
.map_err(|e| AppError::io("Could not write the data file", e))?;
|
.map_err(|e| AppError::io("Could not write the data file", e))?;
|
||||||
file.set_permissions(fs::Permissions::from_mode(0o600))
|
file.set_permissions(fs::Permissions::from_mode(0o600))
|
||||||
|
|
@ -285,8 +290,24 @@ pub fn backup_file(path: &Path) -> Result<PathBuf, AppError> {
|
||||||
return Ok(backup);
|
return Ok(backup);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::copy(path, &backup)
|
// Copy by hand instead of `fs::copy`, which gives the new file the
|
||||||
|
// source's permission bits: a group-readable legacy vault would sit there
|
||||||
|
// fully readable until the later chmod landed. Created with `mode(0o600)`,
|
||||||
|
// the backup is owner-only from its first byte.
|
||||||
|
let mut input =
|
||||||
|
fs::File::open(path).map_err(|e| AppError::io("Could not read your profile data", e))?;
|
||||||
|
let mut output = fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.mode(0o600)
|
||||||
|
.open(&backup)
|
||||||
.map_err(|e| AppError::io("Could not create a backup of your profile data", e))?;
|
.map_err(|e| AppError::io("Could not create a backup of your profile data", e))?;
|
||||||
|
std::io::copy(&mut input, &mut output)
|
||||||
|
.map_err(|e| AppError::io("Could not create a backup of your profile data", e))?;
|
||||||
|
output
|
||||||
|
.sync_all()
|
||||||
|
.map_err(|e| AppError::io("Could not save your profile data backup", e))?;
|
||||||
let _ = fs::set_permissions(&backup, fs::Permissions::from_mode(0o600));
|
let _ = fs::set_permissions(&backup, fs::Permissions::from_mode(0o600));
|
||||||
Ok(backup)
|
Ok(backup)
|
||||||
}
|
}
|
||||||
|
|
@ -394,6 +415,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The legacy flat-array form of [`sample_profile`], as on disk.
|
||||||
|
fn sample_profile_json() -> String {
|
||||||
|
r#"[
|
||||||
|
{ "label": "Alice", "public_key": "npub1test", "secret_key": "00ff", "created_at": 1700000000 }
|
||||||
|
]"#
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_vault_accepts_legacy_array_format() {
|
fn parse_vault_accepts_legacy_array_format() {
|
||||||
let json = r#"[
|
let json = r#"[
|
||||||
|
|
@ -478,6 +507,23 @@ mod tests {
|
||||||
assert_eq!(mode & 0o777, 0o600, "vault must be readable only by owner");
|
assert_eq!(mode & 0o777, 0o600, "vault must be readable only by owner");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backup_of_group_readable_source_is_0600() {
|
||||||
|
let path = temp_vault_path();
|
||||||
|
fs::write(&path, sample_profile_json()).unwrap();
|
||||||
|
fs::set_permissions(&path, fs::Permissions::from_mode(0o664)).unwrap();
|
||||||
|
|
||||||
|
let backup = backup_file(&path).expect("backup should succeed");
|
||||||
|
|
||||||
|
let mode = fs::metadata(&backup).unwrap().permissions().mode();
|
||||||
|
assert_eq!(
|
||||||
|
mode & 0o777,
|
||||||
|
0o600,
|
||||||
|
"backup must be owner-only even when the source is group-readable"
|
||||||
|
);
|
||||||
|
assert_eq!(fs::read_to_string(backup).unwrap(), sample_profile_json());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recognised_legacy_vault_is_tightened_to_0600() {
|
fn recognised_legacy_vault_is_tightened_to_0600() {
|
||||||
let path = temp_vault_path();
|
let path = temp_vault_path();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue