cellar_core/
lib.rs

1//! Cellar is a simple password generation / retrieval tool inspired by Technology Preview for secure value recovery. The main algorithm is (a little bit tweak against original one):
2//!
3//! ```bash
4//! salt            = Secure-Random(output_length=32)
5//! stretched_key   = Argon2(passphrase=user_passphrase, salt=salt)
6//!
7//! auth_key        = HMAC-BLAKE2s(key=stretched_key, "Auth Key")
8//! c1              = HMAC-BLAKE2s(key=stretched_key, "Master Key")
9//! c2              = Secure-Random(output_length=32)
10//! encrypted_c2    = ChaCha20(c2, key=auth_key, nonce=salt[0..CHACHA20_NONCE_LENGTH])
11//!
12//! master_key      = HMAC-BLAKE2s(key=c1, c2)
13//! application_key = HMAC-BLAKE2s(key=master_key, "app info, e.g. yourname@gmail.com")
14//! ```
15//!
16//! The main purpose of cellar is to allow people to just remember a single password, and by using the above algorithm, one can create as many application passwords which is cryptographically strong. A user just need to store the randomly gnerated salt and encrypted_c2 in local disk and the cloud so when she wants to generate or retrieve an application password, she could use her passphrase, plus the salt and encrypted_c2 to recover the master key, and then derive the application password. As long as user kept the passphrase secret in her mind, all the application passwords are secure. Even if the salt and encrypted_c2 are leaked, a hacker still need to brute force the master key.
17
18//! By using Cellar, you don't need to trust the cloud provider to store your passwords, and you don't need to bother to remember a large number of passwords for different sites / applications.
19//!
20//! ## Usage
21//! ```rust
22//! let passphrase = "hello";
23//! let aux = cellar_core::init(passphrase).unwrap();
24//! let app_key = cellar_core::generate_app_key(passphrase, &aux, b"user@gmail.com", Default::default()).unwrap();
25//! ```
26//!
27//! You can also use the CLI version of the tool, which could be found in the repository.
28use base64::engine::general_purpose::URL_SAFE_NO_PAD;
29use base64::Engine;
30use bincode::{Decode, Encode};
31use blake2s_simd::Params;
32use c2_chacha::stream_cipher::{NewStreamCipher, SyncStreamCipher};
33use c2_chacha::ChaCha20;
34use certify::{CertInfo, KeyPair, CA};
35use ed25519_compact::{KeyPair as Ed25519KeyPair, Seed as Ed25519Seed};
36use rand::{rngs::StdRng, RngCore, SeedableRng};
37use serde::{Deserialize, Serialize};
38use zeroize::{Zeroize, Zeroizing};
39
40mod error;
41pub use error::CellarError;
42
43pub const KEY_SIZE: usize = 32;
44pub type Key = Zeroizing<[u8; KEY_SIZE]>;
45
46#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)]
47#[zeroize(drop)]
48pub struct AuxiliaryData {
49    salt: String,
50    encrypted_seed: String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
54pub struct CertificatePem {
55    pub cert: String,
56    pub sk: String,
57}
58
59#[derive(Debug, Clone)]
60pub enum KeyType {
61    Password,
62    Keypair,
63    CA(CertInfo),
64    ServerCert((String, String, CertInfo)),
65    ClientCert((String, String, CertInfo)),
66}
67
68impl Default for KeyType {
69    fn default() -> Self {
70        KeyType::Password
71    }
72}
73
74impl AuxiliaryData {
75    pub fn new(salt: &str, seed: &str) -> Self {
76        Self {
77            salt: salt.to_owned(),
78            encrypted_seed: seed.to_owned(),
79        }
80    }
81}
82
83const AUTH_KEY_INFO: &[u8] = b"Auth Key";
84const MASTER_KEY_INFO: &[u8] = b"Master Key";
85const CHACHA20_NONCE_LENGTH: usize = 8;
86
87/// generate random passphrase
88pub fn random_passphrase() -> String {
89    let mut rng = StdRng::from_entropy();
90    let mut buf = [0u8; 32];
91    rng.fill_bytes(&mut buf);
92    URL_SAFE_NO_PAD.encode(buf)
93}
94
95/// initialize a cellar. Return the salt and encrypted seed that user shall store them for future password generation and retrieval.
96pub fn init(passphrase: &str) -> Result<AuxiliaryData, CellarError> {
97    let mut rng = StdRng::from_entropy();
98    let mut salt: Key = Zeroizing::new([0u8; KEY_SIZE]);
99    let mut seed: Key = Zeroizing::new([0u8; KEY_SIZE]);
100
101    rng.fill_bytes(salt.as_mut());
102    rng.fill_bytes(seed.as_mut());
103
104    let stretch_key = generate_stretch_key(passphrase, salt.as_ref())?;
105    let auth_key = generate_derived_key(&stretch_key, AUTH_KEY_INFO);
106
107    let mut encrypted_seed = seed.as_ref().to_vec();
108    let nonce = &salt[..CHACHA20_NONCE_LENGTH];
109    let mut cipher = ChaCha20::new_var(auth_key.as_ref(), nonce).unwrap();
110    cipher.apply_keystream(&mut encrypted_seed);
111
112    Ok(AuxiliaryData {
113        salt: URL_SAFE_NO_PAD.encode(salt.as_ref()),
114        encrypted_seed: URL_SAFE_NO_PAD.encode(&encrypted_seed),
115    })
116}
117
118/// generate master key from the passphrase and entropy
119pub fn generate_master_key(passphrase: &str, aux: &AuxiliaryData) -> Result<Key, CellarError> {
120    let salt = URL_SAFE_NO_PAD.decode(&aux.salt)?;
121    let mut seed = URL_SAFE_NO_PAD.decode(&aux.encrypted_seed)?;
122
123    // stretch the passphrase to 32 bytes long
124    let stretch_key = generate_stretch_key(passphrase, &salt)?;
125
126    // generate a not so strong auth key for encrypting the secure random
127    let auth_key = generate_derived_key(&stretch_key, AUTH_KEY_INFO);
128
129    // generate master key main part
130    let partial_key = generate_derived_key(&stretch_key, MASTER_KEY_INFO);
131
132    // recover master seed
133    let nonce = &salt[..CHACHA20_NONCE_LENGTH];
134    let mut cipher = ChaCha20::new_var(auth_key.as_ref(), nonce).unwrap();
135    cipher.apply_keystream(&mut seed);
136
137    // recover master key
138    let master_key = generate_derived_key(&partial_key, &seed);
139    Ok(master_key)
140}
141
142/// generate application key based on user's passphrase, auxiliary data (salt and seed), as well as the app info as an entropy.
143pub fn generate_app_key(
144    passphrase: &str,
145    aux: &AuxiliaryData,
146    info: &[u8],
147    key_type: KeyType,
148) -> Result<Vec<u8>, CellarError> {
149    let master_key = generate_master_key(passphrase, aux)?;
150    let app_key = generate_derived_key(&master_key, info);
151    generate_by_key_type(app_key, key_type)
152}
153
154/// generate application key based on parent key and path. e.g. `apps/my/awesome/app`.
155pub fn generate_app_key_by_path(
156    parent_key: Key,
157    path: &str,
158    key_type: KeyType,
159) -> Result<Vec<u8>, CellarError> {
160    //let sum = a.iter().fold(0, |acc, x| acc + x);
161    let app_key = path.split('/').fold(parent_key, |acc, part| {
162        generate_derived_key(&acc, part.as_bytes())
163    });
164    generate_by_key_type(app_key, key_type)
165}
166
167pub fn to_base64(key: &[u8]) -> String {
168    URL_SAFE_NO_PAD.encode(key)
169}
170
171pub fn from_base64(key: &str) -> Result<Key, CellarError> {
172    let data = URL_SAFE_NO_PAD.decode(key)?;
173
174    let key: [u8; KEY_SIZE] = data
175        .try_into()
176        .map_err(|_e| CellarError::InvalidKey("Cannot convert the Vec<u8> to Key".to_owned()))?;
177    Ok(key.into())
178}
179
180/// covert the generated application key to a parent key which could be used to derive other keys
181pub fn as_parent_key(app_key: &[u8]) -> Key {
182    let mut key = Zeroizing::new([0u8; KEY_SIZE]);
183    key.copy_from_slice(app_key);
184    key
185}
186
187#[inline]
188fn generate_stretch_key(passphrase: &str, salt: &[u8]) -> Result<Key, CellarError> {
189    let hash = argon2::hash_raw(passphrase.as_bytes(), salt, &argon2::Config::default())?;
190    let mut key = Zeroizing::new([0u8; KEY_SIZE]);
191    key.copy_from_slice(&hash);
192    Ok(key)
193}
194
195#[inline]
196fn generate_derived_key(stretch_key: &Key, info: &[u8]) -> Key {
197    let mut params = Params::new();
198    params.key(stretch_key.as_ref());
199    let hash = params.hash(info).as_array().to_owned();
200    let mut key = Zeroizing::new([0u8; KEY_SIZE]);
201    key.copy_from_slice(&hash);
202    key
203}
204
205#[inline]
206fn generate_by_key_type(app_key: Key, key_type: KeyType) -> Result<Vec<u8>, CellarError> {
207    match key_type {
208        KeyType::Password => Ok(Vec::from(&app_key[..])),
209        KeyType::Keypair => {
210            let keypair = Ed25519KeyPair::from_seed(Ed25519Seed::from_slice(app_key.as_ref())?);
211            let mut ret = keypair.sk.to_der();
212            ret.extend_from_slice(&keypair.pk.to_der());
213            Ok(ret)
214        }
215        KeyType::CA(info) => {
216            let key = Ed25519KeyPair::from_seed(Ed25519Seed::from_slice(app_key.as_ref())?);
217            let keypair = KeyPair::from_der(&key.sk.to_der())?;
218            let ca = info.ca_cert(Some(keypair))?;
219            let cert_pem = CertificatePem {
220                cert: ca.serialize_pem().unwrap(),
221                sk: ca.serialize_private_key_pem(),
222            };
223            Ok(bincode::encode_to_vec(
224                &cert_pem,
225                bincode::config::standard(),
226            )?)
227        }
228        KeyType::ServerCert((ca_pem, key_pem, info)) => {
229            let ca = CA::load(&ca_pem, &key_pem)?;
230            let key = Ed25519KeyPair::from_seed(Ed25519Seed::from_slice(app_key.as_ref())?);
231            let keypair = KeyPair::from_der(&key.sk.to_der())?;
232            let cert = info.server_cert(Some(keypair))?;
233            let (server_cert_pem, server_key_pem) = ca.sign_cert(&cert)?;
234            let cert_pem = CertificatePem {
235                cert: server_cert_pem,
236                sk: server_key_pem,
237            };
238            Ok(bincode::encode_to_vec(
239                &cert_pem,
240                bincode::config::standard(),
241            )?)
242        }
243        KeyType::ClientCert((ca_pem, key_pem, info)) => {
244            let ca = CA::load(&ca_pem, &key_pem)?;
245            let key = Ed25519KeyPair::from_seed(Ed25519Seed::from_slice(app_key.as_ref())?);
246            let keypair = KeyPair::from_der(&key.sk.to_der())?;
247            let cert = info.client_cert(Some(keypair))?;
248            let (server_cert_pem, server_key_pem) = ca.sign_cert(&cert)?;
249            let cert_pem = CertificatePem {
250                cert: server_cert_pem,
251                sk: server_key_pem,
252            };
253            Ok(bincode::encode_to_vec(
254                &cert_pem,
255                bincode::config::standard(),
256            )?)
257        }
258    }
259}
260
261#[cfg(test)]
262extern crate quickcheck;
263#[cfg(test)]
264#[macro_use(quickcheck)]
265extern crate quickcheck_macros;
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270    use certify::CertSigAlgo;
271
272    #[test]
273    fn same_passphrase_produce_same_keys() -> Result<(), CellarError> {
274        let passphrase = "hello";
275        let aux = init(passphrase)?;
276        let app_key = generate_app_key(passphrase, &aux, b"user@gmail.com", KeyType::Password)?;
277        let app_key1 = generate_app_key(passphrase, &aux, b"user1@gmail.com", KeyType::Password)?;
278
279        assert_ne!(app_key1, app_key);
280
281        let app_key2 = generate_app_key(passphrase, &aux, b"user@gmail.com", KeyType::Password)?;
282        assert_eq!(app_key2, app_key);
283        Ok(())
284    }
285
286    #[test]
287    fn generate_usable_keypair_should_work() -> Result<(), CellarError> {
288        let passphrase = "hello";
289        let aux = init(passphrase)?;
290        let key = generate_app_key(passphrase, &aux, b"user@gmail.com", KeyType::Keypair)?;
291
292        let keypair = Ed25519KeyPair::from_der(&key[..48]).unwrap();
293        let content = b"hello world";
294        let sig = keypair.sk.sign(content, None);
295        let verified = keypair.pk.verify(content, &sig);
296        assert!(verified.is_ok());
297        Ok(())
298    }
299
300    #[test]
301    fn generate_key_by_path_should_work() -> Result<(), CellarError> {
302        let passphrase = "hello";
303        let aux = init(passphrase)?;
304        let key = generate_master_key(passphrase, &aux)?;
305        let parent_key = generate_app_key(passphrase, &aux, b"apps", KeyType::Password)?;
306        let app_key = generate_app_key_by_path(key, "apps/my/awesome/key", KeyType::Password)?;
307        let app_key1 = generate_app_key_by_path(
308            as_parent_key(&parent_key),
309            "my/awesome/key",
310            KeyType::Password,
311        )?;
312        assert_eq!(app_key, app_key1);
313        Ok(())
314    }
315
316    #[test]
317    fn generate_ca_cert_should_work() -> Result<(), CellarError> {
318        let info = CertInfo::new(
319            vec!["localhost"],
320            Vec::<String>::new(),
321            "US",
322            "Domain Inc.",
323            "Domain CA",
324            None,
325            CertSigAlgo::ED25519,
326        );
327        let (_, parent_key, cert_pem) = generate_ca(info.clone())?;
328
329        CA::load(&cert_pem.cert, &cert_pem.sk)?;
330
331        let cert1 = generate_app_key_by_path(
332            as_parent_key(&parent_key),
333            "localhost/ca",
334            KeyType::CA(info),
335        )?;
336
337        let (cert_pem1, _) =
338            bincode::decode_from_slice::<CertificatePem, _>(&cert1, bincode::config::standard())?;
339
340        assert_eq!(&cert_pem.sk, &cert_pem1.sk);
341        assert_eq!(&cert_pem.cert, &cert_pem1.cert);
342
343        Ok(())
344    }
345
346    #[test]
347    fn generate_server_cert_should_work() -> Result<(), CellarError> {
348        let info = CertInfo::new(
349            vec!["localhost"],
350            Vec::<String>::new(),
351            "US",
352            "Domain Inc.",
353            "Domain CA",
354            None,
355            CertSigAlgo::ED25519,
356        );
357        let (key, parent_key, cert_pem) = generate_ca(info)?;
358
359        let info = CertInfo::new(
360            vec!["localhost"],
361            Vec::<String>::new(),
362            "US",
363            "Domain Inc.",
364            "GRPC Server",
365            Some(365),
366            CertSigAlgo::ED25519,
367        );
368        let cert = generate_app_key_by_path(
369            key,
370            "apps/localhost/server",
371            KeyType::ServerCert((cert_pem.cert.clone(), cert_pem.sk.clone(), info.clone())),
372        )?;
373
374        let cert1 = generate_app_key_by_path(
375            as_parent_key(&parent_key),
376            "localhost/server",
377            KeyType::ServerCert((cert_pem.cert.clone(), cert_pem.sk.clone(), info)),
378        )?;
379
380        println!("{}\n{}", &cert_pem.cert, &cert_pem.sk);
381
382        let (cert_pem, _) =
383            bincode::decode_from_slice::<CertificatePem, _>(&cert, bincode::config::standard())?;
384        println!("{}\n{}", &cert_pem.cert, &cert_pem.sk);
385
386        assert_eq!(cert, cert1);
387
388        Ok(())
389    }
390
391    #[test]
392    fn generate_client_cert_should_work() -> Result<(), CellarError> {
393        let info = CertInfo::new(
394            vec!["localhost"],
395            Vec::<String>::new(),
396            "US",
397            "Domain Inc.",
398            "Domain CA",
399            None,
400            CertSigAlgo::ED25519,
401        );
402        let (key, parent_key, ca_cert_pem) = generate_ca(info)?;
403
404        println!("CA cert:\n\n{}\n{}", &ca_cert_pem.cert, &ca_cert_pem.sk);
405
406        let info = CertInfo::new(
407            vec!["localhost"],
408            Vec::<String>::new(),
409            "US",
410            "Domain Inc.",
411            "GRPC Server",
412            Some(365),
413            CertSigAlgo::ED25519,
414        );
415        let server_cert = generate_app_key_by_path(
416            as_parent_key(&parent_key),
417            "localhost/server",
418            KeyType::ServerCert((ca_cert_pem.cert.clone(), ca_cert_pem.sk.clone(), info)),
419        )?;
420
421        let (server_cert_pem, _) = bincode::decode_from_slice::<CertificatePem, _>(
422            &server_cert,
423            bincode::config::standard(),
424        )?;
425        println!(
426            "Server cert:\n\n{}\n{}",
427            &server_cert_pem.cert, &server_cert_pem.sk
428        );
429
430        let info = CertInfo::new(
431            vec!["localhost"],
432            Vec::<String>::new(),
433            "US",
434            "android",
435            "abcd1234",
436            Some(180),
437            CertSigAlgo::ED25519,
438        );
439        let client_cert = generate_app_key_by_path(
440            key,
441            "apps/localhost/client/abcd1234",
442            KeyType::ClientCert((
443                ca_cert_pem.cert.clone(),
444                ca_cert_pem.sk.clone(),
445                info.clone(),
446            )),
447        )?;
448
449        let cert1 = generate_app_key_by_path(
450            as_parent_key(&parent_key),
451            "localhost/client/abcd1234",
452            KeyType::ClientCert((ca_cert_pem.cert.clone(), ca_cert_pem.sk, info)),
453        )?;
454
455        let (client_cert_pem, _) = bincode::decode_from_slice::<CertificatePem, _>(
456            &client_cert,
457            bincode::config::standard(),
458        )?;
459        println!(
460            "Client cert:\n\n{}\n{}",
461            &client_cert_pem.cert, &client_cert_pem.sk
462        );
463
464        assert_eq!(client_cert, cert1);
465
466        Ok(())
467    }
468
469    #[ignore]
470    #[quickcheck]
471    fn prop_same_passphrase_produce_same_keys(passphrase: String, app_info: String) -> bool {
472        let aux = init(&passphrase).unwrap();
473        let app_key =
474            generate_app_key(&passphrase, &aux, app_info.as_bytes(), KeyType::Password).unwrap();
475
476        app_key
477            == generate_app_key(&passphrase, &aux, app_info.as_bytes(), KeyType::Password).unwrap()
478    }
479
480    fn generate_ca(info: CertInfo) -> Result<(Key, Vec<u8>, CertificatePem), CellarError> {
481        let passphrase = "hello";
482        let aux = init(passphrase)?;
483        let key = generate_master_key(passphrase, &aux)?;
484        let parent_key = generate_app_key(passphrase, &aux, b"apps", KeyType::Password)?;
485
486        let cert = generate_app_key_by_path(key.clone(), "apps/localhost/ca", KeyType::CA(info))?;
487        let (cert_pem, _) =
488            bincode::decode_from_slice::<CertificatePem, _>(&cert, bincode::config::standard())?;
489        Ok((key, parent_key, cert_pem))
490    }
491}