Add ckm aes key wrap pad#448
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements support for the CKM_AES_KEY_WRAP_PAD mechanism, including documentation, FIPS indicators, and OpenSSL backend integration. However, the current implementation incorrectly handles multi-part operations by performing padding and wrapping/unwrapping within the update methods instead of buffering data for the final methods. Additionally, the padding validation logic in the decryption path is inefficient due to heap allocations and should be replaced with a more performant constant-time check.
355bb86 to
41cc8bb
Compare
| let enc = ret_or_panic!(encrypt( | ||
| session, | ||
| handle, | ||
| data.as_slice(), | ||
| &mechanism, | ||
| )); | ||
| let dec = ret_or_panic!(decrypt( | ||
| session, | ||
| handle, | ||
| enc.as_slice(), | ||
| &mechanism, | ||
| )); |
There was a problem hiding this comment.
this tests the encrypt/decrypt opearations, but not the wrap/unwrap too?
| | CKF_DERIVE, | ||
| }, | ||
| FipsMechanism { | ||
| mechanism: CKM_AES_KEY_WRAP_PAD, |
There was a problem hiding this comment.
@simo5 is this mechanism OK from FIPS point of view?
41cc8bb to
8bbc7d8
Compare
Implement CKM_AES_KEY_WRAP_PAD as defined in PKCS#11 v3.0 section 2.16. This mechanism applies PKCS#7 padding (block size 8) to input before wrapping with the standard AES Key Wrap algorithm (RFC 3394 / NIST SP 800-38F Section 6.2), using the same OpenSSL cipher as CKM_AES_KEY_WRAP. - Register CKM_AES_KEY_WRAP_PAD with encrypt/decrypt/wrap/unwrap flags - Accept optional 8-byte IV parameter (same as CKM_AES_KEY_WRAP) - Buffer input in encrypt_update; apply PKCS#7 padding and encrypt in encrypt_final to correctly handle multi-part operations - Buffer ciphertext in decrypt_update; unwrap and strip PKCS#7 padding in decrypt_final using an allocation-free constant-time XOR loop - Update rustdoc for wrap/unwrap/encrypt_update/decrypt_update methods and add module-level SP 800-38F reference This is needed for FreeIPA migration from SoftHSMv2 to Kryoptic, where existing wrapped key material uses this mechanism. Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
8bbc7d8 to
0aaa71f
Compare
Register CKM_AES_KEY_WRAP_PAD in the FIPS approved mechanism table with encrypt/decrypt/wrap/unwrap operations permitted. Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Extend the existing wrap/unwrap mechanism loop to include CKM_AES_KEY_WRAP_PAD, and add a dedicated test for non-aligned input sizes (8, 9, 13, 15 bytes) to verify correct PKCS#7 padding round-trips. Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
0aaa71f to
395cb2f
Compare
|
Ok so the mechanism sounded familiar to me and the reason it was not implement is: |
|
@abbra any reason why you can't use CKM_AES_KEY_WRAP_PKCS7 instead ? |
|
Also PKCS#11 v3.0 section 2.16 does not exist ... and all the mechanisms are defined in section 6 anyway |
Nevermind we renamed those section in 3.1, it did exist in the old 3.0 ... |
|
That said I would accept a PR for CKM_AES_KEY_WRAP_PKCS7, but I am not so hot on accepting a PR for a deprecated mechanism. I would accept it after CKM_AES_KEY_WRAP_PKCS7 is implemented and only if the data stored using CKM_AES_KEY_WRAP_PAD can't be unwrapped with CKM_AES_KEY_WRAP_PKCS7. Note that CKM_AES_KEY_WRAP_PKCS7 should eb binary compatible with CKM_AES_KEY_WRAP_PAD so we shouldn't need to implement it, except perhaps as an alias... |
simo5
left a comment
There was a problem hiding this comment.
The main issue here seems that decryption depadding is not constant time.
| 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); |
There was a problem hiding this comment.
we should probably rename AES_KWP_BLOCK to something like AES_KEYWRAP_BLOCK or similar if we start using it for multiple mechanisms.
| CKM_AES_KEY_WRAP_PAD => { | ||
| let buf_len = self.buffer.len(); | ||
| let pad_len = AES_KWP_BLOCK - (buf_len % AES_KWP_BLOCK); | ||
| self.buffer.resize(buf_len + pad_len, pad_len as u8); |
There was a problem hiding this comment.
pad_len as u8 is unsafe, use u8::try_feom() and handle the error
| Err(CKR_DEVICE_ERROR) | ||
| }, | ||
| )?; | ||
| zeromem(self.buffer.as_mut_slice()); |
There was a problem hiding this comment.
This should not be necessary, key wrap is single-shot only and we zeromem the buffer on deallocation
| } | ||
| } | ||
| CKM_AES_KEY_WRAP_PAD => { | ||
| self.buffer.extend_from_slice(cipher); |
There was a problem hiding this comment.
if the buffer length is not zero before the extend this should fail because wrapping is a one shot operation.
| } | ||
| CKM_AES_KEY_WRAP_PAD => { | ||
| self.buffer.extend_from_slice(cipher); | ||
| cipher_offset = cipher_end; |
There was a problem hiding this comment.
this shouldn't even happen, if padding is deferred to _final then probably this mechanism should return w/o going though openssl encryption here.
| if outlen < AES_KWP_BLOCK { | ||
| return Err(CKR_ENCRYPTED_DATA_INVALID)?; | ||
| } | ||
| let pad_byte = plain[outlen - 1]; |
There was a problem hiding this comment.
The code here and below is not constant time, and will easily reveal the last byte resulting from decryption.
| } | ||
| } | ||
| CKM_AES_KEY_WRAP_PAD => { | ||
| ((data_len / AES_KWP_BLOCK) + 2) * AES_KWP_BLOCK |
There was a problem hiding this comment.
A coment that explains why +2 would be useful here
Implement CKM_AES_KEY_WRAP_PAD as defined in PKCS#11 v3.0 section 2.16. This mechanism applies PKCS#7 padding (block size 8) to input data before wrapping with the standard AES Key Wrap algorithm (RFC 3394 / NIST SP 800-38F Section 6.2). It uses the same OpenSSL cipher as CKM_AES_KEY_WRAP (AES-NNN-WRAP), with padding handled in the Kryoptic layer.
This is needed for FreeIPA migration from SoftHSMv2 to Kryoptic, where existing wrapped key material uses this mechanism.
Assisted-by: Claude Code, using Opus 4.6 model
Checklist
Reviewer's checklist: