pyrus_cert_store/
lib.rs

1//! # Pyrus Cert Store
2//!
3//! This crate's main purpose is to provide a simple API for "secure" 
4//! [`Cert`](pyrus_crypto::cert::Cert) storage. It is largely inspired 
5//! by [`sequoia-cert-store`](https://docs.rs/sequoia-cert-store/latest/sequoia_cert_store/).
6//!
7//! # A note on security
8//!
9//! This crate makes no security guarantees and its security goes as far 
10//! as the author's will to make his school project secure.
11//!
12//! There were no and will not be any security audits of this crate and 
13//! so use with caution.
14//!
15//! # How it works?
16//!
17//! The storage backend is 
18//! [`rusqlite`] with crate feature `bundled-sqlcipher-vendored-openssl` 
19//! enabled. This allows for encrypting the SQL database and keeps the secrets 
20//! "secure".
21//!
22//! Certificates are stored as [`LazyCert`]s, which is basically a serialized 
23//! (unparsed) certificate, a fingerprint, and a user id, which allows for 
24//! filtering and listing the certificates without parsing them which is 
25//! fallible.
26//!
27//! # Examples
28//! ## Openning a store and saving a certificate
29//! ```rust
30//! use pyrus_cert_store::{CertStore, LazyCert};
31//! # use std::error::Error;
32//! # use pyrus_crypto::prelude::*;
33//!
34//! # fn main() -> Result<(), Box<dyn Error>> {
35//! let my_cert: Cert = //..
36//! # Cert::new("Alice");
37//! let store = CertStore::open("certstore.db3")
38//! # ;
39//! # let store = CertStore::open("")
40//!     .with_passphrase(String::from("password123"), b"use a better password and salt")
41//!     .connect()?;
42//!
43//! store.insert(LazyCert::try_from(&my_cert)?)?;
44//! let stored_cert: LazyCert = store.get(my_cert.fingerprint())?;
45//! let stored_cert = stored_cert.to_cert()?;
46//!
47//! assert_eq!(my_cert, stored_cert);
48//! # Ok(())
49//! # }
50//! ```
51//! ## Openning a store in memory and removing a saved certificate
52//! ```rust
53//! use pyrus_cert_store::{CertStore, LazyCert};
54//! # use std::error::Error;
55//! # use pyrus_crypto::prelude::*;
56//!
57//! # fn main() -> Result<(), Box<dyn Error>> {
58//! let my_cert: Cert = //..
59//! # Cert::new("Alice");
60//! 
61//! // passing "" as path opens the store in memory
62//! let store = CertStore::open("")
63//!     .with_passphrase(String::from("password123"), b"use a better password and salt")
64//!     .connect()?;
65//!
66//! store.insert(LazyCert::try_from(&my_cert)?)?;
67//! store.remove(my_cert.fingerprint())?;
68//! 
69//! assert!(store.get(my_cert.fingerprint()).is_err());
70//! # Ok(())
71//! # }
72//! ```
73//! ## Openning an unencrypted store
74//!
75//! Since encryption is done using an SQL pragma there is no way to prove 
76//! that in tests.
77//! ```rust
78//! use pyrus_cert_store::{CertStore, LazyCert};
79//! # use std::error::Error;
80//! # use pyrus_crypto::prelude::*;
81//!
82//! # fn main() -> Result<(), Box<dyn Error>> {
83//! # let my_cert: Cert = //..
84//! # Cert::new("Alice");
85//! 
86//! // not configuring a passphrase assumes no encryption
87//! let store = CertStore::open("").connect()?;
88//!
89//! // dropping a store safely flushes all statements and saves it
90//! // well this one is in memory so it will be simply dropped
91//! # Ok(())
92//! # }
93//! ```
94
95mod connection;
96pub use connection::CertStoreConn;
97pub mod error;
98mod lazy_cert;
99pub use lazy_cert::LazyCert;
100mod store;
101pub use store::CertStore;
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use pyrus_crypto::prelude::*;
107    use std::error::Error;
108
109    fn make_certs() -> Vec<Cert> {
110        let mut certs = vec![];
111        for i in 0..5 {
112            certs.push(Cert::new(&format!("Cert {i}")));
113        }
114        certs
115    }
116
117    #[test]
118    fn unencrypted_store_works() -> Result<(), Box<dyn Error>> {
119        let certs: Vec<Cert> = make_certs()
120            .iter()
121            .map(|i| i.clone().strip_secrets())
122            .collect();
123
124        let store = CertStore::open("").connect()?;
125
126        for c in certs.iter().map(|i| LazyCert::try_from(i).unwrap()) {
127            store.insert(c)?;
128        }
129
130        let c0 = certs.get(0).unwrap();
131
132        let c0_stored = store.get(c0.fingerprint())?;
133        let c0_retrieved = c0_stored.to_cert()?;
134
135        assert_eq!(c0, &c0_retrieved);
136
137        store.remove(c0.fingerprint())?;
138        assert!(store.get(c0.fingerprint()).is_err());
139
140        Ok(())
141    }
142
143    #[test]
144    fn encrypted_store_works() -> Result<(), Box<dyn Error>> {
145        let certs = make_certs();
146
147        let password = String::from("secretpass2137");
148        let salt = blake3::hash(b"saltysalt");
149        let store = CertStore::open("")
150            .with_passphrase(password, salt.as_bytes())
151            .connect()?;
152
153        for c in certs.iter().map(|i| LazyCert::try_from(i).unwrap()) {
154            store.insert(c)?;
155        }
156
157        let c0 = certs.get(0).unwrap();
158
159        let c0_stored = store.get(c0.fingerprint())?;
160        let c0_retrieved = c0_stored.to_cert()?;
161
162        assert_eq!(c0, &c0_retrieved);
163
164        store.remove(c0.fingerprint())?;
165        assert!(store.get(c0.fingerprint()).is_err());
166
167        Ok(())
168    }
169
170    #[test]
171    fn store_in_dir() -> Result<(), Box<dyn Error>> {
172        let certs = make_certs();
173
174        let store_file = temp_file::empty();
175        let password = String::from("secretpass2137");
176        let salt = blake3::hash(b"saltysalt");
177        {
178            let store = CertStore::open(store_file.path())
179                .with_passphrase(password.clone(), salt.as_bytes())
180                .connect()?;
181            for c in certs.iter().map(|i| LazyCert::try_from(i).unwrap()) {
182                store.insert(c)?;
183            }
184        }
185        {
186            let store = CertStore::open(store_file.path())
187                .with_passphrase(password, salt.as_bytes())
188                .connect()?;
189            let store_certs = store.certs()?;
190            assert!(!store_certs.is_empty());
191
192            for (cert, store_cert) in certs.iter().zip(store_certs.iter()) {
193                let store_cert = store_cert.to_cert()?;
194                assert_eq!(cert, &store_cert);
195            }
196        }
197        Ok(())
198    }
199
200    #[test]
201    fn encrypted_store_passwords() -> Result<(), Box<dyn Error>> {
202        let store_file = temp_file::empty();
203        let password1 = String::from("secretpass2137");
204        let salt1 = blake3::hash(b"saltysalt");
205        {
206            let _store = CertStore::open(store_file.path())
207                .with_passphrase(password1.clone(), salt1.as_bytes())
208                .connect()?;
209        }
210        let salt2 = blake3::hash(b"sweetsalt");
211        {
212            let store = CertStore::open(store_file.path())
213                .with_passphrase(password1, salt2.as_bytes())
214                .connect();
215            assert!(store.is_err());
216        }
217        let password2 = String::from("secretpass69420");
218        {
219            let store = CertStore::open(store_file.path())
220                .with_passphrase(password2, salt1.as_bytes())
221                .connect();
222            assert!(store.is_err());
223        }
224        Ok(())
225    }
226
227    #[test]
228    fn store_retains_all() -> Result<(), Box<dyn Error>> {
229        let certs: Vec<Cert> = make_certs()
230            .iter()
231            .map(|i| i.clone().strip_secrets())
232            .collect();
233
234        let store = CertStore::open("").connect()?;
235
236        for c in certs.iter().map(|i| LazyCert::try_from(i).unwrap()) {
237            store.insert(c)?;
238        }
239
240        let uids = store.uids()?;
241
242        for (cert, store_cert) in certs.iter().zip(uids.iter()) {
243            let (fpr, uid) = store_cert;
244            assert_eq!(cert.fingerprint(), fpr);
245            assert_eq!(cert.user_id(), uid);
246        }
247
248        let store_certs = store.certs()?;
249
250        for (cert, store_cert) in certs.iter().zip(store_certs.iter()) {
251            let store_cert = store_cert.to_cert()?;
252            assert_eq!(cert, &store_cert);
253        }
254
255        Ok(())
256    }
257}