From 446c9f01274badf89c70d1ec4e3470f8561f8568 Mon Sep 17 00:00:00 2001 From: Joao Duarte Date: Tue, 2 Sep 2025 09:55:05 +0100 Subject: [PATCH] Fix JRuby test suite: OpenSSL 3 expired certs, TLS 1.3 mTLS, DER key parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * OpenSSL 3 compatibility: stop relying on negative/zero -days for expired cert fixtures (rejected by OpenSSL 3, preventing local running of tests on MacOS). Synthesize an expired self‑signed cert at runtime in the test helper * TLS 1.3 interop: work around mTLS handshake timeouts with JRuby on JDK 21 by disabling TLSv1.3 on the WEBrick SSL test server * PKCS#8 key parsing: do not chomp decoded DER. Trimming binary DER can remove trailing newline bytes and corrupt the key, causing InvalidKeySpecException (covered by an existing test) --- Rakefile | 16 +++++++++------- lib/manticore/client.rb | 3 ++- lib/org/manticore/manticore-ext.jar | Bin 4449 -> 4480 bytes spec/spec_helper.rb | 20 +++++++++++++++++++- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Rakefile b/Rakefile index 70d3c75..c0e96bd 100644 --- a/Rakefile +++ b/Rakefile @@ -54,24 +54,26 @@ task :generate_certs do cmds = [ # Create the CA "#{openssl} genrsa 4096 | #{openssl} pkcs8 -topk8 -nocrypt -out #{root}/root-ca.key", - "#{openssl} req -sha256 -x509 -newkey rsa:4096 -nodes -key #{root}/root-ca.key -sha256 -days 365 -out #{root}/root-ca.crt -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore CA/OU=Manticore/CN=localhost\"", - "#{openssl} req -sha256 -x509 -newkey rsa:4096 -nodes -key #{root}/root-ca.key -sha256 -days 365 -out #{root}/root-untrusted-ca.crt -subj \"/C=US/ST=The Darknet/L=The Darknet/O=Manticore CA/OU=Manticore/CN=localhost\"", + "#{openssl} req -newkey rsa:4096 -x509 -nodes -key #{root}/root-ca.key -sha256 -days 365 -out #{root}/root-ca.crt -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore CA/OU=Manticore/CN=localhost\"", + "#{openssl} req -newkey rsa:4096 -x509 -nodes -key #{root}/root-ca.key -sha256 -days 365 -out #{root}/root-untrusted-ca.crt -subj \"/C=US/ST=The Darknet/L=The Darknet/O=Manticore CA/OU=Manticore/CN=localhost\"", # Create the client CSR, key, and signed cert "#{openssl} genrsa 4096 | #{openssl} pkcs8 -topk8 -nocrypt -out #{root}/client.key", - "#{openssl} req -sha256 -key #{root}/client.key -newkey rsa:4096 -out #{root}/client.csr -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore Client/OU=Manticore/CN=localhost\"", + "#{openssl} req -newkey rsa:4096 -sha256 -key #{root}/client.key -out #{root}/client.csr -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore Client/OU=Manticore/CN=localhost\"", "#{openssl} x509 -req -in #{root}/client.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/client.crt -sha256 -days 1", - "#{openssl} x509 -req -in #{root}/client.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/client-expired.crt -sha256 -days -7", + # OpenSSL 3 rejects negative -days; use 0 days to produce an immediately-expired cert + "#{openssl} x509 -req -in #{root}/client.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/client-expired.crt -sha256 -days 0", # Create the client_whitespace CSR and signed cert - "#{openssl} req -sha256 -key #{root}/client_whitespace.key -newkey rsa:4096 -out #{root}/client_whitespace.csr -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore Client/OU=Manticore/CN=localhost\"", + "#{openssl} req -newkey rsa:4096 -sha256 -key #{root}/client_whitespace.key -out #{root}/client_whitespace.csr -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore Client/OU=Manticore/CN=localhost\"", "#{openssl} x509 -req -in #{root}/client_whitespace.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/client_whitespace.crt -sha256 -days 1", # Create the server cert "#{openssl} genrsa 4096 | #{openssl} pkcs8 -topk8 -nocrypt -out #{root}/host.key", - "#{openssl} req -sha256 -key #{root}/host.key -newkey rsa:4096 -out #{root}/host.csr -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore Host/OU=Manticore/CN=localhost\"", + "#{openssl} req -newkey rsa:4096 -sha256 -key #{root}/host.key -out #{root}/host.csr -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore Host/OU=Manticore/CN=localhost\"", "#{openssl} x509 -req -in #{root}/host.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/host.crt -sha256 -days 1", - "#{openssl} x509 -req -in #{root}/host.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/host-expired.crt -sha256 -days -1", + # OpenSSL 3 rejects negative -days; use 0 days to produce an immediately-expired cert + "#{openssl} x509 -req -in #{root}/host.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/host-expired.crt -sha256 -days 0", "#{openssl} x509 -req -in #{root}/host.csr -CA #{root}/root-untrusted-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/host-untrusted.crt -sha256 -days 1", "#{keytool} -import -file #{root}/root-ca.crt -alias rootCA -keystore #{root}/truststore.jks -noprompt -storepass test123", diff --git a/lib/manticore/client.rb b/lib/manticore/client.rb index 96a58ce..53bf725 100644 --- a/lib/manticore/client.rb +++ b/lib/manticore/client.rb @@ -750,7 +750,8 @@ def setup_key_store(ssl_options, context) key_parts = key_str.scan(KEY_EXTRACTION_REGEXP) key_parts.each do |type, b64key| body = Base64.decode64 b64key - spec = PKCS8EncodedKeySpec.new(body.chomp.to_java_bytes) + # Do not chomp binary DER content; trimming can corrupt the key structure. + spec = PKCS8EncodedKeySpec.new(body.to_java_bytes) type = type.strip type = "RSA" if type == "" key = KeyFactory.getInstance(type).generatePrivate(spec) diff --git a/lib/org/manticore/manticore-ext.jar b/lib/org/manticore/manticore-ext.jar index ac738e16986f0615aa4b3f1ebb448eaee747854c..502923795bf8e9bbf7077e631e15c6e1860853b3 100644 GIT binary patch literal 4480 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 X51OjL>)rtX% pkey, :AccessLog => [], :Logger => WEBrick::Log.new("/dev/null"), + # Disable TLS 1.3 to avoid interop issues with some JRuby/JDK combos during mTLS + :SSLOptions => (defined?(OpenSSL::SSL::OP_NO_TLSv1_3) ? OpenSSL::SSL::OP_NO_TLSv1_3 : 0), }.merge(options) ) server.mount_proc "/" do |req, res|