Repository navigation
Never ask the terminal for a PEM passphrase - #111
Merged
Merged
Conversation
Any PEM block may carry encryption headers (Proc-Type: 4,ENCRYPTED), and OpenSSL then asks for a passphrase. The package passed no callback, so OpenSSL's default one prompted on the terminal and read the answer there: X509Certificate(pem), read_pubkey(pem) and EvpPKey(pem) on such text blocked the calling thread inside the ccall until someone typed a line, and with it every other thread at the next collection. Without a terminal it prompted on stderr and read stdin. The reads now pass a callback that fails, so the parse throws at once. EvpPKey takes a `passphrase` keyword for an encrypted key, and writing a key with a cipher takes one too (both or neither), since the prompt was the only way to give one. The write's callback argument was declared Cint; it is a pointer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #111 +/- ##
==========================================
+ Coverage 83.07% 83.38% +0.31%
==========================================
Files 2 2
Lines 1341 1348 +7
==========================================
+ Hits 1114 1124 +10
+ Misses 227 224 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merged
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.
Any PEM block can carry the legacy encryption headers (
Proc-Type: 4,ENCRYPTEDplusDEK-Info), not just private keys. OpenSSL then asks the passphrase callback for a passphrase. This package passed no callback, so OpenSSL used its default one. That callback printsEnter PEM pass phrase:on the terminal and reads a line from it, or uses stderr and stdin when there is no terminal.This affects
X509Certificate(pem),OpenSSL.read_pubkey(pem)andEvpPKey(pem). In a process with a controlling terminal (started from a shell, tmux or ssh), parsing such text waits inside theccalluntil someone types a line. That thread never reaches a GC safe point, so every other thread stops at the next collection too.Fix
EvpPKey(pem; passphrase="...")reads an encrypted private key. OpenSSL's default callback hands over the given passphrase; it only prompts when given neither a callback nor a passphrase.write(io, key, cipher; passphrase="...")writes an encrypted key. Until now the terminal prompt was the only way to supply a passphrase. A cipher without a passphrase, or a passphrase without a cipher (which would write the key unencrypted), throwsArgumentError.Cint; it is now a pointer.Behavior change
passphraseused to prompt in an interactive session; it now throwsOpenSSLError.write(io, key, cipher)without a passphrase used to prompt; it now throwsArgumentError.Verification
PEMNeverAsksForPassphrase: a subprocess (stdindevnull) parses a certificate, a public key and a private key that carry encryption headers.mainthe stderr check fails: OpenSSL printed its prompt.OpenSSLErrorand nothing is printed.EncryptedPrivateKey:write(...; passphrase)andEvpPKey(...; passphrase);ArgumentErrorcases.script) whose input stays silent:main: all three parses were still blocked after 20 s. A task on another thread that allocates froze within a second of the prompt (heartbeat stuck at 10 for the next 9 s).-t 4: passes.Self-review
SSL_CTX_load_verify_locations, CA directories) already passes""as the callback argument, so it never prompts. These four calls were the only ones in the package without a callback.UI_UTIL_wrap_read_pem_callback, so a -1 return fails the UI step without opening the console. The 1.1PEM_do_headerpath treats< 0as "bad password read". Both paths were exercised on 3.5.Cstring, so an embedded NUL throws instead of silently shortening it. OpenSSL's default callback copies at mostPEM_BUFSIZE(1024) bytes for reads; that is OpenSSL's limit.@cfunctionof a top-level function with a fixed signature is computed when called, so precompilation is unaffected.🤖 Generated with Claude Code