1require 'c_rehash'
2require 'crlstore'
3
4
5class CertStore
6  include OpenSSL
7  include X509
8
9  attr_reader :self_signed_ca
10  attr_reader :other_ca
11  attr_reader :ee
12  attr_reader :crl
13  attr_reader :request
14
15  def initialize(certs_dir)
16    @certs_dir = certs_dir
17    @c_store = CHashDir.new(@certs_dir)
18    @c_store.hash_dir(true)
19    @crl_store = CrlStore.new(@c_store)
20    @x509store = Store.new
21    @self_signed_ca = @other_ca = @ee = @crl = nil
22
23    # Uncomment this line to let OpenSSL to check CRL for each certs.
24    # @x509store.flags = V_FLAG_CRL_CHECK | V_FLAG_CRL_CHECK_ALL
25
26    add_path
27    scan_certs
28  end
29
30  def generate_cert(filename)
31    @c_store.load_pem_file(filename)
32  end
33
34  def verify(cert)
35    error, crl_map = do_verify(cert)
36    if error
37      [[false, cert, crl_map[cert.subject], error]]
38    else
39      @x509store.chain.collect { |c| [true, c, crl_map[c.subject], nil] }
40    end
41  end
42
43  def match_cert(cert1, cert2)
44    (cert1.issuer.cmp(cert2.issuer) == 0) and cert1.serial == cert2.serial
45  end
46
47  def is_ca?(cert)
48    case guess_cert_type(cert)
49    when CERT_TYPE_SELF_SIGNED
50      true
51    when CERT_TYPE_OTHER
52      true
53    else
54      false
55    end
56  end
57
58  def scan_certs
59    @self_signed_ca = []
60    @other_ca = []
61    @ee = []
62    @crl = []
63    @request = []
64    load_certs
65  end
66
67private
68
69  def add_path
70    @x509store.add_path(@certs_dir)
71  end
72
73  def do_verify(cert)
74    error_map = {}
75    crl_map = {}
76    result = @x509store.verify(cert) do |ok, ctx|
77      cert = ctx.current_cert
78      if ctx.current_crl
79	crl_map[cert.subject] = true
80      end
81      if ok
82	if !ctx.current_crl
83	  if crl = @crl_store.find_crl(cert)
84	    crl_map[cert.subject] = true
85	    if crl.revoked.find { |revoked| revoked.serial == cert.serial }
86	      ok = false
87	      error_string = 'certification revoked'
88	    end
89	  end
90	end
91      end
92      error_map[cert.subject] = error_string if error_string
93      ok
94    end
95    error = if result
96	nil
97      else
98	error_map[cert.subject] || @x509store.error_string
99      end
100    return error, crl_map
101  end
102
103  def load_certs
104    @c_store.get_certs.each do |certfile|
105      cert = generate_cert(certfile)
106      case guess_cert_type(cert)
107      when CERT_TYPE_SELF_SIGNED
108	@self_signed_ca << cert
109      when CERT_TYPE_OTHER
110	@other_ca << cert
111      when CERT_TYPE_EE
112	@ee << cert
113      else
114	raise "Unknown cert type."
115      end
116    end
117    @c_store.get_crls.each do |crlfile|
118      @crl << generate_cert(crlfile)
119    end
120  end
121
122  CERT_TYPE_SELF_SIGNED = 0
123  CERT_TYPE_OTHER = 1
124  CERT_TYPE_EE = 2
125  def guess_cert_type(cert)
126    ca = self_signed = is_cert_self_signed(cert)
127    cert.extensions.each do |ext|
128      # Ignores criticality of extensions.  It's 'guess'ing.
129      case ext.oid
130      when 'basicConstraints'
131	/CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ext.value
132	ca = ($1 == 'TRUE') unless ca
133      when 'keyUsage'
134	usage = ext.value.split(/\s*,\s*/)
135	ca = usage.include?('Certificate Sign') unless ca
136      when 'nsCertType'
137	usage = ext.value.split(/\s*,\s*/)
138	ca = usage.include?('SSL CA') unless ca
139      end
140    end
141    if ca
142      if self_signed
143	CERT_TYPE_SELF_SIGNED
144      else
145	CERT_TYPE_OTHER
146      end
147    else
148      CERT_TYPE_EE
149    end
150  end
151
152  def is_cert_self_signed(cert)
153    # cert.subject.cmp(cert.issuer) == 0
154    cert.subject.to_s == cert.issuer.to_s
155  end
156end
157
158
159if $0 == __FILE__
160  c = CertStore.new("trust_certs")
161end
162