Skip to main content

hyperion_vault_core/
crypto.rs

1use chacha20poly1305::aead::{Aead, KeyInit, Payload};
2use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce};
3use zeroize::Zeroizing;
4
5use crate::error::{Error, Result};
6
7pub const DEK_LEN: usize = 32;
8pub const NONCE_LEN: usize = 24;
9
10pub type Dek = Zeroizing<[u8; DEK_LEN]>;
11
12pub fn fill_random(buf: &mut [u8]) {
13    getrandom::fill(buf).expect("operating system CSPRNG is unavailable");
14}
15
16pub fn generate_dek() -> Dek {
17    let mut k = [0u8; DEK_LEN];
18    fill_random(&mut k);
19    Zeroizing::new(k)
20}
21
22pub fn generate_nonce() -> [u8; NONCE_LEN] {
23    let mut n = [0u8; NONCE_LEN];
24    fill_random(&mut n);
25    n
26}
27
28pub fn dek_from_slice(bytes: &[u8]) -> Result<Dek> {
29    if bytes.len() != DEK_LEN {
30        return Err(Error::KeyLength {
31            expected: DEK_LEN,
32            got: bytes.len(),
33        });
34    }
35    let mut k = [0u8; DEK_LEN];
36    k.copy_from_slice(bytes);
37    Ok(Zeroizing::new(k))
38}
39
40pub fn seal(
41    dek: &[u8; DEK_LEN],
42    nonce: &[u8; NONCE_LEN],
43    aad: &[u8],
44    plaintext: &[u8],
45) -> Result<Vec<u8>> {
46    let cipher = XChaCha20Poly1305::new(Key::from_slice(&dek[..]));
47    cipher
48        .encrypt(
49            XNonce::from_slice(&nonce[..]),
50            Payload {
51                msg: plaintext,
52                aad,
53            },
54        )
55        .map_err(|_| Error::Encryption)
56}
57
58pub fn open(
59    dek: &[u8; DEK_LEN],
60    nonce: &[u8; NONCE_LEN],
61    aad: &[u8],
62    ciphertext: &[u8],
63) -> Result<Zeroizing<Vec<u8>>> {
64    let cipher = XChaCha20Poly1305::new(Key::from_slice(&dek[..]));
65    cipher
66        .decrypt(
67            XNonce::from_slice(&nonce[..]),
68            Payload {
69                msg: ciphertext,
70                aad,
71            },
72        )
73        .map(Zeroizing::new)
74        .map_err(|_| Error::Decryption)
75}
76
77pub struct DataKey {
78    pub plaintext: Dek,
79    pub wrapped: Vec<u8>,
80    pub key_id: String,
81}
82
83pub trait KeyWrapper {
84    fn generate_data_key(&self) -> Result<DataKey>;
85    fn unwrap_data_key(&self, wrapped: &[u8], key_id: &str) -> Result<Dek>;
86}
87
88#[derive(Clone)]
89pub struct Envelope {
90    pub key_id: String,
91    pub wrapped_dek: Vec<u8>,
92    pub nonce: [u8; NONCE_LEN],
93    pub ciphertext: Vec<u8>,
94}
95
96pub fn seal_envelope<W: KeyWrapper>(wrapper: &W, aad: &[u8], plaintext: &[u8]) -> Result<Envelope> {
97    let dk = wrapper.generate_data_key()?;
98    let nonce = generate_nonce();
99    let ciphertext = seal(&dk.plaintext, &nonce, aad, plaintext)?;
100    Ok(Envelope {
101        key_id: dk.key_id,
102        wrapped_dek: dk.wrapped,
103        nonce,
104        ciphertext,
105    })
106}
107
108pub fn open_envelope<W: KeyWrapper>(
109    wrapper: &W,
110    env: &Envelope,
111    aad: &[u8],
112) -> Result<Zeroizing<Vec<u8>>> {
113    let dek = wrapper.unwrap_data_key(&env.wrapped_dek, &env.key_id)?;
114    open(&dek, &env.nonce, aad, &env.ciphertext)
115}
116
117const WRAP_AAD: &[u8] = b"pg_vault:dek-wrap:v1";
118
119pub struct LocalKeyWrapper {
120    master: Zeroizing<[u8; DEK_LEN]>,
121    key_id: String,
122}
123
124impl LocalKeyWrapper {
125    pub fn new(master: [u8; DEK_LEN], key_id: impl Into<String>) -> Self {
126        Self {
127            master: Zeroizing::new(master),
128            key_id: key_id.into(),
129        }
130    }
131
132    pub fn random() -> Self {
133        let mut master = [0u8; DEK_LEN];
134        fill_random(&mut master);
135        Self::new(master, "local-dev")
136    }
137
138    pub fn from_base64(encoded: &str, key_id: impl Into<String>) -> Result<Self> {
139        use base64::Engine;
140        let raw = base64::engine::general_purpose::STANDARD
141            .decode(encoded.trim())
142            .map_err(|_| Error::KeyUnwrap)?;
143        if raw.len() != DEK_LEN {
144            return Err(Error::KeyLength {
145                expected: DEK_LEN,
146                got: raw.len(),
147            });
148        }
149        let mut master = [0u8; DEK_LEN];
150        master.copy_from_slice(&raw);
151        Ok(Self::new(master, key_id))
152    }
153}
154
155impl KeyWrapper for LocalKeyWrapper {
156    fn generate_data_key(&self) -> Result<DataKey> {
157        let dek = generate_dek();
158        let nonce = generate_nonce();
159        let ct = seal(&self.master, &nonce, WRAP_AAD, &dek[..]).map_err(|_| Error::KeyWrap)?;
160        let mut wrapped = Vec::with_capacity(NONCE_LEN + ct.len());
161        wrapped.extend_from_slice(&nonce);
162        wrapped.extend_from_slice(&ct);
163        Ok(DataKey {
164            plaintext: dek,
165            wrapped,
166            key_id: self.key_id.clone(),
167        })
168    }
169
170    fn unwrap_data_key(&self, wrapped: &[u8], _key_id: &str) -> Result<Dek> {
171        if wrapped.len() < NONCE_LEN {
172            return Err(Error::KeyUnwrap);
173        }
174        let (nonce, ct) = wrapped.split_at(NONCE_LEN);
175        let nonce: &[u8; NONCE_LEN] = nonce.try_into().map_err(|_| Error::KeyUnwrap)?;
176        let pt = open(&self.master, nonce, WRAP_AAD, ct).map_err(|_| Error::KeyUnwrap)?;
177        dek_from_slice(&pt)
178    }
179}