From fcbf63e62c627deae76c1b8cb8c0876c536ed811 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Mon, 16 Mar 2020 18:49:26 +0900 Subject: Fresh start --- jni/ruby/lib/rubygems/security/policy.rb | 295 +++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 jni/ruby/lib/rubygems/security/policy.rb (limited to 'jni/ruby/lib/rubygems/security/policy.rb') diff --git a/jni/ruby/lib/rubygems/security/policy.rb b/jni/ruby/lib/rubygems/security/policy.rb new file mode 100644 index 0000000..b9bcb17 --- /dev/null +++ b/jni/ruby/lib/rubygems/security/policy.rb @@ -0,0 +1,295 @@ +require 'rubygems/user_interaction' + +## +# A Gem::Security::Policy object encapsulates the settings for verifying +# signed gem files. This is the base class. You can either declare an +# instance of this or use one of the preset security policies in +# Gem::Security::Policies. + +class Gem::Security::Policy + + include Gem::UserInteraction + + attr_reader :name + + attr_accessor :only_signed + attr_accessor :only_trusted + attr_accessor :verify_chain + attr_accessor :verify_data + attr_accessor :verify_root + attr_accessor :verify_signer + + ## + # Create a new Gem::Security::Policy object with the given mode and + # options. + + def initialize name, policy = {}, opt = {} + require 'openssl' + + @name = name + + @opt = opt + + # Default to security + @only_signed = true + @only_trusted = true + @verify_chain = true + @verify_data = true + @verify_root = true + @verify_signer = true + + policy.each_pair do |key, val| + case key + when :verify_data then @verify_data = val + when :verify_signer then @verify_signer = val + when :verify_chain then @verify_chain = val + when :verify_root then @verify_root = val + when :only_trusted then @only_trusted = val + when :only_signed then @only_signed = val + end + end + end + + ## + # Verifies each certificate in +chain+ has signed the following certificate + # and is valid for the given +time+. + + def check_chain chain, time + raise Gem::Security::Exception, 'missing signing chain' unless chain + raise Gem::Security::Exception, 'empty signing chain' if chain.empty? + + begin + chain.each_cons 2 do |issuer, cert| + check_cert cert, issuer, time + end + + true + rescue Gem::Security::Exception => e + raise Gem::Security::Exception, "invalid signing chain: #{e.message}" + end + end + + ## + # Verifies that +data+ matches the +signature+ created by +public_key+ and + # the +digest+ algorithm. + + def check_data public_key, digest, signature, data + raise Gem::Security::Exception, "invalid signature" unless + public_key.verify digest.new, signature, data.digest + + true + end + + ## + # Ensures that +signer+ is valid for +time+ and was signed by the +issuer+. + # If the +issuer+ is +nil+ no verification is performed. + + def check_cert signer, issuer, time + raise Gem::Security::Exception, 'missing signing certificate' unless + signer + + message = "certificate #{signer.subject}" + + if not_before = signer.not_before and not_before > time then + raise Gem::Security::Exception, + "#{message} not valid before #{not_before}" + end + + if not_after = signer.not_after and not_after < time then + raise Gem::Security::Exception, "#{message} not valid after #{not_after}" + end + + if issuer and not signer.verify issuer.public_key then + raise Gem::Security::Exception, + "#{message} was not issued by #{issuer.subject}" + end + + true + end + + ## + # Ensures the public key of +key+ matches the public key in +signer+ + + def check_key signer, key + unless signer and key then + return true unless @only_signed + + raise Gem::Security::Exception, 'missing key or signature' + end + + raise Gem::Security::Exception, + "certificate #{signer.subject} does not match the signing key" unless + signer.public_key.to_pem == key.public_key.to_pem + + true + end + + ## + # Ensures the root certificate in +chain+ is self-signed and valid for + # +time+. + + def check_root chain, time + raise Gem::Security::Exception, 'missing signing chain' unless chain + + root = chain.first + + raise Gem::Security::Exception, 'missing root certificate' unless root + + raise Gem::Security::Exception, + "root certificate #{root.subject} is not self-signed " + + "(issuer #{root.issuer})" if + root.issuer.to_s != root.subject.to_s # HACK to_s is for ruby 1.8 + + check_cert root, root, time + end + + ## + # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and + # the digests of the two certificates match according to +digester+ + + def check_trust chain, digester, trust_dir + raise Gem::Security::Exception, 'missing signing chain' unless chain + + root = chain.first + + raise Gem::Security::Exception, 'missing root certificate' unless root + + path = Gem::Security.trust_dir.cert_path root + + unless File.exist? path then + message = "root cert #{root.subject} is not trusted" + + message << " (root of signing cert #{chain.last.subject})" if + chain.length > 1 + + raise Gem::Security::Exception, message + end + + save_cert = OpenSSL::X509::Certificate.new File.read path + save_dgst = digester.digest save_cert.public_key.to_s + + pkey_str = root.public_key.to_s + cert_dgst = digester.digest pkey_str + + raise Gem::Security::Exception, + "trusted root certificate #{root.subject} checksum " + + "does not match signing root certificate checksum" unless + save_dgst == cert_dgst + + true + end + + ## + # Extracts the email or subject from +certificate+ + + def subject certificate # :nodoc: + certificate.extensions.each do |extension| + next unless extension.oid == 'subjectAltName' + + return extension.value + end + + certificate.subject.to_s + end + + def inspect # :nodoc: + ("[Policy: %s - data: %p signer: %p chain: %p root: %p " + + "signed-only: %p trusted-only: %p]") % [ + @name, @verify_chain, @verify_data, @verify_root, @verify_signer, + @only_signed, @only_trusted, + ] + end + + ## + # For +full_name+, verifies the certificate +chain+ is valid, the +digests+ + # match the signatures +signatures+ created by the signer depending on the + # +policy+ settings. + # + # If +key+ is given it is used to validate the signing certificate. + + def verify chain, key = nil, digests = {}, signatures = {}, + full_name = '(unknown)' + if signatures.empty? then + if @only_signed then + raise Gem::Security::Exception, + "unsigned gems are not allowed by the #{name} policy" + elsif digests.empty? then + # lack of signatures is irrelevant if there is nothing to check + # against + else + alert_warning "#{full_name} is not signed" + return + end + end + + opt = @opt + digester = Gem::Security::DIGEST_ALGORITHM + trust_dir = opt[:trust_dir] + time = Time.now + + _, signer_digests = digests.find do |algorithm, file_digests| + file_digests.values.first.name == Gem::Security::DIGEST_NAME + end + + if @verify_data then + raise Gem::Security::Exception, 'no digests provided (probable bug)' if + signer_digests.nil? or signer_digests.empty? + else + signer_digests = {} + end + + signer = chain.last + + check_key signer, key if key + + check_cert signer, nil, time if @verify_signer + + check_chain chain, time if @verify_chain + + check_root chain, time if @verify_root + + if @only_trusted then + check_trust chain, digester, trust_dir + elsif signatures.empty? and digests.empty? then + # trust is irrelevant if there's no signatures to verify + else + alert_warning "#{subject signer} is not trusted for #{full_name}" + end + + signatures.each do |file, _| + digest = signer_digests[file] + + raise Gem::Security::Exception, "missing digest for #{file}" unless + digest + end + + signer_digests.each do |file, digest| + signature = signatures[file] + + raise Gem::Security::Exception, "missing signature for #{file}" unless + signature + + check_data signer.public_key, digester, signature, digest if @verify_data + end + + true + end + + ## + # Extracts the certificate chain from the +spec+ and calls #verify to ensure + # the signatures and certificate chain is valid according to the policy.. + + def verify_signatures spec, digests, signatures + chain = spec.cert_chain.map do |cert_pem| + OpenSSL::X509::Certificate.new cert_pem + end + + verify chain, nil, digests, signatures, spec.full_name + + true + end + + alias to_s name # :nodoc: + +end + -- cgit v1.2.3