1## 2# A Gem::Security::Policy object encapsulates the settings for verifying 3# signed gem files. This is the base class. You can either declare an 4# instance of this or use one of the preset security policies in 5# Gem::Security::Policies. 6 7class Gem::Security::Policy 8 9 attr_reader :name 10 11 attr_accessor :only_signed 12 attr_accessor :only_trusted 13 attr_accessor :verify_chain 14 attr_accessor :verify_data 15 attr_accessor :verify_root 16 attr_accessor :verify_signer 17 18 ## 19 # Create a new Gem::Security::Policy object with the given mode and 20 # options. 21 22 def initialize name, policy = {}, opt = {} 23 require 'openssl' 24 25 @name = name 26 27 @opt = opt 28 29 # Default to security 30 @only_signed = true 31 @only_trusted = true 32 @verify_chain = true 33 @verify_data = true 34 @verify_root = true 35 @verify_signer = true 36 37 policy.each_pair do |key, val| 38 case key 39 when :verify_data then @verify_data = val 40 when :verify_signer then @verify_signer = val 41 when :verify_chain then @verify_chain = val 42 when :verify_root then @verify_root = val 43 when :only_trusted then @only_trusted = val 44 when :only_signed then @only_signed = val 45 end 46 end 47 end 48 49 ## 50 # Verifies each certificate in +chain+ has signed the following certificate 51 # and is valid for the given +time+. 52 53 def check_chain chain, time 54 raise Gem::Security::Exception, 'missing signing chain' unless chain 55 raise Gem::Security::Exception, 'empty signing chain' if chain.empty? 56 57 begin 58 chain.each_cons 2 do |issuer, cert| 59 check_cert cert, issuer, time 60 end 61 62 true 63 rescue Gem::Security::Exception => e 64 raise Gem::Security::Exception, "invalid signing chain: #{e.message}" 65 end 66 end 67 68 ## 69 # Verifies that +data+ matches the +signature+ created by +public_key+ and 70 # the +digest+ algorithm. 71 72 def check_data public_key, digest, signature, data 73 raise Gem::Security::Exception, "invalid signature" unless 74 public_key.verify digest.new, signature, data.digest 75 76 true 77 end 78 79 ## 80 # Ensures that +signer+ is valid for +time+ and was signed by the +issuer+. 81 # If the +issuer+ is +nil+ no verification is performed. 82 83 def check_cert signer, issuer, time 84 raise Gem::Security::Exception, 'missing signing certificate' unless 85 signer 86 87 message = "certificate #{signer.subject}" 88 89 if not_before = signer.not_before and not_before > time then 90 raise Gem::Security::Exception, 91 "#{message} not valid before #{not_before}" 92 end 93 94 if not_after = signer.not_after and not_after < time then 95 raise Gem::Security::Exception, "#{message} not valid after #{not_after}" 96 end 97 98 if issuer and not signer.verify issuer.public_key then 99 raise Gem::Security::Exception, 100 "#{message} was not issued by #{issuer.subject}" 101 end 102 103 true 104 end 105 106 ## 107 # Ensures the public key of +key+ matches the public key in +signer+ 108 109 def check_key signer, key 110 unless signer and key then 111 return true unless @only_signed 112 113 raise Gem::Security::Exception, 'missing key or signature' 114 end 115 116 raise Gem::Security::Exception, 117 "certificate #{signer.subject} does not match the signing key" unless 118 signer.public_key.to_pem == key.public_key.to_pem 119 120 true 121 end 122 123 ## 124 # Ensures the root certificate in +chain+ is self-signed and valid for 125 # +time+. 126 127 def check_root chain, time 128 raise Gem::Security::Exception, 'missing signing chain' unless chain 129 130 root = chain.first 131 132 raise Gem::Security::Exception, 'missing root certificate' unless root 133 134 raise Gem::Security::Exception, 135 "root certificate #{root.subject} is not self-signed " + 136 "(issuer #{root.issuer})" if 137 root.issuer.to_s != root.subject.to_s # HACK to_s is for ruby 1.8 138 139 check_cert root, root, time 140 end 141 142 ## 143 # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and 144 # the digests of the two certificates match according to +digester+ 145 146 def check_trust chain, digester, trust_dir 147 raise Gem::Security::Exception, 'missing signing chain' unless chain 148 149 root = chain.first 150 151 raise Gem::Security::Exception, 'missing root certificate' unless root 152 153 path = Gem::Security.trust_dir.cert_path root 154 155 unless File.exist? path then 156 message = "root cert #{root.subject} is not trusted" 157 158 message << " (root of signing cert #{chain.last.subject})" if 159 chain.length > 1 160 161 raise Gem::Security::Exception, message 162 end 163 164 save_cert = OpenSSL::X509::Certificate.new File.read path 165 save_dgst = digester.digest save_cert.public_key.to_s 166 167 pkey_str = root.public_key.to_s 168 cert_dgst = digester.digest pkey_str 169 170 raise Gem::Security::Exception, 171 "trusted root certificate #{root.subject} checksum " + 172 "does not match signing root certificate checksum" unless 173 save_dgst == cert_dgst 174 175 true 176 end 177 178 def inspect # :nodoc: 179 ("[Policy: %s - data: %p signer: %p chain: %p root: %p " + 180 "signed-only: %p trusted-only: %p]") % [ 181 @name, @verify_chain, @verify_data, @verify_root, @verify_signer, 182 @only_signed, @only_trusted, 183 ] 184 end 185 186 ## 187 # Verifies the certificate +chain+ is valid, the +digests+ match the 188 # signatures +signatures+ created by the signer depending on the +policy+ 189 # settings. 190 # 191 # If +key+ is given it is used to validate the signing certificate. 192 193 def verify chain, key = nil, digests = {}, signatures = {} 194 if @only_signed and signatures.empty? then 195 raise Gem::Security::Exception, 196 "unsigned gems are not allowed by the #{name} policy" 197 end 198 199 opt = @opt 200 digester = Gem::Security::DIGEST_ALGORITHM 201 trust_dir = opt[:trust_dir] 202 time = Time.now 203 204 _, signer_digests = digests.find do |algorithm, file_digests| 205 file_digests.values.first.name == Gem::Security::DIGEST_NAME 206 end 207 208 if @verify_data then 209 raise Gem::Security::Exception, 'no digests provided (probable bug)' if 210 signer_digests.nil? or signer_digests.empty? 211 else 212 signer_digests = {} 213 end 214 215 signer = chain.last 216 217 check_key signer, key if key 218 219 check_cert signer, nil, time if @verify_signer 220 221 check_chain chain, time if @verify_chain 222 223 check_root chain, time if @verify_root 224 225 check_trust chain, digester, trust_dir if @only_trusted 226 227 signatures.each do |file, _| 228 digest = signer_digests[file] 229 230 raise Gem::Security::Exception, "missing digest for #{file}" unless 231 digest 232 end 233 234 signer_digests.each do |file, digest| 235 signature = signatures[file] 236 237 raise Gem::Security::Exception, "missing signature for #{file}" unless 238 signature 239 240 check_data signer.public_key, digester, signature, digest if @verify_data 241 end 242 243 true 244 end 245 246 ## 247 # Extracts the certificate chain from the +spec+ and calls #verify to ensure 248 # the signatures and certificate chain is valid according to the policy.. 249 250 def verify_signatures spec, digests, signatures 251 chain = spec.cert_chain.map do |cert_pem| 252 OpenSSL::X509::Certificate.new cert_pem 253 end 254 255 verify chain, nil, digests, signatures 256 257 true 258 end 259 260 alias to_s name # :nodoc: 261 262end 263 264