Module: Gem::PqTlsPolicy

Defined in:
lib/rubygems_pq_tls_policy.rb,
lib/rubygems_pq_tls_policy/error.rb,
lib/rubygems_pq_tls_policy/patch.rb,
lib/rubygems_pq_tls_policy/config.rb,
lib/rubygems_pq_tls_policy/version.rb,
lib/rubygems_pq_tls_policy/diagnostic.rb

Defined Under Namespace

Modules: Diagnostic, PerConnectionCheck, RequestHTTPSPoolPatch Classes: Config, Error, InvalidConfiguration, UnsupportedRuntime, Violation

Constant Summary collapse

VERSION =
"1.1.0"

Class Method Summary collapse

Class Method Details

.check_net_http_connection!(http) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rubygems_pq_tls_policy.rb', line 48

def check_net_http_connection!(http)
  config = self.config
  return true unless config.enabled?

  config.validate!

  socket = net_http_ssl_socket(http)
  group = socket&.respond_to?(:group) ? socket.group : nil
  hostname = http.respond_to?(:address) ? http.address : "unknown"

  if config.trace?
    warn "[rubygems:tls] host=#{hostname} version=#{safe_ssl_version(socket)} cipher=#{safe_cipher_name(socket)} group=#{group.inspect}"
  end

  if config.pq_required? && !config.allowed_groups.include?(group)
    raise Gem::PqTlsPolicy::Violation,
      "RubyGems gem-server TLS key exchange policy violation: " \
      "host=#{hostname.inspect} used group=#{group.inspect}; " \
      "allowed=#{config.allowed_groups.join(',')}"
  end

  true
end

.configObject



53
54
55
# File 'lib/rubygems_pq_tls_policy/config.rb', line 53

def self.config
  Config.new
end

.extend_net_http_connection!(http) ⇒ Object



43
44
45
46
# File 'lib/rubygems_pq_tls_policy.rb', line 43

def extend_net_http_connection!(http)
  http.extend(PerConnectionCheck) unless http.is_a?(PerConnectionCheck)
  http
end

.install!Object



26
27
28
29
30
31
32
33
# File 'lib/rubygems_pq_tls_policy.rb', line 26

def install!
  return true if installed?

  validate_runtime!
  Gem::Request::HTTPSPool.prepend(RequestHTTPSPoolPatch)
  @installed = true
  true
end

.install_if_enabledObject



12
13
14
15
16
17
18
# File 'lib/rubygems_pq_tls_policy.rb', line 12

def install_if_enabled
  config = self.config
  return false unless config.enabled?

  config.validate!
  install!
end

.install_if_enabled_for_plugin!Object



20
21
22
23
24
# File 'lib/rubygems_pq_tls_policy.rb', line 20

def install_if_enabled_for_plugin!
  install_if_enabled
rescue Error => e
  abort "RubyGems PQ TLS policy failed to load: #{e.message} (#{e.class})"
end

.installed?Boolean

Returns:

  • (Boolean)


35
36
37
# File 'lib/rubygems_pq_tls_policy.rb', line 35

def installed?
  @installed == true
end

.openssl_runtime_version_at_least?(major, minor, patch) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rubygems_pq_tls_policy.rb', line 110

def openssl_runtime_version_at_least?(major, minor, patch)
  version = if defined?(OpenSSL::OPENSSL_LIBRARY_VERSION)
    OpenSSL::OPENSSL_LIBRARY_VERSION
  elsif defined?(OpenSSL::OPENSSL_VERSION)
    OpenSSL::OPENSSL_VERSION
  end

  match = version.to_s.match(/\AOpenSSL\s+(\d+)\.(\d+)\.(\d+)/)
  return false unless match

  ([match[1].to_i, match[2].to_i, match[3].to_i] <=> [major, minor, patch]) >= 0
end

.validate_runtime!Object

Raises:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/rubygems_pq_tls_policy.rb', line 72

def validate_runtime!
  begin
    require "openssl"
  rescue LoadError => e
    raise UnsupportedRuntime, "OpenSSL is unavailable: #{e.message}"
  end
  require "rubygems/request"

  requirements = []
  socket = defined?(OpenSSL::SSL::SSLSocket) ? OpenSSL::SSL::SSLSocket : nil
  https_pool = defined?(Gem::Request::HTTPSPool) ? Gem::Request::HTTPSPool : nil
  http_pool = defined?(Gem::Request::HTTPPool) ? Gem::Request::HTTPPool : nil

  requirements << "OpenSSL::SSL::SSLSocket is unavailable" unless socket
  unless socket&.method_defined?(:group)
    requirements << "OpenSSL::SSL::SSLSocket#group is unavailable"
  end
  requirements << "Gem::Request::HTTPSPool is unavailable" unless https_pool
  requirements << "Gem::Request::HTTPPool is unavailable" unless http_pool
  unless https_pool&.private_method_defined?(:setup_connection)
    requirements << "Gem::Request::HTTPSPool#setup_connection is unavailable"
  end
  unless http_pool&.private_method_defined?(:setup_connection)
    requirements << "Gem::Request::HTTPPool#setup_connection is unavailable"
  end

  unless openssl_runtime_version_at_least?(3, 5, 0)
    requirements << "OpenSSL runtime must be 3.5.0 or newer for the default PQ TLS group"
  end

  return true if requirements.empty?

  raise UnsupportedRuntime,
    "RubyGems PQ TLS policy cannot be enabled on this Ruby/OpenSSL runtime. " \
    "#{requirements.join('; ')}. " \
    "Use Ruby linked against OpenSSL 3.5 or newer, such as ruby:4.0.5-trixie."
end

.warn_unavailable(message) ⇒ Object



39
40
41
# File 'lib/rubygems_pq_tls_policy.rb', line 39

def warn_unavailable(message)
  warn "[rubygems:tls] #{message}" if config.trace?
end