matrix-rust-sdk/crates/matrix-sdk-crypto/src/store/pickle_key.rs

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);
}
}