Updating common annotated security key generation and validation in Rust (#57)

* adding an end-to-end test

* adding constants for long form and standard

* adding validation

* fixing failing test

* adding long-form support

* fixing generation

* adding support for long-form, normalizing rust and c# common key generation and validation

* fixes and cleanups

* adding common key generator test

* adding validation test

* adding test

* adding test

* adding test
This commit is contained in:
Suvam Mukherjee 2024-06-27 23:38:31 -04:00 коммит произвёл GitHub
Родитель c4ab2fcf34
Коммит e92d236f5d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
8 изменённых файлов: 392 добавлений и 105 удалений

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

@ -83,12 +83,6 @@ public static class IdentifiableSecrets
bool longForm = key.Length == LongFormCommonAnnotatedKeySize;
string signature = key.Substring(CommonAnnotatedKey.ProviderFixedSignatureOffset, CommonAnnotatedKey.ProviderFixedSignatureLength);
string alternate = char.IsUpper(signature[0])
? signature.ToLowerInvariant()
: signature.ToUpperInvariant();
ulong checksumSeed = VersionTwoChecksumSeed;
string componentToChecksum = key.Substring(0, CommonAnnotatedKey.ChecksumOffset);
@ -238,12 +232,17 @@ public static class IdentifiableSecrets
throw new InvalidOperationException($"The key length is less than 86 characters: {key}");
}
key = key.Substring(0, 86);
key = $"{key}==";
key = key.Substring(0, 85);
// We use Q== as the suffix to keep the key format consistent with the usage in Rust.
// In C#, the base64 decoder can handle illegal base64 strings, but not in Rust.
key = $"{key}Q==";
}
else
{
key = $"{new string(testChar!.Value, 86)}==";
// We use Q== as the suffix to keep the key format consistent with the usage in Rust.
// In C#, the base64 decoder can handle illegal base64 strings, but not in Rust.
key = $"{new string(testChar!.Value, 85)}Q==";
}
try

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

@ -1,24 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in library 'microsoft_security_utilities'",
"cargo": {
"args": [
"test",
"--no-run",
"--lib",
"--package=microsoft_security_utilities"
],
"filter": {
"name": "microsoft_security_utilities",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

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

@ -0,0 +1,36 @@
#[cfg(test)]
use super::*;
#[test]
fn generate_and_detect_common_annotated_key_test() {
let options = microsoft_security_utilities_core::identifiable_scans::ScanOptions::default();
let mut scan = microsoft_security_utilities_core::identifiable_scans::Scan::new(options);
let input = microsoft_security_utilities_core::identifiable_secrets::generate_common_annotated_test_key(
microsoft_security_utilities_core::identifiable_secrets::VERSION_TWO_CHECKSUM_SEED.clone(),
"TEST",
true,
None,
None,
false,
Some('a')
);
let generated_input = input.clone().unwrap();
let input_as_bytes = generated_input.as_bytes();
scan.parse_bytes(input_as_bytes);
let check = scan.possible_matches().first().unwrap();
let scan_match = check.matches_bytes(input_as_bytes, true);
assert!(scan_match.is_some(), "identifiable_scan: at least one match found");
let scan_match = scan_match.unwrap();
assert_eq!(generated_input, scan_match.text(), "identifiable_scan: matched string equals generated input");
let validation_result = microsoft_security_utilities_core::identifiable_secrets::try_validate_common_annotated_key(
&generated_input,
"TEST"
);
assert_eq!(validation_result, true, "checksum validation of generated input passes");
}

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

@ -3,14 +3,165 @@
#[cfg(test)]
use super::*;
use std::{collections::HashSet, hash::Hash};
use base64::alphabet;
use std::{collections::HashSet};
use rand::prelude::*;
use rand_chacha::ChaCha20Rng;
use uuid::Uuid;
static S_BASE62_ALPHABET: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
#[test]
fn identifiable_secrets_try_validate_common_annotated_key_generate_common_annotated_key_long_form() {
for &long_form in &[true, false] {
let valid_signature = "ABCD";
let valid_key = microsoft_security_utilities_core::identifiable_secrets::
generate_common_annotated_key(
valid_signature,
true,
Some(&vec![0; 9]),
Some(&vec![0; 3]),
long_form.clone(),
None
);
let valid_key = valid_key.unwrap().clone();
let valid_key_len = valid_key.len();
let result = microsoft_security_utilities_core::identifiable_secrets::try_validate_common_annotated_key(
&valid_key,
valid_signature,
);
assert!(result, "a generated key should validate");
let expected_length = if long_form {
microsoft_security_utilities_core::identifiable_secrets::LONG_FORM_COMMON_ANNOTATED_KEY_SIZE
} else {
microsoft_security_utilities_core::identifiable_secrets::STANDARD_COMMON_ANNOTATED_KEY_SIZE
};
assert_eq!(
valid_key_len,
expected_length
);
}
}
#[test]
fn identifiable_secrets_try_validate_common_annotated_key_reject_null_empty_and_whitespace_arguments() {
let valid_signature = "ABCD";
let valid_key = microsoft_security_utilities_core::identifiable_secrets::
generate_common_annotated_key(
valid_signature,
true,
Some(&vec![0; 9]),
Some(&vec![0; 3]),
true,
None
);
let valid_key = valid_key.unwrap().clone();
let result = microsoft_security_utilities_core::identifiable_secrets::try_validate_common_annotated_key(&valid_key, valid_signature);
assert!(result, "a generated key should validate");
let args = vec![String::new(), String::from(" ")];
for arg in args {
let result = microsoft_security_utilities_core::identifiable_secrets::try_validate_common_annotated_key(&arg, valid_signature);
assert!(!result, "{}", format!("the key {} is not a valid argument", arg));
let result = microsoft_security_utilities_core::identifiable_secrets::try_validate_common_annotated_key(&valid_key, &arg);
assert!(!result, "{}", format!("the signature '{}' is not a valid argument", arg.to_string()));
}
}
#[test]
fn identifiable_secrets_try_validate_common_annotated_key_reject_invalid_signatures() {
let valid_signature = "ABCD";
let valid_key = microsoft_security_utilities_core::identifiable_secrets::
generate_common_annotated_key(
valid_signature,
true,
Some(&vec![0; 9]),
Some(&vec![0; 3]),
true,
None
);
let valid_key = valid_key.unwrap().clone();
let result = microsoft_security_utilities_core::identifiable_secrets::
try_validate_common_annotated_key(&valid_key, valid_signature);
assert!(result, "a generated key should validate");
let signatures = vec!["Z", "YY", "XXX", "WWWWW", "1AAA"];
for signature in signatures {
for &long_form in &[true, false] {
let action =
microsoft_security_utilities_core::identifiable_secrets::
generate_common_annotated_key(
signature,
true,
Some(&vec![0; 9]),
Some(&vec![0; 3]),
long_form,
None
);
assert!(action.is_err(), "{}", format!("the signature '{}' is not valid", signature));
let result = microsoft_security_utilities_core::identifiable_secrets::
try_validate_common_annotated_key(&valid_key, signature);
assert!(!result, "{}", format!("'{}' is not a valid signature argument", signature));
}
}
}
#[test]
fn identifiable_secrets_try_validate_common_annotated_key_reject_invalid_key() {
let valid_signature = "Z123";
let valid_key = microsoft_security_utilities_core::identifiable_secrets::
generate_common_annotated_key(
valid_signature,
true,
Some(&vec![0; 9]),
Some(&vec![0; 3]),
true,
None
);
let valid_key = valid_key.unwrap().clone();
let result = microsoft_security_utilities_core::identifiable_secrets::
try_validate_common_annotated_key(&valid_key, valid_signature);
assert!(result, "a generated key should validate");
let result = microsoft_security_utilities_core::identifiable_secrets::
try_validate_common_annotated_key(
&format!("{}a", valid_key),
valid_signature,
);
assert!(!result, "a key with an invalid length should not validate");
let result = microsoft_security_utilities_core::identifiable_secrets::
try_validate_common_annotated_key(
&valid_key[1..],
valid_signature,
);
assert!(!result, "a key with an invalid length should not validate");
}
#[test]
fn identifiable_secrets_validate_common_annotated_key_signature() {
for invalid_signature in ["AbAA", "aaaB", "1AAA"] {
assert!(matches!(
microsoft_security_utilities_core::identifiable_secrets::validate_common_annotated_key_signature(invalid_signature),
Err(_)
));
}
}
#[test]
fn identifiable_secrets_compute_checksum_seed_enforces_length_requirement()
{
@ -42,7 +193,7 @@ fn identifiable_secrets_platform_annotated_security_keys() {
{
for k in 0..iterations
{
let mut signature = format!("{:?}", Uuid::new_v4().simple()).chars().skip(1).take(4).collect::<String>();
let mut signature: String = format!("{:?}", Uuid::new_v4().simple()).chars().skip(1).take(4).collect::<String>();
signature = format!("{}{}", alphabet.chars().nth(((keys_generated as i32) % (alphabet.len() as i32)) as usize).unwrap().to_string(), &signature[1..]);
@ -86,15 +237,24 @@ fn identifiable_secrets_platform_annotated_security_keys() {
let provider_reserved_vec = provider_reserved.to_vec();
for &customer_managed in &[true, false] {
let key = microsoft_security_utilities_core::identifiable_secrets::generate_common_annotated_key(&signature, customer_managed, Some(&platform_reserved_vec), Some(&provider_reserved_vec), None).unwrap();
for &long_form in &[true, false] {
let mut cased_signature = signature.clone();
if customer_managed {
cased_signature = cased_signature.to_uppercase();
} else {
cased_signature = cased_signature.to_lowercase();
}
let mut result = microsoft_security_utilities_core::identifiable_secrets::COMMON_ANNOTATED_KEY_REGEX.is_match(key.as_str());
assert!(result, "the key '{}' should match the common annotated key regex", key);
let key = microsoft_security_utilities_core::identifiable_secrets::generate_common_annotated_key(&cased_signature, customer_managed, Some(&platform_reserved_vec), Some(&provider_reserved_vec), long_form, None).unwrap();
result = microsoft_security_utilities_core::identifiable_secrets::try_validate_common_annotated_key(key.as_str(), &signature);
assert!(result, "the key '{}' should comprise an HIS v2-conformant pattern", key);
let mut result = microsoft_security_utilities_core::identifiable_secrets::COMMON_ANNOTATED_KEY_REGEX.is_match(key.as_str());
assert!(result, "the key '{}' should match the common annotated key regex", key);
keys_generated += 1;
result = microsoft_security_utilities_core::identifiable_secrets::try_validate_common_annotated_key(key.as_str(), &cased_signature);
assert!(result, "the key '{}' should comprise an HIS v2-conformant pattern", key);
keys_generated += 1;
}
}
}
}

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

@ -3,6 +3,7 @@
pub mod microsoft_security_utilities_core;
mod end_to_end_tests;
mod marvin_tests;
mod identifiable_secrets_tests;
mod cross_company_correlating_id_tests;

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

@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
use base64::{engine::general_purpose::STANDARD, Engine as _};
use lazy_static::lazy_static;
use sha2::{Sha256, Digest};
use std::fmt;
use std::cell::RefCell;

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

@ -9,6 +9,7 @@ use lazy_static::lazy_static;
use std::{mem};
use super::*;
use rand::prelude::*;
use rand::RngCore;
use rand_chacha::ChaCha20Rng;
use regex::Regex;
use substring::Substring;
@ -25,17 +26,54 @@ lazy_static! {
pub static MAXIMUM_GENERATED_KEY_SIZE: u32 = 4096;
pub static MINIMUM_GENERATED_KEY_SIZE: u32 = 24;
pub static STANDARD_COMMON_ANNOTATED_KEY_SIZE: usize = 84;
pub static LONG_FORM_COMMON_ANNOTATED_KEY_SIZE: usize = 88;
pub static COMMON_ANNOTATED_KEY_SIGNATURE: &str = "JQQJ99";
pub static COMMON_ANNOTATED_DERIVED_KEY_SIGNATURE: &str = "JQQJ9D";
static BITS_IN_BYTES: i32 = 8;
static BITS_IN_BASE64_CHARACTER: i32 = 6;
static SIZE_OF_CHECKSUM_IN_BYTES: i32 = mem::size_of::<u32>() as i32;
static COMMON_ANNOTATED_KEY_SIZE_IN_BYTES: usize = 63;
pub fn is_base62_encoding_char(ch: char) -> bool
{
return (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9');
/// The offset to the encoded standard fixed signature ('JQQJ99' or 'JQQJ9D').
pub static STANDARD_FIXED_SIGNATURE_OFFSET: usize = 52;
/// The encoded length of the standard fixed signature ('JQQJ99' or 'JQQJ9D').
pub static STANDARD_FIXED_SIGNATURE_LENGTH: usize = 6;
/// The offset to the encoded character that denotes a derived ('D')
/// or standard ('9') common annotated security key.
pub static DERIVED_KEY_CHARACTER_OFFSET: usize = STANDARD_FIXED_SIGNATURE_OFFSET + STANDARD_FIXED_SIGNATURE_LENGTH - 1;
/// The offset to the two-character encoded key creation date.
pub static DATE_OFFSET: usize = STANDARD_FIXED_SIGNATURE_OFFSET + STANDARD_FIXED_SIGNATURE_LENGTH;
/// The encoded length of the creation date (a value such as 'AE').
pub static DATE_LENGTH: usize = 2;
/// The offset to the 12-character encoded platform-reserved data.
pub static PLATFORM_RESERVED_OFFSET: usize = DATE_OFFSET + DATE_LENGTH;
/// The encoded length of the platform-reserved bytes.
pub static PLATFORM_RESERVED_LENGTH: usize = 12;
/// The offset to the 4-character encoded provider-reserved data.
pub static PROVIDER_RESERVED_OFFSET: usize = PLATFORM_RESERVED_OFFSET + PLATFORM_RESERVED_LENGTH;
/// The encoded length of the provider-reserved bytes.
pub static PROVIDER_RESERVED_LENGTH: usize = 4;
/// The offset to the 4-character encoded provider fixed signature.
pub static PROVIDER_FIXED_SIGNATURE_OFFSET: usize = PROVIDER_RESERVED_OFFSET + PROVIDER_RESERVED_LENGTH;
/// The encoded length of the provider fixed signature, e.g., 'AZEG'.
pub static PROVIDER_FIXED_SIGNATURE_LENGTH: usize = 4;
pub static CHECKSUM_OFFSET: usize = PROVIDER_FIXED_SIGNATURE_OFFSET + PROVIDER_FIXED_SIGNATURE_LENGTH;
pub fn is_base62_encoding_char(ch: char) -> bool {
ch.is_ascii_alphanumeric()
}
pub fn is_base64_encoding_char(ch: char) -> bool
@ -52,6 +90,46 @@ pub fn is_base64_url_encoding_char(ch: char) -> bool
ch == '_';
}
pub fn try_validate_common_annotated_key(key: &str, base64_encoded_signature: &str) -> bool {
if key.is_empty() || key.trim().is_empty() {
return false;
}
match validate_common_annotated_key_signature(base64_encoded_signature) {
Ok(_) => (),
Err(s) => {
println!("{}", s);
return false;
},
};
if key.len() != STANDARD_COMMON_ANNOTATED_KEY_SIZE && key.len() != LONG_FORM_COMMON_ANNOTATED_KEY_SIZE {
return false;
}
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_bytes = checksum.to_ne_bytes();
// A long-form has a full 4-byte checksum, while a standard form has only 3.
let encoded = general_purpose::STANDARD.encode(if long_form {
&checksum_bytes[..4]
} else {
&checksum_bytes[..3]
});
encoded == checksum_text
}
/// Generate a u64 an HIS v1 compliant checksum seed from a string literal
/// 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
@ -80,37 +158,18 @@ pub fn compute_his_v1_checksum_seed(versioned_key_kind: &str) -> u64 {
result
}
pub fn try_validate_common_annotated_key(key: &str, base64_encoded_signature: &str) -> bool {
let checksum_seed: u64 = VERSION_TWO_CHECKSUM_SEED.clone();
let key_bytes = general_purpose::STANDARD.decode(&key).unwrap();
assert_eq!(key_bytes.len(), COMMON_ANNOTATED_KEY_SIZE_IN_BYTES);
let key_bytes_length = key_bytes.len();
let bytes_for_checksum = &key_bytes[..key_bytes_length - 3];
let actual_checksum_bytes = &key_bytes[key_bytes_length - 3..key_bytes_length];
let computed_marvin = marvin::compute_hash32(bytes_for_checksum, checksum_seed, 0, bytes_for_checksum.len() as i32);
let computed_marvin_bytes = computed_marvin.to_ne_bytes();
// The HIS v2 standard requires a match for the first 3-bytes (24 bits) of the Marvin checksum.
actual_checksum_bytes[0] == computed_marvin_bytes[0]
&& actual_checksum_bytes[1] == computed_marvin_bytes[1]
&& actual_checksum_bytes[2] == computed_marvin_bytes[2]
}
pub fn generate_common_annotated_key(base64_encoded_signature: &str,
customer_managed_key: bool,
platform_reserved: Option<&[u8]>,
provider_reserved: Option<&[u8]>,
long_form: bool,
test_char: Option<char>) -> Result<String, String> {
generate_common_annotated_test_key(VERSION_TWO_CHECKSUM_SEED.clone(),
base64_encoded_signature,
customer_managed_key,
platform_reserved,
provider_reserved,
long_form,
test_char)
}
@ -120,11 +179,17 @@ pub fn generate_common_annotated_test_key(
customer_managed_key: bool,
platform_reserved: Option<&[u8]>,
provider_reserved: Option<&[u8]>,
long_form: bool,
test_char: Option<char>,
) -> Result<String, String> {
const PLATFORM_RESERVED_LENGTH: usize = 9;
const PROVIDER_RESERVED_LENGTH: usize = 3;
match validate_common_annotated_key_signature(base64_encoded_signature) {
Ok(_) => base64_encoded_signature,
Err(s) => return Err(format!("Common Annotated Key generation failed due to: {}", s)),
};
let platform_reserved = match platform_reserved {
Some(reserved) if reserved.len() != PLATFORM_RESERVED_LENGTH => {
return Err(format!(
@ -153,21 +218,33 @@ pub fn generate_common_annotated_test_key(
base64_encoded_signature.to_lowercase()
};
let mut key = String::new();
let mut key: String;
loop {
let key_length_in_bytes = 66;
let mut key_bytes = vec![0; key_length_in_bytes];
if let Some(test_char) = test_char {
key = format!("{:86}==", test_char.to_string().repeat(86));
} else {
key = format!("{:85}Q==", test_char.to_string().repeat(85));
}
else {
let mut rng = rand::thread_rng();
rng.fill_bytes(&mut key_bytes);
rng.try_fill_bytes(&mut key_bytes).expect("Failed to generate random bytes.");
key = base_62::encode(&key_bytes);
if key.len() < 86 {
return Err(format!("The key length is less than 86 characters: {}", key));
}
key = key.substring(0, 85).to_string();
key = format!("{}Q==", key);
}
let key_string = general_purpose::STANDARD.encode(&key_bytes);
key_bytes = general_purpose::STANDARD.decode(&key_string).unwrap();
key_bytes = match general_purpose::STANDARD.decode(&key) {
Ok(bytes) => bytes,
Err(_) => return Err(format!("Key could not be decoded: {}.", key)),
};
let j_bits = b'J' - b'A';
let q_bits = b'Q' - b'A';
@ -177,61 +254,66 @@ pub fn generate_common_annotated_test_key(
let key_bytes_length = key_bytes.len();
key_bytes[key_bytes_length - 27] = reserved_bytes[2];
key_bytes[key_bytes_length - 26] = reserved_bytes[1];
key_bytes[key_bytes_length - 25] = reserved_bytes[0];
key_bytes[key_bytes_length - 25] = reserved_bytes[2];
key_bytes[key_bytes_length - 24] = reserved_bytes[1];
key_bytes[key_bytes_length - 23] = reserved_bytes[0];
// Simplistic timestamp computation.
let years_since_2024 = (chrono::Utc::now().year() - 2024) as u8;
let zero_indexed_month = (chrono::Utc::now().month() - 1) as u8;
let metadata: i32 = (61 << 18) | (61 << 12) | (years_since_2024 << 6) as i32 | zero_indexed_month as i32;
let metadata_bytes = metadata.to_ne_bytes();
key_bytes[key_bytes_length - 24] = metadata_bytes[2];
key_bytes[key_bytes_length - 23] = metadata_bytes[1];
key_bytes[key_bytes_length - 22] = metadata_bytes[0];
key_bytes[key_bytes_length - 22] = metadata_bytes[2];
key_bytes[key_bytes_length - 21] = metadata_bytes[1];
key_bytes[key_bytes_length - 20] = metadata_bytes[0];
key_bytes[key_bytes_length - 21] = platform_reserved[0];
key_bytes[key_bytes_length - 20] = platform_reserved[1];
key_bytes[key_bytes_length - 19] = platform_reserved[2];
key_bytes[key_bytes_length - 18] = platform_reserved[3];
key_bytes[key_bytes_length - 17] = platform_reserved[4];
key_bytes[key_bytes_length - 16] = platform_reserved[5];
key_bytes[key_bytes_length - 15] = platform_reserved[6];
key_bytes[key_bytes_length - 14] = platform_reserved[7];
key_bytes[key_bytes_length - 13] = platform_reserved[8];
key_bytes[key_bytes_length - 19] = platform_reserved[0];
key_bytes[key_bytes_length - 18] = platform_reserved[1];
key_bytes[key_bytes_length - 17] = platform_reserved[2];
key_bytes[key_bytes_length - 16] = platform_reserved[3];
key_bytes[key_bytes_length - 15] = platform_reserved[4];
key_bytes[key_bytes_length - 14] = platform_reserved[5];
key_bytes[key_bytes_length - 13] = platform_reserved[6];
key_bytes[key_bytes_length - 12] = platform_reserved[7];
key_bytes[key_bytes_length - 11] = platform_reserved[8];
key_bytes[key_bytes_length - 12] = provider_reserved[0];
key_bytes[key_bytes_length - 11] = provider_reserved[1];
key_bytes[key_bytes_length - 10] = provider_reserved[2];
key_bytes[key_bytes_length - 10] = provider_reserved[0];
key_bytes[key_bytes_length - 9] = provider_reserved[1];
key_bytes[key_bytes_length - 8] = provider_reserved[2];
let signature_offset = key_bytes_length - 9;
let signature_offset = key_bytes_length - 7;
let sig_bytes = general_purpose::STANDARD.decode(&base64_encoded_signature).unwrap();
for i in 0..sig_bytes.len() {
key_bytes[signature_offset + i] = sig_bytes[i];
}
let checksum = marvin::compute_hash32(&key_bytes, checksum_seed, 0, (key_bytes_length - 6) as i32);
let checksum = marvin::compute_hash32(&key_bytes, checksum_seed, 0, (key_bytes_length - 4) as i32);
let checksum_bytes = checksum.to_ne_bytes();
key_bytes[key_bytes_length - 6] = checksum_bytes[0];
key_bytes[key_bytes_length - 5] = checksum_bytes[1];
key_bytes[key_bytes_length - 4] = checksum_bytes[2];
key_bytes[key_bytes_length - 3] = checksum_bytes[3];
key_bytes[key_bytes_length - 4] = checksum_bytes[0];
key_bytes[key_bytes_length - 3] = checksum_bytes[1];
key_bytes[key_bytes_length - 2] = checksum_bytes[2];
key_bytes[key_bytes_length - 1] = checksum_bytes[3];
key = general_purpose::STANDARD.encode(&key_bytes[..COMMON_ANNOTATED_KEY_SIZE_IN_BYTES]);
key = general_purpose::STANDARD.encode(&key_bytes);
// The HIS v2 standard requires that there be no special characters in the generated key.
if !key.contains('+') && !key.contains('/') {
break;
if !long_form {
key = key.substring(0, key.len() - 4).to_string();
}
return Ok(key);
} else if test_char.is_some() {
// We could not produce a valid test key given the current signature,
// checksum seed, reserved bits and specified test character.
key = String::new();
break;
}
}
Ok(key)
}
@ -474,6 +556,40 @@ fn generate_base64_key_helper(checksum_seed: u64,
encode_for_url);
}
pub fn validate_common_annotated_key_signature(base64_encoded_signature: &str) -> Result<String, String> {
const REQUIRED_ENCODED_SIGNATURE_LENGTH: usize = 4;
if base64_encoded_signature.len() != REQUIRED_ENCODED_SIGNATURE_LENGTH {
return Err(format!("Base64-encoded signature {} must be 4 characters long.",
base64_encoded_signature));
}
if base64_encoded_signature.chars().next().unwrap().is_digit(10) {
return Err(format!("The first character of the signature {} must not be a digit.",
base64_encoded_signature));
}
for ch in base64_encoded_signature.chars() {
if !is_base62_encoding_char(ch) {
return Err(format!("Signature {} can only contain alphabetic or numeric values.",
base64_encoded_signature));
}
}
let all_upper = base64_encoded_signature.to_uppercase();
if base64_encoded_signature == all_upper {
return Ok(format!("Valid signature {}", base64_encoded_signature));
}
let all_lower = base64_encoded_signature.to_lowercase();
if base64_encoded_signature == all_lower {
return Ok(format!("Valid signature {}", base64_encoded_signature));
}
return Err(format!("Signature {} characters must all upper- or all lower-case.",
base64_encoded_signature));
}
fn validate_base64_encoded_signature(base64_encoded_signature: &String, encode_for_url: bool)
{
let required_encoded_signature_length = 4;
@ -514,7 +630,7 @@ fn get_csrandom_bytes(key_length_in_bytes: u32) -> Vec<u8>
let mut rng = ChaCha20Rng::from_entropy();
let mut random_bytes: Vec<u8> = Vec::new();
for i in 0..key_length_in_bytes
for _i in 0..key_length_in_bytes
{
random_bytes.push(rng.gen::<u8>());
}

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

@ -68,13 +68,13 @@ pub fn compute_hash(data: &[u8], seed: u64, offset: i32, mut length: i32) -> i64
1 => p0 = p0.wrapping_add(0x8000 | (data[remaining_data_offset as usize] as u32)),
2 => {
let d1 = (data[(remaining_data_offset as usize) + 1] as u32) << 8;
let d0: u32 = data[(remaining_data_offset as usize)] as u32;
let d0: u32 = data[remaining_data_offset as usize] as u32;
p0 = p0.wrapping_add(0x800000 | d1 | d0);
},
3 => {
let d2 = (data[(remaining_data_offset as usize) + 2] as u32) << 16;
let d1 = (data[(remaining_data_offset as usize) + 1] as u32) << 8;
let d0: u32 = data[(remaining_data_offset as usize)] as u32;
let d0: u32 = data[remaining_data_offset as usize] as u32;
p0 = p0.wrapping_add(0x80000000 | d2 | d1 | d0);
},
_ => panic!("Hash computation reached an invalid state")