~gpanders/passage

ref: 849cef24e44e6b1979a78f9244c02a329a8ed634 passage/src/key.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use age::{x25519::Identity, IdentityFile};
use secrecy::ExposeSecret;
use std::fs::{self, File, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;

use crate::crypt;
use crate::error::Error;
use crate::input;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::crypt;
    use std::env;

    #[test]
    fn saving_and_reading_secret_key() -> Result<(), Error> {
        let plaintext = "Testing saving_and_reading_secret_key";
        let key = Identity::generate();
        let path = env::temp_dir().join("key.txt");
        let encrypted = crypt::encrypt_with_keys(plaintext, &[key.to_public()])?;

        save_secret_key(&key, &path, true)?;

        let key = read_secret_key(&path)?;
        let decrypted = crypt::decrypt_with_key(&encrypted, &key)?;

        assert_eq!(decrypted, plaintext);

        Ok(())
    }
}

pub fn secret_key_path() -> PathBuf {
    dirs::data_dir().unwrap().join("passage").join("key.txt")
}

pub fn save_secret_key<P: AsRef<Path>>(key: &Identity, path: P, force: bool) -> Result<(), Error> {
    let path = path.as_ref();
    if !path.exists() {
        fs::create_dir_all(path.parent().unwrap())?;
    }

    let mut options = OpenOptions::new();
    options.write(true).truncate(true);

    if force {
        options.create(true);
    } else {
        options.create_new(true);
    }

    #[cfg(unix)]
    options.mode(0o600);

    let mut key_file = match options.open(&path) {
        Ok(f) => f,
        Err(e) => match e.kind() {
            io::ErrorKind::AlreadyExists => return Err(Error::SecretKeyExists),
            _ => return Err(e.into()),
        },
    };

    key_file.write_all(key.to_string().expose_secret().as_bytes())?;

    Ok(())
}

pub fn read_secret_key<P: AsRef<Path>>(path: P) -> Result<Identity, Error> {
    let path = path.as_ref();
    if !path.exists() {
        return Err(Error::NoSecretKey);
    }

    match IdentityFile::from_file(path.to_str().unwrap().to_string()) {
        Ok(identity_file) => identity_file
            .into_identities()
            .pop()
            .ok_or(Error::NoSecretKey),
        // The key file might be encrypted with a passphrase
        Err(_) => {
            let mut bytes = vec![];
            File::open(path)?.read_to_end(&mut bytes)?;

            let passphrase = input::read_secret("Passphrase for secret key", None)?;
            let decrypted = crypt::decrypt_with_passphrase(&bytes, Some(&passphrase))?;
            match IdentityFile::from_buffer(decrypted.as_bytes()) {
                Ok(identity_file) => identity_file
                    .into_identities()
                    .pop()
                    .ok_or(Error::NoSecretKey),
                Err(e) => Err(e.into()),
            }
        }
    }
}

pub fn encrypt_secret_key<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<(), Error> {
    let key = read_secret_key(&path)?;
    let encrypted = crypt::encrypt_with_passphrase(&key.to_string().expose_secret(), &passphrase)?;

    File::create(&path)?.write_all(&encrypted)?;

    Ok(())
}

pub fn decrypt_secret_key<P: AsRef<Path>>(path: P, passphrase: Option<&str>) -> Result<(), Error> {
    let mut encrypted = vec![];
    File::open(&path)?.read_to_end(&mut encrypted)?;

    let key = IdentityFile::from_buffer(
        crypt::decrypt_with_passphrase(&encrypted, passphrase)?.as_bytes(),
    )?
    .into_identities()
    .pop()
    .ok_or_else(|| Error::NoSecretKey)?;

    save_secret_key(&key, path, true)?;

    Ok(())
}