// 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();
#[apply(all_engines)] fn decode_detect_1_valid_symbol_in_last_quad_invalid_length<E: EngineWrapper>(engine_wrapper: E) { for len in (0_usize..256).map(|len| len * 4 + 1) { for mode in all_pad_modes() { letmut input = vec![b'A'; len];
let engine = E::standard_with_pad_mode(true, mode);
assert_eq!(Err(DecodeError::InvalidLength(len)), engine.decode(&input)); // if we add padding, then the first pad byte in the quad is invalid because it should // be the second symbol for _ in0..3 {
input.push(PAD_BYTE);
assert_eq!(
Err(DecodeError::InvalidByte(len, PAD_BYTE)),
engine.decode(&input)
);
}
}
}
}
#[apply(all_engines)] fn decode_detect_1_invalid_byte_in_last_quad_invalid_byte<E: EngineWrapper>(engine_wrapper: E) { for prefix_len in (0_usize..256).map(|len| len * 4) { for mode in all_pad_modes() { letmut input = vec![b'A'; prefix_len];
input.push(b'*');
let engine = E::standard_with_pad_mode(true, mode);
/// Any amount of padding anywhere before the final non padding character = invalid byte at first /// pad byte. /// From this and [decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_non_canonical_padding_suffix_all_modes], /// we know padding must extend contiguously to the end of the input. #[apply(all_engines)] fn decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_all_modes<
E: EngineWrapper,
>(
engine_wrapper: E,
) { // Different amounts of padding, w/ offset from end for the last non-padding char. // Only canonical padding, so Canonical mode will work. let suffixes = &[("AA==", 2), ("AAA=", 1), ("AAAA", 0)];
for mode in pad_modes_allowing_padding() { // We don't encode, so we don't care about encode padding. let engine = E::standard_with_pad_mode(true, mode);
/// See [decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_all_modes] #[apply(all_engines)] fn decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_non_canonical_padding_suffix<
E: EngineWrapper,
>(
engine_wrapper: E,
) { // Different amounts of padding, w/ offset from end for the last non-padding char, and // non-canonical padding. let suffixes = [
("AA==", 2),
("AA=", 1),
("AA", 0),
("AAA=", 1),
("AAA", 0),
("AAAA", 0),
];
// We don't encode, so we don't care about encode padding. // Decoding is indifferent so that we don't get caught by missing padding on the last quad let engine = E::standard_with_pad_mode(true, DecodePaddingMode::Indifferent);
let prefix_quads_range = distributions::Uniform::from(0..=256);
for _ in0..100_000 { for (suffix, suffix_offset) in suffixes.iter() { letmut s = "AAAA".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 - suffix_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);
// should still have non-padding before any final padding
assert_ne!(PAD_BYTE, encoded[last_non_padding_offset]);
assert_eq!(
Err(DecodeError::InvalidByte(padding_start, PAD_BYTE)),
engine.decode(&encoded), "len: {}, input: {}",
encoded.len(),
String::from_utf8(encoded).unwrap()
);
}
}
}
/// Any amount of padding before final chunk that crosses over into final chunk with 1-4 bytes = /// invalid byte at first pad byte. /// From this we know the padding must start in the final chunk. #[apply(all_engines)] fn decode_padding_starts_before_final_chunk_error_invalid_byte_at_first_pad<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); let suffix_pad_len_range = distributions::Uniform::from(1..=4); // don't use no-padding mode, as the reader decode might decode a block that ends with // valid padding, which should then be referenced when encountering the later invalid byte for mode in pad_modes_allowing_padding() { // 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); // all 0 bits so we don't hit InvalidLastSymbol with the reader decoder letmut encoded = "AAAA"
.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);
/// 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);
// for all possible padding lengths for padding_len in1..=(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);
assert_eq!(
Err(DecodeError::InvalidByte(
prefix_quad_len * 4 + suffix_data_len,
PAD_BYTE,
)),
engine.decode(&encoded), "input {} suffix data len {} pad len {}",
String::from_utf8(encoded).unwrap(),
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);
/// 1 trailing byte that's not padding is detected as invalid byte even though there's padding /// in the middle of the input. This is essentially mandating the eager check for 1 trailing byte /// to catch the \n suffix case. // 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_all_pad_modes_invalid_byte<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_invalid_byte<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);
}
} fn do_invalid_trailing_byte(engine: impl Engine, mode: DecodePaddingMode) { for last_byte in [b'*', b'\n'] { for num_prefix_quads in0..256 { letmut s: String = "ABCD".repeat(num_prefix_quads);
s.push_str("Cg=="); letmut input = s.into_bytes();
input.push(last_byte);
// 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,
last_byte
)),
engine.decode(&input), "mode: {:?}, input: {}",
mode,
String::from_utf8(input).unwrap()
);
}
}
}
/// When there's 1 trailing byte, but it's padding, it's only InvalidByte if there isn't padding /// earlier. #[apply(all_engines)] fn decode_invalid_trailing_padding_as_invalid_byte_at_first_pad_byte<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_byte_at_first_padding(
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_byte_at_first_byte_all_modes<E: EngineWrapper>(
engine_wrapper: E,
) { for mode in all_pad_modes() {
do_invalid_trailing_padding_as_invalid_byte_at_first_padding(
E::standard_with_pad_mode(true, mode),
mode,
);
}
} fn do_invalid_trailing_padding_as_invalid_byte_at_first_padding(
engine: impl Engine,
mode: DecodePaddingMode,
) { for num_prefix_quads in0..256 { for (suffix, pad_offset) in [("AA===", 2), ("AAA==", 3), ("AAAA=", 4)] { letmut s: String = "ABCD".repeat(num_prefix_quads);
s.push_str(suffix);
assert_eq!( // pad after `g`, not the last one
Err(DecodeError::InvalidByte(
num_prefix_quads * 4 + pad_offset,
PAD_BYTE
)),
engine.decode(&s), "mode: {:?}, input: {}",
mode,
s
);
}
}
}
#[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
);
})
}
#[apply(all_engines)] fn decode_slice_checked_fails_gracefully_at_all_output_lengths<E: EngineWrapper>(
engine_wrapper: E,
) { letmut rng = seeded_rng(); for original_len in0..1000 { letmut original = vec![0; original_len];
rng.fill(&mut original[..]);
for mode in all_pad_modes() { let engine = E::standard_with_pad_mode( match mode {
DecodePaddingMode::Indifferent | DecodePaddingMode::RequireCanonical => true,
DecodePaddingMode::RequireNone => false,
},
mode,
);
/// 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.