base: Use a CSPRNG to get our randomness and handle randomness errors
Since we're going to encrypt a lot of small objects separately we're gonna need a lot of random nonces, it doesn't help that our nonces are 24 bytes long either. So use a CSPRNG to random data faster, also don't panic if there wasn't enough randomness.master
parent
0a6b0e5804
commit
2bcc0afb91
|
@ -36,7 +36,7 @@ matrix-sdk-crypto = { version = "0.2.0", path = "../matrix_sdk_crypto", optional
|
||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
sled = "0.34.6"
|
sled = "0.34.6"
|
||||||
chacha20poly1305 = "0.7.1"
|
chacha20poly1305 = "0.7.1"
|
||||||
getrandom = "0.2.1"
|
rand = "0.8.2"
|
||||||
zeroize = { version = "1.2.0", features = ["zeroize_derive"] }
|
zeroize = { version = "1.2.0", features = ["zeroize_derive"] }
|
||||||
pbkdf2 = { version = "0.6.0", default-features = false }
|
pbkdf2 = { version = "0.6.0", default-features = false }
|
||||||
hmac = "0.10.1"
|
hmac = "0.10.1"
|
||||||
|
|
|
@ -67,6 +67,7 @@ impl From<SerializationError> for StoreError {
|
||||||
match e {
|
match e {
|
||||||
SerializationError::Json(e) => StoreError::Json(e),
|
SerializationError::Json(e) => StoreError::Json(e),
|
||||||
SerializationError::Encryption(e) => match e {
|
SerializationError::Encryption(e) => match e {
|
||||||
|
store_key::Error::Random(e) => StoreError::Encryption(e.to_string()),
|
||||||
store_key::Error::Serialization(e) => StoreError::Json(e),
|
store_key::Error::Serialization(e) => StoreError::Json(e),
|
||||||
store_key::Error::Encryption(e) => StoreError::Encryption(e),
|
store_key::Error::Encryption(e) => StoreError::Encryption(e),
|
||||||
},
|
},
|
||||||
|
@ -153,8 +154,11 @@ impl SledStore {
|
||||||
return Err(StoreError::UnencryptedStore);
|
return Err(StoreError::UnencryptedStore);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let key = StoreKey::new();
|
let key = StoreKey::new().map_err::<StoreError, _>(|e| e.into())?;
|
||||||
let encrypted_key = DatabaseType::Encrypted(key.export(passphrase));
|
let encrypted_key = DatabaseType::Encrypted(
|
||||||
|
key.export(passphrase)
|
||||||
|
.map_err::<StoreError, _>(|e| e.into())?,
|
||||||
|
);
|
||||||
db.insert("store_key", serde_json::to_vec(&encrypted_key)?)?;
|
db.insert("store_key", serde_json::to_vec(&encrypted_key)?)?;
|
||||||
key
|
key
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,14 +20,16 @@ use chacha20poly1305::{
|
||||||
aead::{Aead, Error as EncryptionError, NewAead},
|
aead::{Aead, Error as EncryptionError, NewAead},
|
||||||
ChaCha20Poly1305, Key, Nonce, XChaCha20Poly1305, XNonce,
|
ChaCha20Poly1305, Key, Nonce, XChaCha20Poly1305, XNonce,
|
||||||
};
|
};
|
||||||
use getrandom::getrandom;
|
|
||||||
use hmac::Hmac;
|
use hmac::Hmac;
|
||||||
use pbkdf2::pbkdf2;
|
use pbkdf2::pbkdf2;
|
||||||
|
use rand::{thread_rng, Error as RngError, Fill};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::StoreError;
|
||||||
|
|
||||||
const VERSION: u8 = 1;
|
const VERSION: u8 = 1;
|
||||||
const KEY_SIZE: usize = 32;
|
const KEY_SIZE: usize = 32;
|
||||||
const NONCE_SIZE: usize = 12;
|
const NONCE_SIZE: usize = 12;
|
||||||
|
@ -44,6 +46,18 @@ pub enum Error {
|
||||||
Serialization(#[from] serde_json::Error),
|
Serialization(#[from] serde_json::Error),
|
||||||
#[error("Error encrypting or decrypting an event {0}")]
|
#[error("Error encrypting or decrypting an event {0}")]
|
||||||
Encryption(String),
|
Encryption(String),
|
||||||
|
#[error("Error generating enough random data for a cryptographic operation")]
|
||||||
|
Random(#[from] RngError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<StoreError> for Error {
|
||||||
|
fn into(self) -> StoreError {
|
||||||
|
match self {
|
||||||
|
Error::Serialization(e) => StoreError::Json(e),
|
||||||
|
Error::Encryption(e) => StoreError::Encryption(e),
|
||||||
|
Error::Random(_) => StoreError::Encryption(self.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EncryptionError> for Error {
|
impl From<EncryptionError> for Error {
|
||||||
|
@ -100,15 +114,6 @@ pub struct StoreKey {
|
||||||
inner: Vec<u8>,
|
inner: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StoreKey {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut key = vec![0u8; KEY_SIZE];
|
|
||||||
getrandom(&mut key).expect("Can't generate new store key");
|
|
||||||
|
|
||||||
Self { inner: key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Vec<u8>> for StoreKey {
|
impl TryFrom<Vec<u8>> for StoreKey {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
@ -123,8 +128,12 @@ impl TryFrom<Vec<u8>> for StoreKey {
|
||||||
|
|
||||||
impl StoreKey {
|
impl StoreKey {
|
||||||
/// Generate a new random store key.
|
/// Generate a new random store key.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self, Error> {
|
||||||
Default::default()
|
let mut key = vec![0u8; KEY_SIZE];
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
key.try_fill(&mut rng)?;
|
||||||
|
|
||||||
|
Ok(Self { inner: key })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand the given passphrase into a KEY_SIZE long key.
|
/// Expand the given passphrase into a KEY_SIZE long key.
|
||||||
|
@ -145,40 +154,44 @@ impl StoreKey {
|
||||||
///
|
///
|
||||||
/// * `passphrase` - The passphrase that should be used to encrypt the
|
/// * `passphrase` - The passphrase that should be used to encrypt the
|
||||||
/// store key.
|
/// store key.
|
||||||
pub fn export(&self, passphrase: &str) -> EncryptedStoreKey {
|
pub fn export(&self, passphrase: &str) -> Result<EncryptedStoreKey, Error> {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let mut salt = vec![0u8; KDF_SALT_SIZE];
|
let mut salt = vec![0u8; KDF_SALT_SIZE];
|
||||||
getrandom(&mut salt).expect("Can't generate new random store key");
|
salt.try_fill(&mut rng)?;
|
||||||
|
|
||||||
let key = StoreKey::expand_key(passphrase, &salt, KDF_ROUNDS);
|
let key = StoreKey::expand_key(passphrase, &salt, KDF_ROUNDS);
|
||||||
let key = Key::from_slice(key.as_ref());
|
let key = Key::from_slice(key.as_ref());
|
||||||
let cipher = ChaCha20Poly1305::new(&key);
|
let cipher = ChaCha20Poly1305::new(&key);
|
||||||
|
|
||||||
let mut nonce = vec![0u8; NONCE_SIZE];
|
let mut nonce = vec![0u8; NONCE_SIZE];
|
||||||
getrandom(&mut nonce).expect("Can't generate new random nonce for the store key");
|
nonce.try_fill(&mut rng)?;
|
||||||
|
|
||||||
let ciphertext = cipher
|
let ciphertext =
|
||||||
.encrypt(Nonce::from_slice(nonce.as_ref()), self.inner.as_slice())
|
cipher.encrypt(Nonce::from_slice(nonce.as_ref()), self.inner.as_slice())?;
|
||||||
.expect("Can't encrypt store key");
|
|
||||||
|
|
||||||
EncryptedStoreKey {
|
Ok(EncryptedStoreKey {
|
||||||
kdf_info: KdfInfo::Pbkdf2ToChaCha20Poly1305 {
|
kdf_info: KdfInfo::Pbkdf2ToChaCha20Poly1305 {
|
||||||
rounds: KDF_ROUNDS,
|
rounds: KDF_ROUNDS,
|
||||||
kdf_salt: salt,
|
kdf_salt: salt,
|
||||||
},
|
},
|
||||||
ciphertext_info: CipherTextInfo::ChaCha20Poly1305 { nonce, ciphertext },
|
ciphertext_info: CipherTextInfo::ChaCha20Poly1305 { nonce, ciphertext },
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nonce() -> Vec<u8> {
|
fn get_nonce() -> Result<Vec<u8>, RngError> {
|
||||||
let mut nonce = vec![0u8; XNONCE_SIZE];
|
let mut nonce = vec![0u8; XNONCE_SIZE];
|
||||||
getrandom(&mut nonce).expect("Can't get random nonce");
|
let mut rng = thread_rng();
|
||||||
nonce
|
|
||||||
|
nonce.try_fill(&mut rng)?;
|
||||||
|
|
||||||
|
Ok(nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encrypt(&self, event: &impl Serialize) -> Result<EncryptedEvent, Error> {
|
pub fn encrypt(&self, event: &impl Serialize) -> Result<EncryptedEvent, Error> {
|
||||||
let event = serde_json::to_vec(event)?;
|
let event = serde_json::to_vec(event)?;
|
||||||
|
|
||||||
let nonce = StoreKey::get_nonce();
|
let nonce = StoreKey::get_nonce()?;
|
||||||
let cipher = XChaCha20Poly1305::new(self.key());
|
let cipher = XChaCha20Poly1305::new(self.key());
|
||||||
let xnonce = XNonce::from_slice(&nonce);
|
let xnonce = XNonce::from_slice(&nonce);
|
||||||
|
|
||||||
|
@ -241,15 +254,15 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generating() {
|
fn generating() {
|
||||||
StoreKey::new();
|
StoreKey::new().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encrypting() {
|
fn encrypting() {
|
||||||
let passphrase = "it's a secret to everybody";
|
let passphrase = "it's a secret to everybody";
|
||||||
let store_key = StoreKey::new();
|
let store_key = StoreKey::new().unwrap();
|
||||||
|
|
||||||
let encrypted = store_key.export(passphrase);
|
let encrypted = store_key.export(passphrase).unwrap();
|
||||||
let decrypted = StoreKey::import(passphrase, encrypted).unwrap();
|
let decrypted = StoreKey::import(passphrase, encrypted).unwrap();
|
||||||
|
|
||||||
assert_eq!(store_key, decrypted);
|
assert_eq!(store_key, decrypted);
|
||||||
|
@ -270,7 +283,7 @@ mod test {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let store_key = StoreKey::new();
|
let store_key = StoreKey::new().unwrap();
|
||||||
|
|
||||||
let encrypted = store_key.encrypt(&event).unwrap();
|
let encrypted = store_key.encrypt(&event).unwrap();
|
||||||
let decrypted: Value = store_key.decrypt(encrypted).unwrap();
|
let decrypted: Value = store_key.decrypt(encrypted).unwrap();
|
||||||
|
|
Loading…
Reference in New Issue