Class: Gem::Security::Signer
- Inherits:
-
Object
- Object
- Gem::Security::Signer
- Includes:
- UserInteraction
- Defined in:
- lib/rubygems/security/signer.rb
Constant Summary collapse
- DEFAULT_OPTIONS =
{ expiration_length_days: 365, }.freeze
Instance Attribute Summary collapse
-
#cert_chain ⇒ Object
The chain of certificates for signing including the signing certificate.
-
#digest_algorithm ⇒ Object
readonly
The digest algorithm used to create the signature.
-
#digest_name ⇒ Object
readonly
The name of the digest algorithm, used to pull digests out of the hash by name.
-
#key ⇒ Object
The private key for the signing certificate.
-
#options ⇒ Object
readonly
Gem::Security::Signer options.
Class Method Summary collapse
-
.re_sign_cert(expired_cert, expired_cert_path, private_key) {|expired_cert_path, new_expired_cert_path| ... } ⇒ Object
Attempts to re-sign an expired cert with a given private key.
Instance Method Summary collapse
-
#extract_name(cert) ⇒ Object
Extracts the full name of
cert
. -
#initialize(key, cert_chain, passphrase = nil, options = {}) ⇒ Signer
constructor
Creates a new signer with an RSA
key
or path to a key, and a certificatechain
containing X509 certificates, encoding certificates or paths to certificates. -
#load_cert_chain ⇒ Object
Loads any missing issuers in the cert chain from the trusted certificates.
-
#re_sign_key(expiration_length: Gem::Security::ONE_YEAR) ⇒ Object
Attempts to re-sign the private key if the signing certificate is expired.
-
#sign(data) ⇒ Object
Sign data with given digest algorithm.
Methods included from UserInteraction
#alert, #alert_error, #alert_warning, #ask, #ask_for_password, #ask_yes_no, #choose_from_list, #say, #terminate_interaction, #verbose
Methods included from DefaultUserInteraction
ui, #ui, ui=, #ui=, use_ui, #use_ui
Methods included from Text
#clean_text, #format_text, #levenshtein_distance, #min3, #truncate_text
Constructor Details
#initialize(key, cert_chain, passphrase = nil, options = {}) ⇒ Signer
Creates a new signer with an RSA key
or path to a key, and a certificate chain
containing X509 certificates, encoding certificates or paths to certificates.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/rubygems/security/signer.rb', line 68 def initialize(key, cert_chain, passphrase = nil, = {}) @cert_chain = cert_chain @key = key @passphrase = passphrase @options = DEFAULT_OPTIONS.merge() unless @key default_key = File.join Gem.default_key_path @key = default_key if File.exist? default_key end unless @cert_chain default_cert = File.join Gem.default_cert_path @cert_chain = [default_cert] if File.exist? default_cert end @digest_name = Gem::Security::DIGEST_NAME @digest_algorithm = Gem::Security.create_digest(@digest_name) if @key && !@key.is_a?(OpenSSL::PKey::PKey) @key = OpenSSL::PKey.read(File.read(@key), @passphrase) end if @cert_chain @cert_chain = @cert_chain.compact.map do |cert| next cert if OpenSSL::X509::Certificate === cert cert = File.read cert if File.exist? cert OpenSSL::X509::Certificate.new cert end load_cert_chain end end |
Instance Attribute Details
#cert_chain ⇒ Object
The chain of certificates for signing including the signing certificate
14 15 16 |
# File 'lib/rubygems/security/signer.rb', line 14 def cert_chain @cert_chain end |
#digest_algorithm ⇒ Object (readonly)
The digest algorithm used to create the signature
24 25 26 |
# File 'lib/rubygems/security/signer.rb', line 24 def digest_algorithm @digest_algorithm end |
#digest_name ⇒ Object (readonly)
The name of the digest algorithm, used to pull digests out of the hash by name.
30 31 32 |
# File 'lib/rubygems/security/signer.rb', line 30 def digest_name @digest_name end |
#key ⇒ Object
The private key for the signing certificate
19 20 21 |
# File 'lib/rubygems/security/signer.rb', line 19 def key @key end |
#options ⇒ Object (readonly)
Gem::Security::Signer options
35 36 37 |
# File 'lib/rubygems/security/signer.rb', line 35 def @options end |
Class Method Details
.re_sign_cert(expired_cert, expired_cert_path, private_key) {|expired_cert_path, new_expired_cert_path| ... } ⇒ Object
Attempts to re-sign an expired cert with a given private key
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/rubygems/security/signer.rb', line 43 def self.re_sign_cert(expired_cert, expired_cert_path, private_key) return unless expired_cert.not_after < Time.now expiry = expired_cert.not_after.strftime("%Y%m%d%H%M%S") expired_cert_file = "#{File.basename(expired_cert_path)}.expired.#{expiry}" new_expired_cert_path = File.join(Gem.user_home, ".gem", expired_cert_file) Gem::Security.write(expired_cert, new_expired_cert_path) re_signed_cert = Gem::Security.re_sign( expired_cert, private_key, (Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days) ) Gem::Security.write(re_signed_cert, expired_cert_path) yield(expired_cert_path, new_expired_cert_path) if block_given? end |
Instance Method Details
#extract_name(cert) ⇒ Object
Extracts the full name of cert
. If the certificate has a subjectAltName this value is preferred, otherwise the subject is used.
108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/rubygems/security/signer.rb', line 108 def extract_name(cert) # :nodoc: subject_alt_name = cert.extensions.find {|e| e.oid == "subjectAltName" } if subject_alt_name /\Aemail:/ =~ subject_alt_name.value # rubocop:disable Performance/StartWith $' || subject_alt_name.value else cert.subject end end |
#load_cert_chain ⇒ Object
Loads any missing issuers in the cert chain from the trusted certificates.
If the issuer does not exist it is ignored as it will be checked later.
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/rubygems/security/signer.rb', line 125 def load_cert_chain # :nodoc: return if @cert_chain.empty? while @cert_chain.first.issuer.to_s != @cert_chain.first.subject.to_s do issuer = Gem::Security.trust_dir.issuer_of @cert_chain.first break unless issuer # cert chain is verified later @cert_chain.unshift issuer end end |
#re_sign_key(expiration_length: Gem::Security::ONE_YEAR) ⇒ Object
Attempts to re-sign the private key if the signing certificate is expired.
The key will be re-signed if:
-
The expired certificate is self-signed
-
The expired certificate is saved at ~/.gem/gem-public_cert.pem and the private key is saved at ~/.gem/gem-private_key.pem
-
There is no file matching the expiry date at ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S
If the signing certificate can be re-signed the expired certificate will be saved as ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S where the expiry time (not after) is used for the timestamp.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/rubygems/security/signer.rb', line 174 def re_sign_key(expiration_length: Gem::Security::ONE_YEAR) # :nodoc: old_cert = @cert_chain.last disk_cert_path = File.join(Gem.default_cert_path) disk_cert = begin File.read(disk_cert_path) rescue StandardError nil end disk_key_path = File.join(Gem.default_key_path) disk_key = begin OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue StandardError nil end return unless disk_key if disk_key.to_pem == @key.to_pem && disk_cert == old_cert.to_pem expiry = old_cert.not_after.strftime("%Y%m%d%H%M%S") old_cert_file = "gem-public_cert.pem.expired.#{expiry}" old_cert_path = File.join(Gem.user_home, ".gem", old_cert_file) unless File.exist?(old_cert_path) Gem::Security.write(old_cert, old_cert_path) cert = Gem::Security.re_sign(old_cert, @key, expiration_length) Gem::Security.write(cert, disk_cert_path) alert("Your cert: #{disk_cert_path} has been auto re-signed with the key: #{disk_key_path}") alert("Your expired cert will be located at: #{old_cert_path}") @cert_chain = [cert] end end end |
#sign(data) ⇒ Object
Sign data with given digest algorithm
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/rubygems/security/signer.rb', line 140 def sign(data) return unless @key raise Gem::Security::Exception, "no certs provided" if @cert_chain.empty? if @cert_chain.length == 1 && @cert_chain.last.not_after < Time.now alert("Your certificate has expired, trying to re-sign it...") re_sign_key( expiration_length: (Gem::Security::ONE_DAY * [:expiration_length_days]) ) end full_name = extract_name @cert_chain.last Gem::Security::SigningPolicy.verify @cert_chain, @key, {}, {}, full_name @key.sign @digest_algorithm.new, data end |