// rstest_reuse template functions have unused variables #![allow(unused_variables)]
use rand::{ self,
distributions::{self, Distribution as _},
rngs, Rng as _, SeedableRng as _,
}; use rstest::rstest; use rstest_reuse::{apply, template}; use std::{collections, fmt, io::Read as _};
// the case::foo syntax includes the "foo" in the generated test method names #[template] #[rstest(engine_wrapper,
case::general_purpose(GeneralPurposeWrapper {}),
case::naive(NaiveWrapper {}),
case::decoder_reader(DecoderReaderEngineWrapper {}),
)] fn all_engines<E: EngineWrapper>(engine_wrapper: E) {}
/// Some decode tests don't make sense for use with `DecoderReader` as they are difficult to /// reason about or otherwise inapplicable given how DecoderReader slice up its input along /// chunk boundaries. #[template] #[rstest(engine_wrapper,
case::general_purpose(GeneralPurposeWrapper {}),
case::naive(NaiveWrapper {}),
)] fn all_engines_except_decoder_reader<E: EngineWrapper>(engine_wrapper: E) {}
// if there was any padding originally, the no padding engine won't decode it if encoded.as_bytes().contains(&PAD_BYTE) {
assert_eq!(
Err(DecodeError::InvalidPadding),
engine_no_padding.decode(encoded)
)
}
}
// if there was (canonical) padding, and we remove it, the standard engine won't decode if encoded.as_bytes().contains(&PAD_BYTE) {
assert_eq!(
Err(DecodeError::InvalidPadding),
engine.decode(encoded_without_padding)
)
}
}
}
}
let orig_len = fill_rand(&mut orig_data, &>mut rng, &input_len_range);
let prefix_len = 1024; // plenty of prefix and suffix
fill_rand_len(&mut encode_buf, &mut rng, prefix_len * 2 + orig_len * 2);
encode_buf_backup.extend_from_slice(&encode_buf[..]);
let expected_encode_len_no_pad = encoded_len(orig_len, false).unwrap();
let encoded_len_no_pad =
engine.internal_encode(&orig_data[..], &mut encode_buf[prefix_len..]);
assert_eq!(expected_encode_len_no_pad, encoded_len_no_pad);
// no writes past what it claimed to write
assert_eq!(&encode_buf_backup[..prefix_len], &encode_buf[..prefix_len]);
assert_eq!(
&encode_buf_backup[(prefix_len + encoded_len_no_pad)..],
&encode_buf[(prefix_len + encoded_len_no_pad)..]
);
let encoded_data = &encode_buf[prefix_len..(prefix_len + encoded_len_no_pad)];
assert_encode_sanity(
std::str::from_utf8(encoded_data).unwrap(), // engines don't pad false,
orig_len,
);
// pad so we can decode it in case our random engine requires padding let pad_len = if padded {
add_padding(
encoded_len_no_pad,
&mut encode_buf[prefix_len + encoded_len_no_pad..],
)
} else { 0
};
let encoded_len = engine
.encode_slice(&orig_data[..], &mut encode_buf[..])
.unwrap();
encode_buf.truncate(encoded_len);
// oversize decode buffer so we can easily tell if it writes anything more than // just the decoded data let prefix_len = 1024; // plenty of prefix and suffix
fill_rand_len(&mut decode_buf, &mut rng, prefix_len * 2 + orig_len * 2);
decode_buf_backup.extend_from_slice(&decode_buf[..]);
let dec_len = engine
.decode_slice_unchecked(&encode_buf, &mut decode_buf[prefix_len..])
.unwrap();
#[apply(all_engines)] fn decode_detect_invalid_last_symbol<E: EngineWrapper>(engine_wrapper: E) { // 0xFF -> "/w==", so all letters > w, 0-9, and '+', '/' should get InvalidLastSymbol let engine = E::standard();
// check across enough lengths that it would likely cover any implementation's various internal // small/large input division for len in (0_usize..256).map(|len| len * 4 + 1) { let engine = E::random_alphabet(&mut rng, &STANDARD);
letmut input = vec![b'A'; len];
// with a valid last char, it's InvalidLength
assert_eq!(Err(DecodeError::InvalidLength), engine.decode(&input)); // after mangling the last char, it's InvalidByte
input[len - 1] = b'"';
assert_eq!(
Err(DecodeError::InvalidByte(len - 1, b'"')),
engine.decode(&input)
);
}
}
#[apply(all_engines)] fn decode_detect_invalid_last_symbol_every_possible_two_symbols<E: EngineWrapper>(
engine_wrapper: E,
) { let engine = E::standard();
/// Any amount of padding anywhere before the final non padding character = invalid byte at first /// pad byte. /// From this, we know padding must extend to the end of the input. // DecoderReader pseudo-engine detects InvalidLastSymbol instead of InvalidLength because it // can end a decode on the quad that happens to contain the start of the padding #[apply(all_engines_except_decoder_reader)] fn decode_padding_before_final_non_padding_char_error_invalid_byte<E: EngineWrapper>(
engine_wrapper: E,
) { letmut rng = seeded_rng();
// the different amounts of proper padding, w/ offset from end for the last non-padding char let suffixes = [("/w==", 2), ("iYu=", 1), ("zzzz", 0)];
let prefix_quads_range = distributions::Uniform::from(0..=256);
for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for _ in0..100_000 { for (suffix, offset) in suffixes.iter() { letmut s = "ABCD".repeat(prefix_quads_range.sample(&mut rng));
s.push_str(suffix); letmut encoded = s.into_bytes();
// calculate a range to write padding into that leaves at least one non padding char let last_non_padding_offset = encoded.len() - 1 - offset;
// don't include last non padding char as it must stay not padding let padding_end = rng.gen_range(0..last_non_padding_offset);
// don't use more than 100 bytes of padding, but also use shorter lengths when // padding_end is near the start of the encoded data to avoid biasing to padding // the entire prefix on short lengths let padding_len = rng.gen_range(1..=usize::min(100, padding_end + 1)); let padding_start = padding_end.saturating_sub(padding_len);
/// Any amount of padding before final chunk that crosses over into final chunk with 2-4 bytes = /// invalid byte at first pad byte. /// From this and [decode_padding_starts_before_final_chunk_error_invalid_length] we know the /// padding must start in the final chunk. // DecoderReader pseudo-engine detects InvalidLastSymbol instead of InvalidLength because it // can end a decode on the quad that happens to contain the start of the padding #[apply(all_engines_except_decoder_reader)] fn decode_padding_starts_before_final_chunk_error_invalid_byte<E: EngineWrapper>(
engine_wrapper: E,
) { letmut rng = seeded_rng();
// must have at least one prefix quad let prefix_quads_range = distributions::Uniform::from(1..256); // excluding 1 since we don't care about invalid length in this test let suffix_pad_len_range = distributions::Uniform::from(2..=4); for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode); for _ in0..100_000 { let suffix_len = suffix_pad_len_range.sample(&mut rng); letmut encoded = "ABCD"
.repeat(prefix_quads_range.sample(&mut rng))
.into_bytes();
encoded.resize(encoded.len() + suffix_len, PAD_BYTE);
// amount of padding must be long enough to extend back from suffix into previous // quads let padding_len = rng.gen_range(suffix_len + 1..encoded.len()); // no non-padding after padding in this test, so padding goes to the end let padding_start = encoded.len() - padding_len;
encoded[padding_start..].fill(PAD_BYTE);
/// Any amount of padding before final chunk that crosses over into final chunk with 1 byte = /// invalid length. /// From this we know the padding must start in the final chunk. // DecoderReader pseudo-engine detects InvalidByte instead of InvalidLength because it starts by // decoding only the available complete quads #[apply(all_engines_except_decoder_reader)] fn decode_padding_starts_before_final_chunk_error_invalid_length<E: EngineWrapper>(
engine_wrapper: E,
) { letmut rng = seeded_rng();
// must have at least one prefix quad let prefix_quads_range = distributions::Uniform::from(1..256); for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode); for _ in0..100_000 { letmut encoded = "ABCD"
.repeat(prefix_quads_range.sample(&mut rng))
.into_bytes();
encoded.resize(encoded.len() + 1, PAD_BYTE);
// amount of padding must be long enough to extend back from suffix into previous // quads let padding_len = rng.gen_range(1 + 1..encoded.len()); // no non-padding after padding in this test, so padding goes to the end let padding_start = encoded.len() - padding_len;
encoded[padding_start..].fill(PAD_BYTE);
/// 0-1 bytes of data before any amount of padding in final chunk = invalid byte, since padding /// is not valid data (consistent with error for pad bytes in earlier chunks). /// From this we know there must be 2-3 bytes of data before padding #[apply(all_engines)] fn decode_too_little_data_before_padding_error_invalid_byte<E: EngineWrapper>(engine_wrapper: E) { letmut rng = seeded_rng();
// want to test no prefix quad case, so start at 0 let prefix_quads_range = distributions::Uniform::from(0_usize..256); let suffix_data_len_range = distributions::Uniform::from(0_usize..=1); for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode); for _ in0..100_000 { let suffix_data_len = suffix_data_len_range.sample(&mut rng); let prefix_quad_len = prefix_quads_range.sample(&mut rng);
// ensure there is a suffix quad let min_padding = usize::from(suffix_data_len == 0);
// for all possible padding lengths for padding_len in min_padding..=(4 - suffix_data_len) { letmut encoded = "ABCD".repeat(prefix_quad_len).into_bytes();
encoded.resize(encoded.len() + suffix_data_len, b'A');
encoded.resize(encoded.len() + padding_len, PAD_BYTE);
if suffix_data_len + padding_len == 1 {
assert_eq!(Err(DecodeError::InvalidLength), engine.decode(&encoded),);
} else {
assert_eq!(
Err(DecodeError::InvalidByte(
prefix_quad_len * 4 + suffix_data_len,
PAD_BYTE,
)),
engine.decode(&encoded), "suffix data len {} pad len {}",
suffix_data_len,
padding_len
);
}
}
}
}
}
// https://eprint.iacr.org/2022/361.pdf table 2, test 7 // DecoderReader pseudo-engine gets InvalidByte at 8 (extra padding) since it decodes the first // two complete quads correctly. #[apply(all_engines_except_decoder_reader)] fn decode_malleability_test_case_2_byte_suffix_too_much_padding<E: EngineWrapper>(
engine_wrapper: E,
) {
assert_eq!(
DecodeError::InvalidByte(6, PAD_BYTE),
E::standard().decode("SGVsbA====").unwrap_err()
);
}
let suffixes = ["/w", "/w=", "iYU"]; for num_prefix_quads in0..256 { for &suffix in suffixes.iter() { letmut encoded = "AAAA".repeat(num_prefix_quads);
encoded.push_str(suffix);
/// Requires no padding -> rejects 2 + 1-2, 3 + 1 final chunk configuration #[apply(all_engines)] fn decode_pad_mode_requires_no_padding_rejects_any_padding<E: EngineWrapper>(engine_wrapper: E) { let engine = E::standard_with_pad_mode(true, DecodePaddingMode::RequireNone);
let suffixes = ["/w=", "/w==", "iYU="]; for num_prefix_quads in0..256 { for &suffix in suffixes.iter() { letmut encoded = "AAAA".repeat(num_prefix_quads);
encoded.push_str(suffix);
//this is a MAY in the rfc: https://tools.ietf.org/html/rfc4648#section-3.3 // DecoderReader pseudo-engine finds the first padding, but doesn't report it as an error, // because in the next decode it finds more padding, which is reported as InvalidByte, just // with an offset at its position in the second decode, rather than being linked to the start // of the padding that was first seen in the previous decode. #[apply(all_engines_except_decoder_reader)] fn decode_pad_byte_in_penultimate_quad_error<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for num_prefix_quads in0..256 { // leave room for at least one pad byte in penultimate quad for num_valid_bytes_penultimate_quad in0..4 { // can't have 1 or it would be invalid length for num_pad_bytes_in_final_quad in2..=4 { letmut s: String = "ABCD".repeat(num_prefix_quads);
// varying amounts of padding in the penultimate quad for _ in0..num_valid_bytes_penultimate_quad {
s.push('A');
} // finish penultimate quad with padding for _ in num_valid_bytes_penultimate_quad..4 {
s.push('=');
} // and more padding in the final quad for _ in0..num_pad_bytes_in_final_quad {
s.push('=');
}
// padding should be an invalid byte before the final quad. // Could argue that the *next* padding byte (in the next quad) is technically the first // erroneous one, but reporting that accurately is more complex and probably nobody cares
assert_eq!(
DecodeError::InvalidByte(
num_prefix_quads * 4 + num_valid_bytes_penultimate_quad,
b'=',
),
engine.decode(&s).unwrap_err(),
);
}
}
}
}
}
#[apply(all_engines)] fn decode_bytes_after_padding_in_final_quad_error<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for num_prefix_quads in0..256 { // leave at least one byte in the quad for padding for bytes_after_padding in1..4 { letmut s: String = "ABCD".repeat(num_prefix_quads);
// every invalid padding position with a 3-byte final quad: 1 to 3 bytes after padding for _ in0..(3 - bytes_after_padding) {
s.push('A');
}
s.push('='); for _ in0..bytes_after_padding {
s.push('A');
}
#[apply(all_engines)] fn decode_absurd_pad_error<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for num_prefix_quads in0..256 { letmut s: String = "ABCD".repeat(num_prefix_quads);
s.push_str("==Y=Wx===pY=2U=====");
// DecoderReader pseudo-engine detects InvalidByte instead of InvalidLength because it starts by // decoding only the available complete quads #[apply(all_engines_except_decoder_reader)] fn decode_too_much_padding_returns_error<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for num_prefix_quads in0..256 { // add enough padding to ensure that we'll hit all decode stages at the different lengths for pad_bytes in1..=64 { letmut s: String = "ABCD".repeat(num_prefix_quads); let padding: String = "=".repeat(pad_bytes);
s.push_str(&padding);
// DecoderReader pseudo-engine detects InvalidByte instead of InvalidLength because it starts by // decoding only the available complete quads #[apply(all_engines_except_decoder_reader)] fn decode_padding_followed_by_non_padding_returns_error<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for num_prefix_quads in0..256 { for pad_bytes in0..=32 { letmut s: String = "ABCD".repeat(num_prefix_quads); let padding: String = "=".repeat(pad_bytes);
s.push_str(&padding);
s.push('E');
#[apply(all_engines)] fn decode_one_char_in_final_quad_with_padding_error<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for num_prefix_quads in0..256 { letmut s: String = "ABCD".repeat(num_prefix_quads);
s.push_str("E=");
#[apply(all_engines)] fn decode_too_few_symbols_in_final_quad_error<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() { // we don't encode so we don't care about encode padding let engine = E::standard_with_pad_mode(true, mode);
for num_prefix_quads in0..256 { // <2 is invalid for final_quad_symbols in0..2 { for padding_symbols in0..=(4 - final_quad_symbols) { letmut s: String = "ABCD".repeat(num_prefix_quads);
for _ in0..final_quad_symbols {
s.push('A');
} for _ in0..padding_symbols {
s.push('=');
}
// DecoderReader pseudo-engine can't handle DecodePaddingMode::RequireNone since it will decode // a complete quad with padding in it before encountering the stray byte that makes it an invalid // length #[apply(all_engines_except_decoder_reader)] fn decode_invalid_trailing_bytes<E: EngineWrapper>(engine_wrapper: E) { for mode in all_pad_modes() {
do_invalid_trailing_byte(E::standard_with_pad_mode(true, mode), mode);
}
}
#[apply(all_engines)] fn decode_invalid_trailing_bytes_all_modes<E: EngineWrapper>(engine_wrapper: E) { // excluding no padding mode because the DecoderWrapper pseudo-engine will fail with // InvalidPadding because it will decode the last complete quad with padding first for mode in pad_modes_allowing_padding() {
do_invalid_trailing_byte(E::standard_with_pad_mode(true, mode), mode);
}
}
#[apply(all_engines)] fn decode_invalid_trailing_padding_as_invalid_length<E: EngineWrapper>(engine_wrapper: E) { // excluding no padding mode because the DecoderWrapper pseudo-engine will fail with // InvalidPadding because it will decode the last complete quad with padding first for mode in pad_modes_allowing_padding() {
do_invalid_trailing_padding_as_invalid_length(E::standard_with_pad_mode(true, mode), mode);
}
}
// DecoderReader pseudo-engine can't handle DecodePaddingMode::RequireNone since it will decode // a complete quad with padding in it before encountering the stray byte that makes it an invalid // length #[apply(all_engines_except_decoder_reader)] fn decode_invalid_trailing_padding_as_invalid_length_all_modes<E: EngineWrapper>(
engine_wrapper: E,
) { for mode in all_pad_modes() {
do_invalid_trailing_padding_as_invalid_length(E::standard_with_pad_mode(true, mode), mode);
}
}
for num_prefix_quads in0..256 { // at least one token, otherwise it wouldn't be a final quad for num_tokens_final_quad in1..=4 { for num_padding in0..=(4 - num_tokens_final_quad) { letmut s: String = "IIII".repeat(num_prefix_quads); for _ in0..num_tokens_final_quad {
s.push('g');
} for _ in0..num_padding {
s.push('=');
}
let res = engine.decode(&s); if num_tokens_final_quad >= 2 {
assert!(res.is_ok());
} elseif num_tokens_final_quad == 1 && num_padding > 0 { // = is invalid if it's too early
assert_eq!(
Err(DecodeError::InvalidByte(
num_prefix_quads * 4 + num_tokens_final_quad, 61
)),
res
);
} elseif num_padding > 2 {
assert_eq!(Err(DecodeError::InvalidPadding), res);
} else {
assert_eq!(Err(DecodeError::InvalidLength), res);
}
}
}
}
}
#[apply(all_engines)] fn decode_length_estimate_delta<E: EngineWrapper>(engine_wrapper: E) { for engine in [E::standard(), E::standard_unpadded()] { for &padding in &[true, false] { for orig_len in0..1000 { let encoded_len = encoded_len(orig_len, padding).unwrap();
#[apply(all_engines)] fn estimate_via_u128_inflation<E: EngineWrapper>(engine_wrapper: E) { // cover both ends of usize
(0..1000)
.chain(usize::MAX - 1000..=usize::MAX)
.for_each(|encoded_len| { // inflate to 128 bit type to be able to safely use the easy formulas let len_128 = encoded_len as u128;
let estimate = E::standard()
.internal_decoded_len_estimate(encoded_len)
.decoded_len_estimate();
// This check is a little too strict: it requires using the (len + 3) / 4 * 3 formula // or equivalent, but until other engines come along that use a different formula // requiring that we think more carefully about what the allowable criteria are, this // will do.
assert_eq!(
((len_128 + 3) / 4 * 3) as usize,
estimate, "enc len {}",
encoded_len
);
})
}
// The case of trailing newlines is common enough to warrant a test for a good error // message.
assert_eq!(
Err(DecodeError::InvalidByte(num_prefix_quads * 4 + 4, b'\n')),
engine.decode(&s), "mode: {:?}, input: {}",
mode,
s
);
}
}
/// Returns a tuple of the original data length, the encoded data length (just data), and the length including padding. /// /// Vecs provided should be empty. fn generate_random_encoded_data<E: Engine, R: rand::Rng, D: distributions::Distribution<usize>>(
engine: &E,
orig_data: &mut Vec<u8>,
encode_buf: &mut Vec<u8>,
rng: &mut R,
length_distribution: &D,
) -> (usize, usize, usize) { let padding: bool = engine.config().encode_padding();
let orig_len = fill_rand(orig_data, rng, length_distribution); let expected_encoded_len = encoded_len(orig_len, padding).unwrap();
encode_buf.resize(expected_encoded_len, 0);
let base_encoded_len = engine.internal_encode(&orig_data[..], &mut encode_buf[..]);
let enc_len_with_padding = if padding {
base_encoded_len + add_padding(base_encoded_len, &mut encode_buf[base_encoded_len..])
} else {
base_encoded_len
};
// fill to a random length fn fill_rand<R: rand::Rng, D: distributions::Distribution<usize>>(
vec: &mut Vec<u8>,
rng: &mut R,
length_distribution: &D,
) -> usize { let len = length_distribution.sample(rng); for _ in0..len {
vec.push(rng.gen());
}
/// A wrapper to make using engines in rstest fixtures easier. /// The functions don't need to be instance methods, but rstest does seem /// to want an instance, so instances are passed to test functions and then ignored. trait EngineWrapper { type Engine: Engine;
/// Return an engine configured for RFC standard base64 fn standard() -> Self::Engine;
/// Return an engine configured for RFC standard base64, except with no padding appended on /// encode, and required no padding on decode. fn standard_unpadded() -> Self::Engine;
/// Return an engine configured for RFC standard alphabet with the provided encode and decode /// pad settings fn standard_with_pad_mode(encode_pad: bool, decode_pad_mode: DecodePaddingMode)
-> Self::Engine;
/// Return an engine configured for RFC standard base64 that allows invalid trailing bits fn standard_allow_trailing_bits() -> Self::Engine;
/// Return an engine configured with a randomized alphabet and config fn random<R: rand::Rng>(rng: &mut R) -> Self::Engine;
/// Return an engine configured with the specified alphabet and randomized config fn random_alphabet<R: rand::Rng>(rng: &mut R, alphabet: &Alphabet) -> Self::Engine;
}
struct GeneralPurposeWrapper {}
impl EngineWrapper for GeneralPurposeWrapper { type Engine = general_purpose::GeneralPurpose;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.