From a999fcde76270abfb459359c9093e613ac67db1d Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Fri, 2 May 2025 23:17:02 +0200 Subject: [PATCH] Enforce user-only-read-write permission on new secret file --- README.md | 1 - secretstore/Cargo.toml | 5 ++++ secretstore/src/secretstore.rs | 39 +++++++++++++++++++++++++++++ secretstore/src/test_secretstore.rs | 6 ++++- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 800c64a..b975779 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ Steps to mitigate the risks: ## TODO -- enforce restricted permissions on the file - encorce strong password - version 1.0, with format guarantee - (later) breaking challenge bounty diff --git a/secretstore/Cargo.toml b/secretstore/Cargo.toml index 47db8f2..4edfcdc 100644 --- a/secretstore/Cargo.toml +++ b/secretstore/Cargo.toml @@ -5,6 +5,11 @@ description = "Store a secret (such as a private key) in an encrypted file" license = "MIT" edition = "2021" +[features] +default = [ "unixfilepermissions" ] +# Use UNIX-style file permissions +unixfilepermissions = [] + [dependencies] bitcoin_hashes = "0.16.0" chacha20poly1305 = "0.10.1" diff --git a/secretstore/src/secretstore.rs b/secretstore/src/secretstore.rs index 88350b8..daba540 100644 --- a/secretstore/src/secretstore.rs +++ b/secretstore/src/secretstore.rs @@ -5,6 +5,8 @@ use crate::encrypt_xor as xor; use bitcoin_hashes::Sha256d; use hex_conservative::{DisplayHex, FromHex}; use std::fs; +#[cfg(feature = "unixfilepermissions")] +use std::os::unix::fs::PermissionsExt; use zeroize::{Zeroize, ZeroizeOnDrop}; /// Minimum accepted password length @@ -141,7 +143,44 @@ impl SecretStore { path_for_secret_file )); } + + // First create empty file + let f = fs::File::create(path_for_secret_file).map_err(|e| { + format!( + "Error writing to file {}, {}", + path_for_secret_file, + e.to_string() + ) + })?; + + // Create contents let encrypted_payload = self.assemble_encrypted_payload(encryption_password)?; + + // Set restricted permissions + #[cfg(feature = "unixfilepermissions")] + { + let metadata = f.metadata().map_err(|e| { + format!( + "Error getting file metadata {}, {}", + path_for_secret_file, + e.to_string() + ) + })?; + let mut permissions = metadata.permissions(); + + permissions.set_mode(0o600); // Read/write for owner, no read for others. + assert_eq!(permissions.mode(), 0o600); + + let _res = f.set_permissions(permissions).map_err(|e| { + format!( + "Error removing permsissions from file {}, {}", + path_for_secret_file, + e.to_string() + ) + })?; + } + + // Write out contents let _res = fs::write(path_for_secret_file, encrypted_payload).map_err(|e| { format!( "Error writing to file {}, {}", diff --git a/secretstore/src/test_secretstore.rs b/secretstore/src/test_secretstore.rs index 21550a9..a5b7b91 100644 --- a/secretstore/src/test_secretstore.rs +++ b/secretstore/src/test_secretstore.rs @@ -325,13 +325,17 @@ fn neg_read_from_file_xor_wrong_pw_wrong_result() { #[test] fn neg_create_password_too_short() { + let temp_file = get_temp_file_name(); let nonsecret_data = Vec::from_hex(NONSECRET_DATA1).unwrap(); let secret_data = Vec::from_hex(SECRET_DATA1).unwrap(); let store = create_store_from_data(nonsecret_data.clone(), &secret_data); let short_password = "no"; - let res = SecretStoreCreator::write_to_file(&store, "dummy_filename", short_password); + let _res = fs::remove_file(&temp_file); + let res = SecretStoreCreator::write_to_file(&store, &temp_file, short_password); assert_eq!(res.err().unwrap(), "Password is too short! (2 vs 7)"); + + let _res = fs::remove_file(&temp_file); } #[test]