#!/usr/bin/env ruby require 'fox' require 'openssl' require 'time' require 'certstore' require 'getopts' 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) ("0" << 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 'Signature Algorithm' signature_algorithm when 'Issuer' issuer when 'Validity' validity when 'Not before' not_before when 'Not after' not_after when 'Subject' subject 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 'Signature Algorithm' signature_algorithm_line when 'Subject' subject_line when 'Issuer' issuer_line when 'Validity' validity_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 signature_algorithm @cert.signature_algorithm end def signature_algorithm_line signature_algorithm end def subject name_text(@cert.subject) end def subject_line @cert.subject.to_s end def issuer name_text(@cert.issuer) end def issuer_line @cert.issuer.to_s end def validity < 0 @tree.removeItem(node.getFirst) end 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) when OpenSSL::X509::Request show_request(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 << ['Signature Algorithm', wrap.get_dump_line('Signature Algorithm')] items << ['Issuer', wrap.get_dump_line('Issuer')] items << ['Serial', wrap.get_dump_line('Serial')] #items << ['Not before', wrap.get_dump_line('Not before')] #items << ['Not after', wrap.get_dump_line('Not after')] items << ['Subject', wrap.get_dump_line('Subject')] items << ['Public key', wrap.get_dump_line('Public key')] items << ['Validity', wrap.get_dump_line('Validity')] (cert.extensions.sort { |a, b| a.oid <=> b.oid }).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 << ['Signature Algorithm', wrap.get_dump_line('Signature Algorithm')] 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_request(req) wrap = RequestDump.new(req) items = [] items << ['Version', wrap.get_dump_line('Version')] items << ['Signature Algorithm', wrap.get_dump_line('Signature Algorithm')] items << ['Subject', wrap.get_dump_line('Subject')] items << ['Public key', wrap.get_dump_line('Public key')] req.attributes.each do |attr| items << [attr.attr, wrap.get_dump_line(attr.oid)] end show_items(req, 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) when OpenSSL::X509::Request show_request(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 def show_request(request, tag) wrap = RequestDump.new(request) @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)) FXMenuCommand.new(tool_menu, "&Show Request\tCtl-R").connect(SEL_COMMAND, method(:on_cmd_tool_request)) 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 @cert_tree.show(@cert_store) show_item(nil) end def show_certs @cert_tree.show_certs(@cert_store) end def show_request(req) @cert_tree.show_request(req) 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) path = verify_certfile(certfile) show_certs # CRL could be change. show_verify_path(path) 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 on_cmd_tool_request(sender, sel, ptr) dialog = FXFileDialog.new(self, "Show request") dialog.filename = '' dialog.patternList = ["All Files (*)", "PEM formatted certificate (*.pem)"] if dialog.execute != 0 req = @cert_store.generate_cert(dialog.filename) show_request(req) end 1 end def verify_certfile(filename) begin cert = @cert_store.generate_cert(filename) result = @cert_store.verify(cert) @cert_store.scan_certs result 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 getopts nil, "cert:" certs_dir = ARGV.shift or raise "#{$0} cert_dir" certfile = $OPT_cert app = FXApp.new("CertStore", "FoxTest") cert_store = CertStore.new(certs_dir) w = CertStoreView.new(app, cert_store) app.create if certfile w.verify(certfile) end app.run