// 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}; use crate::{ alphabet::{Alphabet, STANDARD}, encode::add_padding, encoded_len, engine::{general_purpose, naive, Config, DecodeEstimate, DecodePaddingMode, Engine}, tests::{assert_encode_sanity, random_alphabet, random_config}, DecodeError, PAD_BYTE, }; // the case::foo syntax includes the "foo" in the generated test method names #[template] #[rstest(engine_wrapper, case::general_purpose(GeneralPurposeWrapper {}), case::naive(NaiveWrapper {}), )] fn all_engines(engine_wrapper: E) {} #[apply(all_engines)] fn rfc_test_vectors_std_alphabet(engine_wrapper: E) { let data = vec![ ("", ""), ("f", "Zg=="), ("fo", "Zm8="), ("foo", "Zm9v"), ("foob", "Zm9vYg=="), ("fooba", "Zm9vYmE="), ("foobar", "Zm9vYmFy"), ]; let engine = E::standard(); let engine_no_padding = E::standard_unpadded(); for (orig, encoded) in &data { let encoded_without_padding = encoded.trim_end_matches('='); // unpadded { let mut encode_buf = [0_u8; 8]; let mut decode_buf = [0_u8; 6]; let encode_len = engine_no_padding.internal_encode(orig.as_bytes(), &mut encode_buf[..]); assert_eq!( &encoded_without_padding, &std::str::from_utf8(&encode_buf[0..encode_len]).unwrap() ); let decode_len = engine_no_padding .decode_slice_unchecked(encoded_without_padding.as_bytes(), &mut decode_buf[..]) .unwrap(); assert_eq!(orig.len(), decode_len); assert_eq!( orig, &std::str::from_utf8(&decode_buf[0..decode_len]).unwrap() ); // 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) ) } } // padded { let mut encode_buf = [0_u8; 8]; let mut decode_buf = [0_u8; 6]; let encode_len = engine.internal_encode(orig.as_bytes(), &mut encode_buf[..]); assert_eq!( // doesn't have padding added yet &encoded_without_padding, &std::str::from_utf8(&encode_buf[0..encode_len]).unwrap() ); let pad_len = add_padding(orig.len(), &mut encode_buf[encode_len..]); assert_eq!(encoded.as_bytes(), &encode_buf[..encode_len + pad_len]); let decode_len = engine .decode_slice_unchecked(encoded.as_bytes(), &mut decode_buf[..]) .unwrap(); assert_eq!(orig.len(), decode_len); assert_eq!( orig, &std::str::from_utf8(&decode_buf[0..decode_len]).unwrap() ); // 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) ) } } } } #[apply(all_engines)] fn roundtrip_random(engine_wrapper: E) { let mut rng = seeded_rng(); let mut orig_data = Vec::::new(); let mut encode_buf = Vec::::new(); let mut decode_buf = Vec::::new(); let len_range = distributions::Uniform::new(1, 1_000); for _ in 0..10_000 { let engine = E::random(&mut rng); orig_data.clear(); encode_buf.clear(); decode_buf.clear(); let (orig_len, _, encoded_len) = generate_random_encoded_data( &engine, &mut orig_data, &mut encode_buf, &mut rng, &len_range, ); // exactly the right size decode_buf.resize(orig_len, 0); let dec_len = engine .decode_slice_unchecked(&encode_buf[0..encoded_len], &mut decode_buf[..]) .unwrap(); assert_eq!(orig_len, dec_len); assert_eq!(&orig_data[..], &decode_buf[..dec_len]); } } #[apply(all_engines)] fn encode_doesnt_write_extra_bytes(engine_wrapper: E) { let mut rng = seeded_rng(); let mut orig_data = Vec::::new(); let mut encode_buf = Vec::::new(); let mut encode_buf_backup = Vec::::new(); let input_len_range = distributions::Uniform::new(0, 1000); for _ in 0..10_000 { let engine = E::random(&mut rng); let padded = engine.config().encode_padding(); orig_data.clear(); encode_buf.clear(); encode_buf_backup.clear(); 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(orig_len, &mut encode_buf[prefix_len + encoded_len_no_pad..]) } else { 0 }; assert_eq!( orig_data, engine .decode(&encode_buf[prefix_len..(prefix_len + encoded_len_no_pad + pad_len)],) .unwrap() ); } } #[apply(all_engines)] fn encode_engine_slice_fits_into_precisely_sized_slice(engine_wrapper: E) { let mut orig_data = Vec::new(); let mut encoded_data = Vec::new(); let mut decoded = Vec::new(); let input_len_range = distributions::Uniform::new(0, 1000); let mut rng = rngs::SmallRng::from_entropy(); for _ in 0..10_000 { orig_data.clear(); encoded_data.clear(); decoded.clear(); let input_len = input_len_range.sample(&mut rng); for _ in 0..input_len { orig_data.push(rng.gen()); } let engine = E::random(&mut rng); let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap(); encoded_data.resize(encoded_size, 0); assert_eq!( encoded_size, engine.encode_slice(&orig_data, &mut encoded_data).unwrap() ); assert_encode_sanity( std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), engine.config().encode_padding(), input_len, ); engine .decode_vec(&encoded_data[0..encoded_size], &mut decoded) .unwrap(); assert_eq!(orig_data, decoded); } } #[apply(all_engines)] fn decode_doesnt_write_extra_bytes(engine_wrapper: E) where E: EngineWrapper, <::Engine as Engine>::Config: fmt::Debug, { let mut rng = seeded_rng(); let mut orig_data = Vec::::new(); let mut encode_buf = Vec::::new(); let mut decode_buf = Vec::::new(); let mut decode_buf_backup = Vec::::new(); let len_range = distributions::Uniform::new(1, 1_000); for _ in 0..10_000 { let engine = E::random(&mut rng); orig_data.clear(); encode_buf.clear(); decode_buf.clear(); decode_buf_backup.clear(); let orig_len = fill_rand(&mut orig_data, &mut rng, &len_range); encode_buf.resize(orig_len * 2 + 100, 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(); assert_eq!(orig_len, dec_len); assert_eq!( &orig_data[..], &decode_buf[prefix_len..prefix_len + dec_len] ); assert_eq!(&decode_buf_backup[..prefix_len], &decode_buf[..prefix_len]); assert_eq!( &decode_buf_backup[prefix_len + dec_len..], &decode_buf[prefix_len + dec_len..] ); } } #[apply(all_engines)] fn decode_detect_invalid_last_symbol(engine_wrapper: E) { // 0xFF -> "/w==", so all letters > w, 0-9, and '+', '/' should get InvalidLastSymbol let engine = E::standard(); assert_eq!(Ok(vec![0x89, 0x85]), engine.decode("iYU=")); assert_eq!(Ok(vec![0xFF]), engine.decode("/w==")); for (suffix, offset) in vec![ // suffix, offset of bad byte from start of suffix ("/x==", 1_usize), ("/z==", 1_usize), ("/0==", 1_usize), ("/9==", 1_usize), ("/+==", 1_usize), ("//==", 1_usize), // trailing 01 ("iYV=", 2_usize), // trailing 10 ("iYW=", 2_usize), // trailing 11 ("iYX=", 2_usize), ] { for prefix_quads in 0..256 { let mut encoded = "AAAA".repeat(prefix_quads); encoded.push_str(suffix); assert_eq!( Err(DecodeError::InvalidLastSymbol( encoded.len() - 4 + offset, suffix.as_bytes()[offset], )), engine.decode(encoded.as_str()) ); } } } #[apply(all_engines)] fn decode_detect_invalid_last_symbol_when_length_is_also_invalid( engine_wrapper: E, ) { let mut rng = seeded_rng(); // 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); let mut 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( engine_wrapper: E, ) { let engine = E::standard(); let mut base64_to_bytes = collections::HashMap::new(); for b in 0_u8..=255 { let mut b64 = vec![0_u8; 4]; assert_eq!(2, engine.internal_encode(&[b], &mut b64[..])); let _ = add_padding(1, &mut b64[2..]); assert!(base64_to_bytes.insert(b64, vec![b]).is_none()); } // every possible combination of trailing symbols must either decode to 1 byte or get InvalidLastSymbol, with or without any leading chunks let mut prefix = Vec::new(); for _ in 0..256 { let mut clone = prefix.clone(); let mut symbols = [0_u8; 4]; for &s1 in STANDARD.symbols.iter() { symbols[0] = s1; for &s2 in STANDARD.symbols.iter() { symbols[1] = s2; symbols[2] = PAD_BYTE; symbols[3] = PAD_BYTE; // chop off previous symbols clone.truncate(prefix.len()); clone.extend_from_slice(&symbols[..]); let decoded_prefix_len = prefix.len() / 4 * 3; match base64_to_bytes.get(&symbols[..]) { Some(bytes) => { let res = engine .decode(&clone) // remove prefix .map(|decoded| decoded[decoded_prefix_len..].to_vec()); assert_eq!(Ok(bytes.clone()), res); } None => assert_eq!( Err(DecodeError::InvalidLastSymbol(1, s2)), engine.decode(&symbols[..]) ), } } } prefix.extend_from_slice(b"AAAA"); } } #[apply(all_engines)] fn decode_detect_invalid_last_symbol_every_possible_three_symbols( engine_wrapper: E, ) { let engine = E::standard(); let mut base64_to_bytes = collections::HashMap::new(); let mut bytes = [0_u8; 2]; for b1 in 0_u8..=255 { bytes[0] = b1; for b2 in 0_u8..=255 { bytes[1] = b2; let mut b64 = vec![0_u8; 4]; assert_eq!(3, engine.internal_encode(&bytes, &mut b64[..])); let _ = add_padding(2, &mut b64[3..]); let mut v = Vec::with_capacity(2); v.extend_from_slice(&bytes[..]); assert!(base64_to_bytes.insert(b64, v).is_none()); } } // every possible combination of symbols must either decode to 2 bytes or get InvalidLastSymbol, with or without any leading chunks let mut prefix = Vec::new(); for _ in 0..256 { let mut input = prefix.clone(); let mut symbols = [0_u8; 4]; for &s1 in STANDARD.symbols.iter() { symbols[0] = s1; for &s2 in STANDARD.symbols.iter() { symbols[1] = s2; for &s3 in STANDARD.symbols.iter() { symbols[2] = s3; symbols[3] = PAD_BYTE; // chop off previous symbols input.truncate(prefix.len()); input.extend_from_slice(&symbols[..]); let decoded_prefix_len = prefix.len() / 4 * 3; match base64_to_bytes.get(&symbols[..]) { Some(bytes) => { let res = engine .decode(&input) // remove prefix .map(|decoded| decoded[decoded_prefix_len..].to_vec()); assert_eq!(Ok(bytes.clone()), res); } None => assert_eq!( Err(DecodeError::InvalidLastSymbol(2, s3)), engine.decode(&symbols[..]) ), } } } } prefix.extend_from_slice(b"AAAA"); } } #[apply(all_engines)] fn decode_invalid_trailing_bits_ignored_when_configured(engine_wrapper: E) { let strict = E::standard(); let forgiving = E::standard_allow_trailing_bits(); fn assert_tolerant_decode( engine: &E, input: &mut String, b64_prefix_len: usize, expected_decode_bytes: Vec, data: &str, ) { let prefixed = prefixed_data(input, b64_prefix_len, data); let decoded = engine.decode(prefixed); // prefix is always complete chunks let decoded_prefix_len = b64_prefix_len / 4 * 3; assert_eq!( Ok(expected_decode_bytes), decoded.map(|v| v[decoded_prefix_len..].to_vec()) ); } let mut prefix = String::new(); for _ in 0..256 { let mut input = prefix.clone(); // example from https://github.com/marshallpierce/rust-base64/issues/75 assert!(strict .decode(prefixed_data(&mut input, prefix.len(), "/w==")) .is_ok()); assert!(strict .decode(prefixed_data(&mut input, prefix.len(), "iYU=")) .is_ok()); // trailing 01 assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![255], "/x=="); assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![137, 133], "iYV="); // trailing 10 assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![255], "/y=="); assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![137, 133], "iYW="); // trailing 11 assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![255], "/z=="); assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![137, 133], "iYX="); prefix.push_str("AAAA"); } } #[apply(all_engines)] fn decode_invalid_byte_error(engine_wrapper: E) { let mut rng = seeded_rng(); let mut orig_data = Vec::::new(); let mut encode_buf = Vec::::new(); let mut decode_buf = Vec::::new(); let len_range = distributions::Uniform::new(1, 1_000); for _ in 0..10_000 { let alphabet = random_alphabet(&mut rng); let engine = E::random_alphabet(&mut rng, alphabet); orig_data.clear(); encode_buf.clear(); decode_buf.clear(); let (orig_len, encoded_len_just_data, encoded_len_with_padding) = generate_random_encoded_data( &engine, &mut orig_data, &mut encode_buf, &mut rng, &len_range, ); // exactly the right size decode_buf.resize(orig_len, 0); // replace one encoded byte with an invalid byte let invalid_byte: u8 = loop { let byte: u8 = rng.gen(); if alphabet.symbols.contains(&byte) { continue; } else { break byte; } }; let invalid_range = distributions::Uniform::new(0, orig_len); let invalid_index = invalid_range.sample(&mut rng); encode_buf[invalid_index] = invalid_byte; assert_eq!( Err(DecodeError::InvalidByte(invalid_index, invalid_byte)), engine.decode_slice_unchecked( &encode_buf[0..encoded_len_with_padding], &mut decode_buf[..], ) ); } } /// 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. #[apply(all_engines)] fn decode_padding_before_final_non_padding_char_error_invalid_byte( engine_wrapper: E, ) { let mut rng = seeded_rng(); // the different amounts of proper padding, w/ offset from end for the last non-padding char let suffixes = vec![("/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 _ in 0..100_000 { for (suffix, offset) in suffixes.iter() { let mut s = "ABCD".repeat(prefix_quads_range.sample(&mut rng)); s.push_str(suffix); let mut 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); encoded[padding_start..=padding_end].fill(PAD_BYTE); assert_eq!( Err(DecodeError::InvalidByte(padding_start, PAD_BYTE)), engine.decode(&encoded), ); } } } } /// Any amount of padding before final chunk that crosses over into final chunk with 1-4 bytes = /// invalid byte at first pad byte (except for 1 byte suffix = invalid length). /// 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( engine_wrapper: E, ) { let mut rng = seeded_rng(); // must have at least one prefix quad let prefix_quads_range = distributions::Uniform::from(1..256); // including 1 just to make sure that it really does produce invalid length let suffix_pad_len_range = distributions::Uniform::from(1..=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 _ in 0..100_000 { let suffix_len = suffix_pad_len_range.sample(&mut rng); let mut 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); if suffix_len == 1 { assert_eq!(Err(DecodeError::InvalidLength), engine.decode(&encoded),); } else { assert_eq!( Err(DecodeError::InvalidByte(padding_start, PAD_BYTE)), engine.decode(&encoded), ); } } } } /// 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(engine_wrapper: E) { let mut 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 _ in 0..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) { let mut 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 1 #[apply(all_engines)] fn decode_malleability_test_case_3_byte_suffix_valid(engine_wrapper: E) { assert_eq!( b"Hello".as_slice(), &E::standard().decode("SGVsbG8=").unwrap() ); } // https://eprint.iacr.org/2022/361.pdf table 2, test 2 #[apply(all_engines)] fn decode_malleability_test_case_3_byte_suffix_invalid_trailing_symbol( engine_wrapper: E, ) { assert_eq!( DecodeError::InvalidLastSymbol(6, 0x39), E::standard().decode("SGVsbG9=").unwrap_err() ); } // https://eprint.iacr.org/2022/361.pdf table 2, test 3 #[apply(all_engines)] fn decode_malleability_test_case_3_byte_suffix_no_padding(engine_wrapper: E) { assert_eq!( DecodeError::InvalidPadding, E::standard().decode("SGVsbG9").unwrap_err() ); } // https://eprint.iacr.org/2022/361.pdf table 2, test 4 #[apply(all_engines)] fn decode_malleability_test_case_2_byte_suffix_valid_two_padding_symbols( engine_wrapper: E, ) { assert_eq!( b"Hell".as_slice(), &E::standard().decode("SGVsbA==").unwrap() ); } // https://eprint.iacr.org/2022/361.pdf table 2, test 5 #[apply(all_engines)] fn decode_malleability_test_case_2_byte_suffix_short_padding(engine_wrapper: E) { assert_eq!( DecodeError::InvalidPadding, E::standard().decode("SGVsbA=").unwrap_err() ); } // https://eprint.iacr.org/2022/361.pdf table 2, test 6 #[apply(all_engines)] fn decode_malleability_test_case_2_byte_suffix_no_padding(engine_wrapper: E) { assert_eq!( DecodeError::InvalidPadding, E::standard().decode("SGVsbA").unwrap_err() ); } // https://eprint.iacr.org/2022/361.pdf table 2, test 7 #[apply(all_engines)] fn decode_malleability_test_case_2_byte_suffix_too_much_padding( engine_wrapper: E, ) { assert_eq!( DecodeError::InvalidByte(6, PAD_BYTE), E::standard().decode("SGVsbA====").unwrap_err() ); } /// Requires canonical padding -> accepts 2 + 2, 3 + 1, 4 + 0 final quad configurations #[apply(all_engines)] fn decode_pad_mode_requires_canonical_accepts_canonical(engine_wrapper: E) { assert_all_suffixes_ok( E::standard_with_pad_mode(true, DecodePaddingMode::RequireCanonical), vec!["/w==", "iYU=", "AAAA"], ); } /// Requires canonical padding -> rejects 2 + 0-1, 3 + 0 final chunk configurations #[apply(all_engines)] fn decode_pad_mode_requires_canonical_rejects_non_canonical(engine_wrapper: E) { let engine = E::standard_with_pad_mode(true, DecodePaddingMode::RequireCanonical); let suffixes = vec!["/w", "/w=", "iYU"]; for num_prefix_quads in 0..256 { for &suffix in suffixes.iter() { let mut encoded = "AAAA".repeat(num_prefix_quads); encoded.push_str(suffix); let res = engine.decode(&encoded); assert_eq!(Err(DecodeError::InvalidPadding), res); } } } /// Requires no padding -> accepts 2 + 0, 3 + 0, 4 + 0 final chunk configuration #[apply(all_engines)] fn decode_pad_mode_requires_no_padding_accepts_no_padding(engine_wrapper: E) { assert_all_suffixes_ok( E::standard_with_pad_mode(true, DecodePaddingMode::RequireNone), vec!["/w", "iYU", "AAAA"], ); } /// 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(engine_wrapper: E) { let engine = E::standard_with_pad_mode(true, DecodePaddingMode::RequireNone); let suffixes = vec!["/w=", "/w==", "iYU="]; for num_prefix_quads in 0..256 { for &suffix in suffixes.iter() { let mut encoded = "AAAA".repeat(num_prefix_quads); encoded.push_str(suffix); let res = engine.decode(&encoded); assert_eq!(Err(DecodeError::InvalidPadding), res); } } } /// Indifferent padding accepts 2 + 0-2, 3 + 0-1, 4 + 0 final chunk configuration #[apply(all_engines)] fn decode_pad_mode_indifferent_padding_accepts_anything(engine_wrapper: E) { assert_all_suffixes_ok( E::standard_with_pad_mode(true, DecodePaddingMode::Indifferent), vec!["/w", "/w=", "/w==", "iYU", "iYU=", "AAAA"], ); } //this is a MAY in the rfc: https://tools.ietf.org/html/rfc4648#section-3.3 #[apply(all_engines)] fn decode_pad_byte_in_penultimate_quad_error(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 in 0..256 { // leave room for at least one pad byte in penultimate quad for num_valid_bytes_penultimate_quad in 0..4 { // can't have 1 or it would be invalid length for num_pad_bytes_in_final_quad in 2..=4 { let mut s: String = "ABCD".repeat(num_prefix_quads); // varying amounts of padding in the penultimate quad for _ in 0..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 _ in 0..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(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 in 0..256 { // leave at least one byte in the quad for padding for bytes_after_padding in 1..4 { let mut s: String = "ABCD".repeat(num_prefix_quads); // every invalid padding position with a 3-byte final quad: 1 to 3 bytes after padding for _ in 0..(3 - bytes_after_padding) { s.push('A'); } s.push('='); for _ in 0..bytes_after_padding { s.push('A'); } // First (and only) padding byte is invalid. assert_eq!( DecodeError::InvalidByte( num_prefix_quads * 4 + (3 - bytes_after_padding), b'=' ), engine.decode(&s).unwrap_err() ); } } } } #[apply(all_engines)] fn decode_absurd_pad_error(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 in 0..256 { let mut s: String = "ABCD".repeat(num_prefix_quads); s.push_str("==Y=Wx===pY=2U====="); // first padding byte assert_eq!( DecodeError::InvalidByte(num_prefix_quads * 4, b'='), engine.decode(&s).unwrap_err() ); } } } #[apply(all_engines)] fn decode_too_much_padding_returns_error(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 in 0..256 { // add enough padding to ensure that we'll hit all decode stages at the different lengths for pad_bytes in 1..=64 { let mut s: String = "ABCD".repeat(num_prefix_quads); let padding: String = "=".repeat(pad_bytes); s.push_str(&padding); if pad_bytes % 4 == 1 { assert_eq!(DecodeError::InvalidLength, engine.decode(&s).unwrap_err()); } else { assert_eq!( DecodeError::InvalidByte(num_prefix_quads * 4, b'='), engine.decode(&s).unwrap_err() ); } } } } } #[apply(all_engines)] fn decode_padding_followed_by_non_padding_returns_error(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 in 0..256 { for pad_bytes in 0..=32 { let mut s: String = "ABCD".repeat(num_prefix_quads); let padding: String = "=".repeat(pad_bytes); s.push_str(&padding); s.push('E'); if pad_bytes % 4 == 0 { assert_eq!(DecodeError::InvalidLength, engine.decode(&s).unwrap_err()); } else { assert_eq!( DecodeError::InvalidByte(num_prefix_quads * 4, b'='), engine.decode(&s).unwrap_err() ); } } } } } #[apply(all_engines)] fn decode_one_char_in_final_quad_with_padding_error(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 in 0..256 { let mut s: String = "ABCD".repeat(num_prefix_quads); s.push_str("E="); assert_eq!( DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), engine.decode(&s).unwrap_err() ); // more padding doesn't change the error s.push('='); assert_eq!( DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), engine.decode(&s).unwrap_err() ); s.push('='); assert_eq!( DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), engine.decode(&s).unwrap_err() ); } } } #[apply(all_engines)] fn decode_too_few_symbols_in_final_quad_error(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 in 0..256 { // <2 is invalid for final_quad_symbols in 0..2 { for padding_symbols in 0..=(4 - final_quad_symbols) { let mut s: String = "ABCD".repeat(num_prefix_quads); for _ in 0..final_quad_symbols { s.push('A'); } for _ in 0..padding_symbols { s.push('='); } match final_quad_symbols + padding_symbols { 0 => continue, 1 => { assert_eq!(DecodeError::InvalidLength, engine.decode(&s).unwrap_err()); } _ => { // error reported at first padding byte assert_eq!( DecodeError::InvalidByte( num_prefix_quads * 4 + final_quad_symbols, b'=', ), engine.decode(&s).unwrap_err() ); } } } } } } } #[apply(all_engines)] fn decode_invalid_trailing_bytes(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 in 0..256 { let mut s: String = "ABCD".repeat(num_prefix_quads); s.push_str("Cg==\n"); // 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) ); // extra padding, however, is still InvalidLength let s = s.replace('\n', "="); assert_eq!(Err(DecodeError::InvalidLength), engine.decode(s)); } } } #[apply(all_engines)] fn decode_wrong_length_error(engine_wrapper: E) { let engine = E::standard_with_pad_mode(true, DecodePaddingMode::Indifferent); for num_prefix_quads in 0..256 { // at least one token, otherwise it wouldn't be a final quad for num_tokens_final_quad in 1..=4 { for num_padding in 0..=(4 - num_tokens_final_quad) { let mut s: String = "IIII".repeat(num_prefix_quads); for _ in 0..num_tokens_final_quad { s.push('g'); } for _ in 0..num_padding { s.push('='); } let res = engine.decode(&s); if num_tokens_final_quad >= 2 { assert!(res.is_ok()); } else if 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 ); } else if num_padding > 2 { assert_eq!(Err(DecodeError::InvalidPadding), res); } else { assert_eq!(Err(DecodeError::InvalidLength), res); } } } } } #[apply(all_engines)] fn decode_into_slice_fits_in_precisely_sized_slice(engine_wrapper: E) { let mut orig_data = Vec::new(); let mut encoded_data = String::new(); let mut decode_buf = Vec::new(); let input_len_range = distributions::Uniform::new(0, 1000); let mut rng = rngs::SmallRng::from_entropy(); for _ in 0..10_000 { orig_data.clear(); encoded_data.clear(); decode_buf.clear(); let input_len = input_len_range.sample(&mut rng); for _ in 0..input_len { orig_data.push(rng.gen()); } let engine = E::random(&mut rng); engine.encode_string(&orig_data, &mut encoded_data); assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len); decode_buf.resize(input_len, 0); // decode into the non-empty buf let decode_bytes_written = engine .decode_slice_unchecked(encoded_data.as_bytes(), &mut decode_buf[..]) .unwrap(); assert_eq!(orig_data.len(), decode_bytes_written); assert_eq!(orig_data, decode_buf); } } #[apply(all_engines)] fn decode_length_estimate_delta(engine_wrapper: E) { for engine in [E::standard(), E::standard_unpadded()] { for &padding in &[true, false] { for orig_len in 0..1000 { let encoded_len = encoded_len(orig_len, padding).unwrap(); let decoded_estimate = engine .internal_decoded_len_estimate(encoded_len) .decoded_len_estimate(); assert!(decoded_estimate >= orig_len); assert!( decoded_estimate - orig_len < 3, "estimate: {}, encoded: {}, orig: {}", decoded_estimate, encoded_len, orig_len ); } } } } /// 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>( engine: &E, orig_data: &mut Vec, encode_buf: &mut Vec, 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(orig_len, &mut encode_buf[base_encoded_len..]) } else { base_encoded_len }; assert_eq!(expected_encoded_len, enc_len_with_padding); (orig_len, base_encoded_len, enc_len_with_padding) } // fill to a random length fn fill_rand>( vec: &mut Vec, rng: &mut R, length_distribution: &D, ) -> usize { let len = length_distribution.sample(rng); for _ in 0..len { vec.push(rng.gen()); } len } fn fill_rand_len(vec: &mut Vec, rng: &mut R, len: usize) { for _ in 0..len { vec.push(rng.gen()); } } fn prefixed_data<'i, 'd>( input_with_prefix: &'i mut String, prefix_len: usize, data: &'d str, ) -> &'i str { input_with_prefix.truncate(prefix_len); input_with_prefix.push_str(data); input_with_prefix.as_str() } /// 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(rng: &mut R) -> Self::Engine; /// Return an engine configured with the specified alphabet and randomized config fn random_alphabet(rng: &mut R, alphabet: &Alphabet) -> Self::Engine; } struct GeneralPurposeWrapper {} impl EngineWrapper for GeneralPurposeWrapper { type Engine = general_purpose::GeneralPurpose; fn standard() -> Self::Engine { general_purpose::GeneralPurpose::new(&STANDARD, general_purpose::PAD) } fn standard_unpadded() -> Self::Engine { general_purpose::GeneralPurpose::new(&STANDARD, general_purpose::NO_PAD) } fn standard_with_pad_mode( encode_pad: bool, decode_pad_mode: DecodePaddingMode, ) -> Self::Engine { general_purpose::GeneralPurpose::new( &STANDARD, general_purpose::GeneralPurposeConfig::new() .with_encode_padding(encode_pad) .with_decode_padding_mode(decode_pad_mode), ) } fn standard_allow_trailing_bits() -> Self::Engine { general_purpose::GeneralPurpose::new( &STANDARD, general_purpose::GeneralPurposeConfig::new().with_decode_allow_trailing_bits(true), ) } fn random(rng: &mut R) -> Self::Engine { let alphabet = random_alphabet(rng); Self::random_alphabet(rng, alphabet) } fn random_alphabet(rng: &mut R, alphabet: &Alphabet) -> Self::Engine { general_purpose::GeneralPurpose::new(alphabet, random_config(rng)) } } struct NaiveWrapper {} impl EngineWrapper for NaiveWrapper { type Engine = naive::Naive; fn standard() -> Self::Engine { naive::Naive::new( &STANDARD, naive::NaiveConfig { encode_padding: true, decode_allow_trailing_bits: false, decode_padding_mode: DecodePaddingMode::RequireCanonical, }, ) } fn standard_unpadded() -> Self::Engine { naive::Naive::new( &STANDARD, naive::NaiveConfig { encode_padding: false, decode_allow_trailing_bits: false, decode_padding_mode: DecodePaddingMode::RequireNone, }, ) } fn standard_with_pad_mode( encode_pad: bool, decode_pad_mode: DecodePaddingMode, ) -> Self::Engine { naive::Naive::new( &STANDARD, naive::NaiveConfig { encode_padding: false, decode_allow_trailing_bits: false, decode_padding_mode: decode_pad_mode, }, ) } fn standard_allow_trailing_bits() -> Self::Engine { naive::Naive::new( &STANDARD, naive::NaiveConfig { encode_padding: true, decode_allow_trailing_bits: true, decode_padding_mode: DecodePaddingMode::RequireCanonical, }, ) } fn random(rng: &mut R) -> Self::Engine { let alphabet = random_alphabet(rng); Self::random_alphabet(rng, alphabet) } fn random_alphabet(rng: &mut R, alphabet: &Alphabet) -> Self::Engine { let mode = rng.gen(); let config = naive::NaiveConfig { encode_padding: match mode { DecodePaddingMode::Indifferent => rng.gen(), DecodePaddingMode::RequireCanonical => true, DecodePaddingMode::RequireNone => false, }, decode_allow_trailing_bits: rng.gen(), decode_padding_mode: mode, }; naive::Naive::new(alphabet, config) } } fn seeded_rng() -> impl rand::Rng { rngs::SmallRng::from_entropy() } fn all_pad_modes() -> Vec { vec![ DecodePaddingMode::Indifferent, DecodePaddingMode::RequireCanonical, DecodePaddingMode::RequireNone, ] } fn assert_all_suffixes_ok(engine: E, suffixes: Vec<&str>) { for num_prefix_quads in 0..256 { for &suffix in suffixes.iter() { let mut encoded = "AAAA".repeat(num_prefix_quads); encoded.push_str(suffix); let res = &engine.decode(&encoded); assert!(res.is_ok()); } } }