summaryrefslogtreecommitdiffstats
Commit message (Expand)AuthorAgeFilesLines
* list: add virDomainListAllSnapshots APIEric Blake2012-06-191-0/+2
* python: add API exports for virConnectListAllDomains()Peter Krempa2012-06-183-4/+70
* lib: Add public api to enable atomic listing of guestPeter Krempa2012-06-181-0/+1
* python: fix snapshot listing bugsEric Blake2012-06-121-17/+39
* python: use simpler methodsEric Blake2012-06-121-10/+4
* Coverity: Fix the forward_null error in Python binding codesv0.9.12-rc2v0.9.12Osier Yang2012-05-041-1/+1
* python: Fix doc directory name for stable releasesv0.9.12-rc1Cole Robinson2012-04-272-2/+2
* Fix compilation error on 32bitStefan Berger2012-04-101-0/+4
* python: improve conversion validationv0.9.11Eric Blake2012-03-311-7/+20
* python: make python APIs use these helper functionsv0.9.11-rc2Guannan Ren2012-03-282-71/+24
* python: Add new helper functions for python to C integral conversionGuannan Ren2012-03-282-0/+141
* Cleanup for a return statement in source filesMartin Kletzander2012-03-264-152/+152
* Add support for the suspend eventv0.9.11-rc1Osier Yang2012-03-232-0/+59
* Add support for the wakeup eventOsier Yang2012-03-232-0/+58
* Add support for event tray moved of removable disksOsier Yang2012-03-232-0/+60
* python: add virDomainGetCPUStats python binding APIGuannan Ren2012-03-223-1/+164
* python: Avoid memory leaks on libvirt_virNodeGetCPUStatsAlex Jia2012-03-221-10/+26
* python: Avoid memory leaks on libvirt_virNodeGetMemoryStatsAlex Jia2012-03-211-10/+26
* python: always include config.h firstEric Blake2012-03-201-1/+2
* python: Expose virDomain{G,S}etInterfaceParameters APIs in python bindingAlex Jia2012-02-162-0/+142
* python: make other APIs share common {get, set}PyVirTypedParameterv0.9.10Guannan Ren2012-02-102-604/+304
* python: refactoring virTypedParameter conversion for NUMA tuning APIsGuannan Ren2012-02-092-0/+364
* python: Correct arguments number for migrateSetMaxSpeedOsier Yang2012-02-091-2/+4
* python: drop unused functionv0.9.10-rc2Eric Blake2012-02-071-15/+1
* pyhton: Don't link against libvirt_util.laMichal Privoznik2012-02-071-2/+0
* maint: consolidate several .gitignore filesv0.9.10-rc1Eric Blake2012-02-031-13/+0
* Added missing memory reporting into python bindingsMartin Kletzander2012-02-031-0/+6
* python: use libvirt_util to avoid raw freeEric Blake2012-02-034-199/+193
* python: drop redundant functionEric Blake2012-02-033-16/+2
* build: clean up CPPFLAGS/INCLUDES usageEric Blake2012-02-031-2/+2
* python: Add binding for virDomainGetDiskErrorsJiri Denemark2012-02-012-0/+60
* virDomainGetDiskErrors public APIJiri Denemark2012-02-011-1/+2
* python: correct a copy-paste errorAlex Jia2012-02-011-1/+1
* Add new public API virDomainGetCPUStats()KAMEZAWA Hiroyuki2012-01-281-0/+1
* resize: add virStorageVolResize() APIZeeshan Ali (Khattak)2012-01-271-0/+1
* API: make declaration of _LAST enum values conditionalEric Blake2012-01-201-1/+2
* domiftune: Add API virDomain{S,G}etInterfaceParametersv0.9.9-rc2v0.9.9-rc1v0.9.9Hu Tao2011-12-291-0/+2
* remove a static limit on max domains in python bindingsDaniel Veillard2011-12-291-4/+23
* python: Fix problems of virDomain{Set, Get}BlockIoTune bindingsAlex Jia2011-12-292-4/+3
* add new API virDomain{G, S}etNumaParametersHu Tao2011-12-201-0/+2
* python: plug memory leak on libvirt_virConnectOpenAuthAlex Jia2011-12-191-0/+1
* python: Expose blockPeek and memoryPeek in Python bindingOsier Yang2011-12-153-0/+91
* python: Fix export of virDomainSnapshotListChildrenNamesPeter Krempa2011-12-131-0/+1
* python: Expose binding for virNodeGetMemoryStats()v0.9.8Peter Krempa2011-12-052-0/+55
* python: Expose binding for virNodeGetCPUStats()Peter Krempa2011-12-052-0/+54
* Support virDomain{Set, Get}BlockIoTune in the python APIv0.9.8-rc2v0.9.8-rc1Lei Li2011-11-303-3/+196
* Add new API virDomain{Set, Get}BlockIoTuneLei Li2011-11-291-0/+3
* python: Fix documentation of virStream recvv0.9.7Matthias Bolte2011-10-311-1/+1
* startupPolicy: Emit event on disk source droppingv0.9.7-rc1Michal Privoznik2011-10-252-0/+62
* Fix two comments related to error handlingPhilipp Hahn2011-10-171-1/+1
d='n665' href='#n665'>665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
#!/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
    <<EOS
Not before: #{not_before}
Not after: #{not_after}
EOS
  end

  def validity_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 'Signature Algorithm'
      signature_algorithm
    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 'Signature Algorithm'
      signature_algorithm_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 signature_algorithm
    @crl.signature_algorithm
  end

  def signature_algorithm_line
    signature_algorithm
  end

  def issuer
    name_text(@crl.issuer)
  end

  def issuer_line
    @crl.issuer.to_s
  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 RequestDump
  include CertDumpSupport

  def initialize(req)
    @req = req
  end

  def get_dump(tag)
    case tag
    when 'Version'
      version
    when 'Signature Algorithm'
      signature_algorithm
    when 'Subject'
      subject
    when 'Public key'
      public_key
    else
      attributes(tag)
    end
  end

  def get_dump_line(tag)
    case tag
    when 'Version'
      version_line
    when 'Signature Algorithm'
      signature_algorithm_line
    when 'Subject'
      subject_line
    when 'Public key'
      public_key_line
    else
      attributes_line(tag)
    end
  end

private

  def version
    "Version: #{@req.version + 1}"
  end

  def version_line
    version
  end

  def signature_algorithm
    @req.signature_algorithm
  end

  def signature_algorithm_line
    signature_algorithm
  end

  def subject
    name_text(@req.subject)
  end

  def subject_line
    @req.subject.to_s
  end

  def public_key
    @req.public_key.to_text
  end

  def public_key_line
    "#{@req.public_key.class} -- " << public_key.scan(/\A[^\n]*/)[0] << '...'
  end

  def attributes(tag)
    "(unknown)"
  end

  def attributes_line(tag)
    attributes(tag).tr("\r\n", '')
  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")
      @request_node = add_item_last(nil, "Request")
      @verify_path_node = add_item_last(nil, "Certification path")
      show_certs(cert_store)
    end

    def show_certs(cert_store)
      remove_items(@self_signed_ca_node)
      remove_items(@other_ca_node)
      remove_items(@ee_node)
      remove_items(@crl_node)
      remove_items(@request_node)
      import_certs(cert_store)
    end

    def show_request(req)
      node = add_item_last(@request_node, name_label(req.subject), req)
      @tree.selectItem(node)
      @observer.show_item(req)
    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
      cert_store.request.each do |req|
	add_item_last(@requestnode, name_label(req.subject), req)
      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

    def remove_items(node)
      while node.getNumChildren > 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