require 'c_rehash' require 'crlstore' class CertStore include OpenSSL include X509 attr_reader :self_signed_ca attr_reader :other_ca attr_reader :ee attr_reader :crl attr_reader :request def initialize(certs_dir) @certs_dir = certs_dir @c_store = CHashDir.new(@certs_dir) @c_store.hash_dir(true) @crl_store = CrlStore.new(@c_store) @x509store = Store.new @self_signed_ca = @other_ca = @ee = @crl = nil # Uncomment this line to let OpenSSL to check CRL for each certs. # @x509store.flags = V_FLAG_CRL_CHECK | V_FLAG_CRL_CHECK_ALL add_path scan_certs end def generate_cert(filename) @c_store.load_pem_file(filename) end def verify(cert) error, crl_map = do_verify(cert) if error [[false, cert, crl_map[cert.subject], error]] else @x509store.chain.collect { |c| [true, c, crl_map[c.subject], nil] } end end def match_cert(cert1, cert2) (cert1.issuer.cmp(cert2.issuer) == 0) and cert1.serial == cert2.serial end def is_ca?(cert) case guess_cert_type(cert) when CERT_TYPE_SELF_SIGNED true when CERT_TYPE_OTHER true else false end end def scan_certs @self_signed_ca = [] @other_ca = [] @ee = [] @crl = [] @request = [] load_certs end private def add_path @x509store.add_path(@certs_dir) end def do_verify(cert) error_map = {} crl_map = {} result = @x509store.verify(cert) do |ok, ctx| cert = ctx.current_cert if ctx.current_crl crl_map[cert.subject] = true end if ok if !ctx.current_crl if crl = @crl_store.find_crl(cert) crl_map[cert.subject] = true if crl.revoked.find { |revoked| revoked.serial == cert.serial } ok = false error_string = 'certification revoked' end end end end error_map[cert.subject] = error_string if error_string ok end error = if result nil else error_map[cert.subject] || @x509store.error_string end return error, crl_map end def load_certs @c_store.get_certs.each do |certfile| cert = generate_cert(certfile) case guess_cert_type(cert) when CERT_TYPE_SELF_SIGNED @self_signed_ca << cert when CERT_TYPE_OTHER @other_ca << cert when CERT_TYPE_EE @ee << cert else raise "Unknown cert type." end end @c_store.get_crls.each do |crlfile| @crl << generate_cert(crlfile) end end CERT_TYPE_SELF_SIGNED = 0 CERT_TYPE_OTHER = 1 CERT_TYPE_EE = 2 def guess_cert_type(cert) ca = self_signed = is_cert_self_signed(cert) cert.extensions.each do |ext| # Ignores criticality of extensions. It's 'guess'ing. case ext.oid when 'basicConstraints' /CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ext.value ca = ($1 == 'TRUE') unless ca when 'keyUsage' usage = ext.value.split(/\s*,\s*/) ca = usage.include?('Certificate Sign') unless ca when 'nsCertType' usage = ext.value.split(/\s*,\s*/) ca = usage.include?('SSL CA') unless ca end end if ca if self_signed CERT_TYPE_SELF_SIGNED else CERT_TYPE_OTHER end else CERT_TYPE_EE end end def is_cert_self_signed(cert) # cert.subject.cmp(cert.issuer) == 0 cert.subject.to_s == cert.issuer.to_s end end if $0 == __FILE__ c = CertStore.new("trust_certs") end