Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ All notable changes to this project should be documented in this file.

### What Changed

--
* Added support for CKM_AES_KEY_WRAP_PAD mechanism (PKCS#7 padding + AES Key
Wrap per PKCS#11 v3.0 section 2.16)

## [1.5.0]
## 2026-03-04
Expand Down
10 changes: 9 additions & 1 deletion src/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
//! _Recommendation for Block Cipher Modes of Operation: Methods and
//! Techniques_, [SP 800-38D](https://doi.org/10.6028/NIST.SP.800-38D):
//! _Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode
//! (GCM) and GMAC_).
//! (GCM) and GMAC_, [SP 800-38F](https://doi.org/10.6028/NIST.SP.800-38F):
//! _Recommendation for Block Cipher Modes of Operation: Methods for
//! Key Wrapping_).

use std::fmt::Debug;
use std::sync::LazyLock;
Expand Down Expand Up @@ -320,6 +322,9 @@ impl Mechanism for AesMechanism {

/// Implements the AES Key wrap operation (Wrap)
///
/// Supports CKM_AES_KEY_WRAP (RFC 3394), CKM_AES_KEY_WRAP_PAD
/// (PKCS#7 padding + RFC 3394), and CKM_AES_KEY_WRAP_KWP (RFC 5649).
///
/// [AES Key Wrap](https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/os/pkcs11-spec-v3.1-os.html#_Toc111203510)
/// (Version 3.1)

Expand All @@ -344,6 +349,9 @@ impl Mechanism for AesMechanism {

/// Implements the AES Key wrap operation (Unwrap)
///
/// Supports CKM_AES_KEY_WRAP (RFC 3394), CKM_AES_KEY_WRAP_PAD
/// (PKCS#7 padding + RFC 3394), and CKM_AES_KEY_WRAP_KWP (RFC 5649).
///
/// [AES Key Wrap](https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/os/pkcs11-spec-v3.1-os.html#_Toc111203510)
/// (Version 3.1)

Expand Down
14 changes: 13 additions & 1 deletion src/fips/indicators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ struct FipsMechanism {
/// Struct that holds FIPS properties for keys and mechanisms
struct FipsChecks {
keys: [FipsKeyType; 17],
mechs: [FipsMechanism; 93],
mechs: [FipsMechanism; 94],
}

/// A constant instantiation of FIPS properties with a list
Expand Down Expand Up @@ -638,6 +638,18 @@ const FIPS_CHECKS: FipsChecks = FipsChecks {
| CKF_UNWRAP
| CKF_DERIVE,
},
FipsMechanism {
mechanism: CKM_AES_KEY_WRAP_PAD,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simo5 is this mechanism OK from FIPS point of view?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No

operations: CKF_ENCRYPT | CKF_DECRYPT | CKF_WRAP | CKF_UNWRAP,
restrictions: [restrict!(KRY_UNSPEC), restrict!()],
genflags: CKF_SIGN
| CKF_VERIFY
| CKF_ENCRYPT
| CKF_DECRYPT
| CKF_WRAP
| CKF_UNWRAP
| CKF_DERIVE,
},
FipsMechanism {
mechanism: CKM_AES_KEY_WRAP_KWP,
operations: CKF_ENCRYPT | CKF_DECRYPT | CKF_WRAP | CKF_UNWRAP,
Expand Down
97 changes: 91 additions & 6 deletions src/ossl/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ impl AesOperation {
CKM_AES_CTR,
CKM_AES_CTS,
CKM_AES_KEY_WRAP,
CKM_AES_KEY_WRAP_PAD,
CKM_AES_KEY_WRAP_KWP,
] {
mechs.add_mechanism(*ckm, &(*AES_MECHS)[0]);
Expand Down Expand Up @@ -375,7 +376,7 @@ impl AesOperation {
taglen: 0,
})
}
CKM_AES_KEY_WRAP => {
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_PAD => {
let iv = match mech.ulParameterLen {
0 => AesIvData::none()?,
8 => AesIvData::simple(bytes_to_vec(
Expand Down Expand Up @@ -464,7 +465,7 @@ impl AesOperation {
CKM_AES_CFB128 => EncAlg::AesCfb128(size),
#[cfg(not(feature = "fips"))]
CKM_AES_OFB => EncAlg::AesOfb(size),
CKM_AES_KEY_WRAP => EncAlg::AesWrap(size),
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_PAD => EncAlg::AesWrap(size),
CKM_AES_KEY_WRAP_KWP => EncAlg::AesWrapPad(size),
_ => return Err(CKR_MECHANISM_INVALID)?,
})
Expand Down Expand Up @@ -670,6 +671,10 @@ impl AesOperation {
}

/// Instantiates a new AES Key-Wrap Operation
///
/// Supports CKM_AES_KEY_WRAP, CKM_AES_KEY_WRAP_PAD, and
/// CKM_AES_KEY_WRAP_KWP. For CKM_AES_KEY_WRAP_PAD, PKCS#7
/// padding is applied during encryption via [AesOperation::encrypt].
pub fn wrap(
mech: &CK_MECHANISM,
wrapping_key: &Object,
Expand Down Expand Up @@ -719,6 +724,10 @@ impl AesOperation {
}

/// Instantiates a new AES Key-Unwrap Operation
///
/// Supports CKM_AES_KEY_WRAP, CKM_AES_KEY_WRAP_PAD, and
/// CKM_AES_KEY_WRAP_KWP. For CKM_AES_KEY_WRAP_PAD, PKCS#7
/// padding is stripped during decryption via [AesOperation::decrypt].
pub fn unwrap(
mech: &CK_MECHANISM,
wrapping_key: &Object,
Expand Down Expand Up @@ -1196,7 +1205,10 @@ impl Encryption for AesOperation {
}

/// Calls the underlying OpenSSL function to encrypt the plaintext buffer
/// provided, according to the configured mode
/// provided, according to the configured mode.
///
/// For CKM_AES_KEY_WRAP_PAD, input is buffered and the actual PKCS#7
/// padding and encryption is deferred to [AesOperation::encrypt_final].
fn encrypt_update(
&mut self,
plain: &[u8],
Expand Down Expand Up @@ -1255,6 +1267,9 @@ impl Encryption for AesOperation {
return Err(self.op_err(CKR_DATA_LEN_RANGE));
}
}
CKM_AES_KEY_WRAP_PAD => {
outlen = 0;
}
_ => (),
}
if cipher.len() < outlen {
Expand Down Expand Up @@ -1344,6 +1359,10 @@ impl Encryption for AesOperation {
plain_offset = plain_end;
}
}
CKM_AES_KEY_WRAP_PAD => {
self.buffer.extend_from_slice(plain);
plain_offset = plain_end;
}
Comment thread
abbra marked this conversation as resolved.
_ => (),
}

Expand Down Expand Up @@ -1453,6 +1472,19 @@ impl Encryption for AesOperation {
}
}
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_KWP => (),
CKM_AES_KEY_WRAP_PAD => {
let buf_len = self.buffer.len();
let pad_len = AES_KWP_BLOCK - (buf_len % AES_KWP_BLOCK);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably rename AES_KWP_BLOCK to something like AES_KEYWRAP_BLOCK or similar if we start using it for multiple mechanisms.

self.buffer.resize(buf_len + pad_len, pad_len as u8);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pad_len as u8 is unsafe, use u8::try_feom() and handle the error

outlen = ctx.update(self.buffer.as_slice(), cipher).or_else(
|_| {
self.finalized = true;
Err(CKR_DEVICE_ERROR)
},
)?;
zeromem(self.buffer.as_mut_slice());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be necessary, key wrap is single-shot only and we zeromem the buffer on deallocation

self.buffer.clear();
}
_ => {
self.finalized = true;
return Err(CKR_GENERAL_ERROR)?;
Expand Down Expand Up @@ -1505,6 +1537,9 @@ impl Encryption for AesOperation {
data_len + AES_KWP_BLOCK
}
}
CKM_AES_KEY_WRAP_PAD => {
((data_len / AES_KWP_BLOCK) + 2) * AES_KWP_BLOCK

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A coment that explains why +2 would be useful here

}
CKM_AES_KEY_WRAP_KWP => {
((data_len + AES_BLOCK_SIZE - 1) / AES_KWP_BLOCK)
* AES_KWP_BLOCK
Expand Down Expand Up @@ -1535,6 +1570,7 @@ impl Encryption for AesOperation {
data_len + AES_KWP_BLOCK
}
}
CKM_AES_KEY_WRAP_PAD => 0,
CKM_AES_KEY_WRAP_KWP => {
((data_len + AES_BLOCK_SIZE - 1) / AES_KWP_BLOCK)
* AES_KWP_BLOCK
Expand Down Expand Up @@ -1563,7 +1599,11 @@ impl Decryption for AesOperation {
}

/// Calls the underlying OpenSSL function to decrypt the ciphertext buffer
/// provided, according to the configured mode
/// provided, according to the configured mode.
///
/// For CKM_AES_KEY_WRAP_PAD, ciphertext is buffered and the actual
/// unwrapping and PKCS#7 padding removal is deferred to
/// [AesOperation::decrypt_final].
fn decrypt_update(
&mut self,
cipher: &[u8],
Expand Down Expand Up @@ -1593,7 +1633,7 @@ impl Decryption for AesOperation {
return Err(self.op_err(CKR_DATA_LEN_RANGE));
}
}
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_KWP => {
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_PAD | CKM_AES_KEY_WRAP_KWP => {
if cipher.len() % AES_KWP_BLOCK != 0 {
return Err(self.op_err(CKR_DATA_LEN_RANGE));
}
Expand Down Expand Up @@ -1647,6 +1687,7 @@ impl Decryption for AesOperation {
* AES_BLOCK_SIZE
}
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_KWP => cipher.len(),
CKM_AES_KEY_WRAP_PAD => 0,
_ => cipher.len(),
};
if plain.len() < outlen {
Expand Down Expand Up @@ -1798,6 +1839,10 @@ impl Decryption for AesOperation {
cipher_offset = cipher_end;
}
}
CKM_AES_KEY_WRAP_PAD => {
self.buffer.extend_from_slice(cipher);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the buffer length is not zero before the extend this should fail because wrapping is a one shot operation.

cipher_offset = cipher_end;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't even happen, if padding is deferred to _final then probably this mechanism should return w/o going though openssl encryption here.

}
_ => (),
}

Expand Down Expand Up @@ -1899,6 +1944,44 @@ impl Decryption for AesOperation {
#[cfg(not(feature = "fips"))]
CKM_AES_CFB8 | CKM_AES_CFB1 | CKM_AES_CFB128 | CKM_AES_OFB => (),
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_KWP => (),
CKM_AES_KEY_WRAP_PAD => {
if self.buffer.len() == 0 {
return Err(CKR_ENCRYPTED_DATA_LEN_RANGE)?;
}
outlen = ctx.update(self.buffer.as_slice(), plain).or_else(
|_| {
self.finalized = true;
Err(CKR_DEVICE_ERROR)
},
)?;
zeromem(self.buffer.as_mut_slice());
self.buffer.clear();

if outlen < AES_KWP_BLOCK {
return Err(CKR_ENCRYPTED_DATA_INVALID)?;
}
let pad_byte = plain[outlen - 1];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code here and below is not constant time, and will easily reveal the last byte resulting from decryption.

let pad_len = pad_byte as usize;
if pad_len == 0 || pad_len > AES_KWP_BLOCK {
zeromem(&mut plain[..outlen]);
return Err(CKR_ENCRYPTED_DATA_INVALID)?;
}
if pad_len > outlen {
zeromem(&mut plain[..outlen]);
return Err(CKR_ENCRYPTED_DATA_INVALID)?;
}
let pad_start = outlen - pad_len;
let mut bad = 0u8;
for b in &plain[pad_start..outlen] {
bad |= b ^ pad_byte;
}
if bad != 0 {
zeromem(&mut plain[..outlen]);
return Err(CKR_ENCRYPTED_DATA_INVALID)?;
}
zeromem(&mut plain[pad_start..outlen]);
outlen = pad_start;
}
_ => return Err(CKR_GENERAL_ERROR)?,
}

Expand Down Expand Up @@ -1947,7 +2030,8 @@ impl Decryption for AesOperation {
}
self.buffer.len() + data_len
}
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_KWP => {
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_PAD
| CKM_AES_KEY_WRAP_KWP => {
if data_len % AES_KWP_BLOCK != 0 {
return Err(self.op_err(CKR_ENCRYPTED_DATA_LEN_RANGE));
}
Expand Down Expand Up @@ -1995,6 +2079,7 @@ impl Decryption for AesOperation {
(data_len / AES_KWP_BLOCK) * AES_KWP_BLOCK
}
}
CKM_AES_KEY_WRAP_PAD => 0,
_ => return Err(self.op_err(CKR_GENERAL_ERROR)),
}
};
Expand Down
Loading
Loading