Skip to main content

bittensor_wallet/
keyfile.rs

1use std::collections::HashMap;
2use std::env;
3use std::fs;
4use std::io::{Read, Write};
5use std::os::unix::fs::PermissionsExt;
6use std::path::PathBuf;
7use std::str::from_utf8;
8
9use ansible_vault::{decrypt_vault, encrypt_vault};
10use fernet::Fernet;
11
12use base64::{engine::general_purpose, Engine as _};
13use passwords::analyzer;
14use passwords::scorer;
15use serde_json::json;
16
17use crate::errors::KeyFileError;
18use crate::keypair::Keypair;
19use crate::utils;
20
21use sodiumoxide::crypto::pwhash;
22use sodiumoxide::crypto::secretbox;
23
24const NACL_SALT: &[u8] = b"\x13q\x83\xdf\xf1Z\t\xbc\x9c\x90\xb5Q\x879\xe9\xb1";
25const LEGACY_SALT: &[u8] = b"Iguesscyborgslikemyselfhaveatendencytobeparanoidaboutourorigins";
26
27/// Serializes keypair object into keyfile data.
28///
29///     Arguments:
30///         keypair (Keypair): The keypair object to be serialized.
31///     Returns:
32///         data (bytes): Serialized keypair data.
33pub fn serialized_keypair_to_keyfile_data(keypair: &Keypair) -> Result<Vec<u8>, KeyFileError> {
34    let mut data: HashMap<&str, serde_json::Value> = HashMap::new();
35
36    // publicKey and privateKey fields are optional. If they exist, hex prefix "0x" is added to them.
37    if let Ok(Some(public_key)) = keypair.public_key() {
38        let public_key_str = hex::encode(&public_key);
39        data.insert("accountId", json!(format!("0x{}", public_key_str)));
40        data.insert("publicKey", json!(format!("0x{}", public_key_str)));
41    }
42    if let Ok(Some(private_key)) = keypair.private_key() {
43        let private_key_str = hex::encode(&private_key);
44        data.insert("privateKey", json!(format!("0x{}", private_key_str)));
45    }
46
47    // mnemonic and ss58_address fields are optional.
48    if let Some(mnemonic) = keypair.mnemonic() {
49        data.insert("secretPhrase", json!(mnemonic.to_string()));
50    }
51
52    // the seed_hex field is optional. If it exists, hex prefix "0x" is added to it.
53    if let Some(seed_hex) = keypair.seed_hex() {
54        let seed_hex_str = match from_utf8(&seed_hex) {
55            Ok(s) => s.to_string(),
56            Err(_) => hex::encode(seed_hex),
57        };
58        data.insert("secretSeed", json!(format!("0x{}", seed_hex_str)));
59    }
60
61    if let Some(ss58_address) = keypair.ss58_address() {
62        data.insert("ss58Address", json!(ss58_address.to_string()));
63    }
64
65    // Serialize the data into JSON string and return it as bytes
66    let json_data = serde_json::to_string(&data)
67        .map_err(|e| KeyFileError::SerializationError(format!("Serialization error: {}", e)))?;
68    Ok(json_data.into_bytes())
69}
70
71/// Deserializes Keypair object from passed keyfile data.
72///
73///     Arguments:
74///         keyfile_data (PyBytes): The keyfile data to be loaded.
75///     Returns:
76///         keypair (Keypair): The Keypair loaded from bytes.
77///     Raises:
78///         KeyFileError: Raised if the passed PyBytes cannot construct a keypair object.
79pub fn deserialize_keypair_from_keyfile_data(keyfile_data: &[u8]) -> Result<Keypair, KeyFileError> {
80    // Decode the keyfile data from bytes to a string
81    let decoded = from_utf8(keyfile_data).map_err(|_| {
82        KeyFileError::DeserializationError("Failed to decode keyfile data.".to_string())
83    })?;
84
85    // Parse the JSON string into a HashMap
86    let keyfile_dict: HashMap<String, Option<String>> =
87        serde_json::from_str(decoded).map_err(|_| {
88            KeyFileError::DeserializationError("Failed to parse keyfile data.".to_string())
89        })?;
90
91    // Extract data from the keyfile
92    let secret_seed = keyfile_dict.get("secretSeed").and_then(|v| v.clone());
93    let secret_phrase = keyfile_dict.get("secretPhrase").and_then(|v| v.clone());
94    let private_key = keyfile_dict.get("privateKey").and_then(|v| v.clone());
95    let ss58_address = keyfile_dict.get("ss58Address").and_then(|v| v.clone());
96
97    // Create the `Keypair` based on the available data
98    if let Some(secret_phrase) = secret_phrase {
99        Keypair::create_from_mnemonic(secret_phrase.as_str()).map_err(|e| KeyFileError::Generic(e))
100    } else if let Some(seed) = secret_seed {
101        // Remove 0x prefix if present
102        let seed = seed.trim_start_matches("0x");
103        let seed_bytes = hex::decode(seed).map_err(|e| KeyFileError::Generic(e.to_string()))?;
104        Keypair::create_from_seed(seed_bytes).map_err(|e| KeyFileError::Generic(e))
105    } else if let Some(private_key) = private_key {
106        // Remove 0x prefix if present
107        let key = private_key.trim_start_matches("0x");
108        Keypair::create_from_private_key(key).map_err(|e| KeyFileError::Generic(e))
109    } else if let Some(ss58) = ss58_address {
110        Keypair::new(Some(ss58.clone()), None, None, 42, None, 1)
111            .map_err(|e| KeyFileError::Generic(e.to_string()))
112    } else {
113        Err(KeyFileError::Generic(
114            "Keypair could not be created from keyfile data.".to_string(),
115        ))
116    }
117}
118
119/// Validates the password against a password policy.
120///
121///     Arguments:
122///         password (str): The password to verify.
123///     Returns:
124///         valid (bool): ``True`` if the password meets validity requirements.
125pub fn validate_password(password: &str) -> Result<bool, KeyFileError> {
126    // Check for an empty password
127    if password.is_empty() {
128        return Ok(false);
129    }
130
131    // Define the password policy
132    let min_length = 6;
133    let min_score = 20.0; // Adjusted based on the scoring system described in the documentation
134
135    // Analyze the password
136    let analyzed = analyzer::analyze(password);
137    let score = scorer::score(&analyzed);
138
139    // Check conditions
140    if password.len() >= min_length && score >= min_score {
141        // Prompt user to retype the password
142        let password_verification_response =
143            utils::prompt_password("Retype your password: ".to_string())
144                .expect("Failed to read the password.");
145
146        // Remove potential newline or whitespace at the end
147        let password_verification = password_verification_response.trim();
148
149        if password == password_verification {
150            Ok(true)
151        } else {
152            utils::print("Passwords do not match.\n".to_string());
153            Ok(false)
154        }
155    } else {
156        utils::print("Password not strong enough. Try increasing the length of the password or the password complexity.\n".to_string());
157        Ok(false)
158    }
159}
160
161/// Prompts the user to enter a password for key encryption.
162///
163///     Arguments:
164///         validation_required (bool): If ``True``, validates the password against policy requirements.
165///     Returns:
166///         password (str): The valid password entered by the user.
167pub fn ask_password(validation_required: bool) -> Result<String, KeyFileError> {
168    let mut valid = false;
169    let mut password = utils::prompt_password("Enter your password: ".to_string());
170
171    if validation_required {
172        while !valid {
173            if let Some(ref pwd) = password {
174                valid = validate_password(&pwd)?;
175                if !valid {
176                    password = utils::prompt_password("Enter your password again: ".to_string());
177                }
178            } else {
179                valid = true
180            }
181        }
182    }
183
184    Ok(password.unwrap_or("".to_string()).trim().to_string())
185}
186
187/// Returns `true` if the keyfile data is NaCl encrypted.
188///
189///     Arguments:
190///         `keyfile_data` - Bytes to validate
191///     Returns:
192///         `is_nacl` - `true` if the data is ansible encrypted.
193pub fn keyfile_data_is_encrypted_nacl(keyfile_data: &[u8]) -> bool {
194    keyfile_data.starts_with(b"$NACL")
195}
196
197/// Returns true if the keyfile data is ansible encrypted.
198///
199///     Arguments:
200///         `keyfile_data` - The bytes to validate.
201///     Returns:
202///         `is_ansible` - ``True`` if the data is ansible encrypted.
203pub fn keyfile_data_is_encrypted_ansible(keyfile_data: &[u8]) -> bool {
204    keyfile_data.starts_with(b"$ANSIBLE_VAULT")
205}
206
207/// Returns true if the keyfile data is legacy encrypted.
208///
209///     Arguments:
210///         `keyfile_data` - The bytes to validate.
211///     Returns:
212///         `is_legacy` - `true` if the data is legacy encrypted.
213pub fn keyfile_data_is_encrypted_legacy(keyfile_data: &[u8]) -> bool {
214    keyfile_data.starts_with(b"gAAAAA")
215}
216
217/// Returns `true` if the keyfile data is encrypted.
218///
219///     Arguments:
220///         keyfile_data (bytes): The bytes to validate.
221///     Returns:
222///         is_encrypted (bool): `true` if the data is encrypted.
223pub fn keyfile_data_is_encrypted(keyfile_data: &[u8]) -> bool {
224    let nacl = keyfile_data_is_encrypted_nacl(keyfile_data);
225    let ansible = keyfile_data_is_encrypted_ansible(keyfile_data);
226    let legacy = keyfile_data_is_encrypted_legacy(keyfile_data);
227    nacl || ansible || legacy
228}
229
230/// Returns type of encryption method as a string.
231///
232///     Arguments:
233///         keyfile_data (bytes): Bytes to validate.
234///     Returns:
235///         (str): A string representing the name of encryption method.
236pub fn keyfile_data_encryption_method(keyfile_data: &[u8]) -> String {
237    if keyfile_data_is_encrypted_nacl(keyfile_data) {
238        "NaCl"
239    } else if keyfile_data_is_encrypted_ansible(keyfile_data) {
240        "Ansible Vault"
241    } else if keyfile_data_is_encrypted_legacy(keyfile_data) {
242        "legacy"
243    } else {
244        "unknown"
245    }
246    .to_string()
247}
248
249/// legacy_encrypt_keyfile_data.
250///
251///     Arguments:
252///         keyfile_data (bytes): Bytes of data from the keyfile.
253///         password (str): Optional string that represents the password.
254///     Returns:
255///         encrypted_data (bytes): The encrypted keyfile data in bytes.
256pub fn legacy_encrypt_keyfile_data(
257    keyfile_data: &[u8],
258    password: Option<String>,
259) -> Result<Vec<u8>, KeyFileError> {
260    let password = password.unwrap_or_else(||
261        // function to get password from user
262        ask_password(true).unwrap());
263
264    utils::print(
265        ":exclamation_mark: Encrypting key with legacy encryption method...\n".to_string(),
266    );
267
268    // Encrypting key with legacy encryption method
269    let encrypted_data = encrypt_vault(keyfile_data, password.as_str())
270        .map_err(|err| KeyFileError::EncryptionError(format!("{}", err)))?;
271
272    Ok(encrypted_data.into_bytes())
273}
274
275/// Retrieves the cold key password from the environment variables.
276///
277///     Arguments:
278///         `coldkey_name` - The name of the cold key.
279///     Returns:
280///         `Option<String>` - The password retrieved from the environment variables, or `None` if not found.
281pub fn get_password_from_environment(env_var_name: String) -> Result<Option<String>, KeyFileError> {
282    match env::var(&env_var_name) {
283        Ok(encrypted_password_base64) => {
284            let encrypted_password = general_purpose::STANDARD
285                .decode(&encrypted_password_base64)
286                .map_err(|_| KeyFileError::Base64DecodeError("Invalid Base64".to_string()))?;
287            let decrypted_password = decrypt_password(&encrypted_password, &env_var_name);
288            Ok(Some(decrypted_password))
289        }
290        Err(_) => Ok(None),
291    }
292}
293
294/// decrypt of keyfile_data with secretbox
295fn derive_key(password: &[u8]) -> secretbox::Key {
296    let nacl_salt = pwhash::argon2i13::Salt::from_slice(NACL_SALT).expect("Invalid NACL_SALT.");
297    let mut key = secretbox::Key([0; secretbox::KEYBYTES]);
298    pwhash::argon2i13::derive_key(
299        &mut key.0,
300        password,
301        &nacl_salt,
302        pwhash::argon2i13::OPSLIMIT_SENSITIVE,
303        pwhash::argon2i13::MEMLIMIT_SENSITIVE,
304    )
305    .expect("Failed to derive key for NaCl decryption.");
306    key
307}
308
309/// Encrypts the passed keyfile data using ansible vault.
310///
311///     Arguments:
312///         keyfile_data (bytes): The bytes to encrypt.
313///         password (str): The password used to encrypt the data. If `None`, asks for user input.
314///     Returns:
315///         encrypted_data (bytes): The encrypted data.
316pub fn encrypt_keyfile_data(
317    keyfile_data: &[u8],
318    password: Option<String>,
319) -> Result<Vec<u8>, KeyFileError> {
320    // get password or ask user
321    let password = match password {
322        Some(pwd) => pwd,
323        None => ask_password(true)?,
324    };
325
326    utils::print("Encrypting...\n".to_string());
327
328    // crate the key with pwhash Argon2i
329    let key = derive_key(password.as_bytes());
330
331    // encrypt the data using SecretBox
332    let nonce = secretbox::gen_nonce();
333    let encrypted_data = secretbox::seal(keyfile_data, &nonce, &key);
334
335    // concatenate with b"$NACL"
336    let mut result = b"$NACL".to_vec();
337    result.extend_from_slice(&nonce.0);
338    result.extend_from_slice(&encrypted_data);
339
340    Ok(result)
341}
342
343/// Decrypts the passed keyfile data using ansible vault.
344///
345///     Arguments:
346///         keyfile_data (): The bytes to decrypt.
347///         password (str): The password used to decrypt the data. If `None`, asks for user input.
348///         coldkey_name (str): The name of the cold key. If provided, retrieves the password from environment variables.
349///     Returns:
350///         decrypted_data (bytes): The decrypted data.
351pub fn decrypt_keyfile_data(
352    keyfile_data: &[u8],
353    password: Option<String>,
354    password_env_var: Option<String>,
355) -> Result<Vec<u8>, KeyFileError> {
356    // decrypt of keyfile_data with secretbox
357    fn nacl_decrypt(keyfile_data: &[u8], key: &secretbox::Key) -> Result<Vec<u8>, KeyFileError> {
358        let data = &keyfile_data[5..]; // Remove the $NACL prefix
359        let nonce = secretbox::Nonce::from_slice(&data[0..secretbox::NONCEBYTES]).ok_or(
360            KeyFileError::InvalidEncryption("Invalid nonce.".to_string()),
361        )?;
362        let ciphertext = &data[secretbox::NONCEBYTES..];
363        secretbox::open(ciphertext, &nonce, key).map_err(|_| {
364            KeyFileError::DecryptionError("Wrong password for nacl decryption.".to_string())
365        })
366    }
367    // decrypt of keyfile_data with legacy way
368    fn legacy_decrypt(password: &str, keyfile_data: &[u8]) -> Result<Vec<u8>, KeyFileError> {
369        let kdf = pbkdf2::pbkdf2_hmac::<sha2::Sha256>;
370        let mut key = vec![0; 32];
371        kdf(password.as_bytes(), LEGACY_SALT, 10000000, &mut key);
372
373        let fernet_key = Fernet::generate_key();
374        let fernet = Fernet::new(&fernet_key).unwrap();
375        let keyfile_data_str = from_utf8(keyfile_data)
376            .map_err(|e| KeyFileError::DeserializationError(e.to_string()))?;
377        fernet.decrypt(keyfile_data_str).map_err(|_| {
378            KeyFileError::DecryptionError("Wrong password for legacy decryption.".to_string())
379        })
380    }
381
382    let mut password = password;
383
384    // Retrieve password from environment variable if env_var_name is provided
385    if let Some(env_var_name_) = password_env_var {
386        if password.is_none() {
387            password = get_password_from_environment(env_var_name_)?;
388        }
389    }
390
391    // If password is still None, ask the user for input
392    if password.is_none() {
393        password = Some(ask_password(false)?);
394    }
395
396    let password = password.unwrap();
397
398    utils::print("Decrypting...\n".to_string());
399    // NaCl decryption
400    if keyfile_data_is_encrypted_nacl(keyfile_data) {
401        let key = derive_key(password.as_bytes());
402        let decrypted_data = nacl_decrypt(keyfile_data, &key).map_err(|_| {
403            KeyFileError::DecryptionError("Wrong password for decryption.".to_string())
404        })?;
405        return Ok(decrypted_data);
406    }
407
408    // Ansible Vault decryption
409    if keyfile_data_is_encrypted_ansible(keyfile_data) {
410        let decrypted_data = decrypt_vault(keyfile_data, password.as_str()).map_err(|_| {
411            KeyFileError::DecryptionError("Wrong password for decryption.".to_string())
412        })?;
413        return Ok(decrypted_data);
414    }
415
416    // Legacy decryption
417    if keyfile_data_is_encrypted_legacy(keyfile_data) {
418        let decrypted_data = legacy_decrypt(&password, keyfile_data).map_err(|_| {
419            KeyFileError::DecryptionError("Wrong password for decryption.".to_string())
420        })?;
421        return Ok(decrypted_data);
422    }
423
424    // If none of the methods work, raise error
425    Err(KeyFileError::InvalidEncryption(
426        "Invalid or unknown encryption method.".to_string(),
427    ))
428}
429
430fn confirm_prompt(question: &str) -> bool {
431    let choice = utils::prompt(format!("{} (y/N): ", question)).expect("Failed to read input.");
432    choice.trim().to_lowercase() == "y"
433}
434
435fn expand_tilde(path: &str) -> String {
436    if path.starts_with("~/") {
437        if let Some(home_dir) = dirs::home_dir() {
438            return path.replacen('~', home_dir.to_str().unwrap(), 1);
439        }
440    }
441    path.to_string()
442}
443
444// Encryption password
445fn encrypt_password(key: &str, value: &str) -> Vec<u8> {
446    let key_bytes = key.as_bytes();
447    value
448        .as_bytes()
449        .iter()
450        .enumerate()
451        .map(|(i, &c)| c ^ key_bytes[i % key_bytes.len()])
452        .collect()
453}
454
455// Decrypting password
456fn decrypt_password(data: &[u8], key: &str) -> String {
457    let key_bytes = key.as_bytes();
458    let decrypted_bytes: Vec<u8> = data
459        .iter()
460        .enumerate()
461        .map(|(i, &c)| c ^ key_bytes[i % key_bytes.len()])
462        .collect();
463    String::from_utf8(decrypted_bytes).unwrap_or_else(|_| String::new())
464}
465
466#[derive(Clone)]
467pub struct Keyfile {
468    pub path: String,
469    _path: PathBuf,
470    name: String,
471    should_save_to_env: bool,
472}
473impl std::fmt::Display for Keyfile {
474    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475        match self.__str__() {
476            Ok(s) => write!(f, "{}", s),
477            Err(e) => write!(f, "Error displaying keyfile: {}", e),
478        }
479    }
480}
481
482impl Keyfile {
483    /// Creates a new Keyfile instance.
484    ///
485    ///     Arguments:
486    ///         path (String): The file system path where the keyfile is stored.
487    ///         name (Option<String>): Optional name for the keyfile. Defaults to "Keyfile" if not provided.
488    ///         should_save_to_env (bool): If ``True``, saves the password to environment variables.
489    ///     Returns:
490    ///         keyfile (Keyfile): A new Keyfile instance.
491    pub fn new(
492        path: String,
493        name: Option<String>,
494        should_save_to_env: bool,
495    ) -> Result<Self, KeyFileError> {
496        let expanded_path: PathBuf = PathBuf::from(expand_tilde(&path));
497        let name = name.unwrap_or_else(|| "Keyfile".to_string());
498        Ok(Keyfile {
499            path,
500            _path: expanded_path,
501            name,
502            should_save_to_env,
503        })
504    }
505
506    #[allow(clippy::bool_comparison)]
507    fn __str__(&self) -> Result<String, KeyFileError> {
508        if self.exists_on_device()? != true {
509            Ok(format!("keyfile (empty, {})>", self.path))
510        } else if self.is_encrypted()? {
511            let encryption_method = self._read_keyfile_data_from_file()?;
512            Ok(format!(
513                "Keyfile ({:?} encrypted, {})>",
514                encryption_method, self.path
515            ))
516        } else {
517            Ok(format!("keyfile (decrypted, {})>", self.path))
518        }
519    }
520
521    fn __repr__(&self) -> Result<String, KeyFileError> {
522        self.__str__()
523    }
524
525    /// Returns the keypair from path, decrypts data if the file is encrypted.
526    ///
527    ///     Arguments:
528    ///         password (Option<String>): The password used to decrypt the data. If ``None``, asks for user input.
529    ///     Returns:
530    ///         keypair (Keypair): The Keypair loaded from the file.
531    pub fn get_keypair(&self, password: Option<String>) -> Result<Keypair, KeyFileError> {
532        // read file
533        let keyfile_data = self._read_keyfile_data_from_file()?;
534
535        // check if encrypted
536        let decrypted_keyfile_data = if keyfile_data_is_encrypted(&keyfile_data) {
537            decrypt_keyfile_data(&keyfile_data, password, Some(self.env_var_name()?))?
538        } else {
539            keyfile_data
540        };
541
542        // deserialization data into the Keypair
543        deserialize_keypair_from_keyfile_data(&decrypted_keyfile_data)
544    }
545
546    /// Loads the name from keyfile.name or raises an error.
547    pub fn get_name(&self) -> Result<String, KeyFileError> {
548        Ok(self.name.clone())
549    }
550
551    /// Loads the name from keyfile.path or raises an error.
552    pub fn get_path(&self) -> Result<String, KeyFileError> {
553        Ok(self.path.clone())
554    }
555
556    /// Returns the keyfile data under path.
557    pub fn data(&self) -> Result<Vec<u8>, KeyFileError> {
558        self._read_keyfile_data_from_file()
559    }
560
561    /// Returns the keyfile data under path.
562    pub fn keyfile_data(&self) -> Result<Vec<u8>, KeyFileError> {
563        self._read_keyfile_data_from_file()
564    }
565
566    /// Returns local environment variable key name based on Keyfile path.
567    pub fn env_var_name(&self) -> Result<String, KeyFileError> {
568        let path = &self
569            .path
570            .replace(std::path::MAIN_SEPARATOR, "_")
571            .replace('.', "_");
572        Ok(format!("BT_PW_{}", path.to_uppercase()))
573    }
574
575    /// Writes the keypair to the file and optionally encrypts data.
576    ///
577    ///     Arguments:
578    ///         keypair (Keypair): The keypair object to be stored.
579    ///         encrypt (bool): If ``True``, encrypts the keyfile data.
580    ///         overwrite (bool): If ``True``, overwrites existing file without prompting.
581    ///         password (Option<String>): The password used to encrypt the data. If ``None``, asks for user input.
582    pub fn set_keypair(
583        &self,
584        keypair: Keypair,
585        encrypt: bool,
586        overwrite: bool,
587        password: Option<String>,
588    ) -> Result<(), KeyFileError> {
589        self.make_dirs()?;
590
591        let keyfile_data = serialized_keypair_to_keyfile_data(&keypair)?;
592
593        let final_keyfile_data = if encrypt {
594            let encrypted_data = encrypt_keyfile_data(&keyfile_data, password.clone())?;
595
596            // store password to local env
597            if self.should_save_to_env {
598                self.save_password_to_env(password.clone())?;
599            }
600
601            encrypted_data
602        } else {
603            keyfile_data
604        };
605
606        self._write_keyfile_data_to_file(&final_keyfile_data, overwrite)?;
607
608        Ok(())
609    }
610
611    /// Creates directories for the path if they do not exist.
612    pub fn make_dirs(&self) -> Result<(), KeyFileError> {
613        if let Some(directory) = self._path.parent() {
614            // check if the dir is exit already
615            if !directory.exists() {
616                // create the dir if not
617                fs::create_dir_all(directory)
618                    .map_err(|e| KeyFileError::DirectoryCreation(e.to_string()))?;
619            }
620        }
621        Ok(())
622    }
623
624    /// Returns ``True`` if the file exists on the device.
625    ///
626    ///     Returns:
627    ///         readable (bool): ``True`` if the file is readable.
628    pub fn exists_on_device(&self) -> Result<bool, KeyFileError> {
629        Ok(self._path.exists())
630    }
631
632    /// Returns ``True`` if the file under path is readable.
633    pub fn is_readable(&self) -> Result<bool, KeyFileError> {
634        // check file exist
635        if !self.exists_on_device()? {
636            return Ok(false);
637        }
638
639        // get file metadata
640        let metadata = fs::metadata(&self._path).map_err(|e| {
641            KeyFileError::MetadataError(format!("Failed to get metadata for file: {}.", e))
642        })?;
643
644        // check permissions
645        let permissions = metadata.permissions();
646        let readable = permissions.mode() & 0o444 != 0; // check readability
647
648        Ok(readable)
649    }
650
651    /// Returns ``True`` if the file under path is writable.
652    ///
653    ///     Returns:
654    ///         writable (bool): ``True`` if the file is writable.
655    pub fn is_writable(&self) -> Result<bool, KeyFileError> {
656        // check if file exist
657        if !self.exists_on_device()? {
658            return Ok(false);
659        }
660
661        // get file metadata
662        let metadata = fs::metadata(&self._path).map_err(|e| {
663            KeyFileError::MetadataError(format!("Failed to get metadata for file: {}", e))
664        })?;
665
666        // check the permissions
667        let permissions = metadata.permissions();
668        let writable = permissions.mode() & 0o222 != 0; // check if file is writable
669
670        Ok(writable)
671    }
672
673    /// Returns ``True`` if the file under path is encrypted.
674    ///
675    ///     Returns:
676    ///         encrypted (bool): ``True`` if the file is encrypted.
677    pub fn is_encrypted(&self) -> Result<bool, KeyFileError> {
678        // check if file exist
679        if !self.exists_on_device()? {
680            return Ok(false);
681        }
682
683        // check readable
684        if !self.is_readable()? {
685            return Ok(false);
686        }
687
688        // get the data from file
689        let keyfile_data = self._read_keyfile_data_from_file()?;
690
691        // check if encrypted
692        let is_encrypted = keyfile_data_is_encrypted(&keyfile_data);
693
694        Ok(is_encrypted)
695    }
696
697    /// Asks the user if it is okay to overwrite the file.
698    pub fn _may_overwrite(&self) -> bool {
699        let choice = utils::prompt(format!(
700            "File {} already exists. Overwrite? (y/N) ",
701            self.path
702        ))
703        .expect("Failed to read input.");
704
705        choice.trim().to_lowercase() == "y"
706    }
707
708    /// Check the version of keyfile and update if needed.
709    ///
710    ///     Arguments:
711    ///         print_result (bool): If ``True``, prints the result of the encryption check.
712    ///         no_prompt (bool): If ``True``, skips user prompts during the update process.
713    ///     Returns:
714    ///         updated (bool): ``True`` if the keyfile was successfully updated to the latest encryption method.
715    pub fn check_and_update_encryption(
716        &self,
717        print_result: bool,
718        no_prompt: bool,
719    ) -> Result<bool, KeyFileError> {
720        if !self.exists_on_device()? {
721            if print_result {
722                utils::print(format!("Keyfile '{}' does not exist.\n", self.path));
723            }
724            return Ok(false);
725        }
726
727        if !self.is_readable()? {
728            if print_result {
729                utils::print(format!("Keyfile '{}' is not readable.\n", self.path));
730            }
731            return Ok(false);
732        }
733
734        if !self.is_writable()? {
735            if print_result {
736                utils::print(format!("Keyfile '{}' is not writable.\n", self.path));
737            }
738            return Ok(false);
739        }
740
741        let update_keyfile = false;
742        if !no_prompt {
743            // read keyfile
744            let keyfile_data = self._read_keyfile_data_from_file()?;
745
746            // check if file is decrypted
747            if keyfile_data_is_encrypted(&keyfile_data)
748                && !keyfile_data_is_encrypted_nacl(&keyfile_data)
749            {
750                utils::print("You may update the keyfile to improve security...\n".to_string());
751
752                // ask user for the confirmation for updating
753                if update_keyfile == confirm_prompt("Update keyfile?") {
754                    let mut stored_mnemonic = false;
755
756                    // check mnemonic if saved
757                    while !stored_mnemonic {
758                        utils::print(
759                            "Please store your mnemonic in case an error occurs...\n".to_string(),
760                        );
761                        if confirm_prompt("Have you stored the mnemonic?") {
762                            stored_mnemonic = true;
763                        } else if !confirm_prompt("Retry and continue keyfile update?") {
764                            return Ok(false);
765                        }
766                    }
767
768                    // try decrypt data
769                    let mut decrypted_keyfile_data: Option<Vec<u8>> = None;
770                    let mut password: Option<String> = None;
771                    while decrypted_keyfile_data.is_none() {
772                        let pwd = ask_password(false)?;
773                        password = Some(pwd.clone());
774
775                        match decrypt_keyfile_data(
776                            &keyfile_data,
777                            Some(pwd),
778                            Some(self.env_var_name()?),
779                        ) {
780                            Ok(decrypted_data) => {
781                                decrypted_keyfile_data = Some(decrypted_data);
782                            }
783                            Err(_) => {
784                                if !confirm_prompt("Invalid password, retry?") {
785                                    return Ok(false);
786                                }
787                            }
788                        }
789                    }
790
791                    // encryption of updated data
792                    if let Some(password) = password {
793                        if let Some(decrypted_data) = decrypted_keyfile_data {
794                            let encrypted_keyfile_data =
795                                encrypt_keyfile_data(&decrypted_data, Some(password))?;
796                            self._write_keyfile_data_to_file(&encrypted_keyfile_data, true)?;
797                        }
798                    }
799                }
800            }
801        }
802
803        if print_result || update_keyfile {
804            // check and get result
805            let keyfile_data = self._read_keyfile_data_from_file()?;
806
807            return if !keyfile_data_is_encrypted(&keyfile_data) {
808                if print_result {
809                    utils::print("Keyfile is not encrypted.\n".to_string());
810                }
811                Ok(false)
812            } else if keyfile_data_is_encrypted_nacl(&keyfile_data) {
813                if print_result {
814                    utils::print("Keyfile is updated.\n".to_string());
815                }
816                Ok(true)
817            } else {
818                if print_result {
819                    utils::print("Keyfile is outdated, please update using 'btcli'.\n".to_string());
820                }
821                Ok(false)
822            };
823        }
824        Ok(false)
825    }
826
827    /// Encrypts the file under the path.
828    ///
829    ///     Arguments:
830    ///         password (Option<String>): The password used to encrypt the data. If ``None``, asks for user input.
831    pub fn encrypt(&self, mut password: Option<String>) -> Result<(), KeyFileError> {
832        // checkers
833        if !self.exists_on_device()? {
834            return Err(KeyFileError::FileNotFound(format!(
835                "Keyfile at: {} does not exist",
836                self.path
837            )));
838        }
839
840        if !self.is_readable()? {
841            return Err(KeyFileError::NotReadable(format!(
842                "Keyfile at: {} is not readable",
843                self.path
844            )));
845        }
846
847        if !self.is_writable()? {
848            return Err(KeyFileError::NotWritable(format!(
849                "Keyfile at: {} is not writable",
850                self.path
851            )));
852        }
853
854        // read the data
855        let keyfile_data = self._read_keyfile_data_from_file()?;
856
857        let final_data = if !keyfile_data_is_encrypted(&keyfile_data) {
858            let as_keypair = deserialize_keypair_from_keyfile_data(&keyfile_data)?;
859            let serialized_data = serialized_keypair_to_keyfile_data(&as_keypair)?;
860
861            // get password from local env if exist
862            if password.is_none() {
863                password = get_password_from_environment(self.env_var_name()?)?;
864            }
865
866            let encrypted_keyfile_data = encrypt_keyfile_data(&serialized_data, password.clone())?;
867
868            if self.should_save_to_env {
869                self.save_password_to_env(password.clone())?;
870            }
871
872            encrypted_keyfile_data
873        } else {
874            keyfile_data
875        };
876
877        // write back
878        self._write_keyfile_data_to_file(&final_data, true)?;
879
880        Ok(())
881    }
882
883    /// Decrypts the file under the path.
884    ///
885    ///     Arguments:
886    ///         password (Option<String>): The password used to decrypt the data. If ``None``, asks for user input.
887    pub fn decrypt(&self, password: Option<String>) -> Result<(), KeyFileError> {
888        // checkers
889        if !self.exists_on_device()? {
890            return Err(KeyFileError::FileNotFound(format!(
891                "Keyfile at: {} does not exist.",
892                self.path
893            )));
894        }
895        if !self.is_readable()? {
896            return Err(KeyFileError::NotReadable(format!(
897                "Keyfile at: {} is not readable.",
898                self.path
899            )));
900        }
901        if !self.is_writable()? {
902            return Err(KeyFileError::NotWritable(format!(
903                "Keyfile at: {} is not writable.",
904                self.path
905            )));
906        }
907
908        // read data
909        let keyfile_data = self._read_keyfile_data_from_file()?;
910
911        let decrypted_data = if keyfile_data_is_encrypted(&keyfile_data) {
912            decrypt_keyfile_data(&keyfile_data, password, Some(self.env_var_name()?))?
913        } else {
914            keyfile_data
915        };
916
917        let as_keypair = deserialize_keypair_from_keyfile_data(&decrypted_data)?;
918
919        let serialized_data = serialized_keypair_to_keyfile_data(&as_keypair)?;
920        self._write_keyfile_data_to_file(&serialized_data, true)?;
921        Ok(())
922    }
923
924    /// Reads the keyfile data from the file.
925    ///
926    ///     Returns:
927    ///         keyfile_data (Vec<u8>): The keyfile data stored under the path.
928    ///     Raises:
929    ///         KeyFileError: Raised if the file does not exist or is not readable.
930    pub fn _read_keyfile_data_from_file(&self) -> Result<Vec<u8>, KeyFileError> {
931        // Check if the file exists
932        if !self.exists_on_device()? {
933            return Err(KeyFileError::FileNotFound(format!(
934                "Keyfile at: {} does not exist.",
935                self.path
936            )));
937        }
938
939        // Check if the file is readable
940        if !self.is_readable()? {
941            return Err(KeyFileError::NotReadable(format!(
942                "Keyfile at: {} is not readable.",
943                self.path
944            )));
945        }
946
947        // Open and read the file
948        let mut file = fs::File::open(&self._path)
949            .map_err(|e| KeyFileError::FileOpen(format!("Failed to open file: {}.", e)))?;
950        let mut data_vec = Vec::new();
951        file.read_to_end(&mut data_vec)
952            .map_err(|e| KeyFileError::FileRead(format!("Failed to read file: {}.", e)))?;
953
954        Ok(data_vec)
955    }
956
957    /// Writes the keyfile data to the file.
958    ///
959    ///     Arguments:
960    ///         keyfile_data: The byte data to store under the path.
961    ///         overwrite: If true, overwrites the data without asking for permission from the user. Default is false.
962    pub fn _write_keyfile_data_to_file(
963        &self,
964        keyfile_data: &[u8],
965        overwrite: bool,
966    ) -> Result<(), KeyFileError> {
967        // ask user for rewriting
968        if self.exists_on_device()? && !overwrite && !self._may_overwrite() {
969            return Err(KeyFileError::NotWritable(format!(
970                "Keyfile at: {} is not writable",
971                self.path
972            )));
973        }
974
975        let mut keyfile = fs::OpenOptions::new()
976            .write(true)
977            .create(true)
978            .truncate(true) // cleanup if rewrite
979            .open(&self._path)
980            .map_err(|e| KeyFileError::FileOpen(format!("Failed to open file: {}.", e)))?;
981
982        // write data
983        keyfile
984            .write_all(keyfile_data)
985            .map_err(|e| KeyFileError::FileWrite(format!("Failed to write to file: {}.", e)))?;
986
987        // set permissions
988        let mut permissions = fs::metadata(&self._path)
989            .map_err(|e| {
990                KeyFileError::MetadataError(format!("Failed to get metadata for file: {}.", e))
991            })?
992            .permissions();
993        permissions.set_mode(0o600); // just for owner
994        fs::set_permissions(&self._path, permissions).map_err(|e| {
995            KeyFileError::PermissionError(format!("Failed to set permissions: {}.", e))
996        })?;
997        Ok(())
998    }
999
1000    /// Saves the key's password to the associated local environment variable.
1001    ///
1002    ///     Arguments:
1003    ///         password (Option<String>): The password to save. If ``None``, asks for user input.
1004    ///     Returns:
1005    ///         encrypted_password_base64 (str): The base64-encoded encrypted password.
1006    pub fn save_password_to_env(&self, password: Option<String>) -> Result<String, KeyFileError> {
1007        // checking the password
1008        let password = match password {
1009            Some(pwd) => pwd,
1010            None => match ask_password(true) {
1011                Ok(pwd) => pwd,
1012                Err(e) => {
1013                    utils::print(format!("Error asking password: {:?}.\n", e));
1014                    return Ok("".to_string());
1015                }
1016            },
1017        };
1018        // saving password
1019        let env_var_name = self.env_var_name()?;
1020        // encrypt password
1021        let encrypted_password = encrypt_password(&env_var_name, &password);
1022        let encrypted_password_base64 = general_purpose::STANDARD.encode(&encrypted_password);
1023        // store encrypted password
1024        env::set_var(&env_var_name, &encrypted_password_base64);
1025        Ok(encrypted_password_base64)
1026    }
1027
1028    /// Removes the password associated with the Keyfile from the local environment.
1029    pub fn remove_password_from_env(&self) -> Result<bool, KeyFileError> {
1030        let env_var_name = self.env_var_name()?;
1031
1032        if env::var(&env_var_name).is_ok() {
1033            env::remove_var(&env_var_name);
1034            let message = format!("Environment variable '{}' removed.\n", env_var_name);
1035            utils::print(message);
1036            Ok(true)
1037        } else {
1038            let message = format!("Environment variable '{}' does not exist.\n", env_var_name);
1039            utils::print(message);
1040            Ok(false)
1041        }
1042    }
1043}