use crate::format;
use crate::key::public::{RECIPIENT_STRING_LEN_LOCAL_CAP_DEFAULT, decode_recipient_string};
pub const PUBLIC_KEY_FILENAME: &str = "public.key";
pub const PRIVATE_KEY_FILENAME: &str = "private.key";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum KeyFileKind {
Public,
Private,
Unknown,
}
impl KeyFileKind {
pub(crate) fn classify(data: &[u8]) -> Self {
const SIGNATURE_LEN: usize = 6;
if data.len() >= SIGNATURE_LEN
&& data[0..4] == format::MAGIC
&& data[5] == format::KIND_PRIVATE_KEY
{
return Self::Private;
}
let probe_len = data.len().min(RECIPIENT_STRING_LEN_LOCAL_CAP_DEFAULT + 1);
if let Ok(text) = std::str::from_utf8(&data[..probe_len]) {
if decode_recipient_string(text.trim(), RECIPIENT_STRING_LEN_LOCAL_CAP_DEFAULT).is_ok()
{
return Self::Public;
}
}
Self::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CryptoError;
use secrecy::SecretString;
use std::fs;
#[test]
fn key_file_kind_classifies_each_shape() -> Result<(), CryptoError> {
let tmp = tempfile::TempDir::new().unwrap();
let pass = SecretString::from("kp".to_string());
let (private_key_path, public_key_path, _fingerprint) = crate::protocol::generate_key_pair(
&pass,
&crate::crypto::kdf::KdfParams::test_fast_default(),
tmp.path(),
&|_| {},
)?;
let pub_bytes = fs::read(&public_key_path)?;
assert_eq!(KeyFileKind::classify(&pub_bytes), KeyFileKind::Public);
let priv_bytes = fs::read(&private_key_path)?;
assert_eq!(KeyFileKind::classify(&priv_bytes), KeyFileKind::Private);
let v2_priv = b"FCR\0\x02K\x01\x00\x00";
assert_eq!(KeyFileKind::classify(v2_priv), KeyFileKind::Private);
let fcr_symmetric = b"FCR\0\x01Sxx\x00\x00";
assert_eq!(KeyFileKind::classify(fcr_symmetric), KeyFileKind::Unknown);
assert_eq!(KeyFileKind::classify(b"FCR\0"), KeyFileKind::Unknown);
assert_eq!(
KeyFileKind::classify(b"this isn't ours at all"),
KeyFileKind::Unknown
);
assert_eq!(KeyFileKind::classify(b"fcr1foobar"), KeyFileKind::Unknown);
assert_eq!(KeyFileKind::classify(b""), KeyFileKind::Unknown);
Ok(())
}
#[test]
fn classify_does_not_scan_oversize_blob() {
let mut blob = vec![0xFFu8; 4 * 1024 * 1024];
blob[0] = b'f';
blob[1] = b'c';
blob[2] = b'r';
blob[3] = b'1';
assert_eq!(KeyFileKind::classify(&blob), KeyFileKind::Unknown);
}
}