~gpanders/passage

ref: 849cef24e44e6b1979a78f9244c02a329a8ed634 passage/src/store.rs -rw-r--r-- 3.5 KiB
849cef24Gregory Anders Add uninstall target to Makefile 11 months ago
                                                                                
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
use age::x25519::{Identity, Recipient};
use std::fs::{self, DirEntry, File, OpenOptions};
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::path::PathBuf;

use crate::{crypt, error::Error, key};

pub struct PasswordStore {
    pub dir: PathBuf,
    pub recipients: Vec<Recipient>,
}

impl PasswordStore {
    pub fn new(dir: PathBuf) -> PasswordStore {
        let mut recipients: Vec<Recipient> = vec![];

        if let Ok(file) = File::open(dir.join(".public-keys")) {
            let buf = BufReader::new(file);
            buf.lines()
                .filter_map(|result| result.ok())
                .filter(|line| !line.starts_with('#'))
                .map(|line| line.parse())
                .filter_map(|result| result.ok())
                .for_each(|recipient| recipients.push(recipient));
        }

        PasswordStore { dir, recipients }
    }

    pub fn exists(&self, name: &str) -> bool {
        self.dir
            .join(PathBuf::from(name.to_string() + ".age"))
            .exists()
    }

    pub fn insert(&self, name: &str, secret: &str) -> Result<(), Error> {
        let path = self.dir.join(PathBuf::from(name.to_string() + ".age"));
        fs::create_dir_all(&path.parent().unwrap())?;

        let mut file = match OpenOptions::new().create_new(true).write(true).open(&path) {
            Ok(f) => f,
            Err(e) => match e.kind() {
                io::ErrorKind::AlreadyExists => return Err(Error::ItemAlreadyExists(name.into())),
                _ => return Err(e.into()),
            },
        };

        let encrypted = crypt::encrypt_with_keys(&secret, &self.recipients)?;
        file.write_all(&encrypted)?;

        Ok(())
    }

    pub fn get(&self, name: &str) -> Result<String, Error> {
        let path = self.dir.join(PathBuf::from(name.to_string() + ".age"));
        if !path.exists() {
            return Err(Error::ItemNotFound(name.into()));
        }

        let key = key::read_secret_key(key::secret_key_path())?;
        let decrypted = crypt::decrypt_with_key(&fs::read(path)?, &key)?;

        Ok(decrypted)
    }

    pub fn update(&self, name: &str, secret: &str) -> Result<(), Error> {
        let path = self.dir.join(PathBuf::from(name.to_string() + ".age"));
        if !path.exists() {
            return Err(Error::ItemNotFound(name.into()));
        }

        let encrypted = crypt::encrypt_with_keys(&secret, &self.recipients)?;
        File::create(path)?.write_all(&encrypted)?;

        Ok(())
    }

    pub fn delete(&self, name: &str) -> Result<(), Error> {
        if let Err(e) = fs::remove_file(self.dir.join(name.to_string() + ".age")) {
            match e.kind() {
                io::ErrorKind::NotFound => return Err(Error::ItemNotFound(name.into())),
                _ => return Err(e.into()),
            }
        }

        Ok(())
    }

    pub fn reencrypt(&self, key: &Identity) -> Result<(), Error> {
        let items: Vec<DirEntry> = fs::read_dir(&self.dir)?
            .filter_map(|e| e.ok())
            .filter(|e| {
                e.file_name()
                    .to_str()
                    .map_or(false, |s| s.ends_with(".age"))
            })
            .collect();

        for item in items {
            let mut cypher = vec![];
            File::open(item.path())?.read_to_end(&mut cypher)?;

            let secret = crypt::decrypt_with_key(&cypher, key)?;
            let encrypted = crypt::encrypt_with_keys(&secret, &self.recipients)?;
            File::create(item.path())?.write_all(&encrypted)?;
        }

        Ok(())
    }
}