aboutsummaryrefslogtreecommitdiff
path: root/src/vault/mod.rs
blob: aef512813f58e3b015a7105819cfd69fba73fac5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//! Vault data module
//!
//! A vault represents a collection of records of sensitive data. Each record
//! is encrypted before being written to disk.
//!
//! A vault can have multiple users which allows login-information to be
//! shared between multiple people. By default only one (root) user
//! is enabled though.
//!

use std::collections::HashMap;
use std::io::prelude::*;
use std::path::PathBuf;
use std::fs::File;
use std::fs;

use crypto::engine::CryptoEngine;
use record::{Record, Payload};

use serde_json;


/// This should be made pretty with actual Errors at some point
#[derive(Debug)]
pub enum ErrorType {
    DirectoryAlreadyExists,
    VaultDoesNotExist,
    GenericError(String),
    Success,
}


/// A vault that represents a collection of records of sensitive data.
/// Each record is encrypted before being written to disk.
///
/// A vault can have multiple users which allows login-information to be
/// shared between multiple people. By default only one (root) user
/// is enabled though.
pub struct Vault {
    name: String,
    path: PathBuf,
    crypto: CryptoEngine,
    pub records: HashMap<String, Record>,
}

impl Vault {
    /// Attempt to create a new vault
    pub fn new(name: &str, path: &str, password: &str) -> Result<Vault, ErrorType> {
        let mut me = Vault {
            name: String::from(name),
            path: PathBuf::new(),
            crypto: CryptoEngine::new(password, ""),
            records: HashMap::new(),
        };

        me.path.push(path);
        me.path.push(format!("{}.vault", name));

        /* Create relevant files */
        match me.create_dirs() {
            ErrorType::Success => {}
            val => return Err(val),
        }

        return Ok(me);
    }

    pub fn load(name: &str, path: &str, password: &str) -> Result<Vault, ErrorType> {

        /* Construct the base path */
        let mut pathbuf = PathBuf::new();
        pathbuf.push(path);
        pathbuf.push(format!("{}.vault", name));

        /* Load the secret key */
        let mut key = String::new();
        {
            pathbuf.push("primary.key");
            let key_path = pathbuf.as_os_str();
            let mut key_file = match File::open(key_path) {
                Ok(k) => k,
                Err(e) => return Err(ErrorType::VaultDoesNotExist),
            };

            match key_file.read_to_string(&mut key) {
                Ok(_) => {}
                Err(_) => return Err(ErrorType::VaultDoesNotExist),
            }
        };

        let crypto = CryptoEngine::load_existing(&key, password);

        /* Load all existing records */
        pathbuf.pop();
        pathbuf.push("records");
        let records = match fs::read_dir(pathbuf.as_path()) {
            Ok(f) => f,
            Err(_) => return Err(ErrorType::VaultDoesNotExist),
        };
        let mut record_map: HashMap<String, Record> = HashMap::new();
        pathbuf.pop();

        /* Decrypt and map all existing records */
        for entry in records {
            let mut encrypted = String::new();
            let record = entry.unwrap();
            let mut file = match File::open(record.path().as_os_str()) {
                Ok(f) => f,
                Err(_) => return Err(ErrorType::VaultDoesNotExist),
            };
            file.read_to_string(&mut encrypted).unwrap();

            /* Decrypt and decode the data */
            let decrypted = crypto.decrypt(&encrypted);
            let a_record: Record = match serde_json::from_str(&decrypted) {
                Ok(obj) => obj,
                Err(_) => return Err(ErrorType::VaultDoesNotExist),
            };

            let name = a_record.header.name.clone();
            record_map.insert(name, a_record);
        }

        /* Create vault and return it */
        let me = Vault {
            name: String::from(name),
            path: PathBuf::new(),
            crypto: crypto,
            records: record_map,
        };
        return Ok(me);
    }

    /// Adds a new record to the vault
    pub fn add_record(&mut self, name: &str, category: &str, tags: Vec<&str>) {
        let mut record = Record::new(name, category);
        for tag in tags {
            record.add_tag(&tag);
        }

        self.records.insert(String::from(name), record);
    }

    pub fn add_data(&mut self, record: &str, key: &str, data: Payload) {
        let r: &mut Record = self.records.get_mut(record).unwrap();
        r.set_data(key, data);
    }

    /// Sync current records to disk, overwriting existing files
    pub fn sync(&self) {

        let mut path = self.path.clone();
        path.push("records");
        println!("Syncing records in: {:?}", path.as_os_str());

        for (name, record) in &self.records {
            let serialised = serde_json::to_string(&record).unwrap();
            let encrypted = self.crypto.encrypt(&serialised);

            /* <vault>/records/<name>.data */
            {
                path.push(format!("{}.data", name));
                let file = path.as_path();
                println!("File exists: {}", file.exists());

                let mut handle = match file.exists() {
                    true => {
                        match File::open(file.as_os_str()) {
                            Ok(k) => k,
                            Err(e) => panic!("Failed to open file: {}", e),
                        }
                    }
                    false => {
                        match File::create(file.as_os_str()) {
                            Ok(k) => k,
                            Err(e) => {
                                panic!("Failed to create file ({:?}): {}", file.as_os_str(), e)
                            }
                        }
                    }
                };

                /* Write to disk */
                match handle.write_all(encrypted.as_bytes()) {
                    Err(e) => println!("An error was encountered while writing '{}': {}", name, e),
                    _ => {}
                }
            }

            path.pop();
        }
    }

    /**************************/

    /// Create all relevant directories
    fn create_dirs(&mut self) -> ErrorType {

        /* Check if the directories already exist */
        if self.path.as_path().exists() {
            return ErrorType::DirectoryAlreadyExists;
        }

        /* Create the directory */
        match fs::create_dir_all(self.path.as_path()) {
            Err(_) => {
                return ErrorType::GenericError("Failed to create vault directory group".to_string())
            }
            _ => {}
        };

        /* Create configs */
        let key = match self.crypto.dump_encrypted_key() {
            Some(k) => k,
            None => return ErrorType::GenericError("Failed to load encryption key!".to_string()),
        };

        /* Write encrypted key to disk */
        {
            self.path.push("primary.key");
            let key_path = self.path.as_os_str();
            let mut key_file = File::create(key_path).unwrap();
            println!("Creating key file at {:?}", key_file);
            key_file.write_all(key.as_bytes()).unwrap();
        }

        /* Create a few other directories */
        {
            self.path.pop();
            self.path.push("records");
            fs::create_dir_all(self.path.as_path()).unwrap();
            self.path.pop();
        }

        return ErrorType::Success;
    }
}