utility_crypto/
key_file.rs

1use crate::{PublicKey, SecretKey};
2use near_account_id::AccountId;
3use std::fs::File;
4use std::io;
5use std::io::{Read, Write};
6use std::path::Path;
7
8#[derive(serde::Serialize, serde::Deserialize)]
9pub struct KeyFile {
10    pub account_id: AccountId,
11    pub public_key: PublicKey,
12    // Credential files generated which near cli works with have private_key
13    // rather than secret_key field.  To make it possible to read those from
14    // neard add private_key as an alias to this field so either will work.
15    #[serde(alias = "private_key")]
16    pub secret_key: SecretKey,
17}
18
19impl KeyFile {
20    pub fn write_to_file(&self, path: &Path) -> io::Result<()> {
21        let data = serde_json::to_string_pretty(self)?;
22        let mut file = Self::create(path)?;
23        file.write_all(data.as_bytes())
24    }
25
26    #[cfg(unix)]
27    fn create(path: &Path) -> io::Result<File> {
28        use std::os::unix::fs::OpenOptionsExt;
29        std::fs::File::options().mode(0o600).write(true).create(true).open(path)
30    }
31
32    #[cfg(not(unix))]
33    fn create(path: &Path) -> io::Result<File> {
34        std::fs::File::create(path)
35    }
36
37    pub fn from_file(path: &Path) -> io::Result<Self> {
38        let mut file = File::open(path)?;
39        let mut json_config_str = String::new();
40        file.read_to_string(&mut json_config_str)?;
41        let json_str_without_comments: String =
42            near_config_utils::strip_comments_from_json_str(&json_config_str)?;
43
44        Ok(serde_json::from_str(&json_str_without_comments)?)
45    }
46}
47
48#[cfg(test)]
49mod test {
50    use super::*;
51
52    const ACCOUNT_ID: &str = "example";
53    const SECRET_KEY: &str = "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr";
54    const KEY_FILE_CONTENTS: &str = r#"{
55  "account_id": "example",
56  "public_key": "ed25519:6DSjZ8mvsRZDvFqFxo8tCKePG96omXW7eVYVSySmDk8e",
57  "secret_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr"
58}"#;
59
60    #[test]
61    fn test_to_file() {
62        let tmp = tempfile::TempDir::new().unwrap();
63        let path = tmp.path().join("key-file");
64
65        let account_id = ACCOUNT_ID.parse().unwrap();
66        let secret_key: SecretKey = SECRET_KEY.parse().unwrap();
67        let public_key = secret_key.public_key();
68        let key = KeyFile { account_id, public_key, secret_key };
69        key.write_to_file(&path).unwrap();
70
71        assert_eq!(KEY_FILE_CONTENTS, std::fs::read_to_string(&path).unwrap());
72
73        #[cfg(unix)]
74        {
75            use std::os::unix::fs::PermissionsExt;
76            let got = std::fs::metadata(&path).unwrap().permissions().mode();
77            assert_eq!(0o600, got & 0o777);
78        }
79    }
80
81    #[test]
82    fn test_from_file() {
83        fn load(contents: &[u8]) -> io::Result<()> {
84            let tmp = tempfile::NamedTempFile::new().unwrap();
85            tmp.as_file().write_all(contents).unwrap();
86            let result = KeyFile::from_file(tmp.path());
87            tmp.close().unwrap();
88
89            result.map(|key| {
90                assert_eq!(ACCOUNT_ID, key.account_id.to_string());
91                let secret_key: SecretKey = SECRET_KEY.parse().unwrap();
92                assert_eq!(secret_key, key.secret_key);
93                assert_eq!(secret_key.public_key(), key.public_key);
94            })
95        }
96
97        load(KEY_FILE_CONTENTS.as_bytes()).unwrap();
98
99        // Test private_key alias for secret_key works.
100        let contents = KEY_FILE_CONTENTS.replace("secret_key", "private_key");
101        load(contents.as_bytes()).unwrap();
102
103        // Test private_key is mutually exclusive with secret_key.
104        let err = load(br#"{
105            "account_id": "example",
106            "public_key": "ed25519:6DSjZ8mvsRZDvFqFxo8tCKePG96omXW7eVYVSySmDk8e",
107            "secret_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr",
108            "private_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr"
109        }"#).unwrap_err();
110        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
111        let inner_msg = err.into_inner().unwrap().to_string();
112        assert!(inner_msg.contains("duplicate field"));
113    }
114}