rubygems: Replace OpenSSL::PKey::{RSA,DSA,EC}#to_pem and remove #private?#9701
Open
junaruga wants to merge 5 commits into
Open
rubygems: Replace OpenSSL::PKey::{RSA,DSA,EC}#to_pem and remove #private?#9701junaruga wants to merge 5 commits into
junaruga wants to merge 5 commits into
Conversation
Gem::Security.write managed the following 2 cases.
* pemmable is RSA/DSA/EC private key, an instance of OpenSSL::PKey::{RSA,DSA,EC}
* pemmable is certificate, an instance of OpenSSL::X509::Certificate
There was no case that pemmable was public RSA/DSA/EC key, an instance of
OpenSSL::PKey::{RSA,DSA,EC}.
This situation is not convenient in the case that pemmable is ML-DSA
private key, an instance of OpenSSL::PKey::PKey which doesn't have #to_pem, and instead
has #public_to_pem and #private_to_pem as alternative.
The Gem::Security.write with optional passphrase and cipher in certificate case
is not intuitive.
So, refactored the code as follows:
* Add Gem::Security.write_private_key for writing private keys, with optional
passphrase and cipher for encryption
* Add Gem::Security.write_certificate for writing certificates, without
passphrase and cipher. Certificates are never encrypted.
OpenSSL::X509::Certificate#to_pem doesn't have passphrase/cipher arguments
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_x509cert.c#L169
* Remove Gem::Security.write
* Update all callers to use the new methods
* Rename test methods to test Gem::Security.write_private_key and add
test_class_write_certificate
Assisted-by: Claude:claude-opus-4-6[1m]
Replace OpenSSL::PKey::{RSA,DSA,EC}#to_pem with
OpenSSL::PKey::{RSA,DSA,EC}#private_to_pem or #public_to_pem.
Continue to use OpenSSL::X509::Certificate#to_pem.
This makes the code ML-DSA compatible since OpenSSL::PKey::PKey used for ML-DSA
doesn't have #to_pem but has #private_to_pem and #public_to_pem.
Note this changes the private key's output format from the following formats to
more common PKCS ruby#8 format.[1][2][3][4]
* RSA: PKCS ruby#1 RSAPrivateKey
* DSA: traditional OpenSSL DSAPrivateKey
* EC: SEC 1/RFC 5915 ECPrivateKey
OpenSSL::PKey.read has supported PKCS ruby#8 format as well as previous RSA/DSA/EC
formats since the beginning.
OpenSSL::PKey::{RSA,DSA,EC}#to_pem are not encouraged to use.[1][2][3]
The public key is format is unchanged.[1][2][3][5]
[1] OpenSSL::PKey::RSA#to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey_rsa.c#L223-L280
[2] OpenSSL::PKey::DSA#to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey_dsa.c#L213-L271
[3] OpenSSL::PKey::EC#to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey_ec.c#L402-L459
[4] OpenSSL::PKey::PKey#private_to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey.c#L991-L1010
[5] OpenSSL::PKey::PKey#public_to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey.c#L1082-L1093
Assisted-by: Claude:claude-opus-4-6[1m]
As I modified logic related to output format in tool/create_certs.rb and tool/create_encrypted_key.rb at the last commit, I ran these scripts to recreate the testing .pem files as follows. Now the private .pem files are PKCS ruby#8 format. ``` $ ruby tool/create_certs.rb $ ruby tool/create_encrypted_key.rb ```
Because these hardcoded values make the tests fail when recreating the keys. This is not convenient. * Remove hardcoded subjectKeyIdentifier value assertions. * In test_sign, replace assertion with hardcoded value with PRIVATE_KEY.verify (OpenSSL::PKey::RSA#verify) to assert signature Assisted-by: Claude:claude-opus-4-6[1m]
…ty.sign
Remove the OpenSSL::PKey::{RSA,DSA,EC}#private? check in open_private_key. This
makes the code ML-DSA compatible since OpenSSL::PKey::PKey used for ML-DSA
doesn't have #private?.
According to the discussion on <ruby/openssl#1085>,
there is no reliable way to check if key is private or not in
OpenSSL::PKey::PKey for now. We shouldn't use
OpenSSL::PKey::{RSA,DSA,EC}#private?.
If a public key is passed, the subsequent signing operations will raise
ArgumentError in Gem::Security.sign. Rescue the ArgumentError, and raise
Gem::Security::Exception in Gem::Security.sign.
Add the following 2 bad key tests.
* test_execute_build_bad_key to test --build with a public
key (bad key) as --private-key
* test_execute_sign_bad_key to test --sign with a public
key (bad key) as --private-key
Assisted-by: Claude:claude-opus-4-6[1m]
This was referenced Jul 17, 2026
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was the end-user or developer problem that led to this PR?
The current code
OpenSSL::PKey::{RSA,DSA,EC}#to_pemand#private?makes the code complicated when adding ML-DSA on the PR #9697.OpenSSL::PKey::{RSA,DSA,EC}#to_pemis also not encouraged to use according to the Ruby OpenSSL documents.What is your fix for the problem, implemented in this PR?
Remove
OpenSSL::PKey::{RSA,DSA,EC}#to_pemand#private?.Summary
This PR has the following 5 commits to remove all of
OpenSSL::PKey::{RSA,DSA,EC}#to_pemand#private?entirely in this repository.The 1st commit is a preparation for the 2nd commit to remove
#to_pem. The 3rd commit is affected by the 2nd commit. The 3rd commit is to recreate testing .pem files as I changed the private key output format by the 2nd commit. I will explain the details on later section. The 4th commit is affected by 3rd commit. The 4th commits fixes the tests that broke when I recreated the testing .pem files. The 5th commit is to remove#private?.Commit messages
1st commit
Split Gem::Security.write into write_private_key and write_certificate
Gem::Security.write managed the following 2 cases.
There was no case that pemmable was public RSA/DSA/EC key, an instance of
OpenSSL::PKey::{RSA,DSA,EC}.
This situation is not convenient in the case that pemmable is ML-DSA
private key, an instance of OpenSSL::PKey::PKey which doesn't have #to_pem, and instead
has #public_to_pem and #private_to_pem as alternative.
The Gem::Security.write with optional passphrase and cipher in certificate case
is not intuitive.
So, refactored the code as follows:
passphrase and cipher for encryption
passphrase and cipher. Certificates are never encrypted.
OpenSSL::X509::Certificate#to_pem doesn't have passphrase/cipher arguments
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_x509cert.c#L169
test_class_write_certificate
Assisted-by: Claude:claude-opus-4-6[1m]
2nd commit
Replace OpenSSL::PKey::{RSA,DSA,EC}#to_pem
Replace OpenSSL::PKey::{RSA,DSA,EC}#to_pem with
OpenSSL::PKey::{RSA,DSA,EC}#private_to_pem or #public_to_pem.
Continue to use OpenSSL::X509::Certificate#to_pem.
This makes the code ML-DSA compatible since OpenSSL::PKey::PKey used for ML-DSA
doesn't have #to_pem but has #private_to_pem and #public_to_pem.
Note this changes the private key's output format from the following formats to
more common PKCS #8 format.[1][2][3][4]
OpenSSL::PKey.read has supported PKCS #8 format as well as previous RSA/DSA/EC
formats since the beginning.
OpenSSL::PKey::{RSA,DSA,EC}#to_pem are not encouraged to use.[1][2][3]
The public key is format is unchanged.[1][2][3][5]
[1] OpenSSL::PKey::RSA#to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey_rsa.c#L223-L280
[2] OpenSSL::PKey::DSA#to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey_dsa.c#L213-L271
[3] OpenSSL::PKey::EC#to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey_ec.c#L402-L459
[4] OpenSSL::PKey::PKey#private_to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey.c#L991-L1010
[5] OpenSSL::PKey::PKey#public_to_pem
https://github.com/ruby/openssl/blob/27c59a4cc8a652004184182f734de0161ba08b41/ext/openssl/ossl_pkey.c#L1082-L1093
Assisted-by: Claude:claude-opus-4-6[1m]
3rd commit
Recreated testing .pem files.
As I modified logic related to output format in tool/create_certs.rb and
tool/create_encrypted_key.rb at the last commit, I ran these scripts to
recreate the testing .pem files as follows. Now the private .pem files are
PKCS #8 format.
4th commit
Remove hardcoded key-dependent values from tests
Because these hardcoded values make the tests fail when recreating the keys.
This is not convenient.
PRIVATE_KEY.verify (OpenSSL::PKey::RSA#verify) to assert
signature
Assisted-by: Claude:claude-opus-4-6[1m]
5th commit
Remove #private? check from open_private_key and check in Gem::Security.sign
Remove the OpenSSL::PKey::{RSA,DSA,EC}#private? check in open_private_key. This
makes the code ML-DSA compatible since OpenSSL::PKey::PKey used for ML-DSA
doesn't have #private?.
According to the discussion on ruby/openssl#1085,
there is no reliable way to check if key is private or not in
OpenSSL::PKey::PKey for now. We shouldn't use
OpenSSL::PKey::{RSA,DSA,EC}#private?.
If a public key is passed, the subsequent signing operations will raise
ArgumentError in Gem::Security.sign. Rescue the ArgumentError, and raise
Gem::Security::Exception in Gem::Security.sign.
Add the following 2 bad key tests.
key (bad key) as --private-key
key (bad key) as --private-key
Assisted-by: Claude:claude-opus-4-6[1m]
Scripts to check private key output formats related to the 2nd commit
I prepared scripts for you to check changed private key output formats if you want to do it.
test_rsa.rb
test_dsa.rb
test_ec.rb
You can run the scripts as follows.
Make sure the following tasks are checked