Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkcs12: KDF support #1154

Merged
merged 22 commits into from
Jul 23, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
19 changes: 18 additions & 1 deletion Cargo.lock

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

16 changes: 15 additions & 1 deletion pkcs12/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pkcs12"
version = "0.0.0"
version = "0.1.0"
xemwebe marked this conversation as resolved.
Show resolved Hide resolved
description = """
Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #12:
Personal Information Exchange Syntax v1.1 (RFC7292)
Expand All @@ -14,6 +14,20 @@ readme = "README.md"
edition = "2021"
rust-version = "1.65"

[features]
alloc = []
xemwebe marked this conversation as resolved.
Show resolved Hide resolved

[dependencies]
cfg-if = "1.0.0"
xemwebe marked this conversation as resolved.
Show resolved Hide resolved
digest = { version = "0.10.7", features=["alloc"] }
zeroize = "1.6.0"

[dev-dependencies]
hex-literal = "0.4"
sha2 = "0.10.7"
whirlpool = "0.10.4"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

110 changes: 110 additions & 0 deletions pkcs12/src/kdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! Implementation of the key derivation function
//! [RFC 7292 Appendix B](https://datatracker.ietf.org/doc/html/rfc7292#appendix-B)

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

/// 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.
pub fn str_to_unicode(utf8_str: &str) -> Vec<u8> {
xemwebe marked this conversation as resolved.
Show resolved Hide resolved
let mut utf16_bytes = Vec::new();
// reserve max number of required bytes to avoid re-allocation
utf16_bytes.reserve(utf8_str.len() * 2 + 2);
xemwebe marked this conversation as resolved.
Show resolved Hide resolved
for code_point in utf8_str.encode_utf16() {
xemwebe marked this conversation as resolved.
Show resolved Hide resolved
utf16_bytes.extend(code_point.to_be_bytes());
}
utf16_bytes.push(0);
utf16_bytes.push(0);
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.
pub enum Pkcs12KeyType {
/// Use key for encryption
EncryptionKey = 1,
/// Use key as initial vector
Iv = 2,
/// Use key as MAC
Mac = 3,
}

/// Derives `key` of type `id` from `pass` and `salt` with length `key_len` using `rounds`
/// iterations of the algorithm
/// ```rust
/// let key = pkcs12::kdf::derive_key::<sha2::Sha256>("top-secret", &[0x1, 0x2, 0x3, 0x4],
/// pkcs12::kdf::Pkcs12KeyType::EncryptionKey, 1000, 32);
/// ```
pub fn derive_key<D>(
pass: &str,
salt: &[u8],
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> Vec<u8>
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
let mut digest = D::new();
let mut pass_utf16 = str_to_unicode(pass);
let output_size = <D as OutputSizeUser>::output_size();
let block_size = D::block_size();
let slen = block_size * ((salt.len() + block_size - 1) / block_size);
let plen = block_size * ((pass_utf16.len() + block_size - 1) / block_size);
let ilen = slen + plen;
let mut init_key = vec![0u8; ilen];
for i in 0..slen {
init_key[i] = salt[i % salt.len()];
}
for i in 0..plen {
init_key[slen + i] = pass_utf16[i % pass_utf16.len()];
}
pass_utf16.zeroize();
Copy link
Member

@baloo baloo Jul 17, 2023

Choose a reason for hiding this comment

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

This looks like step2,3 & 4

   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
       string, then so is S.

   3.  Concatenate copies of the password together to create a string P
       of length v(ceiling(p/v)) bits (the final copy of the password
       may be truncated to create P).  Note that if the password is the
       empty string, then so is P.
   
   4.  Set I=S||P to be the concatenation of S and P.

Could you move code around (to preserve ordering) and copy the algorithm from the RFC as comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added comments to the code describing the single steps of the implementation, as far as possible since I derive at some point slightly from the algorithm.


let id_block = match id {
Pkcs12KeyType::EncryptionKey => vec![1u8; block_size],
Pkcs12KeyType::Iv => vec![2u8; block_size],
Pkcs12KeyType::Mac => vec![3u8; block_size],
baloo marked this conversation as resolved.
Show resolved Hide resolved
};

let mut m = key_len;
let mut n = 0;
let mut out = vec![0u8; key_len];
loop {
Copy link
Member

Choose a reason for hiding this comment

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

RFC calls for a c = ceiling(n/u) and for the loop to be:

for _i in 1..=c {
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using a loop instead has the advantage that the last step 6. could be skipped.

<D as Update>::update(&mut digest, &id_block);
<D as Update>::update(&mut digest, &init_key);
let mut result = digest.finalize_fixed_reset();
for _ in 1..rounds {
<D as Update>::update(&mut digest, &result[0..output_size]);
result = digest.finalize_fixed_reset();
}
let new_bytes_num = m.min(output_size);
out[n..n + new_bytes_num].copy_from_slice(&result[0..new_bytes_num]);
n += new_bytes_num;
if m <= new_bytes_num {
break;
}

// prepare `init_key` for next block if `ouput_size` is smaller than `key_len`
m -= new_bytes_num;
let mut j = 0;
while j < ilen {
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;
init_key[j + k] = (c & 0x00ff) as u8;
c >>= 8;
if k == 0 {
break;
}
k -= 1;
}
j += block_size;
}
}
init_key.zeroize();
out
}
14 changes: 13 additions & 1 deletion pkcs12/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,16 @@
unused_qualifications
)]

//! TODO: PKCS#12 crate
//! TODO: complete PKCS#12 crate

use cfg_if::cfg_if;

cfg_if! {
if #[cfg(feature = "alloc")] {

#[macro_use]
extern crate alloc;

pub mod kdf;
}
}
xemwebe marked this conversation as resolved.
Show resolved Hide resolved
130 changes: 130 additions & 0 deletions pkcs12/tests/kdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use cfg_if::cfg_if;
xemwebe marked this conversation as resolved.
Show resolved Hide resolved

cfg_if! {
if #[cfg(feature="alloc")] {

#[test]
fn pkcs12_key_derive_sha256() {
xemwebe marked this conversation as resolved.
Show resolved Hide resolved
use hex_literal::hex;
use pkcs12::kdf::{derive_key, Pkcs12KeyType};

const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 100, 32),
hex!("fae4d4957a3cc781e1180b9d4fb79c1e0c8579b746a3177e5b0768a3118bf863")
);

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

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

assert_eq!(
derive_key::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 100, 20),
hex!("fae4d4957a3cc781e1180b9d4fb79c1e0c8579b7")
);

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

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

assert_eq!(
derive_key::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 100, 12),
hex!("fae4d4957a3cc781e1180b9d")
);

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

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

assert_eq!(
derive_key::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
1000,
32
),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527")
);

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

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

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

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

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

#[test]
fn pkcs12_key_derive_sha512() {
use hex_literal::hex;
use pkcs12::kdf::{derive_key, Pkcs12KeyType};

const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key::<sha2::Sha512>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 100, 32),
hex!("b14a9f01bfd9dce4c9d66d2fe9937e5fd9f1afa59e370a6fa4fc81c1cc8ec8ee")
);
}

#[test]
fn pkcs12_key_derive_whirlpool() {
use hex_literal::hex;
use pkcs12::kdf::{derive_key, Pkcs12KeyType};

const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key::<whirlpool::Whirlpool>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
32
),
hex!("3324282adb468bff0734d3b7e399094ec8500cb5b0a3604055da107577aaf766")
);
}
}
}