Skip to content

Commit 985ba27

Browse files
committed
pkey: stop retrying after non-retryable error from OSSL_DECODER
Continue processing only when OSSL_DECODER_from_bio() returns the error code ERR_R_UNSUPPORTED. Otherwise, raise an exception without retrying decoding the input in another format. This fixes another case where OpenSSL::PKey.read prompts for a passphrase multiple times when the input contains multiple passphrase-protected PEM blocks and the first one cannot be decoded. I am not entirely sure if the error code ERR_R_UNSUPPORTED is considered part of the public interface of OpenSSL, but this seems to be the only option available and is the approach used internally by the PEM_read_bio_*() functions. Fixes #927
1 parent 933503f commit 985ba27

File tree

2 files changed

+35
-14
lines changed

2 files changed

+35
-14
lines changed

ext/openssl/ossl_pkey.c

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,15 @@ ossl_pkey_wrap(EVP_PKEY *pkey)
8383
# include <openssl/decoder.h>
8484

8585
static EVP_PKEY *
86-
ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass)
86+
ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass,
87+
int *retryable)
8788
{
8889
void *ppass = (void *)pass;
8990
OSSL_DECODER_CTX *dctx;
9091
EVP_PKEY *pkey = NULL;
9192
int pos = 0, pos2;
9293

94+
*retryable = 0;
9395
dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, input_type, NULL, NULL,
9496
selection, NULL, NULL);
9597
if (!dctx)
@@ -100,25 +102,29 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass)
100102
goto out;
101103
while (1) {
102104
if (OSSL_DECODER_from_bio(dctx, bio) == 1)
103-
goto out;
104-
if (BIO_eof(bio))
105105
break;
106+
if (ERR_GET_REASON(ERR_peek_error()) != ERR_R_UNSUPPORTED)
107+
break;
108+
if (BIO_eof(bio) == 1) {
109+
*retryable = 1;
110+
break;
111+
}
106112
pos2 = BIO_tell(bio);
107-
if (pos2 < 0 || pos2 <= pos)
113+
if (pos2 < 0 || pos2 <= pos) {
114+
*retryable = 1;
108115
break;
116+
}
109117
ossl_clear_error();
110118
pos = pos2;
111119
}
112120
out:
113-
OSSL_BIO_reset(bio);
114121
OSSL_DECODER_CTX_free(dctx);
115122
return pkey;
116123
}
117124

118125
EVP_PKEY *
119126
ossl_pkey_read_generic(BIO *bio, VALUE pass)
120127
{
121-
EVP_PKEY *pkey = NULL;
122128
/* First check DER, then check PEM. */
123129
const char *input_types[] = {"DER", "PEM"};
124130
int input_type_num = (int)(sizeof(input_types) / sizeof(char *));
@@ -167,18 +173,22 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass)
167173
EVP_PKEY_PUBLIC_KEY
168174
};
169175
int selection_num = (int)(sizeof(selections) / sizeof(int));
170-
int i, j;
171176

172-
for (i = 0; i < input_type_num; i++) {
173-
for (j = 0; j < selection_num; j++) {
174-
pkey = ossl_pkey_read(bio, input_types[i], selections[j], pass);
175-
if (pkey) {
176-
goto out;
177+
for (int i = 0; i < input_type_num; i++) {
178+
for (int j = 0; j < selection_num; j++) {
179+
if (i || j) {
180+
ossl_clear_error();
181+
BIO_reset(bio);
177182
}
183+
184+
int retryable;
185+
EVP_PKEY *pkey = ossl_pkey_read(bio, input_types[i], selections[j],
186+
pass, &retryable);
187+
if (pkey || !retryable)
188+
return pkey;
178189
}
179190
}
180-
out:
181-
return pkey;
191+
return NULL;
182192
}
183193
#else
184194
EVP_PKEY *

test/openssl/test_pkey.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ def test_s_read_passphrase
124124
}
125125
}
126126
assert_equal(1, called)
127+
128+
# Incorrect passphrase returned by the block. The input contains two PEM
129+
# blocks.
130+
called = 0
131+
assert_raise(OpenSSL::PKey::PKeyError) {
132+
OpenSSL::PKey.read(encrypted_pem + encrypted_pem) {
133+
called += 1
134+
"incorrect_passphrase"
135+
}
136+
}
137+
assert_equal(1, called)
127138
end
128139

129140
def test_s_read_passphrase_tty

0 commit comments

Comments
 (0)