-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Module for Extracting PKCS#7 Padded Data (#7)
* unpadding iter adapter * slice unpadder * continue UnpadByValue
- Loading branch information
1 parent
bc550ee
commit f5d0a0e
Showing
5 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
mod tests; | ||
pub mod unpad; | ||
|
||
use openssl::symm; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,108 @@ | ||
#![cfg(test)] | ||
|
||
use super::*; | ||
|
||
mod test_get_padding_length { | ||
use super::*; | ||
use unpad::get_padding_length; | ||
|
||
/// Tests various conditions related to the padding length, including valid and invalid scenarios. | ||
#[test] | ||
fn padding_length_variations() { | ||
// Valid PKCS#7 padding | ||
let mut valid_padding = vec![1, 2, 3, 4, 4, 4, 4]; | ||
assert_eq!(get_padding_length(&valid_padding), Some(4)); | ||
|
||
// Data with single byte padding | ||
valid_padding = vec![1, 1]; | ||
assert_eq!(get_padding_length(&valid_padding), Some(1)); | ||
|
||
// Data consists only of padding bytes | ||
valid_padding = vec![2, 2]; | ||
assert_eq!(get_padding_length(&valid_padding), Some(2)); | ||
|
||
// Larger padding to ensure it scales correctly | ||
valid_padding = vec![0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8]; | ||
assert_eq!(get_padding_length(&valid_padding), Some(8)); | ||
} | ||
|
||
/// Tests invalid padding scenarios where the padding is not according to PKCS#7 rules. | ||
#[test] | ||
fn invalid_padding_scenarios() { | ||
// Data without explicit padding | ||
let no_padding = vec![1, 2, 3, 4]; | ||
assert_eq!(get_padding_length(&no_padding), None); | ||
|
||
// Last byte is 0, which is invalid in PKCS#7 padding | ||
let padding_length_zero = vec![1, 2, 3, 4, 0]; | ||
assert_eq!(get_padding_length(&padding_length_zero), None); | ||
|
||
// Padding bytes do not match the last byte's value | ||
let mismatched_values = vec![1, 2, 3, 4, 3, 2]; | ||
assert_eq!(get_padding_length(&mismatched_values), None); | ||
|
||
// Padding byte value suggests more padding than data, which is invalid | ||
let longer_than_data = vec![5]; | ||
assert_eq!(get_padding_length(&longer_than_data), None); | ||
|
||
// Empty data slice should return None | ||
let empty_data: Vec<u8> = vec![]; | ||
assert_eq!(get_padding_length(&empty_data), None); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test_unpad_slice { | ||
use super::*; | ||
use unpad::unpad_slice; | ||
|
||
#[test] | ||
fn unpad_mixed() { | ||
let padded_data = vec![1, 2, 3, 4, 4, 4, 4, 4]; | ||
|
||
let unpadded = unpad_slice(&padded_data, Some(4)).unwrap(); | ||
assert_eq!(unpadded, &[1, 2, 3, 4]); | ||
|
||
// flexible block size | ||
let unpadded = unpad_slice(&padded_data, None).unwrap(); | ||
assert_eq!(unpadded, &[1, 2, 3, 4]); | ||
|
||
// incorrect block size | ||
assert!(unpad_slice(&padded_data, Some(3)).is_none()); | ||
} | ||
|
||
#[test] | ||
fn unpad_no_padding_when_optional() { | ||
let data_without_padding = vec![1, 2, 3, 4]; | ||
assert!(unpad_slice(&data_without_padding, None).is_none()); | ||
} | ||
|
||
#[test] | ||
fn unpad_invalid_padding() { | ||
let invalid_padding = vec![1, 2, 3, 4, 5]; | ||
assert!(unpad_slice(&invalid_padding, Some(5)).is_none()); | ||
} | ||
|
||
#[test] | ||
fn unpad_empty_data() { | ||
let empty_data = vec![]; | ||
assert!(unpad_slice(&empty_data, Some(8)).is_none()); | ||
} | ||
|
||
#[test] | ||
fn unpad_padding_longer_than_data() { | ||
let invalid_data = vec![5]; | ||
assert!(unpad_slice(&invalid_data, Some(1)).is_none()); | ||
} | ||
|
||
#[test] | ||
fn unpad_big_block_size() { | ||
let mut padded_data = vec![5; 255]; // 255 bytes in total, last 5 are padding | ||
|
||
let unpadded = unpad_slice(&padded_data, Some(255)).unwrap(); | ||
assert_eq!(unpadded.len(), 250); | ||
|
||
padded_data.push(5); // 256 bytes in total, last 5 are padding | ||
assert!(unpad_slice(&padded_data, Some(256)).is_none()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
//! ## Overview of PKCS#7 Padding | ||
//! | ||
//! PKCS#7 padding is applicable to data of any length and is used to ensure that the length of the | ||
//! data is a multiple of a specific block size. The value of each padding byte is the total number | ||
//! of padding bytes added. For instance, if the block size is 16 bytes and the data is 13 bytes long, | ||
//! three bytes of padding will be added, each with the value `03`. | ||
//! | ||
//! For our purposes, the block size will always be 16 bytes (or 128 bits) because that is the | ||
//! blocksize of AES-192. | ||
use std::iter::{repeat, FusedIterator}; | ||
|
||
/// This function inspects the padding and, | ||
/// if valid, returns a subslice of the original data without the padding. | ||
/// | ||
/// # Parameters | ||
/// | ||
/// * `padded`: The data slice that may contain PKCS#7 padding. | ||
/// * `block_size`: An optional block size used for padding. If specified, the function checks whether | ||
/// the padding is valid for this specific block size: | ||
/// - If `block_size` is `Some(size)`, the function returns a subslice only if the input is correctly | ||
/// padded according to PKCS#7 rules for the given block size. This includes ensuring that the `padded` | ||
/// slice's length is a multiple of `block_size` and that the padding length does not exceed `block_size`. | ||
/// - If `block_size` is `None`, the function is more lenient and only checks the padding's validity | ||
/// according to PKCS#7 rules, without enforcing a specific block size. This can be useful when the | ||
/// block size is unknown or variable. | ||
/// | ||
/// # Returns | ||
/// | ||
/// * `Some(&[u8])`: A subslice of the original data without PKCS#7 padding if the padding is valid. | ||
/// * `None`: If the padding is invalid, the block size is not respected, or other conditions prevent | ||
/// unpadded data from being safely returned. This includes scenarios where the padding length is zero, | ||
/// exceeds the block size, or the padding bytes do not conform to PKCS#7 rules. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use libproj3::decryption::unpad::unpad_slice; | ||
/// | ||
/// let padded_data = [1, 2, 3, 4, 4, 4, 4, 4]; | ||
/// let unpadded = unpad_slice(&padded_data, Some(8)).unwrap(); | ||
/// assert_eq!(unpadded, &[1, 2, 3, 4]); | ||
/// | ||
/// let invalid_padding = [1, 2, 3, 4, 5]; | ||
/// assert!(unpad_slice(&invalid_padding, Some(8)).is_none()); | ||
/// ``` | ||
pub fn unpad_slice(padded: &[u8], block_size: Option<usize>) -> Option<&[u8]> { | ||
let padding_len = get_padding_length(padded)?; | ||
|
||
if let Some(b) = block_size { | ||
if b > u8::MAX as _ || padded.len() % b != 0 || padding_len > b { | ||
return None; | ||
} | ||
} | ||
|
||
let (subslice, _) = padded.split_at(padded.len() - padding_len); | ||
Some(subslice) | ||
} | ||
|
||
/// This iterator adapter is to allow unpadding of encryption output without keeping the | ||
/// whole output in memory at once. | ||
/// | ||
/// I have no idea if this works. | ||
pub struct UnpadByValue<I, const B: usize> | ||
where | ||
I: ExactSizeIterator<Item = [u8; B]> + FusedIterator, | ||
{ | ||
inner: I, | ||
current_block: [u8; B], | ||
blocks_left: usize, | ||
index: usize, | ||
} | ||
|
||
impl<I, const B: usize> UnpadByValue<I, B> | ||
where | ||
I: ExactSizeIterator<Item = [u8; B]> + FusedIterator, | ||
{ | ||
pub fn new(iter: I) -> Self { | ||
let len = iter.len(); | ||
Self { | ||
inner: iter, | ||
current_block: [0; B], | ||
blocks_left: len, | ||
index: B, | ||
} | ||
} | ||
} | ||
|
||
impl<I, const B: usize> Iterator for UnpadByValue<I, B> | ||
where | ||
I: ExactSizeIterator<Item = [u8; B]> + FusedIterator, | ||
{ | ||
type Item = u8; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if self.index >= B { | ||
self.index = 0; | ||
self.current_block = self.inner.next()?; | ||
self.blocks_left -= 1; | ||
debug_assert_eq!(self.blocks_left, self.inner.len()); | ||
} | ||
if self.blocks_left == 0 { | ||
if self.index >= B - get_padding_length(&self.current_block).unwrap() { | ||
self.index = B; | ||
return None; | ||
} | ||
} | ||
let ret = self.current_block[self.index]; | ||
self.index += 1; | ||
Some(ret) | ||
} | ||
|
||
fn size_hint(&self) -> (usize, Option<usize>) { | ||
if self.blocks_left == 0 { | ||
let padding_len = get_padding_length(&self.current_block).unwrap(); | ||
let len_left = (B - self.index).saturating_sub(padding_len); | ||
(len_left, Some(len_left)) | ||
} else { | ||
let lo = match (self.blocks_left - 1).checked_mul(B) { | ||
Some(x) => x.checked_add(B - self.index), | ||
None => None, | ||
}; | ||
match lo { | ||
Some(x) => (x, x.checked_add(B - 1)), | ||
None => (usize::MAX, None), | ||
} | ||
} | ||
} | ||
} | ||
|
||
pub trait UnpadByValueIterator<const B: usize>: | ||
ExactSizeIterator<Item = [u8; B]> + FusedIterator + Sized | ||
{ | ||
/// Creates a new iterator of bytes from a padded iterator of blocks. | ||
fn unpad(self) -> UnpadByValue<Self, B> { | ||
UnpadByValue::new(self) | ||
} | ||
} | ||
|
||
impl<I, const B: usize> FusedIterator for UnpadByValue<I, B> where | ||
I: ExactSizeIterator<Item = [u8; B]> + FusedIterator | ||
{ | ||
} | ||
|
||
/// Calculates the length of PKCS#7 padding in a given data slice. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `in_slice` - A slice of bytes that potentially contains PKCS#7 padding. | ||
/// | ||
/// # Returns | ||
/// | ||
/// * `Some(usize)` - The length of the padding if the input slice is correctly padded | ||
/// according to PKCS#7 rules. The padding length is determined based on the value | ||
/// of the last byte in the slice, and the function verifies that all padding bytes | ||
/// have the same value. | ||
/// | ||
/// * `None` - If the input slice is not correctly padded according to PKCS#7 rules. | ||
/// This includes cases where the last byte is 0 (indicating an invalid padding length), | ||
/// or the padding bytes do not all have the same value as required by PKCS#7. | ||
pub(super) fn get_padding_length(in_slice: &[u8]) -> Option<usize> { | ||
let last = *in_slice.last()?; | ||
println!("{}", last); | ||
if last == 0 | ||
|| in_slice | ||
.iter() | ||
.rev() | ||
.take(last as _) | ||
.ne(repeat(&last).take(last as _)) | ||
{ | ||
return None; | ||
} | ||
|
||
Some(last as _) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
#![cfg(test)] | ||
|
||
#[allow(unused_imports)] | ||
use super::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
#![cfg(test)] | ||
|
||
#[allow(unused_imports)] | ||
use super::*; |