Add post-quantum cryptography (PQC) support to SSLConfig - #53
Conversation
|
I see the following CI failure. Let me fix it. https://github.com/ruby/drb/actions/runs/30444292244/job/90550763624?pr=53 |
7e5fc39 to
2a55f51
Compare
The cause of the CI failure was that https://github.com/ruby/openssl/blob/v2.2.0/History.md?plain=1#L72-L75 Now use And use the following logic in ML-DSA-NN case. |
I tested this PR in RubyCI's RHEL 9 and OpenBSD servers. In RHEL 9 server, I confirmed the tests passed. The ML-DSA tests are successfully omitted by In OpenBSD server, I confirmed the tests passed as well. The used OpenSSL version was In I was not sure if However, |
|
@seki or other maintainers, I updated notes section on the first comment. This PR is ready to review. I would appreciate your reviews. |
|
@rhenium I would appreciate your review for this PR as Ruby OpenSSL (openssl gem) maintainer. |
| # drbssl server with pre-generated ML-DSA-65/RSA multi cert and client cert | ||
|
|
||
| require_relative 'ut_drb' | ||
| require_relative 'drbtest_utils' |
There was a problem hiding this comment.
Previously, this file required require_relative 'drbtest' here. However, I got the following errors. The test result was 0 failures, 0 errors. But you see the RuntimeError tracktrace logs in test/drb/ut_drb_drbssl_pqc_multi_cert.rb. The cause was test/drb/drbtest.rb requiring test-unit via require 'test/unit'. It seems that the test-unit's at_exit calling Test::Unit::AutoRunner.run below was triggered when the child process exited, and caused errors when DRbTests::TestDRbSSLPQCMultiCertMLDSA65 and DRbTests::TestDRbSSLPQCMultiCertRSA were executed. So, now this file requires require_relative 'drbtest_utils' which doesn't require test-unit.
https://github.com/test-unit/test-unit/blob/3.7.8/lib/test/unit.rb#L516-L519
$ bundle exec ruby -I test/lib -rhelper test/drb/test_drbssl.rb -v
Loaded suite test/drb/test_drbssl
Started
DRbTests::TestDRbSSLAry:
test_01: .: (0.413258)
test_02_collect: .: (0.436087)
test_03_redo: .: (0.433541)
test_05_break: .: (0.435188)
test_06_next: .: (0.389877)
test_07_break_18: .: (0.408441)
DRbTests::TestDRbSSLCore:
test_00_DRbObject: .: (0.419140)
test_01: .: (0.509643)
test_01_02_loop: .: (0.453699)
test_02_basic_object: .: (0.380818)
test_02_unknown: .: (0.421713)
test_03: .: (0.417325)
test_04: .: (0.416480)
test_05_eq: .: (0.385149)
test_06_timeout: .: (2.705462)
test_07_private_missing: .: (0.422167)
test_07_protected_missing: .: (0.451488)
test_07_public_missing: .: (0.409648)
test_07_send_missing: .: (0.544292)
test_08_here: .: (0.382050)
test_09_option: .: (0.420762)
test_10_yield: .: (0.367299)
test_10_yield_undumped: .: (0.408900)
test_11_remote_no_method_error: .: (0.442798)
DRbTests::TestDRbSSLPQC:
test_01_hello: .: (0.326881)
test_group_sigalg: .: (0.336463)
DRbTests::TestDRbSSLPQCMultiCertMLDSA65:
test_01_hello: .: (0.460257)
test_group_sigalg: /home/jaruga/var/git/ruby/drb/test/drb/ut_drb_drbssl_pqc_multi_cert.rb:12:in 'shift': usage: /home/jaruga/var/git/ruby/drb/test/drb/ut_drb_drbssl_pqc_multi_cert.rb <manager-uri> <name> (RuntimeError)
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1735:in 'block in OptionParser#parse_in_order'
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1734:in 'Kernel#catch'
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1734:in 'OptionParser#parse_in_order'
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1728:in 'OptionParser#order!'
from /home/jaruga/var/git/ruby/drb/vendor/bundle/ruby/4.1.0+1/gems/test-unit-3.7.8/lib/test/unit/autorunner.rb:201:in 'Test::Unit::AutoRunner#process_args'
from /home/jaruga/var/git/ruby/drb/vendor/bundle/ruby/4.1.0+1/gems/test-unit-3.7.8/lib/test/unit/autorunner.rb:68:in 'Test::Unit::AutoRunner.run'
from /home/jaruga/var/git/ruby/drb/vendor/bundle/ruby/4.1.0+1/gems/test-unit-3.7.8/lib/test/unit.rb:518:in 'block (2 levels) in <top (required)>'
.: (0.479292)
DRbTests::TestDRbSSLPQCMultiCertRSA:
test_01_hello: .: (0.447127)
test_group_sigalg: /home/jaruga/var/git/ruby/drb/test/drb/ut_drb_drbssl_pqc_multi_cert.rb:12:in 'shift': usage: /home/jaruga/var/git/ruby/drb/test/drb/ut_drb_drbssl_pqc_multi_cert.rb <manager-uri> <name> (RuntimeError)
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1735:in 'block in OptionParser#parse_in_order'
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1734:in 'Kernel#catch'
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1734:in 'OptionParser#parse_in_order'
from /home/jaruga/.local/ruby-4.1.0-debug-3ef48ef9c8-openssl-4.1.0-7194354488/lib/ruby/4.1.0+1/optparse.rb:1728:in 'OptionParser#order!'
from /home/jaruga/var/git/ruby/drb/vendor/bundle/ruby/4.1.0+1/gems/test-unit-3.7.8/lib/test/unit/autorunner.rb:201:in 'Test::Unit::AutoRunner#process_args'
from /home/jaruga/var/git/ruby/drb/vendor/bundle/ruby/4.1.0+1/gems/test-unit-3.7.8/lib/test/unit/autorunner.rb:68:in 'Test::Unit::AutoRunner.run'
from /home/jaruga/var/git/ruby/drb/vendor/bundle/ruby/4.1.0+1/gems/test-unit-3.7.8/lib/test/unit.rb:518:in 'block (2 levels) in <top (required)>'
.: (0.482582)
Finished in 15.021564814 seconds.
---------------------------------------------------------------------------------------------------
30 tests, 88 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---------------------------------------------------------------------------------------------------
2.00 tests/s, 5.86 assertions/s
2a55f51 to
692613d
Compare
DRb::DRbSSLSocket::SSLConfig already works with pre-generated ML-DSA cert/key by
:SSLCertificate and :SSLPrivateKey config options.
This commit adds the following features in PQC use cases to #setup_certificate
and #setup_ssl_context.
* Add :SSLCertificates config option accepting an Array of
[certificate, private_key] pairs for dual/multiple certificate
support via OpenSSL::SSL::SSLContext#add_certificate
changing from OpenSSL::SSL::SSLContext#cert and #key.
Because an SSL server that accepts clients with either ML-DSA-NN
or RSA certificates is useful in the use case of PQC migration from RSA
(non-PQC) to ML-DSA (PQC).
:SSLCertificate and :SSLPrivateKey are available for backward compatibility.
But use OpenSSL::SSL::SSLContext#add_certificate internally.
This is a behavior change.
* Add :SSLPrivateKeyAlgorithms option accepting an Array of key
algorithms (RSA, ML-DSA-44, ML-DSA-65, ML-DSA-87) for
multi-certificate generation, defaulting to ["RSA"] for backward
compatibility.
* Add :SSLGroups option to control key exchange group such as ML-KEM in PQC.
* Add :SSLSignatureAlgorithms, :SSLClientSignatureAlgorithms to control
signature algorithms such as ML-DSA-NN in PQC, and RSA in non-PQC.
* Add tool/create_certs.sh to generate test/drb/fixtures/*.{crt,key}
to test with pre-generated certs/keys and add DRbTests::Fixtures module
to read the certs/keys.
* Add test/drb/test_ssl.rb to test lib/drb/ssl.rb as an unit test level.
This approach aligns with test/drb/test_acl.rb to test lib/drb/acl.rb
as an unit test level.
* Add TestDRbSSLPQC, TestDRbSSLPQCMultiCertMLDSA65, TestDRbSSLPQCMultiCertRSA
in test/drb/test_drbssl.rb.
TestDRbSSLPQC is to test single PQC ML-KEM/ML-DSA server via
test/drb/ut_drb_drbssl_pqc.rb. The testing class is inspired by
TestDRbSSLCore.
TestDRbSSLPQCMultiCertMLDSA65 and TestDRbSSLPQCMultiCertRSA are to test
ML-DSA-65 (PQC) and RSA (non-PQC) dual (multiple) certificate server
with client certificate via test/drb/ut_drb_drbssl_pqc_multi_cert.rb.
The test is designed for a high-security use case with :SSLVerifyMode
OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
Assisted-by: Claude:claude-opus-4-6[1m]
692613d to
a3c95bc
Compare
|
I added and updated the code comments. |
| [true, nil] | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
I want to simplify the above tests TestDRbSSLPQC, TestDRbSSLPQCMultiCertMLDSA65 and TestDRbSSLPQCMultiCertRSA. These class designs align with the existing TestDRbSSL. However, I am not sure how much important or practical the current two-ways server client tests between manger and ut_*.rb to achieve both client (manager) connection to server (ut_*.rb) and client (ut_*.rb) connection to server (manager) via TestDRbSSL are. It's hard to understand and maintain the code.
Is it better just to create test_* without child classes of DRbService and ut_*.rb with the simple one-way server/client connection like run_drbssl_server_multi_cert_mldsa65_rsa_client_cert_pre_generated.rb and run_drbssl_client_multi_cert_mldsa65_rsa_client_cert_pre_generated.rb?
This PR is related to #52.
Proof of concept
I prepared proof-of-concept scripts for this PR.
The document (
drb/README.md) is here. A list of the sections in thedrb/README.mdis below. The sections without "(development)" test with the current master branch. These work. The sections with "(development)" test with this PR.The script is here. The CI result is here.
Commit message
DRb::DRbSSLSocket::SSLConfig already works
with pre-generated ML-DSA cert/key by
:SSLCertificate and :SSLPrivateKey config options.
This commit adds the following features in PQC use cases to #setup_certificate and #setup_ssl_context.
Assisted-by: Claude:claude-opus-4-6[1m]
Notes
CI cases supporting PQC
PQC (ML-KEM/ML-DSA) works in OpenSSL >= 3.5. OpenSSL 3.5 added the feature. And works in Ruby OpenSSL >= 4.0. Ruby OpenSSL 4.0.0 added some functions in PQC use cases, and fixed a bug.
The
.github/workflows/test.ymluses runner ubuntu-latest, macos-latest, windows-latest.According to https://github.com/actions/runner-images, ubuntu-latest is ubuntu-24.04, macos-latest is macOS-26-arm64, windows-latest is windows-2025-vs2026.
The ubuntu-24.04 uses OpenSSL 3.0.13 which doesn't support PQC. The windows-2025-vs2026 uses OpenSSL 3.6.3 which supports PQC. The macos-26 uses OpenSSL 3.6.2 which supports PQC. The new PQC tests should be executed on the macos-latest and windows-latest CI cases.
test/drb/test_ssl.rb testing lib/drb/ssl.rb as an unit test level
I checked all the existing
test/drb/test_*.rbfiles.So, I created test/drb/test_ssl.rb to test
DRb::DRbSSLSocket::SSLConfigin lib/drb/ssl.rb as an unit test level.tool/create_certs.sh design decisions
The tool/create_certs.sh is inspired by ruby/rubygems#9678. The directory test/drb/fixtures to manage SSL key/cert files is inspired by ruby/openssl managing SSL keys in test/openssl/fixtures/pkey directory. ruby/openssl manages only key files, not certificate files. ruby/openssl is generating testing certificates from the keys in tests.
Testing in RubyCI servers
I plan to test this PR with RHEL 9 server (OpenSSL version is >= 3.5, but OpenSSL config disables PQC by default), which is the case of
support_pqc_handshake?isfalse,and OpenBSD server as I saw the following logic inDRbSSLService, test/drb/test_drbssl.rb.Edited: I confirmed the tests passed in RubyCI RHEL 9 and OpenBSD servers.
drbssl PQC tests
Single cert tests ML-DSA-65
This test with
TestDRbSSLPQCandDRbSSLPQCServiceis inspired by my proof-of-concept "## drbssl (SSL) auto-generated ML-DSA-65 cert". Also closed to theTestDRbSSLCoreandDRbSSLService.Multi cert tests ML-DSA-65/RSA
This test is inspired by my proof-of-concept "drbssl (SSL) pre-generated ML-DSA-65/RSA multi cert with client cert" case, and also inspired by ruby/openssl test/openssl/test_ssl.rb test_pqc_sigalg (https://github.com/ruby/openssl/blob/9796ee8f47003ec78fd8e052bc8dd6d1fcb0ae2b/test/openssl/test_ssl.rb#L2097).
PQC multi cert tests are executed via the following classes.
It is hard to understand the testing framework in test/drb.
In
DRbSSLPQCMultiCertMLDSA65Service, there aremanager_configandclient_config.manager_configis to manage the config used in manager to manage a child process ut_drb_drbssl_pqc_multi_cert.rb in this case. The main test workflow is between the server test/drb/ut_drb_drbssl_pqc_multi_cert.rb and client withclient_configpart. Client withclient_configconnects to the server ut_drb_drbssl_pqc_multi_cert.rb. However, it seems the ut_drb_drbssl_pqc_multi_cert.rb's config is also used as a client to connect to the servermanager_config.