1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
##
# Basic OpenSSL-based package signing class.
class Gem::Security::Signer
##
# The chain of certificates for signing including the signing certificate
attr_accessor :cert_chain
##
# The private key for the signing certificate
attr_accessor :key
##
# The digest algorithm used to create the signature
attr_reader :digest_algorithm
##
# The name of the digest algorithm, used to pull digests out of the hash by
# name.
attr_reader :digest_name # :nodoc:
##
# 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.
def initialize key, cert_chain, passphrase = nil
@cert_chain = cert_chain
@key = key
unless @key then
default_key = File.join Gem.default_key_path
@key = default_key if File.exist? default_key
end
unless @cert_chain then
default_cert = File.join Gem.default_cert_path
@cert_chain = [default_cert] if File.exist? default_cert
end
@digest_algorithm = Gem::Security::DIGEST_ALGORITHM
@digest_name = Gem::Security::DIGEST_NAME
@key = OpenSSL::PKey::RSA.new File.read(@key), passphrase if
@key and not OpenSSL::PKey::RSA === @key
if @cert_chain then
@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
##
# Extracts the full name of +cert+. If the certificate has a subjectAltName
# this value is preferred, otherwise the subject is used.
def extract_name cert # :nodoc:
subject_alt_name = cert.extensions.find { |e| 'subjectAltName' == e.oid }
if subject_alt_name then
/\Aemail:/ =~ subject_alt_name.value
$' || subject_alt_name.value
else
cert.subject
end
end
##
# 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.
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
##
# Sign data with given digest algorithm
def sign data
return unless @key
if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now then
re_sign_key
end
full_name = extract_name @cert_chain.last
Gem::Security::SigningPolicy.verify @cert_chain, @key, {}, {}, full_name
@key.sign @digest_algorithm.new, data
end
##
# 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
# * 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.
def re_sign_key # :nodoc:
old_cert = @cert_chain.last
disk_cert_path = File.join Gem.default_cert_path
disk_cert = File.read disk_cert_path rescue nil
disk_key =
File.read File.join(Gem.default_key_path) rescue nil
if disk_key == @key.to_pem and disk_cert == old_cert.to_pem then
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 then
Gem::Security.write old_cert, old_cert_path
cert = Gem::Security.re_sign old_cert, @key
Gem::Security.write cert, disk_cert_path
@cert_chain = [cert]
end
end
end
end
|