Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions crates/malachite-cli/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,21 @@ fn save(path: &Path, data: &str) -> Result<(), Error> {
// Create file with secure permissions (0600) on Unix systems
#[cfg(unix)]
let mut f = {
use std::os::unix::fs::OpenOptionsExt;
fs::OpenOptions::new()
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
let f = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600) // Set permissions at creation time
.open(path)
.map_err(|_| Error::OpenFile(path.to_path_buf()))?
.map_err(|_| Error::OpenFile(path.to_path_buf()))?;
// `mode()` above applies only when the file is created. If the path already
// exists with looser permissions, it is silently ignored and the private key
// would be written into a world-readable file. Enforce 0600 explicitly so an
// existing file is tightened before the key is written to it.
f.set_permissions(fs::Permissions::from_mode(0o600))
.map_err(|_| Error::OpenFile(path.to_path_buf()))?;
f
};

#[cfg(not(unix))]
Expand All @@ -68,3 +75,48 @@ fn save(path: &Path, data: &str) -> Result<(), Error> {

Ok(())
}

#[cfg(all(test, unix))]
mod tests {
use super::*;
use arc_consensus_types::signing::PrivateKey;
use rand::rngs::OsRng;
use std::os::unix::fs::PermissionsExt;
use tempfile::tempdir;

fn mode_of(path: &Path) -> u32 {
fs::metadata(path).unwrap().permissions().mode() & 0o777
}

#[test]
fn save_priv_validator_key_creates_file_with_0600() {
let dir = tempdir().unwrap();
let key_file = dir.path().join("priv_validator_key.json");

save_priv_validator_key(&key_file, &PrivateKey::generate(OsRng)).unwrap();

assert_eq!(mode_of(&key_file), 0o600);
}

#[test]
fn save_priv_validator_key_tightens_existing_loose_permissions() {
let dir = tempdir().unwrap();
let key_file = dir.path().join("priv_validator_key.json");

// Simulate a key file left world-readable by a backup restore or an
// older version. `OpenOptions::mode()` is ignored for existing files,
// so without an explicit set_permissions the key would be written into
// this 0644 file.
fs::write(&key_file, "{}").unwrap();
fs::set_permissions(&key_file, fs::Permissions::from_mode(0o644)).unwrap();
assert_eq!(mode_of(&key_file), 0o644);

save_priv_validator_key(&key_file, &PrivateKey::generate(OsRng)).unwrap();

assert_eq!(
mode_of(&key_file),
0o600,
"existing file should be tightened to 0600 before the key is written"
);
}
}