Skip to content

Commit 554f12d

Browse files
committed
Add PKCS#12
1 parent c627228 commit 554f12d

44 files changed

Lines changed: 3793 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/api_ref/contents.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ API Reference
1616
cipher_modes
1717
pubkey
1818
x509
19+
pkcs12
1920
tls
2021
credentials_manager
2122
bigint

doc/api_ref/pkcs12.rst

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
PKCS#12
2+
========================================
3+
4+
PKCS#12 (also known as PFX) is a file format defined in :rfc:`7292` for
5+
storing cryptographic objects - typically a private key and its associated
6+
X.509 certificate chain - protected by a password. It is widely used for
7+
importing and exporting credentials in TLS servers, browsers, and
8+
certificate management tools.
9+
10+
This API is defined in ``botan/pkcs12.h``.
11+
12+
.. versionadded:: 3.13
13+
14+
PKCS12_Export_Options
15+
-----------------------------------------
16+
17+
.. cpp:class:: PKCS12_Export_Options
18+
19+
Options controlling how a PKCS#12 file is generated. Construct with the
20+
password (mandatory) and an optional friendly name; tweak individual
21+
fields with the chainable ``with_*`` mutators, or pick a preset via the
22+
static pseudo-constructors below.
23+
24+
.. cpp:function:: explicit PKCS12_Export_Options(std::string_view password, \
25+
std::optional<std::string> friendly_name = {})
26+
27+
Constructs an options object with modern defaults: PBES2-SHA256-AES256
28+
key encryption, SHA-256 MAC, 100 000 KDF iterations, certificates
29+
stored unencrypted.
30+
31+
.. cpp:function:: static PKCS12_Export_Options modern(std::string_view password, \
32+
std::optional<std::string> friendly_name = {})
33+
34+
Pseudo-constructor for modern defaults (identical to the regular
35+
constructor). Spelled out for clarity at call sites.
36+
37+
.. cpp:function:: static PKCS12_Export_Options legacy_compat(std::string_view password, \
38+
std::optional<std::string> friendly_name = {})
39+
40+
Pseudo-constructor for legacy-compatible defaults: PBE-SHA1-3DES key
41+
encryption, SHA-1 MAC, 2 048 KDF iterations. Use when interoperability
42+
with old software (Java keytool pre-2019, legacy OpenSSL releases,
43+
pre-Windows-10) is required.
44+
45+
.. cpp:function:: PKCS12_Export_Options& with_friendly_name(std::string name)
46+
47+
Overrides the friendly name. If unset, the bundle-level friendly name
48+
(see :cpp:func:`PKCS12::set_friendly_name`) is used.
49+
50+
.. cpp:function:: PKCS12_Export_Options& with_iterations(size_t n)
51+
52+
Sets the KDF iteration count. Values of 0 or above 1 000 000
53+
(``PKCS12_MAX_ITERATIONS``) cause an ``Invalid_Argument`` exception at
54+
export time.
55+
56+
.. cpp:function:: PKCS12_Export_Options& with_key_encryption_algo(std::string algo)
57+
58+
Algorithm used to encrypt the private key (PKCS8ShroudedKeyBag).
59+
Supported values:
60+
61+
- ``"PBES2-SHA256-AES256"`` - modern (default)
62+
- ``"PBES2-SHA256-AES128"`` - modern
63+
- ``"PBE-SHA1-3DES"`` - legacy
64+
- ``"PBE-SHA1-2DES"`` - legacy
65+
66+
.. cpp:function:: PKCS12_Export_Options& with_cert_encryption_algo(std::string algo)
67+
68+
Algorithm used to encrypt certificates. If the algo is empty (default),
69+
certificates are stored unencrypted. A non-empty value requires a
70+
non-empty password.
71+
72+
.. cpp:function:: PKCS12_Export_Options& with_mac_digest(std::string algo)
73+
74+
Hash algorithm for the integrity MAC. Supported: ``"SHA-1"``,
75+
``"SHA-224"``, ``"SHA-256"``, ``"SHA-384"``, ``"SHA-512"``,
76+
``"SHA-512-256"``. Default is ``"SHA-256"``.
77+
78+
.. cpp:function:: PKCS12_Export_Options& without_mac()
79+
80+
Disables the integrity MAC. Generally not recommended; required if the
81+
password is empty.
82+
83+
PKCS12
84+
-----------------------------------------
85+
86+
.. cpp:class:: PKCS12
87+
88+
PKCS#12/PFX bundle: parse, inspect, mutate, and export. The default
89+
constructor produces an empty bundle that the caller fills via the
90+
``add_*`` / ``set_*`` mutators before invoking :cpp:func:`export_to`.
91+
92+
.. cpp:function:: PKCS12()
93+
94+
Constructs an empty bundle.
95+
96+
.. cpp:function:: PKCS12(std::span<const uint8_t> data, std::string_view password)
97+
98+
Parses a DER-encoded PFX file. Throws ``Decoding_Error`` if the file
99+
is malformed, or ``Invalid_Authentication_Tag`` if MAC verification
100+
fails.
101+
102+
Accessors
103+
^^^^^^^^^
104+
105+
.. cpp:function:: const std::vector<std::shared_ptr<Private_Key>>& private_keys() const
106+
107+
Private keys stored in the bundle, in storage order (parse-order for
108+
parsed PFX, insertion-order for built ones). PKCS#12 supports multiple
109+
keys per file.
110+
111+
.. cpp:function:: const std::vector<X509_Certificate>& certificates() const
112+
113+
All certificates stored in the bundle. The end-entity, if any, comes
114+
first when produced by parsing; insertion order is preserved for
115+
bundles built in memory.
116+
117+
.. cpp:function:: std::optional<X509_Certificate> end_entity_certificate() const
118+
119+
The first certificate whose ``subjectPublicKeyInfo`` matches one of
120+
the stored private keys, or ``nullopt`` if none match (e.g. a
121+
certificate-only or key-only bundle).
122+
123+
.. cpp:function:: std::vector<X509_Certificate> ca_certificates() const
124+
125+
Convenience helper: every certificate except the one returned by
126+
:cpp:func:`end_entity_certificate`. Returned in storage order. For a
127+
key-less bundle, returns all certificates after the first.
128+
129+
.. cpp:function:: const std::optional<std::string>& friendly_name() const
130+
131+
The ``friendlyName`` attribute, if present.
132+
133+
.. cpp:function:: const std::optional<std::vector<uint8_t>>& local_key_id() const
134+
135+
The ``localKeyId`` attribute, if present.
136+
137+
.. cpp:function:: const std::vector<OID>& unknown_bag_types() const
138+
139+
OIDs of bag types encountered during parsing but not handled by this
140+
implementation (e.g. ``SecretBag``). Empty for normal PKCS#12 files
141+
and for bundles constructed in-memory.
142+
143+
Mutators
144+
^^^^^^^^
145+
146+
.. cpp:function:: void add_key(std::shared_ptr<Private_Key> key)
147+
148+
Adds a private key to the bundle.
149+
150+
.. cpp:function:: void add_certificate(X509_Certificate cert)
151+
152+
Adds a certificate. End-entity vs CA is determined at export time by
153+
matching against stored keys.
154+
155+
.. cpp:function:: void set_friendly_name(std::string name)
156+
.. cpp:function:: void clear_friendly_name()
157+
.. cpp:function:: void set_local_key_id(std::vector<uint8_t> id)
158+
.. cpp:function:: void clear_local_key_id()
159+
160+
Set or clear the bundle-level friendly name / localKeyId attributes.
161+
162+
Export
163+
^^^^^^
164+
165+
.. cpp:function:: std::vector<uint8_t> export_to(const PKCS12_Export_Options& options, \
166+
RandomNumberGenerator& rng) const
167+
168+
Serializes the bundle as a PKCS#12/PFX file.
169+
170+
Throws ``Invalid_Argument`` if the options are inconsistent (e.g.
171+
unsupported algorithm, MAC enabled with empty password) or if a
172+
stored private key does not match any stored certificate.
173+
174+
Iteration counts above ``PKCS12_MAX_ITERATIONS`` (1 000 000) and a
175+
SafeContentsBag nesting depth above ``PKCS12_MAX_NESTING`` (10) are
176+
rejected.
177+
178+
Example
179+
-----------------------------------------
180+
181+
Generating a PFX file:
182+
183+
.. code-block:: cpp
184+
185+
#include <botan/pkcs12.h>
186+
#include <botan/auto_rng.h>
187+
#include <botan/ec_group.h>
188+
#include <botan/ecdsa.h>
189+
#include <botan/x509self.h>
190+
191+
Botan::AutoSeeded_RNG rng;
192+
auto key = std::make_shared<Botan::ECDSA_PrivateKey>(
193+
rng, Botan::EC_Group::from_name("secp256r1"));
194+
195+
Botan::X509_Cert_Options cert_opts("CN=example.com");
196+
auto cert = Botan::X509::create_self_signed_cert(cert_opts, *key, "SHA-256", rng);
197+
198+
Botan::PKCS12 bundle;
199+
bundle.add_key(key);
200+
bundle.add_certificate(cert);
201+
bundle.set_friendly_name("My Key");
202+
203+
const auto pfx = bundle.export_to(
204+
Botan::PKCS12_Export_Options::modern("secret"), rng);
205+
// For maximum compatibility with old software:
206+
// Botan::PKCS12_Export_Options::legacy_compat("secret")
207+
208+
Parsing a PFX file:
209+
210+
.. code-block:: cpp
211+
212+
#include <botan/pkcs12.h>
213+
#include <botan/exceptn.h>
214+
215+
// pfx_bytes loaded from file
216+
try {
217+
const Botan::PKCS12 bundle(pfx_bytes, "secret");
218+
219+
if(!bundle.private_keys().empty()) {
220+
const auto& key = bundle.private_keys().front();
221+
// key->algo_name(), key->key_length(), ...
222+
}
223+
if(const auto ee = bundle.end_entity_certificate()) {
224+
// ee->subject_dn(), ee->fingerprint("SHA-256"), ...
225+
}
226+
for(const auto& ca : bundle.ca_certificates()) {
227+
// Process CA chain entries
228+
}
229+
} catch(const Botan::Invalid_Authentication_Tag&) {
230+
// Wrong password or corrupted MAC
231+
} catch(const Botan::Decoding_Error& e) {
232+
// Malformed or unsupported PFX file
233+
}
234+
235+
Creating a PFX with a certificate chain:
236+
237+
.. code-block:: cpp
238+
239+
// Assuming key, end_cert, and ca_chain are already available
240+
Botan::PKCS12 bundle;
241+
bundle.add_key(key);
242+
bundle.add_certificate(end_cert);
243+
for(const auto& ca : ca_chain) {
244+
bundle.add_certificate(ca);
245+
}
246+
247+
const auto pfx = bundle.export_to(
248+
Botan::PKCS12_Export_Options::modern("secret", "Server Key"), rng);
249+
250+
.. note::
251+
252+
The default encryption algorithm is ``PBES2-SHA256-AES256`` with
253+
``SHA-256`` MAC and 100 000 KDF iterations. For maximum compatibility
254+
with legacy software (older Java keytool, legacy OpenSSL builds), use
255+
:cpp:func:`PKCS12_Export_Options::legacy_compat` or explicitly configure
256+
``with_key_encryption_algo("PBE-SHA1-3DES")``,
257+
``with_mac_digest("SHA-1")``, and ``with_iterations(2048)``.
258+
259+
Supported Algorithms
260+
-----------------------------------------
261+
262+
The following algorithms are available depending on which Botan modules are built:
263+
264+
.. list-table::
265+
:header-rows: 1
266+
:widths: 25 30 25 20
267+
268+
* - Field
269+
- Value
270+
- Required module
271+
- Notes
272+
* - ``with_key_encryption_algo``
273+
- ``"PBES2-SHA256-AES256"``
274+
- ``pbes2``, ``aes``
275+
- Default; recommended for modern use
276+
* - ``with_key_encryption_algo``
277+
- ``"PBES2-SHA256-AES128"``
278+
- ``pbes2``, ``aes``
279+
- Modern
280+
* - ``with_key_encryption_algo``
281+
- ``"PBE-SHA1-3DES"``
282+
- ``pkcs12_pbe``, ``des``
283+
- Legacy; widest compatibility
284+
* - ``with_key_encryption_algo``
285+
- ``"PBE-SHA1-2DES"``
286+
- ``pkcs12_pbe``, ``des``
287+
- Legacy
288+
* - ``with_cert_encryption_algo``
289+
- Same as above, or ``""``
290+
- -
291+
- Empty = certificates stored unencrypted
292+
* - ``with_mac_digest``
293+
- ``"SHA-256"``
294+
- ``sha2_32``
295+
- Default; required by OpenSSL 3.x default policy
296+
* - ``with_mac_digest``
297+
- ``"SHA-1"``
298+
- ``sha1``
299+
- Legacy; widest compatibility
300+
* - ``with_mac_digest``
301+
- ``"SHA-384"``, ``"SHA-512"``
302+
- ``sha2_64``
303+
- Uncommon; supported for parsing and generation
304+
305+
Command Line Interface
306+
-----------------------------------------
307+
308+
Two CLI commands are available when Botan is built with filesystem support.
309+
310+
``pkcs12_export``
311+
^^^^^^^^^^^^^^^^^
312+
313+
.. code-block:: none
314+
315+
botan pkcs12_export [--pass=<pfx-password>] [--in-key-pass=<key-password>]
316+
[--friendly-name=<name>]
317+
[--key-cipher=<algo>] (default: PBES2-SHA256-AES256)
318+
[--cert-cipher=<algo>] (default: unencrypted)
319+
[--no-mac]
320+
[--mac-digest=<digest>] (SHA-256, SHA-1, SHA-224, SHA-384, SHA-512, SHA-512-256)
321+
[--iterations=<n>] (default: 100000)
322+
<key-file> <cert-file> [ca-cert ...]
323+
324+
Exports a private key and certificate(s) to a PFX file written to stdout
325+
or ``--output``.
326+
327+
``pkcs12_import``
328+
^^^^^^^^^^^^^^^^^
329+
330+
.. code-block:: none
331+
332+
botan pkcs12_import [--pass=<pfx-password>]
333+
[--key-out=<file>] (PEM private key)
334+
[--cert-out=<file>] (PEM end-entity certificate)
335+
[--chain-out=<file>] (PEM CA certificate chain)
336+
[--out-key-pass=<password>]
337+
[--out-key-cipher=<algo>] (default: AES-256/CBC)
338+
[--key-pbkdf-iter=<n>] (default: 100000)
339+
<pfx-file>
340+
341+
Parses a PFX file. Without output file arguments, prints a summary of the
342+
contents to stdout including subject, issuer, validity, serial number, and
343+
both SHA-1 and SHA-256 fingerprints of the end-entity certificate.

0 commit comments

Comments
 (0)