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: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions pkcs12/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ const-oid = { version = "0.10", features = ["db"], default-features = false }
cms = { version = "=0.3.0-pre.2", default-features = false }

# optional dependencies
digest = { version = "0.11", features = ["alloc"], optional = true }
# "zeroize" feature enables zeroing of the digest's internal block buffer, which
# holds input fragments of the id||salt||password data between Update calls.
digest = { version = "0.11", features = ["alloc", "zeroize"], optional = true }
# digest/zeroize does not propagate hybrid-array/zeroize, so Array<u8, OutputSize>: Zeroize
# is not satisfied without this direct dep. If digest ever adds hybrid-array/zeroize to
# its own zeroize feature, this dep can be removed.
hybrid-array = { version = "0.4", features = ["zeroize"], optional = true }
zeroize = { version = "1.8.1", optional = true, default-features = false }

[dev-dependencies]
Expand All @@ -35,7 +41,7 @@ whirlpool = "0.11"

[features]
default = ["pem"]
kdf = ["dep:digest", "zeroize/alloc"]
kdf = ["dep:digest", "dep:hybrid-array", "zeroize/alloc"]
pem = ["der/pem", "x509-cert/pem"]

[package.metadata.docs.rs]
Expand Down
41 changes: 30 additions & 11 deletions pkcs12/src/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub enum Pkcs12KeyType {
/// Derives `key` of type `id` from `pass` and `salt` with length `key_len` using `rounds`
/// iterations of the algorithm
/// `pass` must be a utf8 string.
///
/// The returned key is wrapped in [`Zeroizing`] and will be erased from memory when dropped.
/// ```rust
/// let key = pkcs12::kdf::derive_key_utf8::<sha2::Sha256>("top-secret", &[0x1, 0x2, 0x3, 0x4],
/// pkcs12::kdf::Pkcs12KeyType::EncryptionKey, 1000, 32);
Expand All @@ -44,22 +46,28 @@ pub fn derive_key_utf8<D>(
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> der::Result<Vec<u8>>
) -> der::Result<Zeroizing<Vec<u8>>>
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
let password_bmp = BmpString::from_utf8(password)?;
Ok(derive_key_bmp::<D>(password_bmp, salt, id, rounds, key_len))
}

/// Derive
/// Derives a key from a BMP-encoded password and salt using the PKCS#12 KDF.
///
/// The password must already be encoded as a [`BmpString`]; use [`derive_key_utf8`]
/// to pass a UTF-8 `&str` directly. The null terminator required by RFC 7292
/// Appendix B is appended automatically before calling [`derive_key`].
///
/// The returned key is wrapped in [`Zeroizing`] and will be erased from memory when dropped.
pub fn derive_key_bmp<D>(
password: BmpString,
salt: &[u8],
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> Vec<u8>
) -> Zeroizing<Vec<u8>>
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
Expand All @@ -75,6 +83,8 @@ where
/// iterations of the algorithm
/// `pass` must be a unicode (utf16) byte array in big endian order without order mark and with two
/// terminating zero bytes.
///
/// The returned key is wrapped in [`Zeroizing`] and will be erased from memory when dropped.
/// ```rust
/// let key = pkcs12::kdf::derive_key_utf8::<sha2::Sha256>("top-secret", &[0x1, 0x2, 0x3, 0x4],
/// pkcs12::kdf::Pkcs12KeyType::EncryptionKey, 1000, 32);
Expand All @@ -85,7 +95,7 @@ pub fn derive_key<D>(
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> Vec<u8>
) -> Zeroizing<Vec<u8>>
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
Expand All @@ -108,7 +118,10 @@ where
let slen = block_size * salt.len().div_ceil(block_size);
let plen = block_size * pass.len().div_ceil(block_size);
let ilen = slen + plen;
let mut init_key = vec![0u8; ilen];
// Zeroizing ensures the S||P buffer (which contains password material) is
// wiped on drop, including on panic unwind. The explicit init_key.zeroize()
// below is retained for eager zeroing at function exit.
let mut init_key = Zeroizing::new(vec![0u8; ilen]);
// 2. Concatenate copies of the salt together to create a string S of
// length v(ceiling(s/v)) bits (the final copy of the salt may be
// truncated to create S). Note that if the salt is the empty
Expand All @@ -130,7 +143,8 @@ where

let mut m = key_len;
let mut n = 0;
let mut out = vec![0u8; key_len];
// Zeroizing ensures key material in `out` is wiped when the caller drops it.
let mut out = Zeroizing::new(vec![0u8; key_len]);
// 5. Set c=ceiling(n/u)
// 6. For i=1, 2, ..., c, do the following:
// [ Instead of following this approach, we use an infinite loop and
Expand All @@ -140,18 +154,23 @@ where
// H(H(H(... H(D||I))))
<D as Update>::update(&mut digest, &id_block);
<D as Update>::update(&mut digest, &init_key);
let mut result = digest.finalize_fixed_reset();
// Zeroizing ensures each intermediate hash value is wiped when replaced
// by the next iteration (via Drop on reassignment) and when the final
// value goes out of scope at the end of derive_key.
// `(*result)[..]`: Zeroizing<T> implements Deref but not Index,
// so the explicit deref is required to index into the wrapped Array.
let mut result = Zeroizing::new(digest.finalize_fixed_reset());
for _ in 1..rounds {
<D as Update>::update(&mut digest, &result[0..output_size]);
result = digest.finalize_fixed_reset();
<D as Update>::update(&mut digest, &(*result)[0..output_size]);
result = Zeroizing::new(digest.finalize_fixed_reset());
}

// 7. Concateate A_1, A_2, ..., A_c together to form a pseudorandom
// bit string, A.
// [ Instead of storing all Ais and concatenating later, we concatenate
// them immediately ]
let new_bytes_num = m.min(output_size);
out[n..n + new_bytes_num].copy_from_slice(&result[0..new_bytes_num]);
out[n..n + new_bytes_num].copy_from_slice(&(*result)[0..new_bytes_num]);
n += new_bytes_num;
if m <= new_bytes_num {
break;
Expand All @@ -170,7 +189,7 @@ where
let mut c = 1_u16;
let mut k = block_size - 1;
loop {
c += init_key[k + j] as u16 + result[k % output_size] as u16;
c += init_key[k + j] as u16 + (*result)[k % output_size] as u16;
init_key[j + k] = (c & 0x00ff) as u8;
c >>= 8;
if k == 0 {
Expand Down
Loading
Loading