summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2025-08-13 03:08:22 +0900
committergit <svn-admin@ruby-lang.org>2025-09-12 12:26:07 +0000
commitc0820058243842d1391d896baf67914a8ea50e13 (patch)
treed7a585e13c62cde6e48a455c697e7e90de9ea8e1
parent8af8582d4c3baf0ba41f8b54b43839ec8ba3dc3d (diff)
[ruby/openssl] 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 https://github.com/ruby/openssl/issues/927 https://github.com/ruby/openssl/commit/985ba27d63
-rw-r--r--ext/openssl/ossl_pkey.c38
-rw-r--r--test/openssl/test_pkey.rb11
2 files changed, 35 insertions, 14 deletions
diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index 0fed03332f..481bd8a8ee 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -83,13 +83,15 @@ ossl_pkey_wrap(EVP_PKEY *pkey)
# include <openssl/decoder.h>
static EVP_PKEY *
-ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass)
+ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass,
+ int *retryable)
{
void *ppass = (void *)pass;
OSSL_DECODER_CTX *dctx;
EVP_PKEY *pkey = NULL;
int pos = 0, pos2;
+ *retryable = 0;
dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, input_type, NULL, NULL,
selection, NULL, NULL);
if (!dctx)
@@ -100,17 +102,22 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass)
goto out;
while (1) {
if (OSSL_DECODER_from_bio(dctx, bio) == 1)
- goto out;
- if (BIO_eof(bio))
break;
+ if (ERR_GET_REASON(ERR_peek_error()) != ERR_R_UNSUPPORTED)
+ break;
+ if (BIO_eof(bio) == 1) {
+ *retryable = 1;
+ break;
+ }
pos2 = BIO_tell(bio);
- if (pos2 < 0 || pos2 <= pos)
+ if (pos2 < 0 || pos2 <= pos) {
+ *retryable = 1;
break;
+ }
ossl_clear_error();
pos = pos2;
}
out:
- OSSL_BIO_reset(bio);
OSSL_DECODER_CTX_free(dctx);
return pkey;
}
@@ -118,7 +125,6 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass)
EVP_PKEY *
ossl_pkey_read_generic(BIO *bio, VALUE pass)
{
- EVP_PKEY *pkey = NULL;
/* First check DER, then check PEM. */
const char *input_types[] = {"DER", "PEM"};
int input_type_num = (int)(sizeof(input_types) / sizeof(char *));
@@ -167,18 +173,22 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass)
EVP_PKEY_PUBLIC_KEY
};
int selection_num = (int)(sizeof(selections) / sizeof(int));
- int i, j;
- for (i = 0; i < input_type_num; i++) {
- for (j = 0; j < selection_num; j++) {
- pkey = ossl_pkey_read(bio, input_types[i], selections[j], pass);
- if (pkey) {
- goto out;
+ for (int i = 0; i < input_type_num; i++) {
+ for (int j = 0; j < selection_num; j++) {
+ if (i || j) {
+ ossl_clear_error();
+ BIO_reset(bio);
}
+
+ int retryable;
+ EVP_PKEY *pkey = ossl_pkey_read(bio, input_types[i], selections[j],
+ pass, &retryable);
+ if (pkey || !retryable)
+ return pkey;
}
}
- out:
- return pkey;
+ return NULL;
}
#else
EVP_PKEY *
diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb
index 8066c4dc19..24a595333b 100644
--- a/test/openssl/test_pkey.rb
+++ b/test/openssl/test_pkey.rb
@@ -124,6 +124,17 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
}
}
assert_equal(1, called)
+
+ # Incorrect passphrase returned by the block. The input contains two PEM
+ # blocks.
+ called = 0
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey.read(encrypted_pem + encrypted_pem) {
+ called += 1
+ "incorrect_passphrase"
+ }
+ }
+ assert_equal(1, called)
end
def test_s_read_passphrase_tty