Skip to content

Commit 933503f

Browse files
committed
pkey: pass pem_password_cb to OSSL_DECODER only when it is needed
Specify OSSL_DECODER_CTX_set_pem_password_cb() only when we expect a passphrase-protected private key. OSSL_DECODER appears to try to decrypt every PEM block in the input even when the PEM header does not match the requested selection. This can cause repeated prompts for a passphrase in a single OpenSSL::PKey.read call.
1 parent 468f8ce commit 933503f

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

ext/openssl/ossl_pkey.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass)
9494
selection, NULL, NULL);
9595
if (!dctx)
9696
goto out;
97-
if (OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb,
97+
if (selection == EVP_PKEY_KEYPAIR &&
98+
OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb,
9899
ppass) != 1)
99100
goto out;
100101
while (1) {

test/openssl/test_pkey.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,86 @@ def test_s_read_der_then_pem
8585
assert_not_predicate(pkey, :private?)
8686
end
8787

88+
def test_s_read_passphrase
89+
orig = Fixtures.pkey("rsa-1")
90+
encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")
91+
assert_match(/\A-----BEGIN ENCRYPTED PRIVATE KEY-----/, encrypted_pem)
92+
93+
# Correct passphrase passed as the second argument
94+
pkey1 = OpenSSL::PKey.read(encrypted_pem, "correct_passphrase")
95+
assert_equal(orig.private_to_der, pkey1.private_to_der)
96+
97+
# Correct passphrase returned by the block. The block gets false
98+
called = 0
99+
flag = nil
100+
pkey2 = OpenSSL::PKey.read(encrypted_pem) { |f|
101+
called += 1
102+
flag = f
103+
"correct_passphrase"
104+
}
105+
assert_equal(orig.private_to_der, pkey2.private_to_der)
106+
assert_equal(1, called)
107+
assert_false(flag)
108+
109+
# Incorrect passphrase passed. The block is not called
110+
called = 0
111+
assert_raise(OpenSSL::PKey::PKeyError) {
112+
OpenSSL::PKey.read(encrypted_pem, "incorrect_passphrase") {
113+
called += 1
114+
}
115+
}
116+
assert_equal(0, called)
117+
118+
# Incorrect passphrase returned by the block. The block is called only once
119+
called = 0
120+
assert_raise(OpenSSL::PKey::PKeyError) {
121+
OpenSSL::PKey.read(encrypted_pem) {
122+
called += 1
123+
"incorrect_passphrase"
124+
}
125+
}
126+
assert_equal(1, called)
127+
end
128+
129+
def test_s_read_passphrase_tty
130+
omit "https://github.com/aws/aws-lc/pull/2555" if aws_lc?
131+
132+
orig = Fixtures.pkey("rsa-1")
133+
encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")
134+
135+
# Correct passphrase passed to OpenSSL's prompt
136+
script = <<~"end;"
137+
require "openssl"
138+
Process.setsid
139+
OpenSSL::PKey.read(#{encrypted_pem.dump})
140+
puts "ok"
141+
end;
142+
assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
143+
"correct_passphrase\n") { |stdout, stderr|
144+
assert_equal(["Enter PEM pass phrase:"], stderr)
145+
assert_equal(["ok"], stdout)
146+
}
147+
148+
# Incorrect passphrase passed to OpenSSL's prompt
149+
script = <<~"end;"
150+
require "openssl"
151+
Process.setsid
152+
begin
153+
OpenSSL::PKey.read(#{encrypted_pem.dump})
154+
rescue OpenSSL::PKey::PKeyError
155+
puts "ok"
156+
else
157+
puts "expected OpenSSL::PKey::PKeyError"
158+
end
159+
end;
160+
stdin = "incorrect_passphrase\n" * 5
161+
assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
162+
stdin) { |stdout, stderr|
163+
assert_equal(1, stderr.count("Enter PEM pass phrase:"))
164+
assert_equal(["ok"], stdout)
165+
}
166+
end if ENV["OSSL_TEST_ALL"] == "1" && Process.respond_to?(:setsid)
167+
88168
def test_hmac_sign_verify
89169
pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "abcd" })
90170

0 commit comments

Comments
 (0)