aboutsummaryrefslogtreecommitdiff
path: root/src/vault.rs
blob: a0276177dbc56ee53ce0c611af3752f574844171 (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
//! 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::{BTreeMap, HashMap};
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};

use record::{Payload, Record};
use security::{CryptoEngine, Key};

use serde_json;

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

pub struct Vault {
    name: String,
    path: String,
    engine: 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 buffer = PathBuf::new();
        buffer.push(path);
        buffer.push(format!("{}.vault", name));

        let mut me = Vault {
            name: String::from(name),
            path: buffer.to_str().unwrap().to_owned(),
            engine: CryptoEngine::generate(Key::generate()),
            records: HashMap::new(),
        };

        /* Create relevant files */
        match me.create_dirs() {
            ErrorType::Success => {
                let mut buffer = buffer.clone();
                buffer.push("primary.key");
                me.engine
                    .save(buffer.to_str().unwrap(), password, &me.name)
                    .unwrap();
            }
            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 primary key */
        pathbuf.push("primary.key");
        let mut engine = match CryptoEngine::load(pathbuf.to_str().unwrap(), password, name) {
            Ok(e) => e,
            Err(e) => return Err(ErrorType::FailedToInitialise),
        };
        pathbuf.pop();

        /* Load all existing records */
        pathbuf.push("records");
        let records = fs::read_dir(pathbuf.as_path()).unwrap();
        let mut record_map = 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 = File::open(record.path().as_os_str()).unwrap();
            file.read_to_string(&mut encrypted).unwrap();

            /* Decrypt and decode the data */
            let a_record: Record = engine.decrypt(encrypted).unwrap();

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

        return Ok(Vault {
            name: String::from(name),
            path: "".to_owned(),
            engine: engine,
            records: record_map,
        });
    }

    /// Adds a new (empty) 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);
    }

    /// Fill an existing record with data
    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(&mut self) {
        let mut buffer = PathBuf::new();
        buffer.push(&self.path);
        buffer.push("records");

        for (name, record) in &self.records {
            let encrypted = self.engine.encrypt(&record).unwrap();

            /* <vault>/records/<name>.data */
            {
                buffer.push(&format!("{}.data", name));
                let file = buffer.as_path();
                // println!("Saving file '{}' to '{}'", name, file.to_str().unwrap());

                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),
                    _ => {}
                }
            }

            buffer.pop();
        }
    }

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

    /// Create all relevant directories
    fn create_dirs(&mut self) -> ErrorType {
        let mut path = PathBuf::new();
        path.push(&self.path);

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

        /* Create the directory */
        match fs::create_dir_all(path.as_path()) {
            Err(_) => return ErrorType::FailedToInitialise,
            _ => {}
        };

        /* Create a few other directories */
        path.push("records");
        fs::create_dir_all(path.as_path()).unwrap();
        return ErrorType::Success;
    }
}