Skip to content

Pkcs12 keyloading check - #5925

Open
falko-strenzke wants to merge 1 commit into
randombit:masterfrom
falko-strenzke:pkcs12-keyloading-check
Open

falko-strenzke wants to merge 1 commit into
randombit:masterfrom
falko-strenzke:pkcs12-keyloading-check

Conversation

@falko-strenzke

Copy link
Copy Markdown
Collaborator

No description provided.

@randombit

Copy link
Copy Markdown
Owner

Needs a squash and a more clear commit message. Test inputs should be pregenerated and stored in src/tests/data/pkcs12

@falko-strenzke
falko-strenzke force-pushed the pkcs12-keyloading-check branch from 433a734 to bfbc8c9 Compare September 8, 2026 06:46
@falko-strenzke

Copy link
Copy Markdown
Collaborator Author

Needs a squash and a more clear commit message. Test inputs should be pregenerated and stored in src/tests/data/pkcs12

All addressed now. The commit message reads:

Reject inconsistent private keys when parsing PKCS#12 files

The MAC in a PKCS#12 file is optional, and the PBE schemes used for
PKCS8ShroudedKeyBag and EncryptedData provide no authentication of their
own. In a file without a MAC, or with a MAC computed under a password the
attacker knows, the CBC encrypted private key is malleable. A modification
that lands inside the private scalar of an EC key yields a key that still
decodes, still carries the original public point (which the EC PKCS#8
decoder takes verbatim), still matches the end-entity certificate, and
signs without any error, producing signatures that verify under neither
the certificate nor the key's own public point.

The parser now runs Private_Key::check_key (non-strong) on every key
loaded from a KeyBag or PKCS8ShroudedKeyBag and rejects the file with a
Decoding_Error if the check fails. No RNG is available in the parser, so a
Null_RNG is passed; the non-strong checks of EC, RSA and DL keys need no
randomness (primality testing falls back to a deterministic method when
the RNG is unseeded). Key types whose check requires randomness, such as
ML-KEM, raise PRNG_Unseeded, which is caught so that such keys continue to
load unchecked.

For RSA the corruption was already caught at signing time by the CRT
consistency check in the private operation; the new check moves the
failure to import time and reports it as a decoding error rather than an
Internal_Error on first use.

Tests use pregenerated inputs under src/tests/data/pkcs12: OpenSSL
generated P-256 and RSA-2048 keys whose PKCS#8 encoding was modified
(foreign public point, respectively flipped bit in dP), exported with
"openssl pkcs12 -export" in PBES2, PBE-SHA1-3DES and unencrypted KeyBag
form, plus a consistent MAC-less file for a bit-flip regression sweep and
two Botan generated ML-KEM-512 bundles covering the fallback path. The
generation procedure is documented in the test source.

@falko-strenzke
falko-strenzke force-pushed the pkcs12-keyloading-check branch 3 times, most recently from 77c6abc to 71409a5 Compare September 11, 2026 10:52
The MAC in a PKCS#12 file is optional, and the PBE schemes used for
PKCS8ShroudedKeyBag and EncryptedData provide no authentication of their
own. In a file without a MAC, or with a MAC computed under a password the
attacker knows, the CBC encrypted private key is malleable. A modification
that lands inside the private scalar of an EC key yields a key that still
decodes, still carries the original public point (which the EC PKCS#8
decoder takes verbatim), still matches the end-entity certificate, and
signs without any error, producing signatures that verify under neither
the certificate nor the key's own public point.

The parser now runs Private_Key::check_key (non-strong) on every key
loaded from a KeyBag or PKCS8ShroudedKeyBag and rejects the file with a
Decoding_Error if the check fails. No RNG is available in the parser, so a
Null_RNG is passed; the non-strong checks of EC, RSA and DL keys need no
randomness (primality testing falls back to a deterministic method when
the RNG is unseeded). Key types whose check requires randomness, such as
ML-KEM, raise PRNG_Unseeded, which is caught so that such keys continue to
load unchecked.

For RSA the corruption was already caught at signing time by the CRT
consistency check in the private operation; the new check moves the
failure to import time and reports it as a decoding error rather than an
Internal_Error on first use.

Tests use pregenerated inputs under src/tests/data/pkcs12: OpenSSL
generated P-256 and RSA-2048 keys whose PKCS#8 encoding was modified
(foreign public point, respectively flipped bit in dP), exported with
"openssl pkcs12 -export" in PBES2, PBE-SHA1-3DES and unencrypted KeyBag
form, plus a consistent MAC-less file for a bit-flip regression sweep and
two Botan generated ML-KEM-512 bundles covering the fallback path. The
generation procedure is documented in the test source.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants