crypto: Add error handling to the key exports.

master
Damir Jelić 2020-09-10 17:02:36 +02:00
parent 126ac3059b
commit 618a58ba34
1 changed files with 56 additions and 22 deletions

View File

@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use serde_json::Error as SerdeError;
use std::io::{Cursor, Read, Seek, SeekFrom}; use std::io::{Cursor, Read, Seek, SeekFrom};
use thiserror::Error;
use base64::{decode_config, encode_config, DecodeError, STANDARD_NO_PAD}; use base64::{decode_config, encode_config, DecodeError, STANDARD_NO_PAD};
use byteorder::{BigEndian, ReadBytesExt}; use byteorder::{BigEndian, ReadBytesExt};
@ -45,6 +47,32 @@ fn encode(input: impl AsRef<[u8]>) -> String {
encode_config(input, STANDARD_NO_PAD) encode_config(input, STANDARD_NO_PAD)
} }
/// Error representing a failure during key export or import.
#[derive(Error, Debug)]
pub enum KeyExportError {
/// The key export doesn't contain valid headers.
#[error("Invalid or missing key export headers.")]
InvalidHeaders,
/// The key export has been encrypted with an unsupported version.
#[error("The key export has been encrypted with an unsupported version.")]
UnsupportedVersion,
/// The MAC of the encrypted payload is invalid.
#[error("The MAC of the encrypted payload is invalid.")]
InvalidMAC,
/// The decrypted key export isn't valid UTF-8.
#[error(transparent)]
InvalidUtf8(#[from] std::string::FromUtf8Error),
/// The decrypted key export doesn't contain valid JSON.
#[error(transparent)]
Json(#[from] SerdeError),
/// The key export string isn't valid base64.
#[error(transparent)]
Decode(#[from] DecodeError),
/// The key export doesn't all the required fields.
#[error(transparent)]
IO(#[from] std::io::Error),
}
/// Try to decrypt a reader into a list of exported room keys. /// Try to decrypt a reader into a list of exported room keys.
/// ///
/// # Arguments /// # Arguments
@ -68,13 +96,13 @@ fn encode(input: impl AsRef<[u8]>) -> String {
pub fn decrypt_key_export( pub fn decrypt_key_export(
mut input: impl Read, mut input: impl Read,
passphrase: &str, passphrase: &str,
) -> Result<Vec<ExportedRoomKey>, DecodeError> { ) -> Result<Vec<ExportedRoomKey>, KeyExportError> {
let mut x: String = String::new(); let mut x: String = String::new();
input.read_to_string(&mut x).expect("Can't read string"); input.read_to_string(&mut x)?;
if !(x.trim_start().starts_with(HEADER) && x.trim_end().ends_with(FOOTER)) { if !(x.trim_start().starts_with(HEADER) && x.trim_end().ends_with(FOOTER)) {
panic!("Invalid header/footer"); return Err(KeyExportError::InvalidHeaders);
} }
let payload: String = x let payload: String = x
@ -82,7 +110,9 @@ pub fn decrypt_key_export(
.filter(|l| !(l.starts_with(HEADER) || l.starts_with(FOOTER))) .filter(|l| !(l.starts_with(HEADER) || l.starts_with(FOOTER)))
.collect(); .collect();
Ok(serde_json::from_str(&decrypt_helper(&payload, passphrase)?).unwrap()) Ok(serde_json::from_str(&decrypt_helper(
&payload, passphrase,
)?)?)
} }
/// Encrypt the list of exported room keys using the given passphrase. /// Encrypt the list of exported room keys using the given passphrase.
@ -113,10 +143,14 @@ pub fn decrypt_key_export(
/// let encrypted_export = encrypt_key_export(&exported_keys, "1234", 1); /// let encrypted_export = encrypt_key_export(&exported_keys, "1234", 1);
/// # }); /// # });
/// ``` /// ```
pub fn encrypt_key_export(keys: &[ExportedRoomKey], passphrase: &str, rounds: u32) -> String { pub fn encrypt_key_export(
let mut plaintext = serde_json::to_string(keys).unwrap().into_bytes(); keys: &[ExportedRoomKey],
passphrase: &str,
rounds: u32,
) -> Result<String, SerdeError> {
let mut plaintext = serde_json::to_string(keys)?.into_bytes();
let ciphertext = encrypt_helper(&mut plaintext, passphrase, rounds); let ciphertext = encrypt_helper(&mut plaintext, passphrase, rounds);
[HEADER.to_owned(), ciphertext, FOOTER.to_owned()].join("\n") Ok([HEADER.to_owned(), ciphertext, FOOTER.to_owned()].join("\n"))
} }
fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> String { fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> String {
@ -133,7 +167,7 @@ fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> St
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys); pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys);
let (key, hmac_key) = derived_keys.split_at(KEY_SIZE); let (key, hmac_key) = derived_keys.split_at(KEY_SIZE);
let mut aes = Aes256Ctr::new_var(&key, &iv.to_be_bytes()).expect("Can't create AES"); let mut aes = Aes256Ctr::new_var(&key, &iv.to_be_bytes()).expect("Can't create AES object");
aes.apply_keystream(&mut plaintext); aes.apply_keystream(&mut plaintext);
@ -145,7 +179,7 @@ fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> St
payload.extend(&rounds.to_be_bytes()); payload.extend(&rounds.to_be_bytes());
payload.extend_from_slice(&plaintext); payload.extend_from_slice(&plaintext);
let mut hmac = Hmac::<Sha256>::new_varkey(hmac_key).unwrap(); let mut hmac = Hmac::<Sha256>::new_varkey(hmac_key).expect("Can't create HMAC object");
hmac.update(&payload); hmac.update(&payload);
let mac = hmac.finalize(); let mac = hmac.finalize();
@ -154,7 +188,7 @@ fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> St
encode(payload) encode(payload)
} }
fn decrypt_helper(ciphertext: &str, passphrase: &str) -> Result<String, DecodeError> { fn decrypt_helper(ciphertext: &str, passphrase: &str) -> Result<String, KeyExportError> {
let decoded = decode(ciphertext)?; let decoded = decode(ciphertext)?;
let mut decoded = Cursor::new(decoded); let mut decoded = Cursor::new(decoded);
@ -164,36 +198,36 @@ fn decrypt_helper(ciphertext: &str, passphrase: &str) -> Result<String, DecodeEr
let mut mac = [0u8; MAC_SIZE]; let mut mac = [0u8; MAC_SIZE];
let mut derived_keys = [0u8; KEY_SIZE * 2]; let mut derived_keys = [0u8; KEY_SIZE * 2];
let version = decoded.read_u8().unwrap(); let version = decoded.read_u8()?;
decoded.read_exact(&mut salt).unwrap(); decoded.read_exact(&mut salt)?;
decoded.read_exact(&mut iv).unwrap(); decoded.read_exact(&mut iv)?;
let rounds = decoded.read_u32::<BigEndian>().unwrap(); let rounds = decoded.read_u32::<BigEndian>()?;
let ciphertext_start = decoded.position() as usize; let ciphertext_start = decoded.position() as usize;
decoded.seek(SeekFrom::End(-32)).unwrap(); decoded.seek(SeekFrom::End(-32))?;
let ciphertext_end = decoded.position() as usize; let ciphertext_end = decoded.position() as usize;
decoded.read_exact(&mut mac).unwrap(); decoded.read_exact(&mut mac)?;
let mut decoded = decoded.into_inner(); let mut decoded = decoded.into_inner();
if version != VERSION { if version != VERSION {
panic!("Unsupported version") return Err(KeyExportError::UnsupportedVersion);
} }
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys); pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys);
let (key, hmac_key) = derived_keys.split_at(KEY_SIZE); let (key, hmac_key) = derived_keys.split_at(KEY_SIZE);
let mut hmac = Hmac::<Sha256>::new_varkey(hmac_key).unwrap(); let mut hmac = Hmac::<Sha256>::new_varkey(hmac_key).expect("Can't create an HMAC object");
hmac.update(&decoded[0..ciphertext_end]); hmac.update(&decoded[0..ciphertext_end]);
hmac.verify(&mac).expect("MAC DOESN'T MATCH"); hmac.verify(&mac).map_err(|_| KeyExportError::InvalidMAC)?;
let mut ciphertext = &mut decoded[ciphertext_start..ciphertext_end]; let mut ciphertext = &mut decoded[ciphertext_start..ciphertext_end];
let mut aes = Aes256Ctr::new_var(&key, &iv).expect("Can't create AES"); let mut aes = Aes256Ctr::new_var(&key, &iv).expect("Can't create an AES object");
aes.apply_keystream(&mut ciphertext); aes.apply_keystream(&mut ciphertext);
Ok(String::from_utf8(ciphertext.to_owned()).expect("Invalid utf-8")) Ok(String::from_utf8(ciphertext.to_owned())?)
} }
#[cfg(test)] #[cfg(test)]
@ -279,7 +313,7 @@ mod test {
assert!(!export.is_empty()); assert!(!export.is_empty());
let encrypted = encrypt_key_export(&export, "1234", 1); let encrypted = encrypt_key_export(&export, "1234", 1).unwrap();
let decrypted = decrypt_key_export(Cursor::new(encrypted), "1234").unwrap(); let decrypted = decrypt_key_export(Cursor::new(encrypted), "1234").unwrap();
assert_eq!(export, decrypted); assert_eq!(export, decrypted);