summaryrefslogtreecommitdiff
path: root/sample/openssl/c_rehash.rb
blob: cd6c9d5fd4298d3f6b56de259ea2f2d8213831a4 (plain)
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/env ruby

require 'openssl'
require 'digest/md5'

class CHashDir
  include Enumerable

  def initialize(dirpath)
    @dirpath = dirpath
    @fingerprint_cache = @cert_cache = @crl_cache = nil
  end

  def hash_dir(silent = false)
    # ToDo: Should lock the directory...
    @silent = silent
    @fingerprint_cache = Hash.new
    @cert_cache = Hash.new
    @crl_cache = Hash.new
    do_hash_dir
  end

  def get_certs(name = nil)
    if name
      @cert_cache[hash_name(name)]
    else
      @cert_cache.values.flatten
    end
  end

  def get_crls(name = nil)
    if name
      @crl_cache[hash_name(name)]
    else
      @crl_cache.values.flatten
    end
  end

  def delete_crl(crl)
    File.unlink(crl_filename(crl))
    hash_dir(true)
  end

  def add_crl(crl)
    File.open(crl_filename(crl), "w") do |f|
      f << crl.to_pem
    end
    hash_dir(true)
  end

  def load_pem_file(filepath)
    str = File.read(filepath)
    begin
      OpenSSL::X509::Certificate.new(str)
    rescue
      begin
        OpenSSL::X509::CRL.new(str)
      rescue
        begin
          OpenSSL::X509::Request.new(str)
        rescue
          nil
        end
      end
    end
  end

private

  def crl_filename(crl)
    path(hash_name(crl.issuer)) + '.pem'
  end

  def do_hash_dir
    Dir.chdir(@dirpath) do
      delete_symlink
      Dir.glob('*.pem') do |pemfile|
        cert = load_pem_file(pemfile)
        case cert
        when OpenSSL::X509::Certificate
          link_hash_cert(pemfile, cert)
        when OpenSSL::X509::CRL
          link_hash_crl(pemfile, cert)
        else
          STDERR.puts("WARNING: #{pemfile} does not contain a certificate or CRL: skipping") unless @silent
        end
      end
    end
  end

  def delete_symlink
    Dir.entries(".").each do |entry|
      next unless /^[\da-f]+\.r{0,1}\d+$/ =~ entry
      File.unlink(entry) if FileTest.symlink?(entry)
    end
  end

  def link_hash_cert(org_filename, cert)
    name_hash = hash_name(cert.subject)
    fingerprint = fingerprint(cert.to_der)
    filepath = link_hash(org_filename, name_hash, fingerprint) { |idx|
      "#{name_hash}.#{idx}"
    }
    unless filepath
      unless @silent
        STDERR.puts("WARNING: Skipping duplicate certificate #{org_filename}")
      end
    else
      (@cert_cache[name_hash] ||= []) << path(filepath)
    end
  end

  def link_hash_crl(org_filename, crl)
    name_hash = hash_name(crl.issuer)
    fingerprint = fingerprint(crl.to_der)
    filepath = link_hash(org_filename, name_hash, fingerprint) { |idx|
      "#{name_hash}.r#{idx}"
    }
    unless filepath
      unless @silent
        STDERR.puts("WARNING: Skipping duplicate CRL #{org_filename}")
      end
    else
      (@crl_cache[name_hash] ||= []) << path(filepath)
    end
  end

  def link_hash(org_filename, name, fingerprint)
    idx = 0
    filepath = nil
    while true
      filepath = yield(idx)
      break unless FileTest.symlink?(filepath) or FileTest.exist?(filepath)
      if @fingerprint_cache[filepath] == fingerprint
        return false
      end
      idx += 1
    end
    STDOUT.puts("#{org_filename} => #{filepath}") unless @silent
    symlink(org_filename, filepath)
    @fingerprint_cache[filepath] = fingerprint
    filepath
  end

  def symlink(from, to)
    begin
      File.symlink(from, to)
    rescue
      File.open(to, "w") do |f|
        f << File.read(from)
      end
    end
  end

  def path(filename)
    File.join(@dirpath, filename)
  end

  def hash_name(name)
    sprintf("%x", name.hash)
  end

  def fingerprint(der)
    Digest::MD5.hexdigest(der).upcase
  end
end

if $0 == __FILE__
  dirlist = ARGV
  dirlist << '/usr/ssl/certs' if dirlist.empty?
  dirlist.each do |dir|
    CHashDir.new(dir).hash_dir
  end
end