From d69a3d1da28fab7e7b8c60137c7389836e00842e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Gajdu=C5=A1ek?= Date: Mon, 13 Jul 2026 19:33:32 +0200 Subject: [PATCH] Fixes #39518 - Fall back safely when tls_ciphers 'PROFILE=SYSTEM' is unsupported resolve_tls_ciphers previously trusted the presence of /etc/crypto-policies/back-ends/opensslcnf.config alone to mean the 'PROFILE=SYSTEM' cipher-list alias would work, but that alias is a Red Hat/Fedora OpenSSL patch. On a nominally-RHEL host whose Ruby links a vendored, non-RHEL-patched OpenSSL (e.g. rbenv/ruby-build compiling openssl-1.1.1w for Ruby 3.0.4 on CentOS Stream 9), SSL_CTX_set_cipher_list rejects it outright and the server fails to start. Probe 'PROFILE=SYSTEM' before committing to it. If unsupported, parse and use the CipherString already resolved in opensslcnf.config directly (plain, portable OpenSSL syntax, not RHEL-specific) to preserve the active crypto policy's intent. Fall back to 'HIGH' only if that also fails, so the auto-detected default can never crash startup. --- lib/launcher.rb | 50 +++++++++++++++++++++++++++++++++---------- test/launcher_test.rb | 30 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/lib/launcher.rb b/lib/launcher.rb index 0967485bb..a245c4ca2 100644 --- a/lib/launcher.rb +++ b/lib/launcher.rb @@ -118,13 +118,46 @@ def resolve_tls_ciphers return nil if configured&.empty? return configured unless configured.nil? - if File.exist?(CRYPTO_POLICIES_CONFIG) - logger.info "Crypto-policies detected, using PROFILE=SYSTEM for TLS ciphers." - 'PROFILE=SYSTEM' - else + unless File.exist?(CRYPTO_POLICIES_CONFIG) logger.debug "No crypto-policies detected, using HIGH cipher string as default." - 'HIGH' + return 'HIGH' + end + + if cipher_string_supported?('PROFILE=SYSTEM') + logger.info "Crypto-policies detected, using PROFILE=SYSTEM for TLS ciphers." + return 'PROFILE=SYSTEM' end + + cipher_string = crypto_policies_cipher_string + if cipher_string && cipher_string_supported?(cipher_string) + logger.info "Crypto-policies detected, but this OpenSSL build does not support the " \ + "'PROFILE=SYSTEM' cipher-list alias. Using the CipherString resolved from " \ + "#{CRYPTO_POLICIES_CONFIG} directly instead." + return cipher_string + end + + logger.warn "Crypto-policies detected but could not be applied on this Ruby/OpenSSL build; " \ + "falling back to the 'HIGH' cipher string." + 'HIGH' + end + + # Extracted so resolve_tls_ciphers can probe candidate cipher strings before + # committing to them, instead of only discovering they're unusable at startup. + def cipher_string_supported?(ciphers) + OpenSSL::SSL::SSLContext.new.ciphers = ciphers + true + rescue OpenSSL::SSL::SSLError + false + end + + def crypto_policies_cipher_string + File.foreach(CRYPTO_POLICIES_CONFIG) do |line| + return Regexp.last_match(1).strip if line =~ /^CipherString\s*=\s*(.+)$/ + end + nil + rescue SystemCallError => e + logger.warn "Unable to read #{CRYPTO_POLICIES_CONFIG}: #{e.message}" + nil end def validate_tls_ciphers!(ciphers) @@ -135,12 +168,7 @@ def validate_tls_ciphers!(ciphers) "The system crypto policy minimum TLS version may be overridden by this setting." end - cipher_list = begin - OpenSSL::SSL::SSLContext.new.ciphers = ciphers - ciphers - rescue OpenSSL::SSL::SSLError - nil - end + cipher_list = cipher_string_supported?(ciphers) ? ciphers : nil if OpenSSL::SSL::SSLContext.method_defined?(:ciphersuites=) ciphersuites = begin diff --git a/test/launcher_test.rb b/test/launcher_test.rb index df8195e53..b5b0c84ac 100644 --- a/test/launcher_test.rb +++ b/test/launcher_test.rb @@ -53,6 +53,7 @@ def test_resolve_tls_ciphers_raises_on_non_string def test_resolve_tls_ciphers_autodetects_profile_system_when_crypto_policies_present launcher = launcher_with({}) File.expects(:exist?).with(CRYPTO_POLICIES_CONFIG).returns(true) + launcher.stubs(:cipher_string_supported?).with('PROFILE=SYSTEM').returns(true) launcher.logger.stubs(:info) assert_equal 'PROFILE=SYSTEM', launcher.resolve_tls_ciphers end @@ -64,6 +65,35 @@ def test_resolve_tls_ciphers_defaults_to_high_when_no_crypto_policies assert_equal 'HIGH', launcher.resolve_tls_ciphers end + def test_resolve_tls_ciphers_falls_back_to_parsed_cipher_string_when_profile_system_unsupported + launcher = launcher_with({}) + File.expects(:exist?).with(CRYPTO_POLICIES_CONFIG).returns(true) + launcher.stubs(:cipher_string_supported?).with('PROFILE=SYSTEM').returns(false) + launcher.stubs(:crypto_policies_cipher_string).returns('HIGH:!aNULL') + launcher.stubs(:cipher_string_supported?).with('HIGH:!aNULL').returns(true) + launcher.logger.stubs(:info) + assert_equal 'HIGH:!aNULL', launcher.resolve_tls_ciphers + end + + def test_resolve_tls_ciphers_falls_back_to_high_when_crypto_policies_unparseable + launcher = launcher_with({}) + File.expects(:exist?).with(CRYPTO_POLICIES_CONFIG).returns(true) + launcher.stubs(:cipher_string_supported?).with('PROFILE=SYSTEM').returns(false) + launcher.stubs(:crypto_policies_cipher_string).returns(nil) + launcher.logger.stubs(:warn) + assert_equal 'HIGH', launcher.resolve_tls_ciphers + end + + def test_resolve_tls_ciphers_falls_back_to_high_when_parsed_cipher_string_also_unsupported + launcher = launcher_with({}) + File.expects(:exist?).with(CRYPTO_POLICIES_CONFIG).returns(true) + launcher.stubs(:cipher_string_supported?).with('PROFILE=SYSTEM').returns(false) + launcher.stubs(:crypto_policies_cipher_string).returns('BOGUS') + launcher.stubs(:cipher_string_supported?).with('BOGUS').returns(false) + launcher.logger.stubs(:warn) + assert_equal 'HIGH', launcher.resolve_tls_ciphers + end + def test_validate_tls_ciphers_returns_nil_pair_for_nil assert_equal [nil, nil], @launcher.validate_tls_ciphers!(nil) end