Skip to content

Commit

Permalink
pkcs12: use der::BmpString for KDF (#1209)
Browse files Browse the repository at this point in the history
Uses the `BmpString` type to implement transcoding from UTF-8 to
BMP/UCS-2.

This type notably restricts the use of UTF-16 surrogate pairs, something
the previous implementation was not doing correctly. This entailed
making the methods which transcode UTF-8 fallible, since we need to
handle the error case that they encode characters outside BMP/UCS-2.
  • Loading branch information
tarcieri authored Sep 5, 2023
1 parent 2f6303b commit 4f41bdb
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 47 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions der/src/asn1/bmp_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ impl BmpString {

for maybe_char in char::decode_utf16(ret.codepoints()) {
match maybe_char {
// All surrogates paired and character is in the Basic Multilingual Plane
// Character is in the Basic Multilingual Plane
Ok(c) if (c as u64) < u64::from(u16::MAX) => (),
// Unpaired surrogates or characters outside Basic Multilingual Plane
// Characters outside Basic Multilingual Plane or unpaired surrogates
_ => return Err(Tag::BmpString.value_error()),
}
}
Expand Down
5 changes: 4 additions & 1 deletion pkcs12/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ edition = "2021"
rust-version = "1.65"

[dependencies]
der = { version = "0.7.8", features = ["alloc"] }
zeroize = "1.6"

# optional dependencies
digest = { version = "0.10.7", features = ["alloc"], optional = true }
zeroize = "1.6.0"

[dev-dependencies]
hex-literal = "0.4"
Expand Down
36 changes: 23 additions & 13 deletions pkcs12/src/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,10 @@
//! [RFC 7292 Appendix B](https://datatracker.ietf.org/doc/html/rfc7292#appendix-B)

use alloc::{vec, vec::Vec};
use der::asn1::BmpString;
use digest::{core_api::BlockSizeUser, Digest, FixedOutputReset, OutputSizeUser, Update};
use zeroize::{Zeroize, Zeroizing};

/// Transform a utf-8 string in a unicode (utf16) string as binary array.
/// The Utf16 code points are stored in big endian format with two trailing zero bytes.
fn str_to_unicode(utf8_str: &str) -> Vec<u8> {
let mut utf16_bytes = Vec::with_capacity(utf8_str.len() * 2 + 2);
for code_point in utf8_str.encode_utf16().chain(Some(0)) {
utf16_bytes.extend(code_point.to_be_bytes());
}
utf16_bytes
}

/// Specify the usage type of the generated key
/// This allows to derive distinct encryption keys, IVs and MAC from the same password or text
/// string.
Expand All @@ -35,7 +26,22 @@ pub enum Pkcs12KeyType {
/// pkcs12::kdf::Pkcs12KeyType::EncryptionKey, 1000, 32);
/// ```
pub fn derive_key_utf8<D>(
pass: &str,
password: &str,
salt: &[u8],
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> der::Result<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
pub fn derive_key_bmp<D>(
password: BmpString,
salt: &[u8],
id: Pkcs12KeyType,
rounds: i32,
Expand All @@ -44,8 +50,12 @@ pub fn derive_key_utf8<D>(
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
let pass_utf16 = Zeroizing::new(str_to_unicode(pass));
derive_key::<D>(&pass_utf16, salt, id, rounds, key_len)
let mut password = Zeroizing::new(Vec::from(password.into_bytes()));

// Password is NULL terminated
password.extend([0u8; 2]);

derive_key::<D>(&password, salt, id, rounds, key_len)
}

/// Derives `key` of type `id` from `pass` and `salt` with length `key_len` using `rounds`
Expand Down
72 changes: 41 additions & 31 deletions pkcs12/tests/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ fn pkcs12_key_derive_sha256() {
Pkcs12KeyType::EncryptionKey,
100,
32
),
)
.unwrap(),
hex!("fae4d4957a3cc781e1180b9d4fb79c1e0c8579b746a3177e5b0768a3118bf863")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 32),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 32).unwrap(),
hex!("e5ff813bc6547de5155b14d2fada85b3201a977349db6e26ccc998d9e8f83d6c")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 32),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 32)
.unwrap(),
hex!("136355ed9434516682534f46d63956db5ff06b844702c2c1f3b46321e2524a4d")
);

Expand All @@ -39,17 +41,19 @@ fn pkcs12_key_derive_sha256() {
Pkcs12KeyType::EncryptionKey,
100,
20
),
)
.unwrap(),
hex!("fae4d4957a3cc781e1180b9d4fb79c1e0c8579b7")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 20),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 20).unwrap(),
hex!("e5ff813bc6547de5155b14d2fada85b3201a9773")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 20),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 20)
.unwrap(),
hex!("136355ed9434516682534f46d63956db5ff06b84")
);

Expand All @@ -60,17 +64,19 @@ fn pkcs12_key_derive_sha256() {
Pkcs12KeyType::EncryptionKey,
100,
12
),
)
.unwrap(),
hex!("fae4d4957a3cc781e1180b9d")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 12),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 12).unwrap(),
hex!("e5ff813bc6547de5155b14d2")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 12),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 12)
.unwrap(),
hex!("136355ed9434516682534f46")
);

Expand All @@ -81,34 +87,38 @@ fn pkcs12_key_derive_sha256() {
Pkcs12KeyType::EncryptionKey,
1000,
32
),
)
.unwrap(),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 1000, 32),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 1000, 32)
.unwrap(),
hex!("6472c0ebad3fab4123e8b5ed7834de21eeb20187b3eff78a7d1cdffa4034851d")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 1000, 32),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 1000, 32)
.unwrap(),
hex!("3f9113f05c30a996c4a516409bdac9d065f44296ccd52bb75de3fcfdbe2bf130")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 1000, 32),
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 1000, 32)
.unwrap(),
hex!("3f9113f05c30a996c4a516409bdac9d065f44296ccd52bb75de3fcfdbe2bf130")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 1000, 100),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527d8d0ebe2ccbf768c51c4d8fbd1bb156be06c1c59cbb69e44052ffc37376fdb47b2de7f9e543de9d096d8e5474b220410ff1c5d8bb7e5bc0f61baeaa12fd0da1d7a970172")
);
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 1000, 100).unwrap(),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527d8d0ebe2ccbf768c51c4d8fbd1bb156be06c1c59cbb69e44052ffc37376fdb47b2de7f9e543de9d096d8e5474b220410ff1c5d8bb7e5bc0f61baeaa12fd0da1d7a970172")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 1000, 200),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527d8d0ebe2ccbf768c51c4d8fbd1bb156be06c1c59cbb69e44052ffc37376fdb47b2de7f9e543de9d096d8e5474b220410ff1c5d8bb7e5bc0f61baeaa12fd0da1d7a9701729cea6014d7fe62a2ed926dc36b61307f119d64edbceb5a9c58133bbf75ba0bef000a1a5180e4b1de7d89c89528bcb7899a1e46fd4da0d9de8f8e65e8d0d775e33d1247e76d596a34303161b219f39afda448bf518a2835fc5e28f0b55a1b6137a2c70cf7")
);
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 1000, 200).unwrap(),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527d8d0ebe2ccbf768c51c4d8fbd1bb156be06c1c59cbb69e44052ffc37376fdb47b2de7f9e543de9d096d8e5474b220410ff1c5d8bb7e5bc0f61baeaa12fd0da1d7a9701729cea6014d7fe62a2ed926dc36b61307f119d64edbceb5a9c58133bbf75ba0bef000a1a5180e4b1de7d89c89528bcb7899a1e46fd4da0d9de8f8e65e8d0d775e33d1247e76d596a34303161b219f39afda448bf518a2835fc5e28f0b55a1b6137a2c70cf7")
);
}

#[test]
Expand All @@ -123,7 +133,8 @@ fn pkcs12_key_derive_sha512() {
Pkcs12KeyType::EncryptionKey,
100,
32
),
)
.unwrap(),
hex!("b14a9f01bfd9dce4c9d66d2fe9937e5fd9f1afa59e370a6fa4fc81c1cc8ec8ee")
);
}
Expand All @@ -140,7 +151,8 @@ fn pkcs12_key_derive_whirlpool() {
Pkcs12KeyType::EncryptionKey,
100,
32
),
)
.unwrap(),
hex!("3324282adb468bff0734d3b7e399094ec8500cb5b0a3604055da107577aaf766")
);
}
Expand All @@ -150,14 +162,12 @@ fn pkcs12_key_derive_special_chars() {
const PASS_SHORT: &str = "🔥";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key_utf8::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
32
),
hex!("d01e72a940b4b1a7a5707fc8264a60cb7606ff9051dedff90930687d2513c006")
);
assert!(derive_key_utf8::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
32
)
.is_err()); // Emoji is not in the Basic Multilingual Plane
}

0 comments on commit 4f41bdb

Please sign in to comment.