diff options
author | NAKAMURA Hiroshi <nahi@keynauts.com> | 2003-07-04 14:53:50 +0000 |
---|---|---|
committer | NAKAMURA Hiroshi <nahi@keynauts.com> | 2003-07-04 14:53:50 +0000 |
commit | d0ca18f049b6488d3d0563635cec55234e8fa707 (patch) | |
tree | ffb10c81e050ff05758424f68a69665ccaea774a | |
parent | c9cd6ba8d6e6c5bf68b62b3886d1b8c65d4762bd (diff) | |
download | ruby-openssl-history-d0ca18f049b6488d3d0563635cec55234e8fa707.tar.gz |
* examples/cert_store.rb: Renamed to cert_store_view.rb.
* examples/cert_store_view.rb: Added.
* examples/certstore.rb: Certification store implementation.
* examples/crlstore.rb: CRL store implementation.
* examples/gen_cert.rb: Typo fixed.
-rw-r--r-- | ChangeLog | 7 | ||||
-rwxr-xr-x | examples/cert_store.rb | 191 | ||||
-rwxr-xr-x | examples/cert_store_view.rb | 733 | ||||
-rw-r--r-- | examples/certstore.rb | 152 | ||||
-rw-r--r-- | examples/crlstore.rb | 115 |
5 files changed, 1052 insertions, 146 deletions
@@ -1,3 +1,10 @@ +Fri, 04 Jul 2003 23:51:52 +0900 -- NAKAMURA, Hiroshi <nahi@ruby-lang.org> + * examples/cert_store.rb: Renamed to cert_store_view.rb. + * examples/cert_store_view.rb: Added. + * examples/certstore.rb: Certification store implementation. + * examples/crlstore.rb: CRL store implementation. + * examples/gen_cert.rb: Typo fixed. + Fri, 04 Jul 2003 23:43:14 +0900 -- NAKAMURA, Hiroshi <nahi@ruby-lang.org> * examples/ca/: Added gen_cert.rb and gen_crl.rb. diff --git a/examples/cert_store.rb b/examples/cert_store.rb index 75e5414..e1dd10e 100755 --- a/examples/cert_store.rb +++ b/examples/cert_store.rb @@ -3,6 +3,7 @@ require 'fox' require 'openssl' require 'time' +require 'certstore' include Fox @@ -173,7 +174,7 @@ EOS end def ext_line(tag) - ext(tag) + ext(tag).tr("\r\n", '') end def ext_detail(tag, value) @@ -245,7 +246,7 @@ private end def next_update - @crl.last_update.to_s + @crl.next_update.to_s end def next_update_line @@ -262,7 +263,7 @@ private end def ext_line(tag) - ext(tag) + ext(tag).tr("\r\n", '') end def ext_detail(tag, value) @@ -327,7 +328,7 @@ private end def ext_line(tag) - ext(tag) + ext(tag).tr("\r\n", '') end def ext_detail(tag, value) @@ -373,6 +374,10 @@ class CertStoreWindow < FXMainWindow node.expanded = node.opened = true end + def close_node(node) + node.expanded = node.opened = false + end + def import_certs(cert_store) cert_store.self_signed_ca.each do |cert| add_item_last(@self_signed_ca_node, cert_label(cert), cert) @@ -383,10 +388,11 @@ class CertStoreWindow < FXMainWindow cert_store.ee.each do |cert| add_item_last(@ee_node, cert_label(cert), cert) end - cert_store.crls.each do |crl| + cert_store.crl.each do |crl| node = add_item_last(@crl_node, name_label(crl.issuer), crl) + close_node(node) crl.revoked.each do |revoked| - add_item_last(node, revoked.serial.to_s, revoked) + add_item_last(node, bn_label(revoked.serial), revoked) end end end @@ -394,12 +400,21 @@ class CertStoreWindow < FXMainWindow def add_verify_path(verify_path) node = @verify_path_node last_cert = nil - verify_path.reverse_each do |ok, cert, error_string| + verify_path.reverse_each do |ok, cert, crl_check, error_string| + warn = [] + if @observer.cert_store.is_ca?(cert) + warn << 'NO ARL' unless crl_check + else + warn << 'NO CRL' unless crl_check + end + warn_str = '(' << warn.join(", ") << ')' + warn_mark = warn.empty? ? '' : '!' label = if ok - 'OK...' + cert_label(cert) + "OK#{warn_mark}..." + cert_label(cert) else "NG(#{error_string})..." + cert_label(cert) end + label << warn_str unless warn.empty? node = add_item_last(node, label, cert) node.expanded = true last_cert = cert @@ -429,8 +444,8 @@ class CertStoreWindow < FXMainWindow @table.showVertGrid(false) @table.showHorzGrid(false) @table.setTableSize(1, 2) - @table.setColumnWidth(0, 100) - @table.setColumnWidth(1, 275) + @table.setColumnWidth(0, 125) + @table.setColumnWidth(1, 350) end def show(item) @@ -465,7 +480,7 @@ class CertStoreWindow < FXMainWindow #items << ['Not after', wrap.get_dump_line('Not after')] items << ['Public key', wrap.get_dump_line('Public key')] cert.extensions.each do |ext| - items << [ext.oid, ext.value] + items << [ext.oid, wrap.get_dump_line(ext.oid)] end show_items(cert, items) end @@ -478,7 +493,7 @@ class CertStoreWindow < FXMainWindow items << ['Last update', wrap.get_dump_line('Last update')] items << ['Next update', wrap.get_dump_line('Next update')] crl.extensions.each do |ext| - items << [ext.oid, ext.value] + items << [ext.oid, wrap.get_dump_line(ext.oid)] end show_items(crl, items) end @@ -489,7 +504,7 @@ class CertStoreWindow < FXMainWindow items << ['Serial', wrap.get_dump_line('Serial')] items << ['Time', wrap.get_dump_line('Time')] revoked.extensions.each do |ext| - items << [ext.oid, ext.value] + items << [ext.oid, wrap.get_dump_line(ext.oid)] end show_items(revoked, items) end @@ -568,13 +583,15 @@ class CertStoreWindow < FXMainWindow end end + attr_reader :cert_store + def initialize(app, cert_store) @cert_store = cert_store @verify_filter = 0 @verify_filename = nil - full_width = 600 - full_height = 400 - horz_pos = 200 + full_width = 800 + full_height = 500 + horz_pos = 300 super(app, "Certificate store", nil, nil, DECOR_ALL, 0, 0, full_width, full_height) @@ -587,8 +604,6 @@ class CertStoreWindow < FXMainWindow file_open_menu = FXMenuPane.new(self) FXMenuCommand.new(file_open_menu, "&Directory\tCtl-O").connect(SEL_COMMAND, method(:on_cmd_file_open_dir)) - FXMenuCommand.new(file_open_menu, "&CRL file\tCtl-R").connect(SEL_COMMAND, - method(:on_cmd_file_open_crl)) FXMenuCascade.new(file_menu, "&Open\tCtl-O", nil, file_open_menu) FXMenuCommand.new(file_menu, "&Quit\tCtl-Q", nil, getApp(), FXApp::ID_QUIT) @@ -659,6 +674,10 @@ class CertStoreWindow < FXMainWindow @cert_detail.show(item, tag) if @cert_detail end + def verify(certfile) + show_verify_path(verify_certfile(certfile)) + end + private def on_cmd_file_open_dir(sender, sel, ptr) @@ -674,15 +693,6 @@ private 1 end - def on_cmd_file_open_crl(sender, sel, ptr) - filename = FXFileDialog.getOpenFilename(self, "Open CRL file", ".") - unless filename.empty? - @cert_store.add_crl(filename) - show_init - end - 1 - end - def on_cmd_tool_verify(sender, sel, ptr) dialog = FXFileDialog.new(self, "Verify certificate") dialog.filename = '' @@ -690,7 +700,7 @@ private dialog.currentPattern = @verify_filter if dialog.execute != 0 @verify_filename = dialog.filename - show_verify_path(verify_certfile(@verify_filename)) + verify(@verify_filename) end @verify_filter = dialog.currentPattern 1 @@ -711,124 +721,13 @@ private end end -class CertStore - include OpenSSL - include X509 - - attr_reader :cert - attr_reader :self_signed_ca - attr_reader :other_ca - attr_reader :ee - attr_reader :crls - - def initialize(trust_certs_dir, crl_file = nil) - @trust_certs_dir = trust_certs_dir - @self_signed_ca = [] - @other_ca = [] - @ee = [] - @crls = [] - @cert_store = Store.new - load_certs - if crl_file - add_crl(crl_file) - end - end - - def generate_cert(filename) - Certificate.new(File.open(filename).read) - end - - def verify(cert) - verify_map = [] - @cert_store.verify(cert) do |ok, ctx| - cert = ctx.current_cert - verify_map << [ok, ctx.current_cert, ctx.error_string] - true - end - @cert_store.chain.collect { |cert| - result = verify_map.find { |v| match_cert(v[1], cert) and !v[0] } - if result - result - else - [true, cert] - end - } - end - - def add_crl(crl_file) - crl = CRL.new(File.open(crl_file).read) - @cert_store.add_crl(crl) - @cert_store.flags = V_FLAG_CRL_CHECK | V_FLAG_CRL_CHECK_ALL - @crls << crl - end - - def match_cert(cert1, cert2) - (cert1.issuer.cmp(cert2.issuer) == 0) and cert1.serial == cert2.serial - end - -private - - def load_certs - Dir.glob(File.join(@trust_certs_dir, '*.pem')).each do |pem| - cert = Certificate.new(File.open(pem).read) - case guess_cert_type(cert) - when CERT_TYPE_SELF_SIGNED - @self_signed_ca << cert - add_cert_to_store(cert) - when CERT_TYPE_OTHER - @other_ca << cert - add_cert_to_store(cert) - when CERT_TYPE_EE - @ee << cert - else - raise "Unknown cert type." - end - end - end - - def add_cert_to_store(cert) - @cert_store.add_cert(cert) - 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 - end -end - -trust_certs_dir = ARGV.shift -crl_file = ARGV.shift +trust_certs_dir = ARGV.shift or raise "#{$0} trust_cert_dir" +certfile = ARGV.shift app = FXApp.new("CertStore", "FoxTest") -cert_store = CertStore.new(trust_certs_dir, crl_file) -CertStoreWindow.new(app, cert_store) +cert_store = CertStore.new(trust_certs_dir) +w = CertStoreWindow.new(app, cert_store) app.create +if certfile + w.verify(certfile) +end app.run diff --git a/examples/cert_store_view.rb b/examples/cert_store_view.rb new file mode 100755 index 0000000..be56de7 --- /dev/null +++ b/examples/cert_store_view.rb @@ -0,0 +1,733 @@ +#!/usr/bin/env ruby + +require 'fox' +require 'openssl' +require 'time' +require 'certstore' + +include Fox + +module CertDumpSupport + def cert_label(cert) + subject_alt_name = + cert.extensions.find { |ext| ext.oid == 'subjectAltName' } + if subject_alt_name + subject_alt_name.value.split(/\s*,\s/).each do |alt_name_pair| + alt_tag, alt_name = alt_name_pair.split(/:/) + return alt_name + end + end + name_label(cert.subject) + end + + def name_label(name) + ary = name.to_a + if (cn = ary.find { |rdn| rdn[0] == 'CN' }) + return cn[1] + end + if ary.last[0] == 'OU' + return ary.last[1] + end + name.to_s + end + + def name_text(name) + name.to_a.collect { |tag, value| + "#{tag} = #{value}" + }.reverse.join("\n") + end + + def bn_label(bn) + sprintf("%X", bn).scan(/../).join(" ") + end +end + +class CertDump + include CertDumpSupport + + def initialize(cert) + @cert = cert + end + + def get_dump(tag) + case tag + when 'Version' + version + when 'Serial' + serial + when 'Subject' + subject + when 'Issuer' + issuer + when 'Valid time' + valid_time + when 'Not before' + not_before + when 'Not after' + not_after + when 'Public key' + public_key + else + ext(tag) + end + end + + def get_dump_line(tag) + case tag + when 'Version' + version_line + when 'Serial' + serial_line + when 'Subject' + subject_line + when 'Issuer' + issuer_line + when 'Valid time' + valid_time_line + when 'Not before' + not_before_line + when 'Not after' + not_after_line + when 'Public key' + public_key_line + else + ext_line(tag) + end + end + +private + + def version + "Version: #{@cert.version + 1}" + end + + def version_line + version + end + + def serial + bn_label(@cert.serial) + end + + def serial_line + serial + end + + def subject + name_text(@cert.subject) + end + + def subject_line + name_label(@cert.subject) + end + + def issuer + name_text(@cert.issuer) + end + + def issuer_line + name_label(@cert.issuer) + end + + def valid_time + <<EOS +Not before: #{not_before} +Not after: #{not_after} +EOS + end + + def valid_time_line + "from #{@cert.not_before.iso8601} to #{@cert.not_after.iso8601}" + end + + def not_before + @cert.not_before.to_s + end + + def not_before_line + not_before + end + + def not_after + @cert.not_after.to_s + end + + def not_after_line + not_after + end + + def public_key + @cert.public_key.to_text + end + + def public_key_line + "#{@cert.public_key.class} -- " << public_key.scan(/\A[^\n]*/)[0] << '...' + end + + def ext(tag) + @cert.extensions.each do |ext| + if ext.oid == tag + return ext_detail(tag, ext.value) + end + end + "(unknown)" + end + + def ext_line(tag) + ext(tag).tr("\r\n", '') + end + + def ext_detail(tag, value) + value + end +end + +class CrlDump + include CertDumpSupport + + def initialize(crl) + @crl = crl + end + + def get_dump(tag) + case tag + when 'Version' + version + when 'Issuer' + issuer + when 'Last update' + last_update + when 'Next update' + next_update + else + ext(tag) + end + end + + def get_dump_line(tag) + case tag + when 'Version' + version_line + when 'Issuer' + issuer_line + when 'Last update' + last_update_line + when 'Next update' + next_update_line + else + ext_line(tag) + end + end + +private + + def version + "Version: #{@crl.version + 1}" + end + + def version_line + version + end + + def issuer + name_text(@crl.issuer) + end + + def issuer_line + name_label(@crl.issuer) + end + + def last_update + @crl.last_update.to_s + end + + def last_update_line + last_update + end + + def next_update + @crl.next_update.to_s + end + + def next_update_line + next_update + end + + def ext(tag) + @crl.extensions.each do |ext| + if ext.oid == tag + return ext_detail(tag, ext.value) + end + end + "(unknown)" + end + + def ext_line(tag) + ext(tag).tr("\r\n", '') + end + + def ext_detail(tag, value) + value + end +end + +class RevokedDump + include CertDumpSupport + + def initialize(revoked) + @revoked = revoked + end + + def get_dump(tag) + case tag + when 'Serial' + serial + when 'Time' + time + else + ext(tag) + end + end + + def get_dump_line(tag) + case tag + when 'Serial' + serial_line + when 'Time' + time_line + else + ext_line(tag) + end + end + +private + + def serial + bn_label(@revoked.serial) + end + + def serial_line + serial + end + + def time + @revoked.time.to_s + end + + def time_line + time + end + + def ext(tag) + @revoked.extensions.each do |ext| + if ext.oid == tag + return ext_detail(tag, ext.value) + end + end + "(unknown)" + end + + def ext_line(tag) + ext(tag).tr("\r\n", '') + end + + def ext_detail(tag, value) + value + end +end + +class CertStoreView < FXMainWindow + class CertTree + include CertDumpSupport + + def initialize(observer, tree) + @observer = observer + @tree = tree + @tree.connect(SEL_COMMAND) do |sender, sel, item| + if item.data + @observer.getApp().beginWaitCursor do + @observer.show_item(item.data) + end + else + @observer.show_item(nil) + end + end + end + + def show(cert_store) + @tree.clearItems + @self_signed_ca_node = add_item_last(nil, "Trusted root CA") + @other_ca_node = add_item_last(nil, "Intermediate CA") + @ee_node = add_item_last(nil, "Personal") + @crl_node = add_item_last(nil, "CRL") + @verify_path_node = add_item_last(nil, "Certification path") + import_certs(cert_store) + end + + def show_verify_path(verify_path) + add_verify_path(verify_path) + end + + private + + def open_node(node) + node.expanded = node.opened = true + end + + def close_node(node) + node.expanded = node.opened = false + end + + def import_certs(cert_store) + cert_store.self_signed_ca.each do |cert| + add_item_last(@self_signed_ca_node, cert_label(cert), cert) + end + cert_store.other_ca.each do |cert| + add_item_last(@other_ca_node, cert_label(cert), cert) + end + cert_store.ee.each do |cert| + add_item_last(@ee_node, cert_label(cert), cert) + end + cert_store.crl.each do |crl| + node = add_item_last(@crl_node, name_label(crl.issuer), crl) + close_node(node) + crl.revoked.each do |revoked| + add_item_last(node, bn_label(revoked.serial), revoked) + end + end + end + + def add_verify_path(verify_path) + node = @verify_path_node + last_cert = nil + verify_path.reverse_each do |ok, cert, crl_check, error_string| + warn = [] + if @observer.cert_store.is_ca?(cert) + warn << 'NO ARL' unless crl_check + else + warn << 'NO CRL' unless crl_check + end + warn_str = '(' << warn.join(", ") << ')' + warn_mark = warn.empty? ? '' : '!' + label = if ok + "OK#{warn_mark}..." + cert_label(cert) + else + "NG(#{error_string})..." + cert_label(cert) + end + label << warn_str unless warn.empty? + node = add_item_last(node, label, cert) + node.expanded = true + last_cert = cert + end + if last_cert + @tree.selectItem(node) + @observer.show_item(last_cert) + end + end + + def add_item_last(parent, label, obj = nil) + node = @tree.addItemLast(parent, FXTreeItem.new(label)) + node.data = obj if obj + open_node(node) + node + end + end + + class CertInfo + def initialize(observer, table) + @observer = observer + @table = table + @table.leadingRows = 0 + @table.leadingCols = 0 + @table.trailingRows = 0 + @table.trailingCols = 0 + @table.showVertGrid(false) + @table.showHorzGrid(false) + @table.setTableSize(1, 2) + @table.setColumnWidth(0, 125) + @table.setColumnWidth(1, 350) + end + + def show(item) + @observer.show_detail(nil, nil) + if item.nil? + set_column_size(1) + return + end + case item + when OpenSSL::X509::Certificate + show_cert(item) + when OpenSSL::X509::CRL + show_crl(item) + when OpenSSL::X509::Revoked + show_revoked(item) + else + raise NotImplementedError.new("Unknown item type #{item.class}.") + end + end + + private + + def show_cert(cert) + wrap = CertDump.new(cert) + items = [] + items << ['Version', wrap.get_dump_line('Version')] + items << ['Serial', wrap.get_dump_line('Serial')] + items << ['Subject', wrap.get_dump_line('Subject')] + items << ['Issuer', wrap.get_dump_line('Issuer')] + items << ['Valid time', wrap.get_dump_line('Valid time')] + #items << ['Not before', wrap.get_dump_line('Not before')] + #items << ['Not after', wrap.get_dump_line('Not after')] + items << ['Public key', wrap.get_dump_line('Public key')] + cert.extensions.each do |ext| + items << [ext.oid, wrap.get_dump_line(ext.oid)] + end + show_items(cert, items) + end + + def show_crl(crl) + wrap = CrlDump.new(crl) + items = [] + items << ['Version', wrap.get_dump_line('Version')] + items << ['Issuer', wrap.get_dump_line('Issuer')] + items << ['Last update', wrap.get_dump_line('Last update')] + items << ['Next update', wrap.get_dump_line('Next update')] + crl.extensions.each do |ext| + items << [ext.oid, wrap.get_dump_line(ext.oid)] + end + show_items(crl, items) + end + + def show_revoked(revoked) + wrap = RevokedDump.new(revoked) + items = [] + items << ['Serial', wrap.get_dump_line('Serial')] + items << ['Time', wrap.get_dump_line('Time')] + revoked.extensions.each do |ext| + items << [ext.oid, wrap.get_dump_line(ext.oid)] + end + show_items(revoked, items) + end + + def show_items(obj, items) + set_column_size(items.size) + items.each_with_index do |ele, idx| + tag, value = ele + @table.setItemText(idx, 0, tag) + @table.getItem(idx, 0).data = tag + @table.setItemText(idx, 1, value.to_s) + @table.getItem(idx, 1).data = tag + end + @table.connect(SEL_COMMAND) do |sender, sel, loc| + item = @table.getItem(loc.row, loc.col) + @observer.show_detail(obj, item.data) + end + justify_table + end + + def set_column_size(size) + col0_width = @table.getColumnWidth(0) + col1_width = @table.getColumnWidth(1) + @table.setTableSize(size, 2) + @table.setColumnWidth(0, col0_width) + @table.setColumnWidth(1, col1_width) + end + + def justify_table + for col in 0..@table.numCols-1 + for row in 0..@table.numRows-1 + @table.getItem(row, col).justify = FXTableItem::LEFT + end + end + end + end + + class CertDetail + def initialize(observer, detail) + @observer = observer + @detail = detail + end + + def show(item, tag) + if item.nil? + @detail.text = '' + return + end + case item + when OpenSSL::X509::Certificate + show_cert(item, tag) + when OpenSSL::X509::CRL + show_crl(item, tag) + when OpenSSL::X509::Revoked + show_revoked(item, tag) + else + raise NotImplementedError.new("Unknown item type #{item.class}.") + end + end + + private + + def show_cert(cert, tag) + wrap = CertDump.new(cert) + @detail.text = wrap.get_dump(tag) + end + + def show_crl(crl, tag) + wrap = CrlDump.new(crl) + @detail.text = wrap.get_dump(tag) + end + + def show_revoked(revoked, tag) + wrap = RevokedDump.new(revoked) + @detail.text = wrap.get_dump(tag) + end + end + + attr_reader :cert_store + + def initialize(app, cert_store) + @cert_store = cert_store + @verify_filter = 0 + @verify_filename = nil + full_width = 800 + full_height = 500 + horz_pos = 300 + + super(app, "Certificate store", nil, nil, DECOR_ALL, 0, 0, full_width, + full_height) + + FXTooltip.new(self.getApp()) + + menubar = FXMenubar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X) + file_menu = FXMenuPane.new(self) + FXMenuTitle.new(menubar, "&File", nil, file_menu) + file_open_menu = FXMenuPane.new(self) + FXMenuCommand.new(file_open_menu, "&Directory\tCtl-O").connect(SEL_COMMAND, + method(:on_cmd_file_open_dir)) + FXMenuCascade.new(file_menu, "&Open\tCtl-O", nil, file_open_menu) + FXMenuCommand.new(file_menu, "&Quit\tCtl-Q", nil, getApp(), FXApp::ID_QUIT) + + tool_menu = FXMenuPane.new(self) + FXMenuTitle.new(menubar, "&Tool", nil, tool_menu) + FXMenuCommand.new(tool_menu, "&Verify\tCtl-N").connect(SEL_COMMAND, + method(:on_cmd_tool_verify)) + + base_frame = FXHorizontalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y) + splitter_horz = FXSplitter.new(base_frame, LAYOUT_SIDE_TOP | LAYOUT_FILL_X | + LAYOUT_FILL_Y | SPLITTER_TRACKING | SPLITTER_HORIZONTAL) + + # Cert tree + cert_tree_frame = FXHorizontalFrame.new(splitter_horz, LAYOUT_FILL_X | + LAYOUT_FILL_Y | FRAME_SUNKEN | FRAME_THICK) + cert_tree_frame.setWidth(horz_pos) + cert_tree = FXTreeList.new(cert_tree_frame, 0, nil, 0, + TREELIST_BROWSESELECT | TREELIST_SHOWS_LINES | TREELIST_SHOWS_BOXES | + TREELIST_ROOT_BOXES | LAYOUT_FILL_X | LAYOUT_FILL_Y) + @cert_tree = CertTree.new(self, cert_tree) + + # Cert info + splitter_vert = FXSplitter.new(splitter_horz, LAYOUT_SIDE_TOP | + LAYOUT_FILL_X | LAYOUT_FILL_Y | SPLITTER_TRACKING | SPLITTER_VERTICAL | + SPLITTER_REVERSED) + cert_list_base = FXVerticalFrame.new(splitter_vert, LAYOUT_FILL_X | + LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0) + cert_list_frame = FXHorizontalFrame.new(cert_list_base, FRAME_SUNKEN | + FRAME_THICK | LAYOUT_FILL_X | LAYOUT_FILL_Y) + cert_info = FXTable.new(cert_list_frame, 2, 10, nil, 0, FRAME_SUNKEN | + TABLE_COL_SIZABLE | LAYOUT_FILL_X | LAYOUT_FILL_Y, 0, 0, 0, 0, 2, 2, 2, 2) + @cert_info = CertInfo.new(self, cert_info) + + cert_detail_base = FXVerticalFrame.new(splitter_vert, LAYOUT_FILL_X | + LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0) + cert_detail_frame = FXHorizontalFrame.new(cert_detail_base, FRAME_SUNKEN | + FRAME_THICK | LAYOUT_FILL_X | LAYOUT_FILL_Y) + cert_detail = FXText.new(cert_detail_frame, nil, 0, TEXT_READONLY | + LAYOUT_FILL_X | LAYOUT_FILL_Y) + @cert_detail = CertDetail.new(self, cert_detail) + + show_init + end + + def create + super + show(PLACEMENT_SCREEN) + end + + def show_init + show_tree + show_item(nil) + end + + def show_tree + @cert_tree.show(@cert_store) + end + + def show_verify_path(verify_path) + @cert_tree.show_verify_path(verify_path) + end + + def show_item(item) + @cert_info.show(item) if @cert_info + end + + def show_detail(item, tag) + @cert_detail.show(item, tag) if @cert_detail + end + + def verify(certfile) + show_verify_path(verify_certfile(certfile)) + end + +private + + def on_cmd_file_open_dir(sender, sel, ptr) + dir = FXFileDialog.getOpenDirectory(self, "Open certificate directory", ".") + unless dir.empty? + begin + @cert_store = CertStore.new(dir) + rescue + show_error($!) + end + show_init + end + 1 + end + + def on_cmd_tool_verify(sender, sel, ptr) + dialog = FXFileDialog.new(self, "Verify certificate") + dialog.filename = '' + dialog.patternList = ["All Files (*)", "PEM formatted certificate (*.pem)"] + dialog.currentPattern = @verify_filter + if dialog.execute != 0 + @verify_filename = dialog.filename + verify(@verify_filename) + end + @verify_filter = dialog.currentPattern + 1 + end + + def verify_certfile(filename) + begin + cert = @cert_store.generate_cert(filename) + result = @cert_store.verify(cert) + rescue + show_error($!) + end + end + + def show_error(e) + msg = e.inspect + "\n" + e.backtrace.join("\n") + FXMessageBox.error(self, MBOX_OK, "Error", msg) + end +end + +trust_certs_dir = ARGV.shift or raise "#{$0} trust_cert_dir" +certfile = ARGV.shift +app = FXApp.new("CertStore", "FoxTest") +cert_store = CertStore.new(trust_certs_dir) +w = CertStoreView.new(app, cert_store) +app.create +if certfile + w.verify(certfile) +end +app.run diff --git a/examples/certstore.rb b/examples/certstore.rb new file mode 100644 index 0000000..c879480 --- /dev/null +++ b/examples/certstore.rb @@ -0,0 +1,152 @@ +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 + + def initialize(trust_certs_dir) + @trust_certs_dir = trust_certs_dir + @c_store = CHashDir.new(@trust_certs_dir) + @c_store.hash_dir(true) + @crl_store = CrlStore.new(@c_store) + @x509store = Store.new + @x509store.add_path(@trust_certs_dir) + @self_signed_ca = @other_ca = @ee = @crl = nil + + # Uncomment thi line to let OpenSSL to check CRL for each certs. + # @x509store.flags = V_FLAG_CRL_CHECK | V_FLAG_CRL_CHECK_ALL + + 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 + +private + + 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 and !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 + 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 scan_certs + @self_signed_ca = [] + @other_ca = [] + @ee = [] + @crl = [] + load_certs + 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 + end +end + + +if $0 == __FILE__ + c = CertStore.new("trust_certs") +end diff --git a/examples/crlstore.rb b/examples/crlstore.rb new file mode 100644 index 0000000..3239edb --- /dev/null +++ b/examples/crlstore.rb @@ -0,0 +1,115 @@ +require 'http-access2' +require 'c_rehash' + + +class CrlStore + def initialize(c_store) + @c_store = c_store + @c_store.hash_dir(true) + end + + def find_crl(cert) + do_find_crl(cert) + end + +private + + def do_find_crl(cert) + unless ca = find_ca(cert) + return nil + end + unless crlfiles = @c_store.get_crls(ca.subject) + if crl = renew_crl(cert, ca) + @c_store.add_crl(crl) + return crl + end + return nil + end + crlfiles.each do |crlfile| + next unless crl = load_crl(crlfile) + if crl.next_update < Time.now + if new_crl = renew_crl(cert, ca) + @c_store.delete_crl(crl) + @c_store.add_crl(new_crl) + crl = new_crl + end + end + if check_valid(crl, ca) + return crl + end + end + nil + end + + def find_ca(cert) + @c_store.get_certs(cert.issuer).each do |cafile| + ca = load_cert(cafile) + if cert.verify(ca.public_key) + return ca + end + end + nil + end + + def fetch(location) + if /\AURI:(.*)\z/ =~ location + p "fetch" + c = HTTPAccess2::Client.new(ENV['http_proxy'] || ENV['HTTP_PROXY']) + c.get_content($1) + else + nil + end + end + + def load_cert(certfile) + load_cert_str(File.read(certfile)) + end + + def load_crl(crlfile) + load_crl_str(File.read(crlfile)) + end + + def load_cert_str(cert_str) + OpenSSL::X509::Certificate.new(cert_str) + end + + def load_crl_str(crl_str) + OpenSSL::X509::CRL.new(crl_str) + end + + def check_valid(crl, ca) + unless crl.verify(ca.public_key) + return false + end + crl.last_update <= Time.now + end + + RE_CDP = /\AcrlDistributionPoints\z/ + def get_cdp(cert) + if cdp_ext = cert.extensions.find { |ext| RE_CDP =~ ext.oid } + cdp_ext.value.chomp + else + false + end + end + + def renew_crl(cert, ca) + if cdp = get_cdp(cert) + if new_crl_str = fetch(cdp) + new_crl = load_crl_str(new_crl_str) + if check_valid(new_crl, ca) + return new_crl + end + end + end + false + end +end + +if $0 == __FILE__ + dir = "trust_certs" + c_store = CHashDir.new(dir) + s = CrlStore.new(c_store) + c = OpenSSL::X509::Certificate.new(File.read("cert_store/google_codesign.pem")) + p s.find_crl(c) +end |