[前][次][番号順一覧][スレッド一覧]

ruby-changes:69448

From: Jenny <ko1@a...>
Date: Tue, 26 Oct 2021 08:02:17 +0900 (JST)
Subject: [ruby-changes:69448] 92ec010595 (master): [rubygems/rubygems] Add support to build and sign certificates with multiple key algorithms

https://git.ruby-lang.org/ruby.git/commit/?id=92ec010595

From 92ec010595bed29567fc08dd4d52d4c4518f0fd4 Mon Sep 17 00:00:00 2001
From: Jenny Shen <jenny.shen@s...>
Date: Wed, 6 Oct 2021 17:39:23 -0400
Subject: [rubygems/rubygems] Add support to build and sign certificates with
 multiple key algorithms

https://github.com/rubygems/rubygems/commit/967876f15d

Co-Authored-By: Frederik Dudzik <frederik.dudzik@s...>
---
 lib/rubygems/commands/cert_command.rb           | 19 ++++---
 lib/rubygems/security.rb                        | 64 +++++++++++++++++------
 lib/rubygems/security/policy.rb                 |  8 +--
 lib/rubygems/security/signer.rb                 |  7 ++-
 test/rubygems/helper.rb                         |  4 +-
 test/rubygems/private_ec_key.pem                |  9 ++++
 test/rubygems/test_gem_commands_cert_command.rb | 67 +++++++++++++++++++++++--
 test/rubygems/test_gem_security.rb              | 36 +++++++++++--
 8 files changed, 176 insertions(+), 38 deletions(-)
 create mode 100644 test/rubygems/private_ec_key.pem

diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index bdfeb0ba6e8..867cb07cca0 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -43,6 +43,11 @@ class Gem::Commands::CertCommand < Gem::Command https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/cert_command.rb#L43
       options[:key] = open_private_key(key_file)
     end
 
+    add_option('-A', '--key-algorithm ALGORITHM',
+               'Select which key algorithm to use for --build') do |algorithm, options|
+      options[:key_algorithm] = algorithm
+    end
+
     add_option('-s', '--sign CERT',
                'Signs CERT with the key from -K',
                'and the certificate from -C') do |cert_file, options|
@@ -89,14 +94,14 @@ class Gem::Commands::CertCommand < Gem::Command https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/cert_command.rb#L94
   def open_private_key(key_file)
     check_openssl
     passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
-    key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase
+    key = OpenSSL::PKey.read File.read(key_file), passphrase
     raise OptionParser::InvalidArgument,
       "#{key_file}: private key not found" unless key.private?
     key
   rescue Errno::ENOENT
     raise OptionParser::InvalidArgument, "#{key_file}: does not exist"
-  rescue OpenSSL::PKey::RSAError
-    raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key"
+  rescue OpenSSL::PKey::PKeyError, ArgumentError
+    raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key"
   end
 
   def execute
@@ -170,7 +175,8 @@ class Gem::Commands::CertCommand < Gem::Command https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/cert_command.rb#L175
     raise Gem::CommandLineError,
           "Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation
 
-    key      = Gem::Security.create_key
+    algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM
+    key = Gem::Security.create_key(algorithm)
     key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase
 
     return key, key_path
@@ -255,13 +261,14 @@ For further reading on signing gems see `ri Gem::Security`. https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/cert_command.rb#L261
     key_file = File.join Gem.default_key_path
     key = File.read key_file
     passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
-    options[:key] = OpenSSL::PKey::RSA.new key, passphrase
+    options[:key] = OpenSSL::PKey.read key, passphrase
+
   rescue Errno::ENOENT
     alert_error \
       "--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
 
     terminate_interaction 1
-  rescue OpenSSL::PKey::RSAError
+  rescue OpenSSL::PKey::PKeyError
     alert_error \
       "--private-key not specified and ~/.gem/gem-private_key.pem is not valid"
 
diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb
index 28f705549c8..8240a1a0591 100644
--- a/lib/rubygems/security.rb
+++ b/lib/rubygems/security.rb
@@ -152,6 +152,7 @@ require_relative 'openssl' https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security.rb#L152
 #                                      certificate for EMAIL_ADDR
 #     -C, --certificate CERT           Signing certificate for --sign
 #     -K, --private-key KEY            Key for --sign or --build
+#     -A, --key-algorithm ALGORITHM    Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA.
 #     -s, --sign CERT                  Signs CERT with the key from -K
 #                                      and the certificate from -C
 #     -d, --days NUMBER_OF_DAYS        Days before the certificate expires
@@ -317,7 +318,6 @@ require_relative 'openssl' https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security.rb#L318
 # * Honor extension restrictions
 # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
 #   file, instead of an array embedded in the metadata.
-# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
 #
 # == Original author
 #
@@ -337,17 +337,19 @@ module Gem::Security https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security.rb#L337
   DIGEST_NAME = 'SHA256' # :nodoc:
 
   ##
-  # Algorithm for creating the key pair used to sign gems
+  # Length of keys created by RSA and DSA keys
 
-  KEY_ALGORITHM =
-    if defined?(OpenSSL::PKey::RSA)
-      OpenSSL::PKey::RSA
-    end
+  RSA_DSA_KEY_LENGTH = 3072
 
   ##
-  # Length of keys created by KEY_ALGORITHM
+  # Default algorithm to use when building a key pair
 
-  KEY_LENGTH = 3072
+  DEFAULT_KEY_ALGORITHM = 'RSA'
+
+  ##
+  # Named curve used for Elliptic Curve
+
+  EC_NAME = 'secp384r1'
 
   ##
   # Cipher used to encrypt the key pair used to sign gems.
@@ -400,7 +402,7 @@ module Gem::Security https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security.rb#L402
                        serial = 1)
     cert = OpenSSL::X509::Certificate.new
 
-    cert.public_key = key.public_key
+    cert.public_key = get_public_key(key)
     cert.version    = 2
     cert.serial     = serial
 
@@ -418,6 +420,24 @@ module Gem::Security https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security.rb#L420
     cert
   end
 
+  ##
+  # Gets the right public key from a PKey instance
+
+  def self.get_public_key(key)
+    return key.public_key unless key.is_a?(OpenSSL::PKey::EC)
+
+    ec_key = OpenSSL::PKey::EC.new(key.group.curve_name)
+    ec_key.public_key = key.public_key
+    ec_key
+  end
+
+  ##
+  # In Ruby 2.3 EC doesn't implement the private_key? but not the private? method
+
+  if defined?(OpenSSL::PKey::EC) && Gem::Version.new(String.new(RUBY_VERSION)) < Gem::Version.new("2.4.0")
+    OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)
+  end
+
   ##
   # Creates a self-signed certificate with an issuer and subject from +email+,
   # a subject alternative name of +email+ and the given +extensions+ for the
@@ -459,11 +479,25 @@ module Gem::Security https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security.rb#L479
   end
 
   ##
-  # Creates a new key pair of the specified +length+ and +algorithm+.  The
-  # default is a 3072 bit RSA key.
-
-  def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM)
-    algorithm.new length
+  # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC
+  # are supported.
+
+  def self.create_key(algorithm)
+    if defined?(OpenSSL::PKey)
+      case algorithm.downcase
+      when 'dsa'
+        OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH)
+      when 'rsa'
+        OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH)
+      when 'ec'
+        domain_key = OpenSSL::PKey::EC.new(EC_NAME)
+        domain_key.generate_key
+        domain_key
+      else
+        raise Gem::Security::Exception,
+        "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported."
+      end
+    end
   end
 
   ##
@@ -492,7 +526,7 @@ module Gem::Security https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security.rb#L526
     raise Gem::Security::Exception,
           "incorrect signing key for re-signing " +
           "#{expired_certificate.subject}" unless
-      expired_certificate.public_key.to_pem == private_key.public_key.to_pem
+      expired_certificate.public_key.to_pem == get_public_key(private_key).to_pem
 
     unless expired_certificate.subject.to_s ==
            expired_certificate.issuer.to_s
diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb
index 9683e55b327..3c3cb647ee3 100644
--- a/lib/rubygems/security/policy.rb
+++ b/lib/rubygems/security/policy.rb
@@ -115,9 +115,11 @@ class Gem::Security::Policy https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security/policy.rb#L115
       raise Gem::Security::Exception, 'missing key or signature'
     end
 
+    public_key = Gem::Security.get_public_key(key)
+
     raise Gem::Security::Exception,
       "certificate #{signer.subject} does not match the signing key" unless
-        signer.public_key.to_pem == key.public_key.to_pem
+        signer.public_key.to_pem == public_key.to_pem
 
     true
   end
@@ -164,9 +166,9 @@ class Gem::Security::Policy https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security/policy.rb#L166
     end
 
     save_cert = OpenSSL::X509::Certificate.new File.read path
-    save_dgst = digester.digest save_cert.public_key.to_s
+    save_dgst = digester.digest save_cert.public_key.to_pem
 
-    pkey_str = root.public_key.to_s
+    pkey_str = root.public_key.to_pem
     cert_dgst = digester.digest pkey_str
 
     raise Gem::Security::Exception,
diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb
index c5c2c4f2200..968cf889730 100644
--- a/lib/rubygems/security/signer.rb
+++ b/lib/rubygems/security/signer.rb
@@ -83,8 +83,8 @@ class Gem::Security::Signer https://github.com/ruby/ruby/blob/trunk/lib/rubygems/security/signer.rb#L83
   (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]