Repository navigation
Adding post-quantum cryptography tests #894
Description
Summary and motivation
I am considering contributing unit-tests to test post-quantum cryptography (PQC) [1] cases in this repository. In my understanding, the PQC is a kind of enhanced crypto which people can use safely in the age of the quantum computing which is more powerful computer than the current computing. I would cite the [1]'s Background section below. That means the practical use of quantum computers which break the current cryptography may come after 20 years. However, I think it's better to prepare for that from now according to the document in the section.
Some engineers even predict that within the next twenty or so years sufficiently large quantum computers will be built to break essentially all public key schemes currently in use. Historically, it has taken almost two decades to deploy our modern public key cryptography infrastructure. Therefore, regardless of whether we can estimate the exact time of the arrival of the quantum computing era, we must begin now to prepare our information security systems to be able to resist quantum computing.
One of the approved cryptographies for the PQC is ML-DSA (FIPS 204)[2]. So, my plan is to contribute one unit test case to the test/openssl/test_ssl.rb, running TLS dual certificates server with ML-DSA (PQC supported) and RSA (PQC not supported) using the test/openssl/utils.rb#start_server, then the 1st client with ML-DSA certificate connects to the server, then 2nd client with RSA certificate connects to the server. Both clients use the test/openssl/test_ssl.rb#server_connect. Both client connections should succeed because the TLS server has the dual certificates. Note that the [3] is a Red Hat's document about PQC. I referred to the document to select the test case and understand the implementation using the openssl s_server and openssl s_client.
Added below on 16th July 2025
There are actually many demands to migrate to PQC. For example, this document[5] is European Commission's movement to migrate to PQC. The PDF document downloaded on the page[5] says below.
The set of recommendations that Member States need to implement for a synchronised transition to PQC are divided into First Steps that are required to initiate the transition, and Next Steps that should follow.
Proof of Concept
I created a proof-of-concept using the Bash and Ruby scripts.[4] I tested the scripts with the current latest openssl/openssl master branch.
Let me explain the scripts in [4].
The following command in a Bash script is to run the TLS server with dual certificates, ML-DSA and RSA.
$ /path/to/openssl s_server \
-cert localhost-mldsa.crt -key localhost-mldsa.key \
-dcert localhost-rsa.crt -dkey localhost-rsa.key
And the following command in a Bash script is to connect to the server the ML-DSA certificate. And I see the expected result.
$ /path/to/openssl s_client \
-connect localhost:4433 \
-CAfile localhost-mldsa.crt </dev/null
...
Peer signature type: mldsa65
Negotiated TLS1.3 group: X25519MLKEM768
...
And the following command in a Bash script is to connect to the server the RSA certificate. And I see the expected result.
$ /path/to/openssl s_client \
-connect localhost:4433 \
-CAfile localhost-rsa.crt \
-sigalgs 'rsa_pss_pss_sha256:rsa_pss_rsae_sha256' </dev/null
...
Peer signing digest: SHA256
Peer signature type: rsa_pss_rsae_sha256
Negotiated TLS1.3 group: X25519MLKEM768
...
And I want to implement the TLS server and client implemented in Ruby that behave the same as the Bash scripts above. The file start-dual-cert-server.rb is for the TLS server. I confirmed the script behaves as the above openssl s_server. And the script connect.rb is for the TLS client with ML-DSA or RSA certificate.
Questions
So, here are 2 questions.
First. How to get the following header values that openssl s_client outputs, in a Ruby TLS client script?
Peer signing digest:
Peer signature type:
Negotiated TLS1.3 group:
Second, on the [4], the TLS server implemented in Ruby behaves as expected. However, the TLS client connecting with an RSA certificate behaves differently in the following output from the openssl s_server between the Bash and Ruby scripts. I am looking for a way for the Ruby TLS client to behave the same as the Bash script. Do you know how to do it?
Below is the output from the openssl s_server when connecting from a TLS client with an RSA certificate implemented in Bash (./pqc.sh connect-rsa = openssl s_client).
Shared ciphers:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA
Signature Algorithms: rsa_pss_pss_sha256:RSA-PSS+SHA256
Shared Signature Algorithms: rsa_pss_pss_sha256:RSA-PSS+SHA256
Supported groups: X25519MLKEM768:x25519:secp256r1:x448:secp384r1:secp521r1:ffdhe2048:ffdhe3072
Shared groups: X25519MLKEM768:x25519:secp256r1:x448:secp384r1:secp521r1:ffdhe2048:ffdhe3072
Below is the output from the openssl s_server when connecting from a TLS client with RSA certifiate implmented in Ruby (ruby -I/path/to/ruby/openssl/lib connect.rb rsa).
Shared ciphers:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA
Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:ed25519:ed448:rsa_pss_pss_sha256:rsa_pss_pss_sha384:rsa_pss_pss_sha512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224
Shared Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:ed25519:ed448:rsa_pss_pss_sha256:rsa_pss_pss_sha384:rsa_pss_pss_sha512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224
Supported groups: x25519:secp256r1:x448:secp521r1:secp384r1:ffdhe2048:ffdhe3072:ffdhe4096:ffdhe6144:ffdhe8192
Shared groups: x25519:secp256r1:x448:secp521r1:secp384r1:ffdhe2048:ffdhe3072
What do you think?
Tasks (added this section later)
Below are the tasks to complete this issue ticket.
- Add the
SSLContext#sigalgs=: ssl: add SSLContext#sigalgs= and #client_sigalgs= #895 - Rename the
SSLContext#ecdh_curves=to#groups=: ssl: renameSSLContext#ecdh_curves=to#groups=#900 - Support provider-only pkeys (reported at this comment): pkey: add support for OpenSSL 3 provider-only pkeys #898
- Add features to get the following data from a TLS client: ssl: add SSLSocket#sigalg, #peer_sigalg, #group #908
Peer signing digest- Peer signature type
- Negotiated TLS1.3 group
- Add PQC unit tests: ssl: add post-quantum cryptography (PQC) tests #913
References
- [1] Post-Quantum Cryptography: https://csrc.nist.gov/projects/post-quantum-cryptography
- [2] FIPS 204: https://csrc.nist.gov/pubs/fips/204/final
- [3] https://www.redhat.com/en/blog/post-quantum-cryptography-red-hat-enterprise-linux-10
- [4] https://github.com/junaruga/ruby-openssl-pqc-test
- [5] A Coordinated Implementation Roadmap for the Transition to Post-Quantum Cryptography: https://digital-strategy.ec.europa.eu/en/library/coordinated-implementation-roadmap-transition-post-quantum-cryptography
Activity
rhenium commentedon Jun 2, 2025
I think ML-DSA and ML-KEM should already be usable in
OpenSSL::SSL::SSLSocket, but it seems we lack convenient methods to control the usage better as you have pointed out.To use them outside TLS, we need some additional work: some
OpenSSL::PKeymethods don't handle provider-only pkeys well, and we'd also need to add new bindings fortheEDIT:EVP_KEMinterfaceEVP_PKEY_encapsulate();EVP_KEMstruct itself does not seem useful.It appears we need the following functions, which haven't been exposed ruby/openssl yet because no one has requested them so far:
SSL_get_peer_signature_nid(), added in OpenSSL 1.1.1a according to the man pageSSL_get0_peer_signature_name(), added in OpenSSL 3.5SSL_get_negotiated_group()is needed for (ML-)KEM, added in OpenSSL 3.0ruby/openssl currently can't restrict signature algorithms.
The
pqc.shscript usesopenssl s_client -sigalgs. #769 adds a wrapper forSSL_CTX_set1_sigalgs_list()that corresponds to this. The PR hasn't been merged yet due to missing tests.FYI, about
connect.rb: settingSSLContext#cert=only, without a matching#key=, has no effect.junaruga commentedon Jun 3, 2025
Thanks for your reply and advice!
As a note, all of the algorithms FIPS-approved for the PQC are, ML-KEM (FIPS 203), ML-DSA (FIPS 204) and SLH-DSA (FIPS 205) according to the [1] and [3]. However, I heard from my colleagues working for OpenSSL, that SLH-DSA in NIST or OpenSSL is not ready to be used. We can ignore SLH-DSA case.
Interestingly, while these algorithms are FIPS-approved, I heard these algorithms are used from the default provider in OpenSSL 3.5, and the fips provider doesn't have these algorithms in it. I tested the [4] with the default provider without fips provider.
I found the design documents about ML-KEM and ML-DSA.
All right. Thank you for your investigation!
Thanks for the info. I missed considering the
openssl s_client -sigalgsused in my Bash script./pqc.sh connect-rsa. I will take a look at the PR.Oh okay. I see the
SSLContext#cert=,SSLContext#key=andSSLContext#extra_chain_cert=are marked as deprecated, and theSSLContext#add_certificateare encouraged as alternative.So, is the correct implementation below in the
connect.rb?Edit: I see the
nilfor thekeyargument doesn't work.So, what is your intention about the current behavior of the method
#add_certificate? There is a case that we only need the certificate file without private key file like this.rhenium commentedon Jun 3, 2025
Yes, I'd recommend using it whenever possible.
SSLContext#add_certificatestill requires a private key (but produces a better error). However I don't see why a certificate is set to theSSLContexthere at all, since the server (pqc.shorstart-dual-cert-server.rb) does not request a client certificate.junaruga commentedon Jun 5, 2025
In the Red Hat blog [3] which I mentioned on the first comment, you see the examples of the
openssl s_client -CAfile .... And I confirmed their intention of the-CAfileoption, by talking with my colleagues working for OpenSSL.The intention is the
openssl s_client -CAfile file.crttries to verify the server's certificate(s), printing the informationVerification:,Verification error:andVerify return code:in the output of theopenssl s_client. Users can verify the server certificate from the information.For example, when running the following TLS dual certificates server with ML-DSA and RSA bits (=
./qpgc.sh start-dual-cert-server):The following
openssl s_clientsuccessfully verifies the server's certificate, printing the lineVerification: OKandVerify return code: 0 (ok). Because the-CAfile localhost-mldsa.crtis the server's certificate.However, when using another MLDSA certificate which is not the server certificate:
The following
openssl s_clientfails to verify the server certificate, printingVerification error: self-signed certificateandVerify return code: 18 (self-signed certificate).The
openssl s_clientwithout the-CAfilealso prints theVerification error: self-signed certificateandVerify return code: 18 (self-signed certificate).As a note, the
openssl s_client -verify_return_erroroption makes theopenssl s_clientcommand fail with non-zero exit-status when the server verification fails.With the
-CAfile localhost-mldsa.crt, the command succeeds.With the
-CAfile localhost-mldsa-2.crt, the command fails.rhenium commentedon Jun 5, 2025
I think the option
openssl s_client -CAfilewould correspond toSSLContext#ca_fileorSSLContext#cert_store, which is used for verifying the certificate sent by the server.junaruga commentedon Jun 5, 2025
Thank you for the info! I was wrongly using the
SSLContext#certandSSLContext#add_certificate. Now I updated theconnect.rbin my proof-of-concept repository [4] with theSSLContext#ca_file. I also applied the commits on the PR #895. And I compared the outputs of theopenssl s_serverrunning the dual certificates (=./pqc.sh start-dual-cert-server).Interestingly the following ruby script with setting
ctx.client_sigalgs = "rsa_pss_pss_sha256:rsa_pss_rsae_sha256"have still differences for the following headers in the output of theopenssl s_server, showing many allowed signature algorithms (Signature Algorithms:,Shared Signature Algorithms:).The headers in the output of the
openssl s_serverare below when running theopenssl s_client -sigalgs 'rsa_pss_pss_sha256:rsa_pss_rsae_sha256. TheSignature Algorithms:andShared Signature Algorithms:print onlyrsa_pss_pss_sha256:RSA-PSS+SHA256.So, what's wrong with my use of the
SSLContext#client_sigalgsin theconnect.rb?rhenium commentedon Jun 5, 2025
Does
SSLContext#sigalgsin #895 work?SSL_CTX_set1_sigalgs_list()will affect signature algorithm selection by the server, i.e., the server certificates selection.SSL_CTX_set1_client_sigalgs_list()on the other hands affects client certificates. Since the scripts don't use any, this would be no-op.(BTW I don't quite like how these functions work differently depending on whether the
SSL_CTXends up with being a client or a server, but I think exposing these functions as-is is the least confusing for users who are familiar with the OpenSSL C API.)junaruga commentedon Jun 5, 2025
You are right! I used the
SSLContext#sigalgsinstead of theSSLContext#client_sigalgs. TheSSLContext#sigalgsworks! And now the content of the headers:Shared ciphers:,Signature Algorithms:andShared Signature Algorithms:in the output of theopenssl s_serveris the same between the cases of theopenssl s_client -sigalgs 'rsa_pss_pss_sha256:rsa_pss_rsae_sha256'andruby -I/path/to/ruby/openssl/lib connect.rb rsa! I updated my proof-of-concept repository [4].I see. Okay.
junaruga commentedon Jun 5, 2025
Okay. I see the
openssl s_client -helpshows the-sigalgsand-client_sigalgsoptions.junaruga commentedon Jun 6, 2025
@rhenium According to the above Red Hat blog [3], also the following OpenSSL change log[5], 3 TLS groups:
X25519MLKEM768,SecP256r1MLKEM768, andSecP384r1MLKEM1024are supported for post-quantum crypto. Though I don't find NIST documents about the supported TLS groups. We talked about theX25519MLKEM768cases, and now I added theSecP256r1MLKEM768cases to my proof-of-concept repository[6].Run the following dual ML-DSA and RSA server enabling the TLS group
SecP256r1MLKEM768(=./pqc.sh start-dual-cert-mldsa-and-rsa-server-SecP256r1MLKEM768). I needed to enable TLS group explicitly by the-groups "SecP256r1MLKEM768".. Because theSecP256r1MLKEM768is disabled as a default on the OpenSSL 3.6 (openssl/openssl@10bd6fa) which I tested.Run the following
openssl s_client(=./pqc.sh connect-mldsa-SecP256r1MLKEM768). I also needed to enable the TLS groupSecP256r1MLKEM768.Run the following
openssl s_client(=./pqc.sh connect-rsa-SecP256r1MLKEM768).So, now I want the instance method
SSLContext#groupswhich is equivalent from theopenssl s_server -groupsandopenssl s_client -groups. Though theSSLContext#sigalgs's priority is higher than theSSLContext#groups.It seems the SSL_CTX_set1_groups_list is the C-API to implement the
SSLContext#groups.What do you think?
References
rhenium commentedon Jun 8, 2025
It seems we already have it, but as a different method name. It appears OpenSSL 1.1.1 introduced
SSL_CTX_set1_groups_list()as an alias ofSSL_CTX_set1_curves_list()because the extension is not just for ECDH in TLS 1.3.I guess we should also rename
SSLContext#ecdh_curves=to#groups=.https://ruby.github.io/openssl/OpenSSL/SSL/SSLContext.html#method-i-ecdh_curves-3D
jackorp commentedon Jun 11, 2025
When testing with the PQC keys from Ruby, key loading and generation for PQC algorithms in style of
OpenSSL::PKey.generate_key('mldsa65'), the OID in the#inspect's string is null and raises argument error when trying to use it. Formldsatype pkeys-1is returned resulting inNULLfrom theOBJ_nid2snwhen the -1 is provided as an argument:openssl/ext/openssl/ossl_pkey.c
Line 713 in 43d1b53
offline discussion with openssl developers (@beldmit), revealed that NID shouldn't really be used. There isn't any for new algos. There was some discussion in upstream OpenSSL regarding the
-1return value openssl/openssl#27738EVP_PKEY_is_awas mentioned as a way to find the key type.EVP_PKEY_get0_type_namemight be a way for the#inspectmethod to provide the type in the inspect Ruby string.junaruga commentedon Jun 13, 2025
All right! That's good news!
I tested the
SSLContext#ecdh_curves=in the TLS groupSecP256r1MLKEM768andSecP384r1MLKEM1024cases, adding the features in my proof-of-concept repository[4], and confirmed the method worked as expected.I would agree with you. I think we should rename the
SSLContext#ecdh_curves=to#groups=, then keep the#ecdh_curves=as an alias of the#groups=as a backward compatibility for a certain term, marking it as deprecated.junaruga commentedon Jun 25, 2025
Note I added the Tasks section to manage the tasks to complete this issue on my first comment of this page.
junaruga commentedon Jun 26, 2025
It seems the above issue was fixed by #898. Thank you for your report and investigation!
junaruga commentedon Jul 11, 2025
I sent the PR #908.
junaruga commentedon Jul 23, 2025
I sent the PR #913.
junaruga commentedon Jul 28, 2025
We completed this task!