Skip to content

Add post-quantum cryptography (PQC) support to SSLConfig - #53

Draft
junaruga wants to merge 1 commit into
ruby:masterfrom
junaruga:wip/support-pqc
Draft

Add post-quantum cryptography (PQC) support to SSLConfig#53
junaruga wants to merge 1 commit into
ruby:masterfrom
junaruga:wip/support-pqc

Conversation

@junaruga

@junaruga junaruga commented Jul 29, 2026

Copy link
Copy Markdown
Member

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 the drb/README.md is below. The sections without "(development)" test with the current master branch. These work. The sections with "(development)" test with this PR.

$ grep ^## drb/README.md
## druby (non-SSL)
## drbssl (SSL) auto-generated RSA cert
## drbssl (SSL) pre-generated RSA cert
## drbssl (SSL) pre-generated ML-DSA-65 cert
## drbssl (SSL) pre-generated RSA cert with client cert
## drbssl (SSL) pre-generated ML-DSA-65 cert with client cert
## drbssl (SSL) auto-generated ML-DSA-65 cert (development)
## drbssl (SSL) auto-generated ML-DSA-65/RSA multi cert (development)
## drbssl (SSL) pre-generated ML-DSA-65/RSA multi cert (development)
## drbssl (SSL) pre-generated ML-DSA-65/RSA multi cert with client cert (development)

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.

  • 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]

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.yml uses 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_*.rb files.

  • test/drb/test_acl.rb tests drb/acl.rb as an unit test level.
  • test/drb/test_drbobject.rb tests DRbObject class as an unit test level.
  • test/drb/test_drb.rb tests druby protocol as an integration test level.
  • test/drb/test_drbssl.rb tests drbssl protocol as an integration test level.
  • test/drb/test_drbunix.rb is tests drbunix protocol as an integration test level.

So, I created test/drb/test_ssl.rb to test DRb::DRbSSLSocket::SSLConfig in 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? is false ,and OpenBSD server as I saw the following logic in DRbSSLService, test/drb/test_drbssl.rb.

    if RUBY_PLATFORM.match?(/openbsd/)
      config[:SSLMinVersion] = OpenSSL::SSL::TLS1_2_VERSION
      config[:SSLMaxVersion] = OpenSSL::SSL::TLS1_2_VERSION
    end

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]
@junaruga

Copy link
Copy Markdown
Member Author

I see the following CI failure. Let me fix it.

https://github.com/ruby/drb/actions/runs/30444292244/job/90550763624?pr=53

@junaruga
junaruga marked this pull request as draft July 29, 2026 10:40
Comment thread lib/drb/ssl.rb
cert.not_before = Time.now
cert.not_after = Time.now + (365*24*60*60)
cert.public_key = rsa.public_key
cert.public_key = OpenSSL::PKey.read(pkey.public_to_der)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this change is because OpenSSL::PKey::PKey class for ML-DSA-NN doesn't have #public_key unlike OpenSSL::PKey::RSA. So, I picked up the following OpenSSL::PKey::RSA#public_key internal logic.

https://github.com/ruby/openssl/blob/9796ee8f47003ec78fd8e052bc8dd6d1fcb0ae2b/lib/openssl/pkey.rb#L353-L355

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant