Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions spec/bundler/fetcher/gem_remote_fetcher_local_ssl_server_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "bundler/fetcher"
require Spec::Path.rubygems_test_dir.join("pem_utilities")
require Spec::Path.rubygems_test_dir.join("local_ssl_server_utilities")

RSpec.describe "Bundler::Fetcher local SSL server", if: Gem::HAVE_OPENSSL do
Expand All @@ -19,7 +20,7 @@
it "connects" do
ssl_server = start_ssl_server
allow(Bundler.settings).to receive(:[]).and_call_original
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(File.join(certs_dir, "ca_cert.pem"))
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(Gem::PemUtilities::CA_CERT_FILE)
response = fetch_path("https://localhost:#{ssl_server.addr[1]}/yaml")
expect(response.code).to eq("200")
end
Expand All @@ -29,8 +30,8 @@
verify_mode: OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
)
allow(Bundler.settings).to receive(:[]).and_call_original
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(File.join(certs_dir, "ca_cert.pem"))
allow(Bundler.settings).to receive(:[]).with(:ssl_client_cert).and_return(File.join(certs_dir, "client.pem"))
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(Gem::PemUtilities::CA_CERT_FILE)
allow(Bundler.settings).to receive(:[]).with(:ssl_client_cert).and_return(Gem::PemUtilities::CLIENT_FILE)
response = fetch_path("https://localhost:#{ssl_server.addr[1]}/yaml")
expect(response.code).to eq("200")
end
Expand All @@ -44,7 +45,7 @@
it "connects" do
ssl_server = start_ssl_server(mode: :pqc)
allow(Bundler.settings).to receive(:[]).and_call_original
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(File.join(certs_dir, "mldsa65_ca_cert.pem"))
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(Gem::PemUtilities::MLDSA65_CA_CERT_FILE)
response = fetch_path("https://localhost:#{ssl_server.addr[1]}/yaml")
expect(response.code).to eq("200")
end
Expand All @@ -55,8 +56,8 @@
verify_mode: OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
)
allow(Bundler.settings).to receive(:[]).and_call_original
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(File.join(certs_dir, "mldsa65_ca_cert.pem"))
allow(Bundler.settings).to receive(:[]).with(:ssl_client_cert).and_return(File.join(certs_dir, "mldsa65_client.pem"))
allow(Bundler.settings).to receive(:[]).with(:ssl_ca_cert).and_return(Gem::PemUtilities::MLDSA65_CA_CERT_FILE)
allow(Bundler.settings).to receive(:[]).with(:ssl_client_cert).and_return(Gem::PemUtilities::MLDSA65_CLIENT_FILE)
response = fetch_path("https://localhost:#{ssl_server.addr[1]}/yaml")
expect(response.code).to eq("200")
end
Expand Down
File renamed without changes.
71 changes: 2 additions & 69 deletions test/rubygems/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
require "rubygems/vendor/uri/lib/uri"
require "zlib"
require_relative "mock_gem_ui"
require_relative "pem_utilities"

# JRuby on Windows raises TypeError inside File.symlink (the wincode helper
# trips on a nil path), so any test that exercises Gem::Installer's symlink
Expand Down Expand Up @@ -1658,75 +1659,7 @@ def prefetch(reqs) # :nodoc:
end
end

##
# Loads certificate named +cert_name+ from <tt>test/rubygems/</tt>.

def self.load_cert(cert_name)
cert_file = cert_path cert_name

cert = File.read cert_file

OpenSSL::X509::Certificate.new cert
end

##
# Returns the path to the certificate named +cert_name+ from
# <tt>test/rubygems/</tt>.

def self.cert_path(cert_name)
if begin
Time.at(2**32)
rescue StandardError
32
end == 32
cert_file = "#{__dir__}/#{cert_name}_cert_32.pem"

return cert_file if File.exist? cert_file
end

"#{__dir__}/#{cert_name}_cert.pem"
end

##
# Loads a private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt>

def self.load_key(key_name, passphrase = nil)
key_file = key_path key_name

key = File.read key_file

OpenSSL::PKey.read key, passphrase
end

##
# Returns the path to the key named +key_name+ from <tt>test/rubygems</tt>

def self.key_path(key_name)
"#{__dir__}/#{key_name}_key.pem"
end

# :stopdoc:
# only available in RubyGems tests

PRIVATE_KEY_PASSPHRASE = "Foo bar"

begin
PRIVATE_KEY = load_key "private"
PRIVATE_KEY_PATH = key_path "private"

# ENCRYPTED_PRIVATE_KEY is PRIVATE_KEY encrypted with PRIVATE_KEY_PASSPHRASE
ENCRYPTED_PRIVATE_KEY = load_key "encrypted_private", PRIVATE_KEY_PASSPHRASE
ENCRYPTED_PRIVATE_KEY_PATH = key_path "encrypted_private"

PUBLIC_KEY = PRIVATE_KEY.public_key

PUBLIC_CERT = load_cert "public"
PUBLIC_CERT_PATH = cert_path "public"
rescue Errno::ENOENT
PRIVATE_KEY = nil
PUBLIC_KEY = nil
PUBLIC_CERT = nil
end if Gem::HAVE_OPENSSL
include Gem::PemUtilities
end

# https://github.com/seattlerb/minitest/blob/13c48a03d84a2a87855a4de0c959f96800100357/lib/minitest/mock.rb#L192
Expand Down
32 changes: 10 additions & 22 deletions test/rubygems/local_ssl_server_utilities.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@

require "socket"
require "openssl"
require_relative "pem_utilities"

module Gem::LocalSSLServerUtilities
CERTS_DIR = __dir__

def certs_dir
CERTS_DIR
end

include Gem::PemUtilities
def initialize_ssl_server
@ssl_server_thread = nil
@ssl_server = nil
Expand Down Expand Up @@ -40,13 +36,13 @@ def start_ssl_server(config = {})

case mode
when :non_pqc
ctx.cert = cert("ssl_cert.pem")
ctx.key = key("ssl_key.pem")
ctx.ca_file = File.join(certs_dir, "ca_cert.pem")
ctx.cert = SSL_CERT
ctx.key = SSL_KEY
ctx.ca_file = CA_CERT_FILE
when :pqc
ctx.cert = cert("mldsa65_ssl_cert.pem")
ctx.key = key("mldsa65_ssl_key.pem")
ctx.ca_file = File.join(certs_dir, "mldsa65_ca_cert.pem")
ctx.cert = MLDSA65_SSL_CERT
ctx.key = MLDSA65_SSL_KEY
ctx.ca_file = MLDSA65_CA_CERT_FILE
ctx.groups = "X25519MLKEM768"
end

Expand Down Expand Up @@ -79,14 +75,6 @@ def handle_request(client)
end
end

def cert(filename)
OpenSSL::X509::Certificate.new(File.read(File.join(certs_dir, filename)))
end

def key(filename)
OpenSSL::PKey.read(File.read(File.join(certs_dir, filename)))
end

def without_pqc_support(&block)
# PQC algorithms ML-KEM and ML-DSA require OpenSSL >= 3.5.
# https://openssl-library.org/post/2025-04-08-openssl-35-final-release/
Expand Down Expand Up @@ -123,8 +111,8 @@ def self.support_pqc_handshake?
def self.probe_pqc_handshake
server = TCPServer.new("127.0.0.1", 0)
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(CERTS_DIR, "mldsa65_ssl_cert.pem")))
ctx.key = OpenSSL::PKey.read(File.read(File.join(CERTS_DIR, "mldsa65_ssl_key.pem")))
ctx.cert = Gem::PemUtilities::MLDSA65_SSL_CERT
ctx.key = Gem::PemUtilities::MLDSA65_SSL_KEY
ctx.groups = "X25519MLKEM768"
ssl_server = OpenSSL::SSL::SSLServer.new(server, ctx)

Expand Down
130 changes: 130 additions & 0 deletions test/rubygems/pem_utilities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# frozen_string_literal: true

# This file can be loaded by RubyGems test-unit files and Bundler rspec files.
# Don't add test-unit or rspec dependent logic in this file.

require "rubygems/openssl"

module Gem::PemUtilities
##
# Loads certificate named +cert_name+ from <tt>test/rubygems/</tt>.

def self.load_cert(cert_name)
cert = File.read(cert_file(cert_name))
OpenSSL::X509::Certificate.new cert
end

##
# Returns the file path to the certificate named +cert_name+ from
# <tt>test/rubygems/</tt>.

def self.cert_file(cert_name)
if begin
Time.at(2**32)
rescue StandardError
32
end == 32
cert_file = "#{__dir__}/#{cert_name}_cert_32.pem"

return cert_file if File.exist? cert_file
end

"#{__dir__}/#{cert_name}_cert.pem"
end

##
# Loads a private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt>

def self.load_key(key_name, passphrase = nil)
key = File.read(key_file(key_name))

# Rescue if unsupported key algorithm's file is read with old OpenSSL versions.
begin
OpenSSL::PKey.read key, passphrase
rescue OpenSSL::PKey::PKeyError
nil
end
end

##
# Returns the file path to the key named +key_name+ from <tt>test/rubygems</tt>

def self.key_file(key_name)
"#{__dir__}/#{key_name}_key.pem"
end

##
# Returns the file path to the PEM file named +pem_name+ from <tt>test/rubygems</tt>

def self.pem_file(pem_name)
"#{__dir__}/#{pem_name}.pem"
end

# :stopdoc:

PRIVATE_KEY_PASSPHRASE = "Foo bar"

if Gem::HAVE_OPENSSL
# Only the key and certificate constants used in tests are managed here. Add
# constants here when adding or using new .pem files. The constant naming
# convention is <algorithm>_<name>.

# Keys and certificates mostly generated by create_certs.sh
# RSA CA
CA_CERT = load_cert "ca"
CA_CERT_FILE = cert_file "ca"
# RSA server
SSL_KEY = load_key "ssl"
SSL_KEY_FILE = key_file "ssl"
SSL_CERT = load_cert "ssl"
SSL_CERT_FILE = cert_file "ssl"
# RSA client key/cert pair
CLIENT_FILE = pem_file "client"
# RSA invalid client manually created without script
INVALID_CLIENT_FILE = pem_file "invalid_client"
# ML-DSA-65 CA
MLDSA65_CA_CERT_FILE = cert_file "mldsa65_ca"
# ML-DSA-65 server
MLDSA65_SSL_KEY = load_key "mldsa65_ssl"
MLDSA65_SSL_KEY_FILE = key_file "mldsa65_ssl"
MLDSA65_SSL_CERT = load_cert "mldsa65_ssl"
MLDSA65_SSL_CERT_FILE = cert_file "mldsa65_ssl"
# ML-DSA-65 client key/cert pair
MLDSA65_CLIENT_FILE = pem_file "mldsa65_client"

# Keys and certificates generated by create_certs.rb
PRIVATE_KEY = load_key "private"
PRIVATE_KEY_FILE = key_file "private"
# ENCRYPTED_PRIVATE_KEY is PRIVATE_KEY encrypted with PRIVATE_KEY_PASSPHRASE
ENCRYPTED_PRIVATE_KEY = load_key "encrypted_private", PRIVATE_KEY_PASSPHRASE
ENCRYPTED_PRIVATE_KEY_FILE = key_file "encrypted_private"
PUBLIC_KEY = PRIVATE_KEY.public_key
PUBLIC_KEY_FILE = key_file "public"
PUBLIC_CERT = load_cert "public"
PUBLIC_CERT_FILE = cert_file "public"
ALTERNATE_KEY = load_key "alternate"
ALTERNATE_KEY_FILE = key_file "alternate"
ALTERNATE_CERT = load_cert "alternate"
ALTERNATE_CERT_FILE = cert_file "alternate"
CHILD_KEY = load_key "child"
CHILD_CERT = load_cert "child"
CHILD_CERT_FILE = cert_file "child"
GRANDCHILD_CERT = load_cert "grandchild"
INVALID_ISSUER_CERT = load_cert "invalid_issuer"
INVALID_SIGNER_CERT = load_cert "invalid_signer"
INVALIDCHILD_CERT = load_cert "invalidchild"
EXPIRED_CERT = load_cert "expired"
EXPIRED_CERT_FILE = cert_file "expired"
FUTURE_CERT = load_cert "future"
WRONG_KEY_CERT = load_cert "wrong_key"

# Keys and certificates manually created without script
# RSA 3072 bits
RSA3072_PRIVATE_KEY_FILE = key_file "rsa3072_private"
RSA3072_PUBLIC_CERT = load_cert "rsa3072_public"
RSA3072_PUBLIC_CERT_FILE = cert_file "rsa3072_public"
# EC
EC_PRIVATE_KEY = load_key "ec_private", PRIVATE_KEY_PASSPHRASE
EC_PRIVATE_KEY_FILE = key_file "ec_private"
end
end
12 changes: 3 additions & 9 deletions test/rubygems/test_gem_commands_build_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@
require "rubygems/package"

class TestGemCommandsBuildCommand < Gem::TestCase
CERT_FILE = cert_path "public3072"
SIGNING_KEY = key_path "private3072"

EXPIRED_CERT_FILE = cert_path "expired"
PRIVATE_KEY_FILE = key_path "private"

def setup
super

Expand Down Expand Up @@ -591,8 +585,8 @@ def test_build_signed_gem
trust_dir = Gem::Security.trust_dir

spec = util_spec "some_gem" do |s|
s.signing_key = SIGNING_KEY
s.cert_chain = [CERT_FILE]
s.signing_key = RSA3072_PRIVATE_KEY_FILE
s.cert_chain = [RSA3072_PUBLIC_CERT_FILE]
end

gemspec_file = File.join(@tempdir, spec.spec_name)
Expand All @@ -605,7 +599,7 @@ def test_build_signed_gem

util_test_build_gem spec

trust_dir.trust_cert OpenSSL::X509::Certificate.new(File.read(CERT_FILE))
trust_dir.trust_cert RSA3072_PUBLIC_CERT

gem = Gem::Package.new(File.join(@tempdir, spec.file_name),
Gem::Security::HighSecurity)
Expand Down
Loading