feat(identifiable_secrets): Add const version of compute_checksum_seed

We can compute checksum seeds from byte arrays at compile time if they are
of the correct size.
This commit is contained in:
Johannes Cornelis Draaijer 2024-07-18 09:23:53 +02:00
Родитель 125e33f498
Коммит 794ab7dc7a
2 изменённых файлов: 72 добавлений и 14 удалений

Просмотреть файл

@ -233,11 +233,16 @@ fn identifiable_secrets_compute_checksum_seed_enforces_length_requirement()
{
let literal = "A".repeat(i) + "0";
let result = std::panic::catch_unwind(|| microsoft_security_utilities_core::identifiable_secrets::compute_his_v1_checksum_seed(&literal));
let result =
std::panic::catch_unwind(|| microsoft_security_utilities_core::identifiable_secrets::compute_his_v1_checksum_seed(&literal));
let array_result = <[u8; 8]>::try_from(literal.as_bytes()).ok().and_then(|v| microsoft_security_utilities_core::identifiable_secrets::compute_his_v1_checksum_seed_from_array(&v).ok());
assert!(result.is_ok() == array_result.is_some(), "Array result and result have differing success.");
if i == 7
{
assert!(result.is_ok(), "literal '{}' should generate a valid seed", literal);
assert_eq!(result.unwrap(), array_result.unwrap(), "Result and array result are not equal.")
} else
{
assert!(result.is_err(), "literal '{}' should raise an exception as it's not the correct length", literal);
@ -335,10 +340,17 @@ fn identifiable_secrets_compute_checksum_seed()
let expected_checksum_seed2 = 0x5257536565643030;
let checksum_seed1 = microsoft_security_utilities_core::identifiable_secrets::compute_his_v1_checksum_seed(input_literal1);
let checksum_seed1_array =
microsoft_security_utilities_core::identifiable_secrets::compute_his_v1_checksum_seed_from_array(input_literal1.as_bytes().try_into().unwrap());
let checksum_seed2 = microsoft_security_utilities_core::identifiable_secrets::compute_his_v1_checksum_seed(input_literal2);
let checksum_seed2_array =
microsoft_security_utilities_core::identifiable_secrets::compute_his_v1_checksum_seed_from_array(input_literal2.as_bytes().try_into().unwrap());
assert_eq!(checksum_seed1, expected_checksum_seed1);
assert_eq!(checksum_seed1, checksum_seed1_array.unwrap());
assert_eq!(checksum_seed2, expected_checksum_seed2);
assert_eq!(checksum_seed2, checksum_seed2_array.unwrap());
}
#[test]

Просмотреть файл

@ -21,11 +21,15 @@ use substring::Substring;
use crate::microsoft_security_utilities_core;
use crate::microsoft_security_utilities_core::identifiable_scans::PossibleScanMatch;
lazy_static! {
pub static ref VERSION_TWO_CHECKSUM_SEED: u64 = compute_his_v1_checksum_seed("Default0");
}
pub const VERSION_TWO_CHECKSUM_SEED: u64 = {
// TODO: Option::unwrap is not const yet
match compute_his_v1_checksum_seed_from_array(b"Default0") {
Ok(v) => v,
Err(e) => panic!("{}", e),
}
};
pub static COMMON_ANNOTATED_KEY_REGEX_PATTERN: &str = r"(?-i)[A-Za-z0-9]{52}JQQJ9(9|D)[A-Za-z0-9][A-L][A-Za-z0-9]{16}[A-Za-z][A-Za-z0-9]{7}([A-Za-z0-9]{2}==)?";
pub const COMMON_ANNOTATED_KEY_REGEX_PATTERN: &str = r"(?-i)[A-Za-z0-9]{52}JQQJ9(9|D)[A-Za-z0-9][A-L][A-Za-z0-9]{16}[A-Za-z][A-Za-z0-9]{7}([A-Za-z0-9]{2}==)?";
lazy_static! {
pub static ref COMMON_ANNOTATED_KEY_REGEX: Regex = Regex::new(COMMON_ANNOTATED_KEY_REGEX_PATTERN).unwrap();
@ -116,14 +120,12 @@ pub fn try_validate_common_annotated_key(key: &str, base64_encoded_signature: &s
let long_form = key.len() == LONG_FORM_COMMON_ANNOTATED_KEY_SIZE;
let checksum_seed = VERSION_TWO_CHECKSUM_SEED.clone();
let component_to_checksum = &key[..CHECKSUM_OFFSET];
let checksum_text = &key[CHECKSUM_OFFSET..];
let key_bytes = general_purpose::STANDARD.decode(component_to_checksum).unwrap();
let checksum = marvin::compute_hash32(&key_bytes, checksum_seed, 0, key_bytes.len() as i32);
let checksum = marvin::compute_hash32(&key_bytes, VERSION_TWO_CHECKSUM_SEED, 0, key_bytes.len() as i32);
let checksum_bytes = checksum.to_ne_bytes();
@ -152,17 +154,61 @@ pub fn try_validate_common_annotated_key(key: &str, base64_encoded_signature: &s
///
/// # Errors
///
/// This function will return an error if the `versioned_key_kind` does not meet the required criteria.
/// This function will panic if the `versioned_key_kind` does not meet the required criteria.
pub fn compute_his_v1_checksum_seed(versioned_key_kind: &str) -> u64 {
if versioned_key_kind.len() != 8 || !versioned_key_kind.chars().nth(7).unwrap().is_digit(10) {
panic!("The versioned literal must be 8 characters long and end with a digit.");
if versioned_key_kind.len() != 8 {
panic!("The versioned literal must be 8 characters long.");
}
let bytes = versioned_key_kind.as_bytes().iter().rev().cloned().collect::<Vec<u8>>();
let result = u64::from_le_bytes(bytes.try_into().unwrap());
let bytes: [u8; 8] = versioned_key_kind.as_bytes().try_into().unwrap();
result
match compute_his_v1_checksum_seed_from_array(&bytes) {
Ok(v) => v,
Err(e) => panic!("{}", e),
}
}
/// Generate a u64 an HIS v1 compliant checksum seed from a literal byte array
/// that is 8 characters long and ends with at least one digit, e.g., 'ReadKey0', 'RWSeed00',
/// etc. The checksum seed is used to initialize the Marvin32 algorithm to watermark a
/// specific class of generated security keys.
///
/// # Arguments
///
/// * `versioned_key_kind` - An ASCII-encoded name that identifies a specific set of generated keys with at least one trailing digit in the name.
///
/// # Returns
///
/// The computed checksum seed as a u64.
///
/// # Errors
///
/// This function return `None` if the last character of the array is not in the range '0'..'9' (ASCII)
pub const fn compute_his_v1_checksum_seed_from_array(versioned_key_kind: &[u8; 8]) -> Result<u64, &'static str> {
if versioned_key_kind[7] < b'0' || versioned_key_kind[7] > b'9' {
return Err("The key must end in an decimal ASCII number ('0'..='9').");
}
let mut count = 8;
let mut output = [0u8; 8];
loop {
if count == 0 {
break;
}
let idx = count - 1;
if !output[idx].is_ascii() {
return Err("The key must only contain ASCII characters.")
}
output[idx] = versioned_key_kind[7 - idx];
count -= 1;
}
Ok(u64::from_le_bytes(output))
}
pub fn generate_common_annotated_key(base64_encoded_signature: &str,