Fix private key file permissions and several CLI correctness bugs - #298
Open
julek-wolfssl wants to merge 21 commits into
Open
julek-wolfssl wants to merge 21 commits into
julek-wolfssl wants to merge 21 commits into
Conversation
Private keys were opened with fopen/BIO_new_file, so under umask 022 they were world-readable. Add wolfCLU_FileOpenOwner/wolfCLU_BioOpenOwner, which create new files with mode 0600 on POSIX, and use them for all genkey private outputs and the XMSS state file. WOLFCLU_POSIX_FILE enables the POSIX path on the platforms where wolfSSL uses it for wc_fopen_owner_only(), and only when wolfSSL defines XFDOPEN and XCLOSE. open() is called directly: wolfSSL's open wrappers are WOLFSSL_LOCAL and not exported from the library. F-8096
pkcs8 -out always holds a private key but was opened with BIO_new_file, so under umask 022 it was world-readable. Use wolfCLU_BioOpenOwner so new files are created 0600 on POSIX. F-9854
rsa opened -out before knowing if a private key would be written, so under umask 022 the key file was world-readable. Open -out after option parsing and use wolfCLU_BioOpenOwner unless the output is public only. -out without a file name fails instead of falling back to stdout. F-9855
dsaparam -genkey wrote the DSA private key through wolfSSL_BIO_new_file, so under umask 022 the file was world-readable. Open the output with wolfCLU_BioOpenOwner when -genkey is given. Params-only output is unchanged. F-9856
pkcs12 opened -out with BIO_new_file, so extracted private keys were world-readable under umask 022. Defer opening until options are parsed and use wolfCLU_BioOpenOwner unless -nokeys is given. -out without a file name fails instead of falling back to stdout. F-9861
req wrote the generated private key with BIO_new_file, so under umask 022 it was world-readable. Open -keyout with wolfCLU_BioOpenOwner so a new key file gets mode 0600, encrypted or not. F-9860
server_test continued with a NULL method and ctx when the requested version was not compiled in. Treat a NULL method or ctx as fatal and propagate server_test's return code so s_server exits non-zero. F-12925
ServerEchoData cast the size_t throughput remainder to int before min(). Remainders that are multiples of 2^32 truncated to 0 and the echo loop spun forever. Compare against the block size as size_t first. F-9845
Mode prefixes like "c" or "cb" passed validation but set no cipher, so encrypt/decrypt used an uninitialised alg and wrote garbage output. Require an exact mode, start alg at WOLFCLU_ALGO_NONE and fail if no cipher was chosen. Initialise alg in wolfCLU_setup too. F-11081
wolfCLU_GetStdinPassword ran strlen on the buffer after fgets failed, reading uninitialised caller buffers. It now returns an empty password of size 0 on failure. pkcs12, pkcs8 and req check the result instead of encrypting or decrypting with a stale or garbage password. F-12121
With -key/-inkey and -iv, the legacy (Camellia) encrypt path wrote an all-zero salt, so decrypt left the block padding in the output. Set salt[0] to the pad count so decrypt strips it. Old files decrypt as before. F-11082
wolfCLU_certSetup set v3 on every parsed cert before checking -req, so -text on a v1 cert reported "Version: 3". Check reqFlag first. certs/server-ecc-v1-cert.pem made with OpenSSL 3.0.13: openssl x509 -req (no extensions) from an ecc-key.pem CSR, signed by ca-ecc-cert.pem. F-12924
wc_HashGetDigestSize's negative result was stored unsigned, and wc_Hash or missing-DER failures never set ret, so -fingerprint exited 0 with no output. Keep the digest size signed, reject <= 0, and return WOLFCLU_FATAL_ERROR with a message on each failure. F-12122
wolfCLU_ParseX509NameString kept CTC_PRINTABLE after a C= entry, so later entries (e.g. a CN with '_' or '@') were encoded as PrintableString. Choose the encoding per entry: C is PrintableString, others UTF8String. Declare the per-entry variables inside the loop so no value can carry over from a previous entry. F-9842
"CA:TRUE, pathlen:0" was split on ':' only, so CA stayed FALSE and pathlen was dropped without an error. Split on ',' then at the first ':', trim whitespace, set pathlen the way wolfSSL_X509_add_ext reads it, and fail on unknown or malformed entries instead of emitting a wrong extension. F-11080
The DER branch always used the traditional encoder, so -topk8 -nocrypt -outform DER wrote RSAPrivateKey/ECPrivateKey. For -topk8, re-encode the key and wrap it with wolfSSL_i2d_PKCS8_PKEY, so PKCS#8 DER input is not double wrapped. Zero the key buffers before freeing. F-9841
pkey opened -out before knowing if a private key would be written, so under umask 022 the converted key file was world-readable. Open -out after option parsing and use wolfCLU_BioOpenOwner unless -pubout/-pubin. -out without a file name fails instead of falling back to stdout. F-9859
dhparam -genkey wrote the DH private key through wolfSSL_BIO_new_file, so under umask 022 the file was world-readable. Open the output with wolfCLU_BioOpenOwner when -genkey is given. Params-only output is unchanged. F-9857
ecparam -genkey wrote the EC private key through wolfSSL_BIO_new_file, so under umask 022 the file was world-readable. Open the output with wolfCLU_BioOpenOwner when -genkey is given. Params-only output is unchanged. F-9858
req -verify always called wolfSSL_X509_REQ_verify, so a certificate made with -x509 was parsed as a CSR and failed. Use wolfSSL_X509_verify for generated certs, and verify with the req/cert public key, since wolfSSL cannot verify with an RSA private key object. F-9846
Concurrent signers could reload the same XMSS state and reuse a one-time key. Hold an flock() on the .priv file (opened read-write, as NFS needs) from reload until the new state is saved and fsynced. On CIFS mounts without nobrl the lock is mandatory and signing fails closed. flock(), fsync() and fileno() are called directly: wolfSSL has no wrappers for them. F-12944
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved critical server null-dereference paths and a moderate pathlen overflow issue block approval.
Review effort: Lite
Findings: 2
Open (3)
What changed in this PR
This PR hardens private-key output permissions and fixes CLI, X.509, PKCS#8, encryption, server, and XMSS behavior.
Changes:
- Adds owner-only file creation and XMSS state locking.
- Fixes parsing, password handling, certificate, encryption, and PKCS#8 behavior.
- Expands regression and permission tests.
Outstanding findings include two critical s_server null-dereference paths and a moderate pathlen integer-overflow issue.
| File | Description |
|---|---|
wolfclu/genkey/clu_genkey.h |
Declares XMSS locking helpers. |
wolfclu/clu_optargs.h |
Adds an empty-algorithm sentinel. |
wolfclu/clu_header_main.h |
Declares secure file helpers. |
tests/x509/x509-req-test.py |
Tests CSR, verification, and permissions. |
tests/x509/x509-process-test.py |
Tests v1 certificate handling. |
tests/wolfclu_test.py |
Adds PTY test support. |
tests/server/server-test.py |
Tests unsupported protocol versions. |
tests/pkey/rsa-test.py |
Tests RSA output and permissions. |
tests/pkey/pkey-test.py |
Tests PKEY output and permissions. |
tests/pkey/ecparam-test.py |
Tests EC output permissions. |
tests/pkcs/pkcs8-test.py |
Tests PKCS#8 DER and passwords. |
tests/pkcs/pkcs12-test.py |
Tests PKCS#12 output and passwords. |
tests/genkey_sign_ver/genkey-sign-ver-test.py |
Tests XMSS locking and permissions. |
tests/encrypt/enc-test.py |
Tests cipher and padding fixes. |
tests/dsa/dsa-test.py |
Tests DSA output permissions. |
tests/dh/dh-test.py |
Tests DH output permissions. |
src/x509/clu_request_setup.c |
Fixes certificate verification and key output. |
src/x509/clu_config.c |
Parses basic constraints. |
src/x509/clu_cert_setup.c |
Fixes certificate versions and fingerprints. |
src/tools/clu_funcs.c |
Implements secure files, parsing, and EOF handling. |
src/sign-verify/clu_sign.c |
Serializes XMSS signing. |
src/server/server.c |
Fixes protocol failures and throughput sizing. |
src/server/clu_server_setup.c |
Propagates server failures. |
src/pkey/clu_rsa.c |
Secures RSA output and validates -out. |
src/pkey/clu_pkey.c |
Secures PKEY output and validates -out. |
src/pkcs/clu_pkcs8.c |
Fixes PKCS#8 DER conversion and passwords. |
src/pkcs/clu_pkcs12.c |
Secures output and handles password EOF. |
src/genkey/clu_genkey.c |
Secures key files and XMSS persistence. |
src/ecparam/clu_ecparam.c |
Secures generated EC keys. |
src/dsa/clu_dsa.c |
Secures generated DSA keys. |
src/dh/clu_dh.c |
Secures generated DH keys. |
src/crypto/clu_encrypt.c |
Records explicit-key padding. |
src/crypto/clu_crypto_setup.c |
Initializes algorithm selection safely. |
certs/server-ecc-v1-cert.pem |
Adds a v1 certificate fixture. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Security:
wolfCLU_FileOpenOwner/wolfCLU_BioOpenOwnerhelpers that create new files with mode 0600 on POSIX (via directopen(), gated byWOLFCLU_POSIX_FILE).fopen/BIO_new_filefor private key output in genkey, pkcs8, rsa, dsaparam -genkey, pkcs12, req -keyout, pkey, dhparam -genkey, ecparam -genkey, and the XMSS state file, so outputs are no longer world-readable under umask 022.-outwithout a file name in rsa, pkcs12, and pkey now fails instead of silently falling back to stdout.flock()on the.privstate file to prevent concurrent signers from reusing a one-time key.Bug fixes:
size_tbefore narrowing toint, fixing an infinite echo loop for remainders that are multiples of 2^32.parseAlgo: reject abbreviated/unsupported cipher modes instead of silently leaving the algorithm uninitialised.wolfCLU_GetStdinPassword: return an empty password on EOF instead of reading uninitialized memory; pkcs12, pkcs8, and req now check this result.-key/-iv: mark the pad count in the header so decrypt strips padding correctly.-req), so-texton a v1 cert reports the correct version.-fingerprint: fail with an error when SHA-1/hashing is unavailable instead of exiting 0 with no output.-subjparsing: reset the entry encoding per field so a value afterC=isn't incorrectly encoded as PrintableString.basicConstraints: parse as comma-separatedNAME:VALUEpairs, fixing droppedpathlenand ignoredCA:TRUE, and fail on malformed entries.-topk8 -outform DER: write proper PKCS#8 DER instead of double-wrapping the traditional key format.-verify: verify-x509-generated certificates as certificates rather than CSRs.