use crate::format;
use crate::key::limits::KeyReadLimits;
use crate::key::public::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], limits: KeyReadLimits) -> 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 cap = limits.recipient_string_chars();
let probe_len = data.len().min(cap.saturating_add(1));
if let Ok(text) = std::str::from_utf8(&data[..probe_len]) {
if decode_recipient_string(text.trim(), cap).is_ok() {
return Self::Public;
}
}
Self::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CryptoError;
use secrecy::SecretString;
use std::fs;
fn classify_default(data: &[u8]) -> KeyFileKind {
KeyFileKind::classify(data, KeyReadLimits::default())
}
#[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!(classify_default(&pub_bytes), KeyFileKind::Public);
let priv_bytes = fs::read(&private_key_path)?;
assert_eq!(classify_default(&priv_bytes), KeyFileKind::Private);
let v2_priv = b"FCR\0\x02K\x01\x00\x00";
assert_eq!(classify_default(v2_priv), KeyFileKind::Private);
let fcr_encrypted = b"FCR\0\x01Exx\x00\x00";
assert_eq!(classify_default(fcr_encrypted), KeyFileKind::Unknown);
assert_eq!(classify_default(b"FCR\0"), KeyFileKind::Unknown);
assert_eq!(
classify_default(b"this isn't ours at all"),
KeyFileKind::Unknown
);
assert_eq!(classify_default(b"fcr1foobar"), KeyFileKind::Unknown);
assert_eq!(classify_default(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!(classify_default(&blob), KeyFileKind::Unknown);
}
}