Skip to content

Commit 8135567

Browse files
committed
rubygems: Add PQC ML-DSA support for cryptographically signed gems workflow
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. It raises `Gem::Security::Exception` if ML-DSA is not supported such as OpenSSL < 3.5. ``` $ 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 ``` PQC algorithms ML-KEM and ML-DSA require OpenSSL >= 3.5. https://openssl-library.org/post/2025-04-08-openssl-35-final-release/ 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 ML-DSA signed gems workflow also includes * `gem cert -C cert.pem` * `gem cert -K private_key.pem` It raises `Gem::OptionParser::InvalidArgument` if ML-DSA is not supported. * `gem cert --add cert.pem` * `gem cert --remove filter` * `gem build gemname.gemspec` with `s.cert_chain` and `s.signing_key` to build signed gem. It raises `Gem::Security::Exception` if ML-DSA is not supported. * `gem install gemname -P HighSecurity` It raises `Gem::Security::Exception` on RSA server/client connection if ML-DSA is not supported. * `gem update gemname -P HighSecurity` It raises `Gem::Security::Exception` on RSA server/client connection if ML-DSA is not supported. 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. * Adding 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`. Note JRuby remains unsupported for the signed gems feature. The following tests have been skipped entirely in JRuby. * test/rubygems/test_gem_security.rb * test/rubygems/test_gem_commands_cert_command.rb See also https://guides.rubygems.org/security/ for the signed gems workflows. Assisted-by: Claude:Fable 5
1 parent 6ccce5d commit 8135567

19 files changed

Lines changed: 1240 additions & 142 deletions

lib/rubygems/commands/cert_command.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ def initialize
4545
end
4646

4747
add_option("-A", "--key-algorithm ALGORITHM",
48-
"Select which key algorithm to use for --build") do |algorithm, options|
48+
"Select key algorithm for --build from",
49+
"RSA, DSA, EC, ML-DSA-44, ML-DSA-65,",
50+
"or ML-DSA-87. Defaults to "\
51+
"#{Gem::Security::DEFAULT_KEY_ALGORITHM}.") do |algorithm, options|
4952
options[:key_algorithm] = algorithm
5053
end
5154

@@ -100,7 +103,8 @@ def open_private_key(key_file)
100103
rescue Errno::ENOENT
101104
raise Gem::OptionParser::InvalidArgument, "#{key_file}: does not exist"
102105
rescue OpenSSL::PKey::PKeyError, ArgumentError
103-
raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key"
106+
raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid "\
107+
"RSA, DSA, EC, ML-DSA-44, ML-DSA-65, or ML-DSA-87 key"
104108
end
105109

106110
def execute

lib/rubygems/security.rb

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@
153153
# certificate for EMAIL_ADDR
154154
# -C, --certificate CERT Signing certificate for --sign
155155
# -K, --private-key KEY Key for --sign or --build
156-
# -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA.
156+
# -A, --key-algorithm ALGORITHM Select key algorithm for --build from
157+
# RSA, DSA, EC, ML-DSA-44, ML-DSA-65,
158+
# or ML-DSA-87. Defaults to RSA.
157159
# -s, --sign CERT Signs CERT with the key from -K
158160
# and the certificate from -C
159161
# -d, --days NUMBER_OF_DAYS Days before the certificate expires
@@ -351,6 +353,20 @@ class Exception < Gem::Exception; end
351353

352354
EC_NAME = "secp384r1"
353355

356+
##
357+
# ML-DSA algorithm names to use when building a key pair.
358+
# ML-DSA-44: NIST security strength category 2, signature size 2420 bytes
359+
# ML-DSA-65: NIST security strength category 3, signature size 3309 bytes
360+
# ML-DSA-87: NIST security strength category 5, signature size 4627 bytes
361+
# See NIST FIPS 204 Section 4 (Parameter Sets).
362+
# https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf
363+
# See Security (Evaluation Criteria) - 4.A.5 Security Strength Categories.
364+
# https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization/evaluation-criteria/security-(evaluation-criteria)
365+
366+
ML_DSA_44_NAME = "ML-DSA-44"
367+
ML_DSA_65_NAME = "ML-DSA-65"
368+
ML_DSA_87_NAME = "ML-DSA-87"
369+
354370
##
355371
# Cipher used to encrypt the key pair used to sign gems.
356372
# Must be in the list returned by OpenSSL::Cipher.ciphers
@@ -464,8 +480,8 @@ def self.create_digest(algorithm = DIGEST_NAME)
464480
end
465481

466482
##
467-
# Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC
468-
# are supported.
483+
# Creates a new key pair of the specified +algorithm+. RSA, DSA, EC,
484+
# ML-DSA-44, ML-DSA-65, and ML-DSA-87 are supported.
469485

470486
def self.create_key(algorithm)
471487
if defined?(OpenSSL::PKey)
@@ -476,13 +492,42 @@ def self.create_key(algorithm)
476492
OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH)
477493
when "ec"
478494
OpenSSL::PKey::EC.generate(EC_NAME)
495+
when "ml-dsa-44"
496+
create_ml_dsa_key(ML_DSA_44_NAME)
497+
when "ml-dsa-65"
498+
create_ml_dsa_key(ML_DSA_65_NAME)
499+
when "ml-dsa-87"
500+
create_ml_dsa_key(ML_DSA_87_NAME)
479501
else
480502
raise Gem::Security::Exception,
481-
"#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported."
503+
"#{algorithm} algorithm not found. RSA, DSA, EC, ML-DSA-44, "\
504+
"ML-DSA-65, and ML-DSA-87 algorithms are supported."
482505
end
483506
end
484507
end
485508

509+
##
510+
# Creates an ML-DSA key pair of the +algorithm+ such as ML-DSA-65. ML-DSA
511+
# requires OpenSSL >= 3.5 or an SSL library supporting ML-DSA.
512+
513+
def self.create_ml_dsa_key(algorithm)
514+
OpenSSL::PKey.generate_key(algorithm)
515+
rescue OpenSSL::PKey::PKeyError
516+
raise Gem::Security::Exception,
517+
"#{algorithm} key generation failed: #{algorithm} requires "\
518+
"OpenSSL >= 3.5 or an SSL library supporting ML-DSA."
519+
end
520+
private_class_method :create_ml_dsa_key
521+
522+
##
523+
# Returns whether the +key+ requires an explicit digest algorithm for signing
524+
# and verification. ML-DSA has a built-in digest and does not accept one.
525+
526+
def self.digest_required?(key)
527+
key.is_a?(OpenSSL::PKey::RSA) || key.is_a?(OpenSSL::PKey::DSA) ||
528+
key.is_a?(OpenSSL::PKey::EC)
529+
end
530+
486531
##
487532
# Turns +email_address+ into an OpenSSL::X509::Name
488533

@@ -562,8 +607,10 @@ def self.sign(certificate, signing_key, signing_cert, age = ONE_YEAR, extensions
562607
signed = create_cert signee_subject, signee_key, age, extensions, serial
563608
signed.issuer = signing_cert.subject
564609

610+
digest_name = Gem::Security::DIGEST_NAME if digest_required?(signing_key)
611+
565612
begin
566-
signed.sign signing_key, Gem::Security::DIGEST_NAME
613+
signed.sign signing_key, digest_name
567614
rescue OpenSSL::PKey::PKeyError, ArgumentError
568615
raise Gem::Security::Exception,
569616
"incorrect signing key for signing"

lib/rubygems/security/policy.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ def check_chain(chain, time)
7373
# the +digest+ algorithm.
7474

7575
def check_data(public_key, digest, signature, data)
76+
filtered_digest = digest if Gem::Security.digest_required?(public_key)
77+
7678
raise Gem::Security::Exception, "invalid signature" unless
77-
public_key.verify digest, signature, data.digest
79+
public_key.verify filtered_digest, signature, data.digest
7880

7981
true
8082
end
@@ -268,6 +270,12 @@ def verify(chain, key = nil, digests = {}, signatures = {}, full_name = "(unknow
268270
end
269271

270272
true
273+
# NotImplementedError: JRuby's Ruby OpenSSL raises it for ML-DSA.
274+
rescue OpenSSL::X509::CertificateError, NotImplementedError
275+
raise Gem::Security::Exception,
276+
"certificate verification failed: The certificate may use an algorithm "\
277+
"such as ML-DSA that the installed OpenSSL does not support. ML-DSA "\
278+
"requires OpenSSL >= 3.5 or an SSL library supporting ML-DSA."
271279
end
272280

273281
##

lib/rubygems/security/signer.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,14 @@ def initialize(key, cert_chain, passphrase = nil, options = {})
8585
@digest_algorithm = Gem::Security.create_digest(@digest_name)
8686

8787
if @key && !@key.is_a?(OpenSSL::PKey::PKey)
88-
@key = OpenSSL::PKey.read(File.read(@key), @passphrase)
88+
begin
89+
@key = OpenSSL::PKey.read(File.read(@key), @passphrase)
90+
rescue OpenSSL::PKey::PKeyError
91+
raise Gem::Security::Exception,
92+
"private key could not be loaded: The key may use an algorithm "\
93+
"such as ML-DSA that the installed OpenSSL does not support. "\
94+
"ML-DSA requires OpenSSL >= 3.5 or an SSL library supporting ML-DSA."
95+
end
8996
end
9097

9198
if @cert_chain
@@ -152,7 +159,8 @@ def sign(data)
152159

153160
Gem::Security::SigningPolicy.verify @cert_chain, @key, {}, {}, full_name
154161

155-
@key.sign @digest_algorithm.new, data
162+
digest = @digest_algorithm.new if Gem::Security.digest_required?(@key)
163+
@key.sign digest, data
156164
end
157165

158166
##

test/rubygems/helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,10 @@ def omit_unless_support_pqc
16911691
omit message
16921692
end
16931693
end
1694+
1695+
def omit_if_support_ml_dsa_key
1696+
omit "OpenSSL supports ML-DSA" if Gem::PQCUtilities.support_ml_dsa_key?
1697+
end
16941698
end
16951699

16961700
# https://github.com/seattlerb/minitest/blob/13c48a03d84a2a87855a4de0c959f96800100357/lib/minitest/mock.rb#L192
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
-----BEGIN ENCRYPTED PRIVATE KEY-----
2+
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
90+
-----END ENCRYPTED PRIVATE KEY-----
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
87+
K217eS//q+3kNLi5FMCDRxX1
88+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)