Skip to content

Commit

Permalink
temporarily copied over kdf.rs from RustCrypto#1154 and refactored de…
Browse files Browse the repository at this point in the history
…crypt support to use it. added a test case with a pfx from PKITS to exercise the kdf. added implementation of EncryptedPrivateKeyInfo.
  • Loading branch information
carl-wallace committed Jul 19, 2023
1 parent 2db1587 commit 2dbc115
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 53 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

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

15 changes: 11 additions & 4 deletions pkcs12/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,27 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
cbc = { version = "0.1.2", optional = true }
der = { version = "0.7.7", features = ["alloc", "derive", "oid", "pem"] }
spki = { version = "0.7" }
x509-cert = { version = "0.2.3", default-features = false, features = ["pem"] }
const-oid = { version = "0.9", features = ["db"] } # TODO: path = "../const-oid"
cms = "0.2.1"
pkcs8 = { version = "0.10.2", features = ["pkcs5"], optional = true }
pkcs5 = {version = "0.7.1", features = ["pbes2"], optional = true}
digest = { version = "0.10.7", features=["alloc"], optional = true }
zeroize = "1.6.0"
sha1 = { version = "0.10.5", optional = true }
des = { version = "0.8.1", optional = true, default-features = false }

[dev-dependencies]
hex-literal = "0.3.3"
subtle-encoding = "0.5.1"
pkcs8 = { version = "0.10.2", features = ["pkcs5"] }
pkcs5 = {version = "0.7.1", features = ["pbes2"]}
pkcs5 = {version = "0.7.1", features = ["pbes2", "3des"]}
subtle-encoding = "0.5.1"
sha2 = "0.10.7"

[features]
decrypt = ["pkcs8", "pkcs5"]
insecure = ["pkcs5/sha1-insecure", "pkcs5/des-insecure"]
decrypt = ["pkcs8", "pkcs5", "cbc", "kdf"]
insecure = ["pkcs5/sha1-insecure", "pkcs5/des-insecure", "des", "sha1"]
kdf = ["dep:digest"]
129 changes: 89 additions & 40 deletions pkcs12/src/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

use crate::authenticated_safe::AuthenticatedSafe;
use crate::cert_type::CertBag;
use crate::pbe_params::EncryptedPrivateKeyInfo as OtherEncryptedPrivateKeyInfo;
use core::str::Utf8Error;

#[cfg(all(feature = "kdf", feature = "insecure"))]
use crate::decrypt_kdf::*;

use crate::pfx::Pfx;
use crate::safe_bag::{PrivateKeyInfo, SafeContents};
use cms::encrypted_data::EncryptedData;
Expand All @@ -10,7 +16,7 @@ use const_oid::ObjectIdentifier;
use der::asn1::ContextSpecific;
use der::asn1::OctetString;
use der::{Any, Decode, Encode};
use pkcs5::pbes2;
use pkcs5::pbes2::PBES2_OID;
use pkcs8::EncryptedPrivateKeyInfo;
use x509_cert::Certificate;

Expand Down Expand Up @@ -41,8 +47,12 @@ pub enum Error {

/// Missing expected content
UnexpectedAlgorithm(ObjectIdentifier),

/// String conversion error
Utf8Error(Utf8Error),
}
type Result<T> = core::result::Result<T, Error>;
/// Result type for PKCS #12 der
pub type Result<T> = core::result::Result<T, Error>;

fn process_safe_contents(
data: &[u8],
Expand All @@ -67,19 +77,46 @@ fn process_safe_contents(
}
}
crate::PKCS_12_PKCS8_KEY_BAG_OID => {
let cs: ContextSpecific<EncryptedPrivateKeyInfo<'_>> =
let cs_tmp: ContextSpecific<OtherEncryptedPrivateKeyInfo> =
ContextSpecific::from_der(&safe_bag.bag_value).map_err(|e| Error::Asn1(e))?;
let mut ciphertext = cs.value.encrypted_data.to_vec();
let plaintext = cs
.value
.encryption_algorithm
.decrypt_in_place(password, &mut ciphertext)
.map_err(|e| Error::Pkcs5(e))?;
if key.is_none() {
key = Some(PrivateKeyInfo::from_der(plaintext).map_err(|e| Error::Asn1(e))?);
} else {
return Err(Error::UnexpectedSafeBag);
}
match cs_tmp.value.encryption_algorithm.oid {
PBES2_OID => {
let cs: ContextSpecific<EncryptedPrivateKeyInfo<'_>> =
ContextSpecific::from_der(&safe_bag.bag_value)
.map_err(|e| Error::Asn1(e))?;
let mut ciphertext = cs.value.encrypted_data.to_vec();
let plaintext = cs
.value
.encryption_algorithm
.decrypt_in_place(password, &mut ciphertext)
.map_err(|e| Error::Pkcs5(e))?;
if key.is_none() {
key = Some(
PrivateKeyInfo::from_der(plaintext).map_err(|e| Error::Asn1(e))?,
);
} else {
return Err(Error::UnexpectedSafeBag);
}
}
#[cfg(all(feature = "kdf", feature = "insecure"))]
_ => {
let cur_key = pkcs12_pbe_key(
cs_tmp.value.encrypted_data,
password,
&cs_tmp.value.encryption_algorithm,
)?;
if key.is_some() {
return Err(Error::UnexpectedAuthSafe);
}
key = Some(cur_key);
}
#[cfg(not(all(feature = "kdf", feature = "insecure")))]
_ => {
return Err(Error::UnexpectedAlgorithm(
cs_tmp.value.encryption_algorithm.oid,
))
}
};
}
crate::PKCS_12_KEY_BAG_OID => {
if key.is_none() {
Expand All @@ -103,38 +140,50 @@ fn process_encrypted_data(
) -> Result<(Option<PrivateKeyInfo>, Option<Certificate>)> {
let enc_data_os = &data.to_der().map_err(|e| Error::Asn1(e))?;
let enc_data = EncryptedData::from_der(enc_data_os.as_slice()).map_err(|e| Error::Asn1(e))?;
let enc_params = match enc_data
.enc_content_info
.content_enc_alg
.parameters
.as_ref()
{
Some(params) => params.to_der().map_err(|e| Error::Asn1(e))?,
None => return Err(Error::MissingParameters),
};

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))?

match enc_data.enc_content_info.content_enc_alg.oid {
PBES2_OID => {
let enc_params = match enc_data
.enc_content_info
.content_enc_alg
.parameters
.as_ref()
{
Some(params) => params.to_der().map_err(|e| Error::Asn1(e))?,
None => return Err(Error::MissingParameters),
};
let params = pkcs8::pkcs5::pbes2::Parameters::from_der(&enc_params)
.map_err(|e| Error::Asn1(e))?;
let scheme = pkcs5::EncryptionScheme::try_from(params.clone())
.map_err(|_e| Error::EncryptionScheme)?;
match enc_data.enc_content_info.encrypted_content {
Some(content) => {
let mut ciphertext = content.as_bytes().to_vec();
let plaintext = scheme
.decrypt_in_place(password, &mut ciphertext)
.map_err(|e| Error::Pkcs5(e))?;
process_safe_contents(plaintext, password)
}
None => return Err(Error::MissingContent),
}
}
#[cfg(all(feature = "kdf", feature = "insecure"))]
crate::PKCS_12_PBE_WITH_SHAAND3_KEY_TRIPLE_DES_CBC => {
let plaintext = match enc_data.enc_content_info.encrypted_content {
Some(encrypted_content) => pkcs12_pbe(
encrypted_content,
password,
&enc_data.enc_content_info.content_enc_alg,
)?,
None => return Err(Error::MissingParameters),
};
process_safe_contents(&plaintext, password)
}
_ => {
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)?;
match enc_data.enc_content_info.encrypted_content {
Some(content) => {
let mut ciphertext = content.as_bytes().to_vec();
let plaintext = scheme
.decrypt_in_place(password, &mut ciphertext)
.map_err(|e| Error::Pkcs5(e))?;
process_safe_contents(plaintext, password)
}
None => return Err(Error::MissingContent),
}
}

Expand Down
76 changes: 76 additions & 0 deletions pkcs12/src/decrypt_kdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Convenience functions for working with some common PKCS #12 use cases

use crate::pbe_params::Pkcs12PbeParams;
use alloc::vec::Vec;
use pkcs5::pbes2::EncryptionScheme;
use spki::AlgorithmIdentifierOwned;

use crate::decrypt::{Error, Result};
use crate::safe_bag::PrivateKeyInfo;
use der::asn1::OctetString;
use der::{Decode, Encode};

#[cfg(all(feature = "kdf", feature = "insecure", feature = "decrypt"))]
pub(crate) fn pkcs12_pbe_key(
encrypted_content: OctetString,
password: &[u8],
alg: &AlgorithmIdentifierOwned,
) -> Result<PrivateKeyInfo> {
let plaintext = pkcs12_pbe(encrypted_content, password, alg)?;
PrivateKeyInfo::from_der(&plaintext).map_err(|e| Error::Asn1(e))
}

#[cfg(all(feature = "kdf", feature = "insecure", feature = "decrypt"))]
pub(crate) fn pkcs12_pbe(
encrypted_content: OctetString,
password: &[u8],
alg: &AlgorithmIdentifierOwned,
) -> Result<Vec<u8>> {
use crate::kdf::*;
use cbc::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
use core::str;
use sha1::Sha1;

let enc_params = match &alg.parameters {
Some(params) => params.to_der().map_err(|e| Error::Asn1(e))?,
None => return Err(Error::MissingParameters),
};

let p12_pbe_params = Pkcs12PbeParams::from_der(&enc_params).map_err(|e| Error::Asn1(e))?;
let s = str::from_utf8(password).map_err(|e| Error::Utf8Error(e))?;

let iv = derive_key::<Sha1>(
&s,
p12_pbe_params.salt.as_bytes(),
Pkcs12KeyType::Iv,
2048,
8,
);
let es = match alg.oid {
crate::PKCS_12_PBE_WITH_SHAAND3_KEY_TRIPLE_DES_CBC => EncryptionScheme::DesEde3Cbc {
iv: iv.as_slice().try_into().unwrap(),
},
// PKCS_12_PBE_WITH_SHAAND2_KEY_TRIPLE_DES_CBC
// PKCS_12_PBE_WITH_SHAAND128_BIT_RC4
// PKCS_12_PBE_WITH_SHAAND40_BIT_RC4
// PKCS_12_PBE_WITH_SHAAND128_BIT_RC2_CBC
// PKCS_12_PBEWITH_SHAAND40_BIT_RC2_CBC
_ => return Err(Error::UnexpectedAlgorithm(alg.oid)),
};
let key = derive_key::<Sha1>(
&s,
p12_pbe_params.salt.as_bytes(),
Pkcs12KeyType::EncryptionKey,
p12_pbe_params.iterations,
es.key_size(),
);

let mut ciphertext = encrypted_content.as_bytes().to_vec();
let plaintext = cbc::Decryptor::<des::TdesEde3>::new_from_slices(key.as_slice(), iv.as_slice())
.map_err(|_| es.to_alg_params_invalid())
.map_err(|_| Error::EncryptionScheme)?
.decrypt_padded_mut::<Pkcs7>(&mut ciphertext)
.unwrap();

Ok(plaintext.to_vec())
}
Loading

0 comments on commit 2dbc115

Please sign in to comment.