Skip to content
41 changes: 26 additions & 15 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,41 +83,48 @@ 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)
goto out;
if (OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb,
if (selection == EVP_PKEY_KEYPAIR &&
OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb,
ppass) != 1)
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;
}

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 *));
Expand Down Expand Up @@ -166,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 *
Expand Down
7 changes: 5 additions & 2 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -5060,8 +5060,11 @@ rb_file_dirname_n(VALUE fname, int n)
break;
}
}
if (p == name)
return rb_usascii_str_new2(".");
if (p == name) {
dirname = rb_str_new(".", 1);
rb_enc_copy(dirname, fname);
return dirname;
}
#ifdef DOSISH_DRIVE_LETTER
if (has_drive_letter(name) && isdirsep(*(name + 2))) {
const char *top = skiproot(name + 2, end, enc);
Expand Down
18 changes: 13 additions & 5 deletions spec/ruby/optional/capi/string_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,19 @@ def inspect
end

it "returns a new String object filled with \\0 bytes" do
s = @s.rb_str_tmp_new(4)
s.encoding.should == Encoding::BINARY
s.bytesize.should == 4
s.size.should == 4
s.should == "\x00\x00\x00\x00"
lens = [4]

ruby_version_is "3.5" do
lens << 100
end

lens.each do |len|
s = @s.rb_str_tmp_new(len)
s.encoding.should == Encoding::BINARY
s.bytesize.should == len
s.size.should == len
s.should == "\x00" * len
end
end
end

Expand Down
3 changes: 3 additions & 0 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,9 @@ str_enc_new(VALUE klass, const char *ptr, long len, rb_encoding *enc)
if (ptr) {
memcpy(RSTRING_PTR(str), ptr, len);
}
else {
memset(RSTRING_PTR(str), 0, len);
}

STR_SET_LEN(str, len);
TERM_FILL(RSTRING_PTR(str) + len, termlen);
Expand Down
116 changes: 116 additions & 0 deletions test/openssl/test_pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,122 @@ def test_s_generate_key
assert_not_equal nil, pkey.private_key
end

def test_s_read_pem_unknown_block
# A PEM-encoded certificate and a PEM-encoded private key are combined.
# Check that OSSL_STORE doesn't stop after the first PEM block.
orig = Fixtures.pkey("rsa-1")
subject = OpenSSL::X509::Name.new([["CN", "test"]])
cert = issue_cert(subject, orig, 1, [], nil, nil)

input = cert.to_text + cert.to_pem + orig.to_text + orig.private_to_pem
pkey = OpenSSL::PKey.read(input)
assert_equal(orig.private_to_der, pkey.private_to_der)
end

def test_s_read_der_then_pem
# If the input is valid as both DER and PEM (which allows garbage data
# before and after the block), it is read as DER
#
# TODO: Garbage data after DER should not be allowed, but it is currently
# ignored
orig1 = Fixtures.pkey("rsa-1")
orig2 = Fixtures.pkey("rsa-2")
pkey = OpenSSL::PKey.read(orig1.public_to_der + orig2.private_to_pem)
assert_equal(orig1.public_to_der, pkey.public_to_der)
assert_not_predicate(pkey, :private?)
end

def test_s_read_passphrase
orig = Fixtures.pkey("rsa-1")
encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")
assert_match(/\A-----BEGIN ENCRYPTED PRIVATE KEY-----/, encrypted_pem)

# Correct passphrase passed as the second argument
pkey1 = OpenSSL::PKey.read(encrypted_pem, "correct_passphrase")
assert_equal(orig.private_to_der, pkey1.private_to_der)

# Correct passphrase returned by the block. The block gets false
called = 0
flag = nil
pkey2 = OpenSSL::PKey.read(encrypted_pem) { |f|
called += 1
flag = f
"correct_passphrase"
}
assert_equal(orig.private_to_der, pkey2.private_to_der)
assert_equal(1, called)
assert_false(flag)

# Incorrect passphrase passed. The block is not called
called = 0
assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey.read(encrypted_pem, "incorrect_passphrase") {
called += 1
}
}
assert_equal(0, called)

# Incorrect passphrase returned by the block. The block is called only once
called = 0
assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey.read(encrypted_pem) {
called += 1
"incorrect_passphrase"
}
}
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
omit "https://github.com/aws/aws-lc/pull/2555" if aws_lc?

orig = Fixtures.pkey("rsa-1")
encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")

# Correct passphrase passed to OpenSSL's prompt
script = <<~"end;"
require "openssl"
Process.setsid
OpenSSL::PKey.read(#{encrypted_pem.dump})
puts "ok"
end;
assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
"correct_passphrase\n") { |stdout, stderr|
assert_equal(["Enter PEM pass phrase:"], stderr)
assert_equal(["ok"], stdout)
}

# Incorrect passphrase passed to OpenSSL's prompt
script = <<~"end;"
require "openssl"
Process.setsid
begin
OpenSSL::PKey.read(#{encrypted_pem.dump})
rescue OpenSSL::PKey::PKeyError
puts "ok"
else
puts "expected OpenSSL::PKey::PKeyError"
end
end;
stdin = "incorrect_passphrase\n" * 5
assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
stdin) { |stdout, stderr|
assert_equal(1, stderr.count("Enter PEM pass phrase:"))
assert_equal(["ok"], stdout)
}
end if ENV["OSSL_TEST_ALL"] == "1" && Process.respond_to?(:setsid)

def test_hmac_sign_verify
pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "abcd" })

Expand Down
6 changes: 3 additions & 3 deletions test/ruby/test_ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def initialize(path, src: nil)
@path = path
@errors = []
@debug = false
@ast = RubyVM::AbstractSyntaxTree.parse(src) if src
@ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse(src) } if src
end

def validate_range
Expand All @@ -67,7 +67,7 @@ def validate_not_cared

def ast
return @ast if defined?(@ast)
@ast = RubyVM::AbstractSyntaxTree.parse_file(@path)
@ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file(@path) }
end

private
Expand Down Expand Up @@ -135,7 +135,7 @@ def validate_not_cared0(node)

Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
define_method("test_all_tokens:#{path}") do
node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true)
node = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true) }
tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] }
tokens_bytes = tokens.map { _1[2]}.join.bytes
source_bytes = File.read("#{SRCDIR}/#{path}").bytes
Expand Down
7 changes: 5 additions & 2 deletions test/ruby/test_optimization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -606,11 +606,11 @@ def foo(n)
end

class Bug10557
def [](_)
def [](_, &)
block_given?
end

def []=(_, _)
def []=(_, _, &)
block_given?
end
end
Expand Down Expand Up @@ -1256,6 +1256,9 @@ def initialize a, **kw
insn = iseq.disasm
assert_match(/opt_new/, insn)
assert_match(/OptNewFoo:.+@a=1, @b=2/, iseq.eval.inspect)
# clean up to avoid warnings
Object.send :remove_const, :OptNewFoo
Object.remove_method :optnew_foo if defined?(optnew_foo)
end
[
'def optnew_foo(&) = OptNewFoo.new(&)',
Expand Down
2 changes: 1 addition & 1 deletion test/ruby/test_variable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ def test_genivar_cache
instance.instance_variable_set(:@a3, 3)
instance.instance_variable_set(:@a4, 4)
end.resume
assert_equal 4, instance.instance_variable_get(:@a4)
assert_equal 4, instance.instance_variable_get(:@a4), bug21547
end

private
Expand Down