Skip to content

Commit ff363eb

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 more than one passphrase-protected PEM blocks and the first one cannot be decoded. I am not entirely sure if 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 8369c0e commit ff363eb

2 files changed

Lines changed: 35 additions & 14 deletions

File tree

‎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
@@ -137,6 +137,17 @@ def test_s_read_passphrase
137137
}
138138
}
139139
assert_equal(1, called)
140+
141+
# Incorrect passphrase returned by the block. The input contains two PEM
142+
# blocks.
143+
called = 0
144+
assert_raise(OpenSSL::PKey::PKeyError) {
145+
OpenSSL::PKey.read(encrypted_pem + encrypted_pem) {
146+
called += 1
147+
"incorrect_passphrase"
148+
}
149+
}
150+
assert_equal(1, called)
140151
end
141152

142153
def test_s_read_passphrase_tty

0 commit comments

Comments
 (0)