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