PKCS#12
Overview
A PKCS#12 file (also called a PFX bundle) packs a private key, its end-entity certificate, and an optional chain of CA certificates into a single password-protected DER blob. It is the interchange format used by browsers, Java keystores, and many TLS deployment tools.
Pkcs12 wraps OpenSSL’s PKCS12* structure. The two main operations are:
Pkcs12::from_der+parse— load an existing bundle and extract its contents.Pkcs12::create+to_der— assemble a new bundle from Rust objects.
Pkcs12 — Bundle Wrapper
#![allow(unused)]
fn main() {
pub struct Pkcs12 { /* PKCS12* */ }
impl Pkcs12 {
/// Allocate a new, empty PKCS#12 structure (PKCS12_new).
pub fn new() -> Result<Self, ErrorStack>;
/// Load a bundle from DER-encoded bytes.
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;
/// Serialise the bundle to DER.
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack>;
/// Parse the bundle, returning the private key, end-entity certificate,
/// and any additional CA certificates.
///
/// Pass `""` for an unencrypted bundle.
pub fn parse(
&self,
password: &str,
) -> Result<(Pkey<Private>, X509, Vec<X509>), ErrorStack>;
/// Create a new bundle from a private key and certificate.
///
/// - `password` — MAC / encryption passphrase.
/// - `name` — friendly name stored in the bundle (e.g. the subject CN).
/// - `key` — private key.
/// - `cert` — end-entity certificate.
/// - `ca` — slice of additional CA certificates (may be empty).
///
/// Uses AES-256-CBC for key encryption and SHA-256 for the MAC.
pub fn create(
password: &str,
name: &str,
key: &Pkey<Private>,
cert: &X509,
ca: &[X509],
) -> Result<Self, ErrorStack>;
}
unsafe impl Send for Pkcs12 {}
unsafe impl Sync for Pkcs12 {}
}
Pkcs12 does not implement Clone — the underlying PKCS12* has no
up_ref function.
Examples
Load a Bundle and Extract Key and Certificate
use native_ossl::pkcs12::Pkcs12;
let der = std::fs::read("bundle.p12")?;
let p12 = Pkcs12::from_der(&der)?;
let (key, cert, ca_chain) = p12.parse("my password")?;
println!("Key algorithm: {}", if key.is_a(c"RSA") { "RSA" } else { "other" });
if let Some(subject) = cert.subject_name().to_string() {
println!("Certificate subject: {subject}");
}
println!("CA chain length: {}", ca_chain.len());
Create a Bundle and Write to Disk
use native_ossl::pkcs12::Pkcs12;
use native_ossl::pkey::KeygenCtx;
use native_ossl::x509::{X509Builder, X509NameOwned};
// Generate a key pair.
let key = KeygenCtx::new(c"ED25519")?.generate()?;
// Build a self-signed certificate.
let mut name = X509NameOwned::new()?;
name.add_entry_by_txt(c"CN", b"example.com")?;
let cert = X509Builder::new()?
.set_version(2)?
.set_serial_number(1)?
.set_not_before_offset(0)?
.set_not_after_offset(365 * 86400)?
.set_subject_name(&name)?
.set_issuer_name(&name)?
.set_public_key(&key)?
.sign(&key, None)?
.build();
// Pack into a PKCS#12 bundle with no CA chain.
let p12 = Pkcs12::create("my password", "example.com", &key, &cert, &[])?;
let der = p12.to_der()?;
std::fs::write("bundle.p12", &der)?;
Round-Trip (Create, Serialise, Parse)
The DER encoding of a bundle is deterministic for a given set of inputs and OpenSSL defaults, so a DER round-trip can be used as a consistency check:
use native_ossl::pkcs12::Pkcs12;
let p12 = Pkcs12::create("pass", "friendly name", &key, &cert, &[])?;
let der1 = p12.to_der()?;
let p12b = Pkcs12::from_der(&der1)?;
let der2 = p12b.to_der()?;
assert_eq!(der1, der2);
let (key2, cert2, _ca) = p12b.parse("pass")?;
assert!(key2.is_a(c"ED25519"));
assert_eq!(cert.to_der()?, cert2.to_der()?);
Include a CA Chain
Pass CA certificates in chain order (intermediate first, root last):
use native_ossl::pkcs12::Pkcs12;
let p12 = Pkcs12::create(
"pass",
"my server",
&server_key,
&server_cert,
&[intermediate_cert, root_cert],
)?;
Design Notes
- Password encoding —
Pkcs12::createandPkcs12::parseaccept a Rust&str. The string is converted to a null-terminated C string internally. Null bytes in the password are rejected. - Encryption defaults —
PKCS12_create_exis called withnid_key = 0andnid_cert = 0, which tells OpenSSL to use its compiled-in defaults: AES-256-CBC for key bags and no additional certificate encryption. The MAC uses SHA-256. - CA certificate ownership —
Pkcs12::createcallsX509_up_refon each CA certificate before adding it to the internal stack. The originalX509values may be dropped before or aftercreatereturns. parseoutput ownership —Pkcs12::parsecallsPKCS12_parse, which allocates a newEVP_PKEY*andX509*(both fully owned). The CA chain is returned as aVec<X509>with each element independently reference-counted.- No
Clone—PKCS12has noup_ref; serialise withto_derand re-parse withfrom_derif a second independent copy is needed. new()returns an empty structure —Pkcs12::newwrapsPKCS12_newand allocates an uninitialised bundle. It is useful for low-level construction via the OpenSSL API directly; for normal use, preferPkcs12::create.