Skip to content

Commit

Permalink
add a few more test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
carl-wallace committed Jul 19, 2023
1 parent cd586b1 commit fc5ca02
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 6 deletions.
3 changes: 2 additions & 1 deletion pkcs12/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ pkcs8 = { version = "0.10.2", features = ["pkcs5"] }
pkcs5 = {version = "0.7.1", features = ["pbes2"]}

[features]
decrypt = ["pkcs8", "pkcs5"]
decrypt = ["pkcs8", "pkcs5"]
insecure = ["pkcs5/sha1-insecure", "pkcs5/des-insecure"]
29 changes: 26 additions & 3 deletions pkcs12/src/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ use crate::pfx::Pfx;
use crate::safe_bag::{PrivateKeyInfo, SafeContents};
use cms::encrypted_data::EncryptedData;
use const_oid::db::rfc5911;
use const_oid::ObjectIdentifier;
use der::asn1::ContextSpecific;
use der::asn1::OctetString;
use der::{Any, Decode, Encode};
use pkcs5::pbes2;
use pkcs8::EncryptedPrivateKeyInfo;
use x509_cert::Certificate;

/// Error type
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// ASN.1 DER-related errors.
Expand All @@ -36,6 +38,9 @@ pub enum Error {

/// Missing expected content
MissingContent,

/// Missing expected content
UnexpectedAlgorithm(ObjectIdentifier),
}
type Result<T> = core::result::Result<T, Error>;

Expand Down Expand Up @@ -76,6 +81,16 @@ fn process_safe_contents(
return Err(Error::UnexpectedSafeBag);
}
}
crate::PKCS_12_KEY_BAG_OID => {
if key.is_none() {
let cs: ContextSpecific<PrivateKeyInfo> =
ContextSpecific::from_der(&safe_bag.bag_value)
.map_err(|e| Error::Asn1(e))?;
key = Some(cs.value);
} else {
return Err(Error::UnexpectedSafeBag);
}
}
_ => return Err(Error::UnexpectedSafeBag),
};
}
Expand All @@ -98,8 +113,16 @@ fn process_encrypted_data(
None => return Err(Error::MissingParameters),
};

let params =
pkcs8::pkcs5::pbes2::Parameters::from_der(&enc_params).map_err(|e| Error::Asn1(e))?;
let params = match enc_data.enc_content_info.content_enc_alg.oid {
pbes2::PBES2_OID => {
pkcs8::pkcs5::pbes2::Parameters::from_der(&enc_params).map_err(|e| Error::Asn1(e))?
}
_ => {
return Err(Error::UnexpectedAlgorithm(
enc_data.enc_content_info.content_enc_alg.oid,
))
}
};

let scheme =
pkcs5::EncryptionScheme::try_from(params.clone()).map_err(|_e| Error::EncryptionScheme)?;
Expand Down
143 changes: 143 additions & 0 deletions pkcs12/src/kdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//! Implementation of the key derivation function
//! [RFC 7292 Appendix B](https://datatracker.ietf.org/doc/html/rfc7292#appendix-B)

use alloc::{vec, 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.
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.
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();

// In the following, the numbered comments relate directly to the algorithm
// described in RFC 7292, Appendix B.2. Actual variable names may differ.
// Comments of the RFC are in enclosed in []
//
// 1. Construct a string, D (the "diversifier"), by concatenating v/8
// copies of ID, where v is the block size in bits.
let id_block = match id {
Pkcs12KeyType::EncryptionKey => vec![1u8; block_size],
Pkcs12KeyType::Iv => vec![2u8; block_size],
Pkcs12KeyType::Mac => vec![3u8; 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];
// 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.
for i in 0..slen {
init_key[i] = salt[i % salt.len()];
}

// 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.
for i in 0..plen {
init_key[slen + i] = pass_utf16[i % pass_utf16.len()];
}
pass_utf16.zeroize();

// 4. Set I=S||P to be the concatenation of S and P.
// [already done in `init_key`]

let mut m = key_len;
let mut n = 0;
let mut out = 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
// use the break condition below, if we have produced n bytes for the key]
loop {
// 6. A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1,
// 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();
for _ in 1..rounds {
<D as Update>::update(&mut digest, &result[0..output_size]);
result = 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]);
n += new_bytes_num;
if m <= new_bytes_num {
break;
}
m -= new_bytes_num;

// 6. B. Concatenate copies of Ai to create a string B of length v
// bits (the final copy of Ai may be truncated to create B).
// [ we achieve this on thy fly with the expression `result[k % output_size]` below]

// 6. C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit
// blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by
// setting I_j=(I_j+B+1) mod 2^v for each j.
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();
// 8. Use the first n bits of A as the output of this entire process.
out
}
60 changes: 58 additions & 2 deletions pkcs12/tests/cert_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ use pkcs12::safe_bag::SafeContents;
#[cfg(feature = "decrypt")]
use pkcs12::decrypt::decrypt_pfx;

#[cfg(feature = "decrypt")]
use pkcs12::decrypt::Error;

#[cfg(feature = "decrypt")]
use pkcs12::PKCS_12_PBE_WITH_SHAAND3_KEY_TRIPLE_DES_CBC;

use pkcs8::pkcs5::pbes2::{AES_256_CBC_OID, HMAC_WITH_SHA256_OID, PBES2_OID, PBKDF2_OID};
use pkcs8::{pkcs5, EncryptedPrivateKeyInfo};
use spki::AlgorithmIdentifierOwned;
Expand Down Expand Up @@ -266,7 +272,7 @@ fn decode_sample_pfx() {

#[cfg(feature = "decrypt")]
#[test]
fn decode_sample_pfx_with_utils() {
fn decode_sample_pfx_with_decrypt() {
let bytes = include_bytes!("examples/example.pfx");
let (key, cert) = decrypt_pfx(bytes, "".as_bytes()).unwrap();
let enc_key = key.unwrap().to_der().unwrap();
Expand Down Expand Up @@ -664,11 +670,61 @@ fn decode_sample_pfx2() {

#[cfg(feature = "decrypt")]
#[test]
fn decode_sample_pfx2_with_utils() {
fn decode_sample_pfx2_with_decrypt() {
let bytes = include_bytes!("examples/example2.pfx");
let (key, cert) = decrypt_pfx(bytes, "1234".as_bytes()).unwrap();
let enc_key = key.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/key.der"), enc_key.as_slice());
let enc_cert = cert.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/cert.der"), enc_cert.as_slice());
}

#[cfg(feature = "decrypt")]
#[test]
fn decode_sample_pfx3_with_decrypt() {
// openssl pkcs12 -export -out example3.pfx -inkey key.pem -in cert.pem -passout pass:1234 -certpbe NONE -keypbe aes-128-cbc
let bytes = include_bytes!("examples/example3.pfx");
let (key, cert) = decrypt_pfx(bytes, "1234".as_bytes()).unwrap();
let enc_key = key.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/key.der"), enc_key.as_slice());
let enc_cert = cert.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/cert.der"), enc_cert.as_slice());
}

#[cfg(all(feature = "insecure", feature = "decrypt"))]
#[test]
fn decode_sample_pfx4_with_decrypt() {
// openssl pkcs12 -export -out example4.pfx -inkey key.pem -in cert.pem -passout pass:1234 -certpbe aes-192-cbc -keypbe aes-256-cbc
let bytes = include_bytes!("examples/example4.pfx");
let (key, cert) = decrypt_pfx(bytes, "1234".as_bytes()).unwrap();
let enc_key = key.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/key.der"), enc_key.as_slice());
let enc_cert = cert.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/cert.der"), enc_cert.as_slice());
}

#[cfg(all(feature = "insecure", feature = "decrypt"))]
#[test]
fn decode_sample_pfx5_with_decrypt() {
// openssl pkcs12 -export -out example5.pfx -inkey key.pem -in cert.pem -passout pass:1234 -certpbe aes-192-cbc -keypbe aes-256-cbc -nomac
let bytes = include_bytes!("examples/example5.pfx");
let (key, cert) = decrypt_pfx(bytes, "1234".as_bytes()).unwrap();
let enc_key = key.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/key.der"), enc_key.as_slice());
let enc_cert = cert.unwrap().to_der().unwrap();
assert_eq!(include_bytes!("examples/cert.der"), enc_cert.as_slice());
}

#[cfg(feature = "decrypt")]
#[test]
fn decode_sample_pkits_with_decrypt_fail() {
let bytes = include_bytes!("examples/ValidCertificatePathTest1EE.p12");
let r = decrypt_pfx(bytes, "password".as_bytes());
assert!(r.is_err());
assert_eq!(
r.err(),
Some(Error::UnexpectedAlgorithm(
PKCS_12_PBE_WITH_SHAAND3_KEY_TRIPLE_DES_CBC
))
)
}
Binary file not shown.
Binary file not shown.
Binary file added pkcs12/tests/examples/example3.pfx
Binary file not shown.
Binary file added pkcs12/tests/examples/example4.pfx
Binary file not shown.
Binary file added pkcs12/tests/examples/example5.pfx
Binary file not shown.

0 comments on commit fc5ca02

Please sign in to comment.