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:
Родитель
125e33f498
Коммит
794ab7dc7a
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче