Skip to content

Commit ceb1151

Browse files
tstennerskmcgrail
andauthored
Add support for decrypting ANSI X9.23 / ISO 10126-padded AES (#847)
* Add support for decrypting ANSI X9.23/ISO 10126-padded AES * add unit test for unpadding iso10126 * Add ISO10126 specific unit test * Add full-block padding KAT * Change cast to fix clippy warning --------- Co-authored-by: Sean McGrail <[email protected]>
1 parent 78258d2 commit ceb1151

File tree

1 file changed

+73
-6
lines changed

1 file changed

+73
-6
lines changed

aws-lc-rs/src/cipher/padded.rs

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use core::fmt::Debug;
1313
#[non_exhaustive]
1414
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1515
pub(crate) enum PaddingStrategy {
16+
/// ISO 10126 padding. For compatibility purposes only. Applies non-random PKCS7 padding.
17+
ISO10126,
1618
/// PKCS#7 Padding. ([See RFC 5652](https://datatracker.ietf.org/doc/html/rfc5652#section-6.3))
1719
PKCS7,
1820
}
@@ -23,7 +25,8 @@ impl PaddingStrategy {
2325
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
2426
{
2527
match self {
26-
PaddingStrategy::PKCS7 => {
28+
// PKCS7 padding can be unpadded as ISO 10126 padding
29+
PaddingStrategy::ISO10126 | PaddingStrategy::PKCS7 => {
2730
let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN];
2831

2932
let in_out_len = in_out.as_mut().len();
@@ -40,14 +43,23 @@ impl PaddingStrategy {
4043
}
4144

4245
fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> {
46+
if in_out.is_empty() || in_out.len() < block_len {
47+
return Err(Unspecified);
48+
}
4349
match self {
44-
PaddingStrategy::PKCS7 => {
45-
let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
46-
47-
if in_out.is_empty() || in_out.len() < block_len {
50+
PaddingStrategy::ISO10126 => {
51+
let padding: u8 = in_out[in_out.len() - 1];
52+
if padding == 0 || padding as usize > block_len {
4853
return Err(Unspecified);
4954
}
5055

56+
// ISO 10126 padding is a random padding scheme, so we cannot verify the padding bytes
57+
let final_len = in_out.len() - padding as usize;
58+
Ok(&mut in_out[0..final_len])
59+
}
60+
PaddingStrategy::PKCS7 => {
61+
let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
62+
5163
let padding: u8 = in_out[in_out.len() - 1];
5264
if padding == 0 || padding > block_size {
5365
return Err(Unspecified);
@@ -208,6 +220,23 @@ impl PaddedBlockDecryptingKey {
208220
Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
209221
}
210222

223+
/// Constructs a new `PaddedBlockDecryptingKey` cipher with chaining block cipher (CBC) mode.
224+
/// Decrypted data is unpadded following the ISO 10126 scheme
225+
/// (compatible with PKCS#7 and ANSI X.923).
226+
///
227+
/// Offered for computability purposes only.
228+
///
229+
// # FIPS
230+
// Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
231+
// * `AES_128`
232+
// * `AES_256`
233+
//
234+
/// # Errors
235+
/// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`.
236+
pub fn cbc_iso10126(key: UnboundCipherKey) -> Result<Self, Unspecified> {
237+
Self::new(key, OperatingMode::CBC, PaddingStrategy::ISO10126)
238+
}
239+
211240
/// Constructs a new `PaddedBlockDecryptingKey` cipher with electronic code book (ECB) mode.
212241
/// Decrypted data is unpadded following the PKCS#7 scheme.
213242
///
@@ -330,6 +359,16 @@ mod tests {
330359
assert_eq!(input.as_slice(), plaintext);
331360
}
332361

362+
#[test]
363+
fn test_unpad_iso10126() {
364+
let mut input = from_hex("01020304050607fedcba9805").unwrap();
365+
let padding = PaddingStrategy::ISO10126;
366+
let block_len = 8;
367+
368+
let unpadded = padding.remove_padding(block_len, &mut input).unwrap();
369+
assert_eq!(unpadded, &mut [1, 2, 3, 4, 5, 6, 7]);
370+
}
371+
333372
#[test]
334373
fn test_aes_128_cbc() {
335374
let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap();
@@ -390,7 +429,13 @@ mod tests {
390429

391430
let context = encrypting_key.less_safe_encrypt(&mut in_out, ec).unwrap();
392431

393-
assert_eq!(expected_ciphertext, in_out);
432+
if ($padding == PaddingStrategy::ISO10126) {
433+
// This padding scheme is technically non-deterministic in nature if the padding is more then one
434+
// byte. So just validate the input length of in_out is no longer the plaintext.
435+
assert_ne!(input, in_out[..input.len()]);
436+
} else {
437+
assert_eq!(expected_ciphertext, in_out);
438+
}
394439

395440
let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap();
396441
let decrypting_key =
@@ -435,6 +480,28 @@ mod tests {
435480
"ad96993f248bd6a29760ec7ccda95ee1"
436481
);
437482

483+
padded_cipher_kat!(
484+
test_openssl_aes_128_cbc_iso10126_15_bytes,
485+
&AES_128,
486+
OperatingMode::CBC,
487+
PaddingStrategy::ISO10126,
488+
"053304bb3899e1d99db9d29343ea782d",
489+
"b5313560244a4822c46c2a0c9d0cf7fd",
490+
"a3e4c990356c01f320043c3d8d6f43",
491+
"ad96993f248bd6a29760ec7ccda95ee1"
492+
);
493+
494+
padded_cipher_kat!(
495+
test_openssl_aes_128_cbc_iso10126_16_bytes,
496+
&AES_128,
497+
OperatingMode::CBC,
498+
PaddingStrategy::ISO10126,
499+
"053304bb3899e1d99db9d29343ea782d",
500+
"b83452fc9c80215a6ecdc505b5154c90",
501+
"736e65616b7920726163636f6f6e7321",
502+
"44563399c6bb2133e013161dc5bd4fa8ce83ef997ddb04bbbbe3632b68e9cde0"
503+
);
504+
438505
padded_cipher_kat!(
439506
test_openssl_aes_128_cbc_16_bytes,
440507
&AES_128,

0 commit comments

Comments
 (0)