Skip to content

Commit

Permalink
Implement encrypted PKCS#12 serialization in Rust
Browse files Browse the repository at this point in the history
  • Loading branch information
alex committed Jun 3, 2024
1 parent d54d673 commit 473fbe6
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 48 deletions.
21 changes: 1 addition & 20 deletions src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,26 +398,7 @@ def serialize_key_and_certificates_to_pkcs12(
if name is not None:
utils._check_bytes("name", name)

assert not isinstance(encryption_algorithm, serialization.NoEncryption)
if isinstance(
encryption_algorithm, serialization.BestAvailableEncryption
):
# PKCS12 encryption is hopeless trash and can never be fixed.
# OpenSSL 3 supports PBESv2, but Libre and Boring do not, so
# we use PBESv1 with 3DES on the older paths.
if rust_openssl.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
nid_cert = self._lib.NID_aes_256_cbc
nid_key = self._lib.NID_aes_256_cbc
else:
nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
# At least we can set this higher than OpenSSL's default
pkcs12_iter = 20000
mac_iter = 0
# MAC algorithm can only be set on OpenSSL 3.0.0+
mac_alg = self._ffi.NULL
password = encryption_algorithm.password
elif (
if (
isinstance(
encryption_algorithm, serialization._KeySerializationEncryption
)
Expand Down
6 changes: 5 additions & 1 deletion src/cryptography/hazmat/primitives/serialization/pkcs12.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,11 @@ def serialize_key_and_certificates(
if key is None and cert is None and not cas:
raise ValueError("You must supply at least one of key, cert, or cas")

if isinstance(encryption_algorithm, serialization.NoEncryption):
if isinstance(
encryption_algorithm, serialization.NoEncryption
) or isinstance(
encryption_algorithm, serialization.BestAvailableEncryption
):
return rust_pkcs12.serialize_key_and_certificates(
name, key, cert, cas, encryption_algorithm
)
Expand Down
28 changes: 28 additions & 0 deletions src/rust/cryptography-x509/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ pub enum AlgorithmParameters<'a> {
#[defined_by(oid::DH_KEY_AGREEMENT_OID)]
DhKeyAgreement(BasicDHParams<'a>),

#[defined_by(oid::PBES2_OID)]
Pbes2(PBES2Params<'a>),

#[defined_by(oid::PBKDF2_OID)]
Pbkdf2(PBKDF2Params<'a>),

#[defined_by(oid::HMAC_WITH_SHA1_OID)]
HmacWithSha1(asn1::Null),
#[defined_by(oid::HMAC_WITH_SHA256_OID)]
Expand Down Expand Up @@ -403,6 +409,28 @@ pub struct DssParams<'a> {
pub g: asn1::BigUint<'a>,
}

#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)]
pub struct PBES2Params<'a> {
pub key_derivation_func: Box<AlgorithmIdentifier<'a>>,
pub encryption_scheme: Box<AlgorithmIdentifier<'a>>,
}

const HMAC_SHA1_ALG: AlgorithmIdentifier<'static> = AlgorithmIdentifier {
oid: asn1::DefinedByMarker::marker(),
params: AlgorithmParameters::HmacWithSha1(()),
};

#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)]
pub struct PBKDF2Params<'a> {
// This is technically a CHOICE that can be an otherSource. We don't
// support that.
pub salt: &'a [u8],
pub iteration_count: u64,
pub key_length: Option<u64>,
#[default(HMAC_SHA1_ALG)]
pub prf: Box<AlgorithmIdentifier<'a>>,
}

/// A VisibleString ASN.1 element whose contents is not validated as meeting the
/// requirements (visible characters of IA5), and instead is only known to be
/// valid UTF-8.
Expand Down
11 changes: 10 additions & 1 deletion src/rust/cryptography-x509/src/pkcs12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.

use crate::common::Utf8StoredBMPString;
use crate::common::{AlgorithmIdentifier, Utf8StoredBMPString};
use crate::pkcs7;

pub const CERT_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 3);
Expand Down Expand Up @@ -55,6 +55,9 @@ pub enum BagValue<'a> {

#[defined_by(KEY_BAG_OID)]
KeyBag(asn1::Tlv<'a>),

#[defined_by(SHROUDED_KEY_BAG_OID)]
ShroudedKeyBag(EncryptedPrivateKeyInfo<'a>),
}

#[derive(asn1::Asn1Write)]
Expand All @@ -69,3 +72,9 @@ pub enum CertType<'a> {
#[defined_by(X509_CERTIFICATE_OID)]
X509(asn1::OctetStringEncoded<crate::certificate::Certificate<'a>>),
}

#[derive(asn1::Asn1Write)]
pub struct EncryptedPrivateKeyInfo<'a> {
pub encryption_algorithm: AlgorithmIdentifier<'a>,
pub encrypted_data: &'a [u8],
}
16 changes: 16 additions & 0 deletions src/rust/cryptography-x509/src/pkcs7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub enum Content<'a> {
SignedData(asn1::Explicit<Box<SignedData<'a>>, 0>),
#[defined_by(PKCS7_DATA_OID)]
Data(Option<asn1::Explicit<&'a [u8], 0>>),
#[defined_by(PKCS7_ENCRYPTED_DATA_OID)]
EncryptedData(asn1::Explicit<EncryptedData<'a>, 0>),
}

#[derive(asn1::Asn1Write)]
Expand Down Expand Up @@ -60,6 +62,20 @@ pub struct IssuerAndSerialNumber<'a> {
pub serial_number: asn1::BigInt<'a>,
}

#[derive(asn1::Asn1Write)]
pub struct EncryptedData<'a> {
pub version: u8,
pub encrypted_content_info: EncryptedContentInfo<'a>,
}

#[derive(asn1::Asn1Write)]
pub struct EncryptedContentInfo<'a> {
pub content_type: asn1::ObjectIdentifier,
pub content_encryption_algorithm: common::AlgorithmIdentifier<'a>,
#[implicit(0)]
pub encrypted_content: Option<&'a [u8]>,
}

#[derive(asn1::Asn1Write)]
pub struct DigestInfo<'a> {
pub algorithm: common::AlgorithmIdentifier<'a>,
Expand Down
8 changes: 4 additions & 4 deletions src/rust/src/backend/ciphers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ use crate::types;
use pyo3::prelude::{PyAnyMethods, PyModuleMethods};
use pyo3::IntoPy;

struct CipherContext {
pub(crate) struct CipherContext {
ctx: openssl::cipher_ctx::CipherCtx,
py_mode: pyo3::PyObject,
}

impl CipherContext {
fn new(
pub(crate) fn new(
py: pyo3::Python<'_>,
algorithm: pyo3::Bound<'_, pyo3::PyAny>,
mode: pyo3::Bound<'_, pyo3::PyAny>,
Expand Down Expand Up @@ -126,7 +126,7 @@ impl CipherContext {
Ok(pyo3::types::PyBytes::new_bound(py, &out_buf[..n]))
}

fn update_into(
pub(crate) fn update_into(
&mut self,
py: pyo3::Python<'_>,
buf: &[u8],
Expand Down Expand Up @@ -167,7 +167,7 @@ impl CipherContext {
Ok(())
}

fn finalize<'p>(
pub(crate) fn finalize<'p>(
&mut self,
py: pyo3::Python<'p>,
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
Expand Down
2 changes: 1 addition & 1 deletion src/rust/src/backend/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::error::CryptographyResult;
use pyo3::prelude::PyModuleMethods;

#[pyo3::prelude::pyfunction]
fn derive_pbkdf2_hmac<'p>(
pub(crate) fn derive_pbkdf2_hmac<'p>(
py: pyo3::Python<'p>,
key_material: CffiBuf<'_>,
algorithm: &pyo3::Bound<'_, pyo3::PyAny>,
Expand Down
10 changes: 9 additions & 1 deletion src/rust/src/buf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ fn _extract_buffer_length<'p>(
Ok((bufobj, ptrval))
}

impl CffiBuf<'_> {
impl<'a> CffiBuf<'a> {
pub(crate) fn from_bytes(py: pyo3::Python<'a>, buf: &'a [u8]) -> Self {
CffiBuf {
_pyobj: py.None().into_bound(py),
_bufobj: py.None().into_bound(py),
buf,
}
}

pub(crate) fn as_bytes(&self) -> &[u8] {
self.buf
}
Expand Down
Loading

0 comments on commit 473fbe6

Please sign in to comment.