rubygems: Add PQC ML-DSA support for cryptographically signed gems workflow - #9697
rubygems: Add PQC ML-DSA support for cryptographically signed gems workflow#9697junaruga wants to merge 5 commits into
Conversation
8157611 to
a558720
Compare
acf46a0 to
23850b4
Compare
|
I added "Proof of concept" section to the first comment. |
For the above topic, I opened ruby/openssl#1085 for feature request. |
For the above challenge, I sent the PR #9701 based on the discussion on this PR. I think the PR #9701 can be a solution for the challenge. I hope the PR #9701 is reviewed before this PR. Then I can rebase this PR on the latest master branch. |
The following help text and error message in lib/rubygems/security.rb show that
the order of algorithms is RSA, DSA, EC. Align the case branches in create_key with
this order.
```
-A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA.
```
```
"#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported."
```
Assisted-by: Claude:claude-opus-4-6[1m]
The create_certs.rb created test/rubygems/expired_cert_32.pem which was not used and managed in git repository. When not_before and not_after are the same between the regular and 32-bit certificates, don't create 32-bit certificate. Because it is redundant. Assisted-by: Claude:claude-opus-4-6[1m]
* Move omit_unless_support_pqc from test_gem_remote_fetcher_local_ssl_server.rb to helper.rb so that all rubygems test files can use it * Create test/rubygems/pqc_utilities.rb to manage PQC utilities * Update local_ssl_server_utilities.rb to require and include pqc_utilities.rb Assisted-by: Claude:claude-opus-4-6[1m]
* Rename test/rubygems/private_ec_key.pem to test/rubygems/ec_private_key.pem to align with the pattern <algorithm>_key_name.pem (e.g., mldsa65_ssl_key.pem) * Rename EC_KEY to EC_PRIVATE_KEY in test_gem_security.rb * Rename PRIVATE_EC_KEY_FILE to EC_PRIVATE_KEY_FILE in test_gem_commands_cert_command.rb Assisted-by: Claude:claude-opus-4-6[1m]
236b34e to
10f2a05
Compare
…rkflow These changes enable the full PQC ML-DSA cryptographically signed gems workflow: `gem cert --build` (key/cert generation), `gem build` (package signing), and `gem install -P HighSecurity` (signature verification). The `gem cert -A` accepts `ML-DSA-44`, `ML-DSA-65`, or `ML-DSA-87` to generate ML-DSA based cert and key. ``` $ gem cert --build your@email.com -A ML-DSA-44 $ gem cert --build your@email.com -A ML-DSA-65 $ gem cert --build your@email.com -A ML-DSA-87 ``` The reason why it accepts all 3 ML-DSA parameter sets rather than one of them is because all 3 ML-DSA parameter sets suit different workflows. While ML-DSA-65 can be used commonly, a high security environment requires ML-DSA-87. ML-DSA-44: NIST security strength category 2, signature size 2420 bytes ML-DSA-65: NIST security strength category 3, signature size 3309 bytes ML-DSA-87: NIST security strength category 5, signature size 4627 bytes See NIST FIPS 204 Section 4 (Parameter Sets). https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf See Security (Evaluation Criteria) - 4.A.5 Security Strength Categories. https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization/evaluation-criteria/security-(evaluation-criteria) The signed gems workflow also includes * `gem cert -C cert.pem` * `gem cert -K private_key.pem` * `gem build gemname.gemspec` with `s.cert_chain` and `s.signing_key` to build signed gem. * `gem install gemname-version.gem -P HighSecurity` # or `-P MediumSecurity` * `gem update gemname-version.gem -P HighSecurity` # or `-P MediumSecurity` The changes come from: * Ruby OpenSSL methods `#sign` (signing) and `#verify` don't accept digest algorithm, in ML-DSA cases because ML-DSA has a built-in digest * Unifying tool/create_certs.rb with tool/create_encrypted_key.rb to generate RSA and ML-DSA-65 keys/certificates. * ML-DSA-65 related .pem files generated by tool/create_certs.rb. * Adding ML-DSA tests. We need many tests due to differences between `OpenSSL::PKey::RSA/DSA/EC` and `OpenSSL::PKey::PKey`. See also https://guides.rubygems.org/security/ for the signed gems workflows. Assisted-by: Claude:claude-opus-4-6[1m]
10f2a05 to
4ac322a
Compare
For the above issue addressed on notes and challenges section on the first comment, I sent the PR #9768. After the PR #9768 is merged, I can rebase this PR on the latest master branch. |
| def public_key_for(key) | ||
| OpenSSL::PKey.read(key.public_to_der) | ||
| end | ||
|
|
There was a problem hiding this comment.
I think it should be possible to avoid duplicating the key object entirely since OpenSSL::PKey::PKey#public_to_pem is now being used.
| end | ||
|
|
||
| def write_keys_and_certificates(keys, certs) | ||
| cipher = OpenSSL::Cipher.new("aes-256-cbc") |
There was a problem hiding this comment.
| cipher = OpenSSL::Cipher.new("aes-256-cbc") | |
| cipher = "aes-256-cbc" |
| require "socket" | ||
| require "openssl" | ||
|
|
||
| module Gem::PqcUtilities |
| unless Gem::PqcUtilities.support_pqc_openssl? | ||
| yield "PQC algorithms require OpenSSL >= 3.5" | ||
| return | ||
| end | ||
| unless Gem::PqcUtilities.support_pqc_ruby_openssl? | ||
| yield "PQC test requires Ruby OpenSSL >= 4.0" | ||
| return | ||
| end |
There was a problem hiding this comment.
This seems redundant if we check for probe_pqc_handshake anyway.
This PR is related to #9542, and to add PQC ML-DSA support for cryptographically signed gems workflow.
The PR has 6 commits. The 1st - 5th commits are not related to PQC, but for preparation to implement the 6th commit, the main commit of this PR. The PR's 6th commit is in draft phase. I want to share my early phase of the implementation to get feedback and adjust the direction. I hope #9678 will be reviewed and merged before this PR.
Proof of concept
I prepared proof-of-concept scripts for signed gems workflow.
Commit message
These changes enable the full PQC ML-DSA cryptographically signed
gems workflow:
gem cert --build(key/cert generation),gem build(package signing), and
gem install -P HighSecurity(signatureverification).
The
gem cert -AacceptsML-DSA-44,ML-DSA-65, orML-DSA-87to generateML-DSA based cert and key.
The reason why it accepts all 3 ML-DSA parameter sets rather than one of them
is because all 3 ML-DSA parameter sets suit different workflows.
While ML-DSA-65 can be used commonly, a high security environment requires
ML-DSA-87.
ML-DSA-44: NIST security strength category 2, signature size 2420 bytes
ML-DSA-65: NIST security strength category 3, signature size 3309 bytes
ML-DSA-87: NIST security strength category 5, signature size 4627 bytes
See NIST FIPS 204 Section 4 (Parameter Sets).
https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf
See Security (Evaluation Criteria) - 4.A.5 Security Strength Categories.
https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization/evaluation-criteria/security-(evaluation-criteria)
The workflow also includes
gem cert -C cert.pemgem cert -K private_key.pemgem build gemname.gemspecwiths.cert_chainands.signing_keyto buildsigned gem.
gem install gemname-version.gem -P HighSecurity# or-P MediumSecuritygem update gemname-version.gem -P HighSecurity# or-P MediumSecurityThe changes come from:
OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::ECfor RSA, DSA, ECcases, vs
OpenSSL::PKey::PKeyfor ML-DSA-NN cases.OpenSSL::PKey::PKeydoesn't have the methods
#to_pem,#private?.#sign(signing) and#verifydon't accept digestalgorithm, in ML-DSA cases because ML-DSA has a built-in digest
to generate both RSA and ML-DSA-65 certificates/keys.
OpenSSL::PKey::RSA/DSA/ECandOpenSSL::PKey::PKeymentioned above.The method chains for the changes are below.
Gem::Security.create_cert_email -> create_cert_self_signed ->
sign
Gem::Package.build -> Package#build -> add_metadata ->
TarWriter#add_file_signed -> signer.sign
RequestSet#install -> Installer#install ->
pre_install_checks -> verify_spec -> Package#spec ->
Package#verify -> Policy#verify_signatures ->
Policy#verify -> check_trust
See also https://guides.rubygems.org/security/ for the signed gems workflows.
Assisted-by: Claude:claude-opus-4-6[1m]
Notes & challenges
Differences between
OpenSSL::PKey::RSA/DSA/ECandOpenSSL::PKey::PKeyThe above situation makes the changes complicated. Ideally I want the new feature
OpenSSL::PKey::ML-DSAclass orOpenSSL::PKey::ML-DSA-NNclasses, orOpenSSL::PKey::PKeyto implement#to_pemand#private?methods in Ruby OpenSSL. I am thinking to implement the following classes in the meantime.Gem::OpenSSL::PKey::ML_DSAclass which is child class ofOpenSSL::PKey::PKeyfor the compatibility withOpenSSL::PKey::RSAand etc. The class can have#to_pemand#private?.Gem::OpenSSL::PKey::PKeyclass to implementself.generate_keywhich is wrapper ofOpenSSL::PKey.generate_key, but can returnGem::OpenSSL::PKey::ML_DSA.After
OpenSSL::PKey::ML-DSAis implemented, we can use useOpenSSL::PKey::ML-DSAifOpenSSL::PKey::ML-DSAis defined. Otherwise can useGem::OpenSSL::PKey::ML_DSA.or
Gem::OpenSSL::PKey::PKeyclass which is child class ofOpenSSL::PKey::PKeyfor the compatibility withOpenSSL::PKey::RSAand etc. The class can have#to_pemand#private?.Gem::OpenSSL::PKey::PKeyclass to implementself.generate_keywhich is wrapper ofOpenSSL::PKey.generate_key, but can returnGem::OpenSSL::PKey::PKey.Constant management in test files
test/rubygems/helper.rb defines KEY/CERT constants.
However, for example, the following files also defines such constants. I also saw duplicated constants from the ones in helper.rb.
These constants can be managed in one place.
omit_if_support_pqc
I added
omit_if_support_pqcto be used in non-PQC specific tests. However, so far it is not used in any places. I will delete the method, minimizing the code changes if it is not used eventually. I usedomit_if_support_pqcfor an error case in non-PQC tests. However, it was hard to handle OpenSSL versions and Ruby OpenSSL versions in the error case. So, I deleted the non-PQC tests.What was the end-user or developer problem that led to this PR?
Users cannot use ML-DSA for signed gem workflow.
https://guides.rubygems.org/security/
What is your fix for the problem, implemented in this PR?
Added the implementation to add PQC ML-DSA support for cryptographically signed gems workflow.
Make sure the following tasks are checked