diff --git a/src/rust/cryptography-key-parsing/src/lib.rs b/src/rust/cryptography-key-parsing/src/lib.rs index 97d59de6f130..7d19bebea39e 100644 --- a/src/rust/cryptography-key-parsing/src/lib.rs +++ b/src/rust/cryptography-key-parsing/src/lib.rs @@ -8,6 +8,7 @@ pub mod dsa; pub mod ec; +pub mod pbe; pub mod pem; pub mod pkcs8; pub mod rsa; @@ -50,6 +51,7 @@ impl From for KeyParsingError { pub type KeyParsingResult = Result; pub enum KeySerializationError { + PasswordMustBeUtf8, Write(asn1::WriteError), OpenSSL(openssl::error::ErrorStack), } diff --git a/src/rust/cryptography-key-parsing/src/pbe.rs b/src/rust/cryptography-key-parsing/src/pbe.rs new file mode 100644 index 000000000000..e0f1deb48cab --- /dev/null +++ b/src/rust/cryptography-key-parsing/src/pbe.rs @@ -0,0 +1,133 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::{KeySerializationError, KeySerializationResult}; + +pub enum EncryptionAlgorithm { + PBESHA1And3KeyTripleDESCBC, + PBESv2SHA256AndAES256CBC, +} + +impl EncryptionAlgorithm { + pub fn salt_length(&self) -> usize { + match self { + EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => 8, + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => 16, + } + } + + pub fn algorithm_identifier<'a>( + &self, + cipher_kdf_iter: u64, + salt: &'a [u8], + iv: &'a [u8], + ) -> cryptography_x509::common::AlgorithmIdentifier<'a> { + match self { + EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => { + cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::PbeWithShaAnd3KeyTripleDesCbc(cryptography_x509::common::Pkcs12PbeParams{ + salt, + iterations: cipher_kdf_iter, + }), + } + } + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { + let kdf_algorithm_identifier = cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::Pbkdf2( + cryptography_x509::common::PBKDF2Params { + salt, + iteration_count: cipher_kdf_iter, + key_length: None, + prf: Box::new(cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: + cryptography_x509::common::AlgorithmParameters::HmacWithSha256( + Some(()), + ), + }), + }, + ), + }; + let encryption_algorithm_identifier = + cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::Aes256Cbc( + iv[..16].try_into().unwrap(), + ), + }; + + cryptography_x509::common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: cryptography_x509::common::AlgorithmParameters::Pbes2( + cryptography_x509::common::PBES2Params { + key_derivation_func: Box::new(kdf_algorithm_identifier), + encryption_scheme: Box::new(encryption_algorithm_identifier), + }, + ), + } + } + } + } + + pub fn encrypt( + &self, + password: &[u8], + cipher_kdf_iter: u64, + salt: &[u8], + iv: &[u8], + data: &[u8], + ) -> KeySerializationResult> { + match self { + EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => { + let password = std::str::from_utf8(password) + .map_err(|_| KeySerializationError::PasswordMustBeUtf8)?; + + let key = cryptography_crypto::pkcs12::kdf( + password, + salt, + cryptography_crypto::pkcs12::KDF_ENCRYPTION_KEY_ID, + cipher_kdf_iter, + 24, + openssl::hash::MessageDigest::sha1(), + )?; + let iv = cryptography_crypto::pkcs12::kdf( + password, + salt, + cryptography_crypto::pkcs12::KDF_IV_ID, + cipher_kdf_iter, + 8, + openssl::hash::MessageDigest::sha1(), + )?; + + Ok(openssl::symm::encrypt( + openssl::symm::Cipher::des_ede3_cbc(), + &key, + Some(&iv), + data, + )?) + } + EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { + let sha256 = openssl::hash::MessageDigest::sha256(); + + let mut key = [0; 32]; + openssl::pkcs5::pbkdf2_hmac( + password, + salt, + cipher_kdf_iter.try_into().unwrap(), + sha256, + &mut key, + )?; + + Ok(openssl::symm::encrypt( + openssl::symm::Cipher::aes_256_cbc(), + &key, + Some(iv), + data, + )?) + } + } + } +} diff --git a/src/rust/cryptography-key-parsing/src/pkcs8.rs b/src/rust/cryptography-key-parsing/src/pkcs8.rs index f6dcf8f1cb9e..82078b38242e 100644 --- a/src/rust/cryptography-key-parsing/src/pkcs8.rs +++ b/src/rust/cryptography-key-parsing/src/pkcs8.rs @@ -10,7 +10,7 @@ use cryptography_x509::pkcs8::EncryptedPrivateKeyInfo; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use crate::MIN_DH_MODULUS_SIZE; -use crate::{ec, rsa, KeyParsingError, KeyParsingResult}; +use crate::{ec, pbe, rsa, KeyParsingError, KeyParsingResult}; // RFC 5208 Section 5 #[derive(asn1::Asn1Read, asn1::Asn1Write)] @@ -457,7 +457,7 @@ pub fn serialize_private_key( Ok(asn1::write_single(&pki)?) } -const KDF_ITERATION_COUNT: usize = 2048; +const KDF_ITERATION_COUNT: u64 = 2048; pub fn serialize_encrypted_private_key( pkey: &openssl::pkey::PKeyRef, @@ -465,54 +465,15 @@ pub fn serialize_encrypted_private_key( ) -> crate::KeySerializationResult> { let plaintext_der = serialize_private_key(pkey)?; + let e = pbe::EncryptionAlgorithm::PBESv2SHA256AndAES256CBC; + let mut salt = [0u8; 16]; let mut iv = [0u8; 16]; cryptography_openssl::rand::rand_bytes(&mut salt)?; cryptography_openssl::rand::rand_bytes(&mut iv)?; - let cipher = openssl::symm::Cipher::aes_256_cbc(); - let mut key = [0u8; 32]; - openssl::pkcs5::pbkdf2_hmac( - password, - &salt, - KDF_ITERATION_COUNT, - openssl::hash::MessageDigest::sha256(), - &mut key, - )?; - - let encrypted_data = openssl::symm::encrypt(cipher, &key, Some(&iv), &plaintext_der)?; - - let kdf_algorithm = cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::Pbkdf2( - cryptography_x509::common::PBKDF2Params { - salt: &salt, - iteration_count: KDF_ITERATION_COUNT as u64, - key_length: None, - prf: Box::new(cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::HmacWithSha256( - Some(()), - ), - }), - }, - ), - }; - - let encryption_algorithm = cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::Aes256Cbc(iv), - }; - - let encryption_alg = cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::Pbes2( - cryptography_x509::common::PBES2Params { - key_derivation_func: Box::new(kdf_algorithm), - encryption_scheme: Box::new(encryption_algorithm), - }, - ), - }; + let encrypted_data = e.encrypt(password, KDF_ITERATION_COUNT, &salt, &iv, &plaintext_der)?; + let encryption_alg = e.algorithm_identifier(KDF_ITERATION_COUNT, &salt, &iv); let epki = cryptography_x509::pkcs8::EncryptedPrivateKeyInfo { encryption_algorithm: encryption_alg, diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs index 37c87e384c67..44c84b295020 100644 --- a/src/rust/src/error.rs +++ b/src/rust/src/error.rs @@ -136,6 +136,11 @@ impl From for CryptographyError { impl From for CryptographyError { fn from(e: cryptography_key_parsing::KeySerializationError) -> CryptographyError { match e { + cryptography_key_parsing::KeySerializationError::PasswordMustBeUtf8 => { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err( + "password must be valid UTF-8", + )) + } cryptography_key_parsing::KeySerializationError::Write(e) => { CryptographyError::Asn1Write(e) } diff --git a/src/rust/src/pkcs12.rs b/src/rust/src/pkcs12.rs index d07d50973168..92aa9c8faa40 100644 --- a/src/rust/src/pkcs12.rs +++ b/src/rust/src/pkcs12.rs @@ -5,12 +5,13 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use cryptography_key_parsing::pbe; use cryptography_x509::common::Utf8StoredBMPString; use cryptography_x509::oid::EKU_ANY_KEY_USAGE_OID; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use pyo3::{IntoPyObject, PyTypeInfo}; -use crate::backend::{ciphers, hashes, hmac, kdf, keys}; +use crate::backend::{ciphers, hashes, hmac, keys}; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::padding::PKCS7PaddingContext; @@ -110,132 +111,6 @@ pub(crate) fn symmetric_encrypt( Ok(ciphertext) } -enum EncryptionAlgorithm { - PBESHA1And3KeyTripleDESCBC, - PBESv2SHA256AndAES256CBC, -} - -impl EncryptionAlgorithm { - fn salt_length(&self) -> usize { - match self { - EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => 8, - EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => 16, - } - } - - fn algorithm_identifier<'a>( - &self, - cipher_kdf_iter: u64, - salt: &'a [u8], - iv: &'a [u8], - ) -> cryptography_x509::common::AlgorithmIdentifier<'a> { - match self { - EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => { - cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::PbeWithShaAnd3KeyTripleDesCbc(cryptography_x509::common::Pkcs12PbeParams{ - salt, - iterations: cipher_kdf_iter, - }), - } - } - EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { - let kdf_algorithm_identifier = cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::Pbkdf2( - cryptography_x509::common::PBKDF2Params { - salt, - iteration_count: cipher_kdf_iter, - key_length: None, - prf: Box::new(cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: - cryptography_x509::common::AlgorithmParameters::HmacWithSha256( - Some(()), - ), - }), - }, - ), - }; - let encryption_algorithm_identifier = - cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::Aes256Cbc( - iv[..16].try_into().unwrap(), - ), - }; - - cryptography_x509::common::AlgorithmIdentifier { - oid: asn1::DefinedByMarker::marker(), - params: cryptography_x509::common::AlgorithmParameters::Pbes2( - cryptography_x509::common::PBES2Params { - key_derivation_func: Box::new(kdf_algorithm_identifier), - encryption_scheme: Box::new(encryption_algorithm_identifier), - }, - ), - } - } - } - } - - fn encrypt( - &self, - py: pyo3::Python<'_>, - password: &str, - cipher_kdf_iter: u64, - salt: &[u8], - iv: &[u8], - data: &[u8], - ) -> CryptographyResult> { - match self { - EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC => { - let key = cryptography_crypto::pkcs12::kdf( - password, - salt, - cryptography_crypto::pkcs12::KDF_ENCRYPTION_KEY_ID, - cipher_kdf_iter, - 24, - openssl::hash::MessageDigest::sha1(), - )?; - let iv = cryptography_crypto::pkcs12::kdf( - password, - salt, - cryptography_crypto::pkcs12::KDF_IV_ID, - cipher_kdf_iter, - 8, - openssl::hash::MessageDigest::sha1(), - )?; - - let triple_des = types::TRIPLE_DES - .get(py)? - .call1((pyo3::types::PyBytes::new(py, &key),))?; - let cbc = types::CBC - .get(py)? - .call1((pyo3::types::PyBytes::new(py, &iv),))?; - - symmetric_encrypt(py, triple_des, cbc, data) - } - EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { - let sha256 = openssl::hash::MessageDigest::sha256(); - - let key = kdf::pbkdf2_hmac_derive( - py, - password.as_bytes(), - sha256, - salt, - cipher_kdf_iter.try_into().unwrap(), - 32, - )?; - - let aes256 = types::AES256.get(py)?.call1((key,))?; - let cbc = types::CBC.get(py)?.call1((iv,))?; - - symmetric_encrypt(py, aes256, cbc, data) - } - } - } -} - fn pkcs12_attributes<'a>( friendly_name: Option<&'a [u8]>, local_key_id: Option<&'a [u8]>, @@ -311,7 +186,7 @@ struct KeySerializationEncryption<'a> { mac_algorithm: pyo3::Bound<'a, pyo3::PyAny>, mac_kdf_iter: u64, cipher_kdf_iter: u64, - encryption_algorithm: Option, + encryption_algorithm: Option, } #[allow(clippy::type_complexity)] @@ -339,12 +214,12 @@ fn decode_encryption_algorithm<'a>( let key_cert_alg = encryption_algorithm.getattr(pyo3::intern!(py, "_key_cert_algorithm"))?; let cipher = if key_cert_alg.is(&types::PBES_PBESV1SHA1AND3KEYTRIPLEDESCBC.get(py)?) { - EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC + pbe::EncryptionAlgorithm::PBESHA1And3KeyTripleDESCBC } else if key_cert_alg.is(&types::PBES_PBESV2SHA256ANDAES256CBC.get(py)?) { - EncryptionAlgorithm::PBESv2SHA256AndAES256CBC + pbe::EncryptionAlgorithm::PBESv2SHA256AndAES256CBC } else { assert!(key_cert_alg.is_none()); - EncryptionAlgorithm::PBESv2SHA256AndAES256CBC + pbe::EncryptionAlgorithm::PBESv2SHA256AndAES256CBC }; let hmac_alg = if let Some(v) = encryption_algorithm @@ -382,7 +257,7 @@ fn decode_encryption_algorithm<'a>( mac_algorithm: default_hmac_alg, mac_kdf_iter: default_hmac_kdf_iter, cipher_kdf_iter: default_cipher_kdf_iter, - encryption_algorithm: Some(EncryptionAlgorithm::PBESv2SHA256AndAES256CBC), + encryption_algorithm: Some(pbe::EncryptionAlgorithm::PBESv2SHA256AndAES256CBC), }) } else { Err(CryptographyError::from( @@ -435,8 +310,7 @@ fn serialize_safebags<'p>( auth_safe_iv = crate::backend::rand::get_rand_bytes(py, 16)? .extract::()?; auth_safe_ciphertext = e.encrypt( - py, - password, + password.as_bytes(), encryption_details.cipher_kdf_iter, &auth_safe_salt, &auth_safe_iv, @@ -560,8 +434,7 @@ fn serialize_key_and_certificates<'p>( encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let encryption_details = decode_encryption_algorithm(py, encryption_algorithm)?; - let password = std::str::from_utf8(&encryption_details.password) - .map_err(|_| pyo3::exceptions::PyValueError::new_err("password must be valid UTF-8"))?; + let password = &encryption_details.password; let mut safebags = vec![]; let (key_salt, key_iv, key_ciphertext, pkcs8_bytes); @@ -631,7 +504,6 @@ fn serialize_key_and_certificates<'p>( key_iv = crate::backend::rand::get_rand_bytes(py, 16)? .extract::()?; key_ciphertext = e.encrypt( - py, password, encryption_details.cipher_kdf_iter, &key_salt, diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index 96b4d59ebc55..61eb4d932f2e 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -23,6 +23,7 @@ from cryptography.hazmat.primitives.ciphers.modes import CBC from cryptography.hazmat.primitives.serialization import ( Encoding, + PrivateFormat, PublicFormat, load_pem_private_key, ) @@ -737,6 +738,19 @@ def test_generate_localkeyid(self, backend, encryption_algorithm): ) assert p12.count(cert.fingerprint(hashes.SHA1())) == count + def test_invalid_utf8_password_pbes1(self, backend): + cert, key = _load_ca(backend) + encryption_algorithm = ( + PrivateFormat.PKCS12.encryption_builder() + .key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) + .build(b"\xff") + ) + + with pytest.raises(ValueError): + serialize_key_and_certificates( + None, key, cert, None, encryption_algorithm + ) + @pytest.mark.skip_fips( reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it."