pub(crate) fn is_known_example_credential(credential: &str) -> bool {
let bytes = credential.as_bytes();
if crate::ascii_ci::ends_with_ignore_ascii_case(bytes, b"EXAMPLE")
|| crate::ascii_ci::ends_with_ignore_ascii_case(bytes, b"EXAMPLEKEY")
{
return true;
}
let x_count = bytes.iter().filter(|&&b| b == b'x' || b == b'X').count();
if bytes.len() >= 16 && x_count > bytes.len() * 3 / 4 {
return true;
}
if is_hex_sequential_placeholder(credential) {
return true;
}
if is_empty_input_hash(credential) {
return true;
}
is_sequential_placeholder(credential)
}
fn is_empty_input_hash(credential: &str) -> bool {
let bytes = credential.as_bytes();
match bytes.len() {
32 => bytes.eq_ignore_ascii_case(b"d41d8cd98f00b204e9800998ecf8427e"), 40 => bytes.eq_ignore_ascii_case(b"da39a3ee5e6b4b0d3255bfef95601890afd80709"), 64 => bytes.eq_ignore_ascii_case(
b"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
), 128 => bytes.eq_ignore_ascii_case(
b"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\
47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
), _ => false,
}
}
const SEQUENTIAL_STEP_RATIO_NUMERATOR: usize = 9;
const SEQUENTIAL_STEP_RATIO_DENOMINATOR: usize = 10;
fn sequential_step_threshold(step_count: usize) -> usize {
step_count * SEQUENTIAL_STEP_RATIO_NUMERATOR / SEQUENTIAL_STEP_RATIO_DENOMINATOR
}
pub(crate) fn is_sequential_placeholder(credential: &str) -> bool {
let body = credential_body_without_known_prefix(credential);
if body.len() < 8 {
return false;
}
let bytes = body.as_bytes();
if bytes.iter().all(|&byte| byte == bytes[0]) {
return true;
}
let pair = &bytes[..2];
bytes
.chunks(2)
.all(|chunk| chunk == pair || (chunk.len() < 2 && chunk[0] == pair[0]))
}
#[cfg(any(feature = "entropy", test))]
pub(crate) fn is_monotonic_sequence_placeholder(credential: &str) -> bool {
let body = credential_body_without_known_prefix(credential);
if body.len() < 8 {
return false;
}
let bytes = body.as_bytes();
let ascending = count_adjacent_byte_steps(bytes, ascii_forward_step);
let descending = count_adjacent_byte_steps(bytes, ascii_reverse_step);
let threshold = sequential_step_threshold(bytes.len().saturating_sub(1));
ascending > threshold || descending > threshold
}
#[cfg(any(feature = "entropy", test))]
fn ascii_forward_step(previous: u8, next: u8) -> bool {
next == previous.wrapping_add(1)
}
#[cfg(any(feature = "entropy", test))]
fn ascii_reverse_step(previous: u8, next: u8) -> bool {
previous == next.wrapping_add(1)
}
fn is_hex_sequential_placeholder(credential: &str) -> bool {
let body = credential_body_without_known_prefix(credential);
if body.len() < 16 || !body.bytes().all(|b| b.is_ascii_hexdigit()) {
return false;
}
let bytes = body.as_bytes();
let ascending = count_adjacent_byte_steps(bytes, hex_forward_step);
let descending = count_adjacent_byte_steps(bytes, hex_reverse_step);
let threshold = sequential_step_threshold(bytes.len() - 1);
if ascending > threshold || descending > threshold {
return true;
}
let pair_count = bytes.len() / 2;
if pair_count < 8 {
return false;
}
if hex_byte_values_are_sequential(bytes, pair_count) {
return true;
}
let ascending = count_pair_column_hex_steps(bytes, pair_count, 0);
let ascending2 = count_pair_column_hex_steps(bytes, pair_count, 1);
let threshold = sequential_step_threshold(pair_count - 1);
ascending > threshold && ascending2 > threshold
}
fn credential_body_without_known_prefix(credential: &str) -> &str {
crate::confidence::known_prefix_body(credential).unwrap_or(credential) }
fn count_adjacent_byte_steps(bytes: &[u8], step: fn(u8, u8) -> bool) -> usize {
bytes
.windows(2)
.filter(|window| step(window[0], window[1]))
.count()
}
fn count_pair_column_hex_steps(bytes: &[u8], pair_count: usize, column: usize) -> usize {
(1..pair_count)
.filter(|&pair| {
let previous = bytes[(pair - 1) * 2 + column];
let next = bytes[pair * 2 + column];
hex_pair_column_step(previous, next)
})
.count()
}
fn hex_byte_values_are_sequential(bytes: &[u8], pair_count: usize) -> bool {
let forward = count_pair_value_steps(bytes, pair_count, |previous, next| {
next == previous.wrapping_add(1)
});
let reverse = count_pair_value_steps(bytes, pair_count, |previous, next| {
previous == next.wrapping_add(1)
});
let threshold = sequential_step_threshold(pair_count - 1);
forward > threshold || reverse > threshold
}
fn count_pair_value_steps(bytes: &[u8], pair_count: usize, step: fn(u8, u8) -> bool) -> usize {
let Some(mut previous) = hex_pair_value(bytes, 0) else {
return 0;
};
let mut count = 0usize;
for pair in 1..pair_count {
let Some(next) = hex_pair_value(bytes, pair) else {
return 0;
};
if step(previous, next) {
count += 1;
}
previous = next;
}
count
}
fn hex_pair_value(bytes: &[u8], pair: usize) -> Option<u8> {
let hi = crate::decode::util::hex_val(bytes[pair * 2]).ok()?; let lo = crate::decode::util::hex_val(bytes[pair * 2 + 1]).ok()?; Some((hi << 4) | lo)
}
fn hex_forward_step(previous: u8, next: u8) -> bool {
let previous = previous.to_ascii_lowercase();
let next = next.to_ascii_lowercase();
next == previous + 1 || (previous == b'9' && next == b'a') || (previous == b'f' && next == b'0')
}
fn hex_reverse_step(previous: u8, next: u8) -> bool {
let previous = previous.to_ascii_lowercase();
let next = next.to_ascii_lowercase();
next + 1 == previous || (previous == b'a' && next == b'9') || (previous == b'0' && next == b'f')
}
fn hex_pair_column_step(previous: u8, next: u8) -> bool {
hex_forward_step(previous, next) || (previous == b'9' && next == b'0')
}
#[cfg(test)]
#[path = "../../tests/unit/context_placeholder_predicates.rs"]
mod sequential_placeholder_tests;
#[cfg(test)]
#[path = "../../tests/unit/context_placeholder_empty_hash.rs"]
mod placeholder_suppression_adversarial_tests;