crypto: Add error handling to the key exports.
parent
126ac3059b
commit
618a58ba34
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue