202 lines
6.2 KiB
Rust
202 lines
6.2 KiB
Rust
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
use aes_gcm::{
|
|
aead::{generic_array::GenericArray, Aead, NewAead},
|
|
Aes256Gcm, Error as DecryptionError,
|
|
};
|
|
use getrandom::getrandom;
|
|
use hmac::Hmac;
|
|
use olm_rs::PicklingMode;
|
|
use pbkdf2::pbkdf2;
|
|
use serde::{Deserialize, Serialize};
|
|
use sha2::Sha256;
|
|
use zeroize::{Zeroize, Zeroizing};
|
|
|
|
const KEY_SIZE: usize = 32;
|
|
const NONCE_SIZE: usize = 12;
|
|
const KDF_SALT_SIZE: usize = 32;
|
|
#[cfg(not(test))]
|
|
const KDF_ROUNDS: u32 = 200_000;
|
|
#[cfg(test)]
|
|
const KDF_ROUNDS: u32 = 1000;
|
|
|
|
/// Version specific info for the key derivation method that is used.
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub enum KdfInfo {
|
|
Pbkdf2 {
|
|
/// The number of PBKDF rounds that were used when deriving the AES key.
|
|
rounds: u32,
|
|
},
|
|
}
|
|
|
|
/// Version specific info for encryption method that is used to encrypt our
|
|
/// pickle key.
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub enum CipherTextInfo {
|
|
Aes256Gcm {
|
|
/// The nonce that was used to encrypt the ciphertext.
|
|
nonce: Vec<u8>,
|
|
/// The encrypted pickle key.
|
|
ciphertext: Vec<u8>,
|
|
},
|
|
}
|
|
|
|
/// An encrypted version of our pickle key, this can be safely stored in a
|
|
/// database.
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct EncryptedPickleKey {
|
|
/// Info about the key derivation method that was used to expand the
|
|
/// passphrase into an encryption key.
|
|
pub kdf_info: KdfInfo,
|
|
/// The ciphertext with it's accompanying additional data that is needed to
|
|
/// decrypt the pickle key.
|
|
pub ciphertext_info: CipherTextInfo,
|
|
/// The salt that was used when the passphrase was expanded into a AES key.
|
|
kdf_salt: Vec<u8>,
|
|
}
|
|
|
|
/// A pickle key that will be used to encrypt all the private keys for Olm.
|
|
///
|
|
/// Olm uses AES256 to encrypt accounts, sessions, inbound group sessions. We
|
|
/// also implement our own pickling for the cross-signing types using
|
|
/// AES256-GCM so the key sizes match.
|
|
#[derive(Debug, Zeroize, PartialEq)]
|
|
pub struct PickleKey {
|
|
aes256_key: Vec<u8>,
|
|
}
|
|
|
|
impl Default for PickleKey {
|
|
fn default() -> Self {
|
|
let mut key = vec![0u8; KEY_SIZE];
|
|
getrandom(&mut key).expect("Can't generate new pickle key");
|
|
|
|
Self { aes256_key: key }
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Vec<u8>> for PickleKey {
|
|
type Error = ();
|
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
|
if value.len() != KEY_SIZE {
|
|
Err(())
|
|
} else {
|
|
Ok(Self { aes256_key: value })
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PickleKey {
|
|
/// Generate a new random pickle key.
|
|
pub fn new() -> Self {
|
|
Default::default()
|
|
}
|
|
|
|
fn expand_key(passphrase: &str, salt: &[u8], rounds: u32) -> Zeroizing<Vec<u8>> {
|
|
let mut key = Zeroizing::from(vec![0u8; KEY_SIZE]);
|
|
pbkdf2::<Hmac<Sha256>>(passphrase.as_bytes(), salt, rounds, &mut *key);
|
|
key
|
|
}
|
|
|
|
/// Get a `PicklingMode` version of this pickle key.
|
|
pub fn pickle_mode(&self) -> PicklingMode {
|
|
PicklingMode::Encrypted { key: self.aes256_key.clone() }
|
|
}
|
|
|
|
/// Get the raw AES256 key.
|
|
pub fn key(&self) -> &[u8] {
|
|
&self.aes256_key
|
|
}
|
|
|
|
/// Encrypt and export our pickle key using the given passphrase.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `passphrase` - The passphrase that should be used to encrypt the
|
|
/// pickle key.
|
|
pub fn encrypt(&self, passphrase: &str) -> EncryptedPickleKey {
|
|
let mut salt = vec![0u8; KDF_SALT_SIZE];
|
|
getrandom(&mut salt).expect("Can't generate new random pickle key");
|
|
|
|
let key = PickleKey::expand_key(passphrase, &salt, KDF_ROUNDS);
|
|
let key = GenericArray::from_slice(key.as_ref());
|
|
let cipher = Aes256Gcm::new(key);
|
|
|
|
let mut nonce = vec![0u8; NONCE_SIZE];
|
|
getrandom(&mut nonce).expect("Can't generate new random nonce for the pickle key");
|
|
|
|
let ciphertext = cipher
|
|
.encrypt(GenericArray::from_slice(nonce.as_ref()), self.aes256_key.as_slice())
|
|
.expect("Can't encrypt pickle key");
|
|
|
|
EncryptedPickleKey {
|
|
kdf_info: KdfInfo::Pbkdf2 { rounds: KDF_ROUNDS },
|
|
kdf_salt: salt,
|
|
ciphertext_info: CipherTextInfo::Aes256Gcm { nonce, ciphertext },
|
|
}
|
|
}
|
|
|
|
/// Restore a pickle key from an encrypted export.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `passphrase` - The passphrase that should be used to encrypt the
|
|
/// pickle key.
|
|
///
|
|
/// * `encrypted` - The exported and encrypted version of the pickle key.
|
|
pub fn from_encrypted(
|
|
passphrase: &str,
|
|
encrypted: EncryptedPickleKey,
|
|
) -> Result<Self, DecryptionError> {
|
|
let key = match encrypted.kdf_info {
|
|
KdfInfo::Pbkdf2 { rounds } => Self::expand_key(passphrase, &encrypted.kdf_salt, rounds),
|
|
};
|
|
|
|
let key = GenericArray::from_slice(key.as_ref());
|
|
|
|
let decrypted = match encrypted.ciphertext_info {
|
|
CipherTextInfo::Aes256Gcm { nonce, ciphertext } => {
|
|
let cipher = Aes256Gcm::new(key);
|
|
let nonce = GenericArray::from_slice(&nonce);
|
|
cipher.decrypt(nonce, ciphertext.as_ref())?
|
|
}
|
|
};
|
|
|
|
Ok(Self { aes256_key: decrypted })
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::PickleKey;
|
|
|
|
#[test]
|
|
fn generating() {
|
|
PickleKey::new();
|
|
}
|
|
|
|
#[test]
|
|
fn encrypting() {
|
|
let passphrase = "it's a secret to everybody";
|
|
let pickle_key = PickleKey::new();
|
|
|
|
let encrypted = pickle_key.encrypt(passphrase);
|
|
let decrypted = PickleKey::from_encrypted(passphrase, encrypted).unwrap();
|
|
|
|
assert_eq!(pickle_key, decrypted);
|
|
}
|
|
}
|