mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00

Bring the local copy of ruby/openssl in sync with the upstream gem release v2.2.1. The commits happened in the upstream repository can be found at: https://github.com/ruby/openssl/compare/v2.2.0...v2.2.1 Note that many of these have already been applied to ruby.git and don't appear in the file changes of this commit.
405 lines
12 KiB
Ruby
405 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
begin
|
|
require "openssl"
|
|
|
|
# Disable FIPS mode for tests for installations
|
|
# where FIPS mode would be enabled by default.
|
|
# Has no effect on all other installations.
|
|
OpenSSL.fips_mode=false
|
|
rescue LoadError
|
|
end
|
|
|
|
# Compile OpenSSL with crypto-mdebug and run this test suite with OSSL_MDEBUG=1
|
|
# environment variable to enable memory leak check.
|
|
if ENV["OSSL_MDEBUG"] == "1"
|
|
if OpenSSL.respond_to?(:print_mem_leaks)
|
|
OpenSSL.mem_check_start
|
|
|
|
END {
|
|
GC.start
|
|
case OpenSSL.print_mem_leaks
|
|
when nil
|
|
warn "mdebug: check what is printed"
|
|
when true
|
|
raise "mdebug: memory leaks detected"
|
|
end
|
|
}
|
|
else
|
|
warn "OSSL_MDEBUG=1 is specified but OpenSSL is not built with crypto-mdebug"
|
|
end
|
|
end
|
|
|
|
require "test/unit"
|
|
require "tempfile"
|
|
require "socket"
|
|
require "envutil"
|
|
|
|
if defined?(OpenSSL)
|
|
|
|
module OpenSSL::TestUtils
|
|
module Fixtures
|
|
module_function
|
|
|
|
def pkey(name)
|
|
OpenSSL::PKey.read(read_file("pkey", name))
|
|
rescue OpenSSL::PKey::PKeyError
|
|
# TODO: DH parameters can be read by OpenSSL::PKey.read atm
|
|
OpenSSL::PKey::DH.new(read_file("pkey", name))
|
|
end
|
|
|
|
def read_file(category, name)
|
|
@file_cache ||= {}
|
|
@file_cache[[category, name]] ||=
|
|
File.read(File.join(__dir__, "fixtures", category, name + ".pem"))
|
|
end
|
|
|
|
def file_path(category, name)
|
|
File.join(__dir__, "fixtures", category, name)
|
|
end
|
|
end
|
|
|
|
module_function
|
|
|
|
def generate_cert(dn, key, serial, issuer,
|
|
not_before: nil, not_after: nil)
|
|
cert = OpenSSL::X509::Certificate.new
|
|
issuer = cert unless issuer
|
|
cert.version = 2
|
|
cert.serial = serial
|
|
cert.subject = dn
|
|
cert.issuer = issuer.subject
|
|
cert.public_key = key
|
|
now = Time.now
|
|
cert.not_before = not_before || now - 3600
|
|
cert.not_after = not_after || now + 3600
|
|
cert
|
|
end
|
|
|
|
|
|
def issue_cert(dn, key, serial, extensions, issuer, issuer_key,
|
|
not_before: nil, not_after: nil, digest: "sha256")
|
|
cert = generate_cert(dn, key, serial, issuer,
|
|
not_before: not_before, not_after: not_after)
|
|
issuer = cert unless issuer
|
|
issuer_key = key unless issuer_key
|
|
ef = OpenSSL::X509::ExtensionFactory.new
|
|
ef.subject_certificate = cert
|
|
ef.issuer_certificate = issuer
|
|
extensions.each{|oid, value, critical|
|
|
cert.add_extension(ef.create_extension(oid, value, critical))
|
|
}
|
|
cert.sign(issuer_key, digest)
|
|
cert
|
|
end
|
|
|
|
def issue_crl(revoke_info, serial, lastup, nextup, extensions,
|
|
issuer, issuer_key, digest)
|
|
crl = OpenSSL::X509::CRL.new
|
|
crl.issuer = issuer.subject
|
|
crl.version = 1
|
|
crl.last_update = lastup
|
|
crl.next_update = nextup
|
|
revoke_info.each{|rserial, time, reason_code|
|
|
revoked = OpenSSL::X509::Revoked.new
|
|
revoked.serial = rserial
|
|
revoked.time = time
|
|
enum = OpenSSL::ASN1::Enumerated(reason_code)
|
|
ext = OpenSSL::X509::Extension.new("CRLReason", enum)
|
|
revoked.add_extension(ext)
|
|
crl.add_revoked(revoked)
|
|
}
|
|
ef = OpenSSL::X509::ExtensionFactory.new
|
|
ef.issuer_certificate = issuer
|
|
ef.crl = crl
|
|
crlnum = OpenSSL::ASN1::Integer(serial)
|
|
crl.add_extension(OpenSSL::X509::Extension.new("crlNumber", crlnum))
|
|
extensions.each{|oid, value, critical|
|
|
crl.add_extension(ef.create_extension(oid, value, critical))
|
|
}
|
|
crl.sign(issuer_key, digest)
|
|
crl
|
|
end
|
|
|
|
def get_subject_key_id(cert, hex: true)
|
|
asn1_cert = OpenSSL::ASN1.decode(cert)
|
|
tbscert = asn1_cert.value[0]
|
|
pkinfo = tbscert.value[6]
|
|
publickey = pkinfo.value[1]
|
|
pkvalue = publickey.value
|
|
digest = OpenSSL::Digest.digest('SHA1', pkvalue)
|
|
if hex
|
|
digest.unpack("H2"*20).join(":").upcase
|
|
else
|
|
digest
|
|
end
|
|
end
|
|
|
|
def openssl?(major = nil, minor = nil, fix = nil, patch = 0)
|
|
return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL")
|
|
return true unless major
|
|
OpenSSL::OPENSSL_VERSION_NUMBER >=
|
|
major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10
|
|
end
|
|
|
|
def libressl?(major = nil, minor = nil, fix = nil)
|
|
version = OpenSSL::OPENSSL_VERSION.scan(/LibreSSL (\d+)\.(\d+)\.(\d+).*/)[0]
|
|
return false unless version
|
|
!major || (version.map(&:to_i) <=> [major, minor, fix]) >= 0
|
|
end
|
|
end
|
|
|
|
class OpenSSL::TestCase < Test::Unit::TestCase
|
|
include OpenSSL::TestUtils
|
|
extend OpenSSL::TestUtils
|
|
|
|
def setup
|
|
if ENV["OSSL_GC_STRESS"] == "1"
|
|
GC.stress = true
|
|
end
|
|
end
|
|
|
|
def teardown
|
|
if ENV["OSSL_GC_STRESS"] == "1"
|
|
GC.stress = false
|
|
end
|
|
# OpenSSL error stack must be empty
|
|
assert_equal([], OpenSSL.errors)
|
|
end
|
|
end
|
|
|
|
class OpenSSL::SSLTestCase < OpenSSL::TestCase
|
|
RUBY = EnvUtil.rubybin
|
|
ITERATIONS = ($0 == __FILE__) ? 100 : 10
|
|
|
|
def setup
|
|
super
|
|
@ca_key = Fixtures.pkey("rsa-1")
|
|
@svr_key = Fixtures.pkey("rsa-2")
|
|
@cli_key = Fixtures.pkey("rsa-3")
|
|
@ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
|
|
@svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
|
|
@cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
|
|
ca_exts = [
|
|
["basicConstraints","CA:TRUE",true],
|
|
["keyUsage","cRLSign,keyCertSign",true],
|
|
]
|
|
ee_exts = [
|
|
["keyUsage","keyEncipherment,digitalSignature",true],
|
|
]
|
|
@ca_cert = issue_cert(@ca, @ca_key, 1, ca_exts, nil, nil)
|
|
@svr_cert = issue_cert(@svr, @svr_key, 2, ee_exts, @ca_cert, @ca_key)
|
|
@cli_cert = issue_cert(@cli, @cli_key, 3, ee_exts, @ca_cert, @ca_key)
|
|
@server = nil
|
|
end
|
|
|
|
def tls12_supported?
|
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
|
|
true
|
|
rescue
|
|
end
|
|
|
|
def tls13_supported?
|
|
return false unless defined?(OpenSSL::SSL::TLS1_3_VERSION)
|
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
|
|
true
|
|
rescue
|
|
end
|
|
|
|
def readwrite_loop(ctx, ssl)
|
|
while line = ssl.gets
|
|
ssl.write(line)
|
|
end
|
|
end
|
|
|
|
def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true,
|
|
ctx_proc: nil, server_proc: method(:readwrite_loop),
|
|
accept_proc: proc{},
|
|
ignore_listener_error: false, &block)
|
|
IO.pipe {|stop_pipe_r, stop_pipe_w|
|
|
store = OpenSSL::X509::Store.new
|
|
store.add_cert(@ca_cert)
|
|
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
|
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
ctx.cert_store = store
|
|
ctx.cert = @svr_cert
|
|
ctx.key = @svr_key
|
|
ctx.tmp_dh_callback = proc { Fixtures.pkey("dh-1") }
|
|
ctx.verify_mode = verify_mode
|
|
ctx_proc.call(ctx) if ctx_proc
|
|
|
|
Socket.do_not_reverse_lookup = true
|
|
tcps = TCPServer.new("127.0.0.1", 0)
|
|
port = tcps.connect_address.ip_port
|
|
|
|
ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
|
|
ssls.start_immediately = start_immediately
|
|
|
|
threads = []
|
|
begin
|
|
server_thread = Thread.new do
|
|
if Thread.method_defined?(:report_on_exception=) # Ruby >= 2.4
|
|
Thread.current.report_on_exception = false
|
|
end
|
|
|
|
begin
|
|
loop do
|
|
begin
|
|
readable, = IO.select([ssls, stop_pipe_r])
|
|
break if readable.include? stop_pipe_r
|
|
ssl = ssls.accept
|
|
accept_proc.call(ssl)
|
|
rescue OpenSSL::SSL::SSLError, IOError, Errno::EBADF, Errno::EINVAL,
|
|
Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET
|
|
retry if ignore_listener_error
|
|
raise
|
|
end
|
|
|
|
th = Thread.new do
|
|
if Thread.method_defined?(:report_on_exception=)
|
|
Thread.current.report_on_exception = false
|
|
end
|
|
|
|
begin
|
|
server_proc.call(ctx, ssl)
|
|
ensure
|
|
ssl.close
|
|
end
|
|
true
|
|
end
|
|
threads << th
|
|
end
|
|
ensure
|
|
tcps.close
|
|
end
|
|
end
|
|
|
|
client_thread = Thread.new do
|
|
if Thread.method_defined?(:report_on_exception=)
|
|
Thread.current.report_on_exception = false
|
|
end
|
|
|
|
begin
|
|
block.call(port)
|
|
ensure
|
|
# Stop accepting new connection
|
|
stop_pipe_w.close
|
|
server_thread.join
|
|
end
|
|
end
|
|
threads.unshift client_thread
|
|
ensure
|
|
# Terminate existing connections. If a thread did 'pend', re-raise it.
|
|
pend = nil
|
|
threads.each { |th|
|
|
begin
|
|
timeout = EnvUtil.apply_timeout_scale(30)
|
|
th.join(timeout) or
|
|
th.raise(RuntimeError, "[start_server] thread did not exit in #{timeout} secs")
|
|
rescue (defined?(MiniTest::Skip) ? MiniTest::Skip : Test::Unit::PendedError)
|
|
# MiniTest::Skip is for the Ruby tree
|
|
pend = $!
|
|
rescue Exception
|
|
end
|
|
}
|
|
raise pend if pend
|
|
assert_join_threads(threads)
|
|
end
|
|
}
|
|
end
|
|
end
|
|
|
|
class OpenSSL::PKeyTestCase < OpenSSL::TestCase
|
|
def check_component(base, test, keys)
|
|
keys.each { |comp|
|
|
assert_equal base.send(comp), test.send(comp)
|
|
}
|
|
end
|
|
|
|
def dup_public(key)
|
|
case key
|
|
when OpenSSL::PKey::RSA
|
|
rsa = OpenSSL::PKey::RSA.new
|
|
rsa.set_key(key.n, key.e, nil)
|
|
rsa
|
|
when OpenSSL::PKey::DSA
|
|
dsa = OpenSSL::PKey::DSA.new
|
|
dsa.set_pqg(key.p, key.q, key.g)
|
|
dsa.set_key(key.pub_key, nil)
|
|
dsa
|
|
when OpenSSL::PKey::DH
|
|
dh = OpenSSL::PKey::DH.new
|
|
dh.set_pqg(key.p, nil, key.g)
|
|
dh
|
|
else
|
|
if defined?(OpenSSL::PKey::EC) && OpenSSL::PKey::EC === key
|
|
ec = OpenSSL::PKey::EC.new(key.group)
|
|
ec.public_key = key.public_key
|
|
ec
|
|
else
|
|
raise "unknown key type"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module OpenSSL::Certs
|
|
include OpenSSL::TestUtils
|
|
|
|
module_function
|
|
|
|
def ca_cert
|
|
ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Timestamp Root CA")
|
|
|
|
ca_exts = [
|
|
["basicConstraints","CA:TRUE,pathlen:1",true],
|
|
["keyUsage","keyCertSign, cRLSign",true],
|
|
["subjectKeyIdentifier","hash",false],
|
|
["authorityKeyIdentifier","keyid:always",false],
|
|
]
|
|
OpenSSL::TestUtils.issue_cert(ca, Fixtures.pkey("rsa2048"), 1, ca_exts, nil, nil)
|
|
end
|
|
|
|
def ts_cert_direct(key, ca_cert)
|
|
dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Server Direct")
|
|
|
|
exts = [
|
|
["basicConstraints","CA:FALSE",true],
|
|
["keyUsage","digitalSignature, nonRepudiation", true],
|
|
["subjectKeyIdentifier", "hash",false],
|
|
["authorityKeyIdentifier","keyid,issuer", false],
|
|
["extendedKeyUsage", "timeStamping", true]
|
|
]
|
|
|
|
OpenSSL::TestUtils.issue_cert(dn, key, 2, exts, ca_cert, Fixtures.pkey("rsa2048"))
|
|
end
|
|
|
|
def intermediate_cert(key, ca_cert)
|
|
dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Timestamp Intermediate CA")
|
|
|
|
exts = [
|
|
["basicConstraints","CA:TRUE,pathlen:0",true],
|
|
["keyUsage","keyCertSign, cRLSign",true],
|
|
["subjectKeyIdentifier","hash",false],
|
|
["authorityKeyIdentifier","keyid:always",false],
|
|
]
|
|
|
|
OpenSSL::TestUtils.issue_cert(dn, key, 3, exts, ca_cert, Fixtures.pkey("rsa2048"))
|
|
end
|
|
|
|
def ts_cert_ee(key, intermediate, im_key)
|
|
dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Server End Entity")
|
|
|
|
exts = [
|
|
["keyUsage","digitalSignature, nonRepudiation", true],
|
|
["subjectKeyIdentifier", "hash",false],
|
|
["authorityKeyIdentifier","keyid,issuer", false],
|
|
["extendedKeyUsage", "timeStamping", true]
|
|
]
|
|
|
|
OpenSSL::TestUtils.issue_cert(dn, key, 4, exts, intermediate, im_key)
|
|
end
|
|
end
|
|
|
|
end
|