]> gitweb.fluxo.info Git - leap/leap_cli.git/commitdiff
added CSR ability (and vendored certificate_authority gem, so we can get the unreleas...
authorelijah <elijah@riseup.net>
Fri, 16 Nov 2012 22:30:20 +0000 (14:30 -0800)
committerelijah <elijah@riseup.net>
Fri, 16 Nov 2012 22:30:20 +0000 (14:30 -0800)
22 files changed:
Gemfile
README.md
lib/leap_cli/commands/ca.rb
lib/leap_cli/log.rb
lib/leap_cli/path.rb
lib/leap_cli/util.rb
lib/leap_cli/version.rb
test/leap_platform/provider_base/provider.json
vendor/certificate_authority/certificate_authority.gemspec [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/certificate.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/extensions.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/key_material.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/revocable.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/serial_number.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/signing_entity.rb [new file with mode: 0644]
vendor/certificate_authority/lib/certificate_authority/signing_request.rb [new file with mode: 0644]
vendor/certificate_authority/lib/tasks/certificate_authority.rake [new file with mode: 0644]

diff --git a/Gemfile b/Gemfile
index 7ee6bbf0a9c9aea3c2d6044a1a5ff73bece6728c..b0b375c20bb83c2c6745011abe78af8ef051c9dd 100644 (file)
--- a/Gemfile
+++ b/Gemfile
@@ -11,7 +11,8 @@ gemspec
 # :vendor_path - where this gem is vendored. this path is used if it exists and we are running in 'production mode'
 #
 development_gems = {
-  'supply_drop' => {:dev_path => '../gems/supply_drop', :vendor_path => 'vendor/supply_drop'}
+  'supply_drop' => {:dev_path => '../gems/supply_drop', :vendor_path => 'vendor/supply_drop'},
+  'certificate_authority' => {:dev_path => '../gems/certificate_authority', :vendor_path => 'vendor/certificate_authority'}
 }
 
 #
index 59767109724a3be9a674ae5d198c0035676a383a..fc1acdd5fc897b02f59a961e05460c9120a6c46a 100644 (file)
--- a/README.md
+++ b/README.md
@@ -157,19 +157,31 @@ Ubuntu
 Install from git
 --------------------------------------
 
-Download the source and install the required gems:
+Download the source:
 
     git clone git://leap.se/leap_cli      # clone leap_cli code
     cd leap_cli
-    bundle                                # install required gems
+
+Running as a gem
+--------------------------------------
+
+To install ``leap`` as a gem, do this:
+
+    cd leap_cli
+    rake build
+    rake install
+
+And then make sure your PATH is set to include where leap is installed.
+It should warn you if this is not the case.
 
 Running from the source directory
 --------------------------------------
 
-To run the ``leap`` command directly from the source tree, symlink bin/leap
-into your path:
+To run the ``leap`` command directly from the source tree, you need to install
+the required gems using ``bundle`` and symlink ``bin/leap`` into your path:
 
     cd leap_cli
+    bundle                        # install required gems
     ln -s `pwd`/bin/leap ~/bin    # link executable somewhere in your bin path
     which leap                    # make sure you will run leap_cli/bin/leap
     leap help
@@ -183,15 +195,3 @@ other places, it is easier to create the symlink. If you run ``leap`` directly,
 the command launcher that rubygems installs, leap will run in a mode that simulates
 ``bundle exec leap`` (i.e. only gems included in Gemfile are allow to be loaded).
 
-Running as a gem
---------------------------------------
-
-To install ``leap`` as a gem, do this:
-
-    cd leap_cli
-    rake build
-    rake install
-
-And then make sure your PATH is set to include where leap is installed.
-It should warn you if this is not the case.
-
index 5b556a32facd49ab5c5e291444fbf7919c0c5c8b..1763ba3800266bad986d3778d6f6d4ff376030e4 100644 (file)
@@ -50,6 +50,7 @@ module LeapCli; module Commands
     c.action do |global_options,options,args|
       assert_files_exist! :ca_cert, :ca_key, :msg => 'Run init-ca to create them'
       assert_config! 'provider.ca.server_certificates.bit_size'
+      assert_config! 'provider.ca.server_certificates.digest'
       assert_config! 'provider.ca.server_certificates.life_span'
       assert_config! 'common.x509.use'
 
@@ -82,6 +83,72 @@ module LeapCli; module Commands
     end
   end
 
+  #
+  # hints:
+  #
+  # inspect CSR:
+  #   openssl req -noout -text -in files/cert/x.csr
+  #
+  # generate CSR with openssl to see how it compares:
+  #   openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr
+  #
+  # validate a CSR:
+  #   http://certlogik.com/decoder/
+  #
+  # nice details about CSRs:
+  #   http://www.redkestrel.co.uk/Articles/CSR.html
+  #
+  desc 'Creates a Certificate Signing Request for use in purchasing a commercial x509 certificate'
+  command :'init-csr' do |c|
+    c.switch 'sign', :desc => 'additionally creates a cert that is signed by your own CA (recommended only for testing)', :negatable => false
+    c.action do |global_options,options,args|
+      assert_config! 'provider.domain'
+      assert_config! 'provider.name'
+      assert_config! 'provider.default_language'
+      assert_config! 'provider.ca.server_certificates.bit_size'
+      assert_config! 'provider.ca.server_certificates.digest'
+      assert_files_missing! [:commercial_key, manager.provider.domain], [:commercial_csr, manager.provider.domain], :msg => 'If you really want to create a new key and CSR, remove these files first.'
+      if options[:sign]
+        assert_files_exist! :ca_cert, :ca_key, :msg => 'Run init-ca to create them'
+      end
+
+      # RSA key
+      keypair = CertificateAuthority::MemoryKeyMaterial.new
+      log :generating, "%s bit RSA key" % manager.provider.ca.server_certificates.bit_size do
+        keypair.generate_key(manager.provider.ca.server_certificates.bit_size)
+        write_file! [:commercial_key, manager.provider.domain], keypair.private_key.to_pem
+      end
+
+      # CSR
+      dn  = CertificateAuthority::DistinguishedName.new
+      csr = CertificateAuthority::SigningRequest.new
+      dn.common_name = manager.provider.domain
+      dn.organization = manager.provider.name[manager.provider.default_language]
+      log :generating, "CSR with commonName => '%s', organization => '%s'" % [dn.common_name, dn.organization] do
+        csr.distinguished_name = dn
+        csr.key_material = keypair
+        csr.digest = manager.provider.ca.server_certificates.digest
+        request = csr.to_x509_csr
+        write_file! [:commercial_csr, manager.provider.domain], csr.to_pem
+      end
+
+      # Sign using our own CA, for use in testing but hopefully not production.
+      # It is not that commerical CAs are so secure, it is just that signing your own certs is
+      # a total drag for the user because they must click through dire warnings.
+      if options[:sign]
+        log :generating, "x509 server certificate for testing purposes" do
+          cert = csr.to_cert
+          cert.serial_number.number = cert_serial_number(manager.provider.domain)
+          cert.not_before = today
+          cert.not_after  = years_from_today(1)
+          cert.parent = ca_root
+          cert.sign! test_cert_signing_profile
+          write_file! [:commercial_cert, manager.provider.domain], cert.to_pem
+        end
+      end
+    end
+  end
+
   private
 
   def cert_needs_updating?(node)
@@ -182,11 +249,11 @@ module LeapCli; module Commands
   # for keyusage, openvpn server certs can have keyEncipherment or keyAgreement. I am not sure which is preferable.
   # going with keyAgreement for now.
   #
-  # digest options: SHA512, SHA1
+  # digest options: SHA512, SHA256, SHA1
   #
   def server_signing_profile(node)
     {
-      "digest" => "SHA256",
+      "digest" => manager.provider.ca.server_certificates.digest,
       "extensions" => {
         "keyUsage" => {
           "usage" => ["digitalSignature", "keyAgreement"]
@@ -202,6 +269,25 @@ module LeapCli; module Commands
     }
   end
 
+  #
+  # This is used when signing the main cert for the provider's domain
+  # with our own CA (for testing purposes). Typically, this cert would
+  # be purchased from a commercial CA, and not signed this way.
+  #
+  def test_cert_signing_profile
+    {
+      "digest" => "SHA256",
+      "extensions" => {
+        "keyUsage" => {
+          "usage" => ["digitalSignature", "keyAgreement"]
+        },
+        "extendedKeyUsage" => {
+          "usage" => ["serverAuth"]
+        }
+      }
+    }
+  end
+
   def dns_names_for_node(node)
     names = [node.domain.internal]
     if node['dns'] && node.dns['aliases'] && node.dns.aliases.any?
index 1cc1c6a6673bc81caf6980c6b6453cea093147f6..0821177caab4d1a61ab4b861441672968abf1e89 100644 (file)
@@ -8,6 +8,12 @@ module LeapCli
   def log_level=(value)
     @log_level = value
   end
+  def indent_level
+    @indent_level ||= 0
+  end
+  def indent_level=(value)
+    @indent_level = value
+  end
 end
 
 ##
@@ -34,7 +40,8 @@ def log(*args)
   level   = args.grep(Integer).first || 1
   title   = args.grep(Symbol).first
   message = args.grep(String).first
-  options = args.grep(Hash).first || {:indent => 0}
+  options = args.grep(Hash).first || {}
+  options[:indent] ||= LeapCli.indent_level
   if message && LeapCli.log_level >= level
     print "  " * (options[:indent]+1)
     if options[:indent] > 0
@@ -66,5 +73,10 @@ def log(*args)
       end
     end
     puts "#{message}"
+    if block_given?
+      LeapCli.indent_level += 1
+      yield
+      LeapCli.indent_level -= 1
+    end
   end
 end
index bf4c89f394512866086d7b0205a96ed4db620a20..de01fdb8754287d261e16eb0c5e41b05f5514106 100644 (file)
@@ -34,6 +34,9 @@ module LeapCli; module Path
     :ca_key           => 'files/ca/ca.key',
     :ca_cert          => 'files/ca/ca.crt',
     :dh_params        => 'files/ca/dh.pem',
+    :commercial_key   => 'files/cert/#{arg}.key',
+    :commercial_csr   => 'files/cert/#{arg}.csr',
+    :commercial_cert  => 'files/cert/#{arg}.crt',
     :node_x509_key    => 'files/nodes/#{arg}/#{arg}.key',
     :node_x509_cert   => 'files/nodes/#{arg}/#{arg}.crt',
     :vagrantfile      => 'test/Vagrantfile'
index 9b0489412fe5bd84295b6fcbe04b574e1ed61f8a..0b0fb9eb54d31810c5694862f066ea4d31d5f872 100644 (file)
@@ -55,7 +55,7 @@ module LeapCli
     #
     def assert_bin!(cmd_name)
       assert! `which #{cmd_name}`.strip.any? do
-        log 0, :missing, "command '%s'" % cmd_name
+        log :missing, "command '%s'" % cmd_name
       end
     end
 
@@ -68,9 +68,9 @@ module LeapCli
       output = `#{cmd}`
       unless $?.success?
         bail! do
-          log 0, :run, cmd
-          log 0, :failed, "(exit #{$?.exitstatus}) #{output}", :indent => 1
-          log 0, message, :indent => 1 if message
+          log :run, cmd
+          log :failed, "(exit #{$?.exitstatus}) #{output}", :indent => 1
+          log message, :indent => 1 if message
         end
       else
         log 2, :ran, cmd
@@ -86,13 +86,13 @@ module LeapCli
       }.compact
       if file_list.length > 1
         bail! do
-          log 0, :error, "Sorry, we can't continue because these files already exist: #{file_list.join(', ')}."
-          log 0, options[:msg] if options[:msg]
+          log :error, "Sorry, we can't continue because these files already exist: #{file_list.join(', ')}."
+          log options[:msg] if options[:msg]
         end
       elsif file_list.length == 1
         bail! do
-          log 0, :error, "Sorry, we can't continue because this file already exists: #{file_list.first}."
-          log 0, options[:msg] if options[:msg]
+          log :error, "Sorry, we can't continue because this file already exists: #{file_list.first}."
+          log options[:msg] if options[:msg]
         end
       end
     end
@@ -104,8 +104,8 @@ module LeapCli
       rescue NoMethodError
       rescue NameError
       end
-      assert! !value.nil? do
-        log 0, :missing, "configuration value for #{conf_path}"
+      assert! !value.nil? && value != "REQUIRED" do
+        log :missing, "required configuration value for #{conf_path}"
       end
     end
 
@@ -117,13 +117,13 @@ module LeapCli
       }.compact
       if file_list.length > 1
         bail! do
-          log 0, :missing, "these files: #{file_list.join(', ')}"
-          log 0, options[:msg] if options[:msg]
+          log :missing, "these files: #{file_list.join(', ')}"
+          log options[:msg] if options[:msg]
         end
       elsif file_list.length == 1
         bail! do
-          log 0, :missing, "file #{file_list.first}"
-          log 0, options[:msg] if options[:msg]
+          log :missing, "file #{file_list.first}"
+          log options[:msg] if options[:msg]
         end
       end
     end
index 83c2159bc96b97c4950b747e9352d4c84ae553b0..0dbd215208c8d1e6c9a7d334e543dc6ad3232cb6 100644 (file)
@@ -3,6 +3,6 @@ module LeapCli
     VERSION = '0.1.3'
     SUMMARY = 'Command line interface to the LEAP platform'
     DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
-    REQUIRE_PATHS = ['lib', 'vendor/supply_drop/lib']
+    REQUIRE_PATHS = ['lib', 'vendor/supply_drop/lib', 'vendor/certificate_authority/lib']
   end
 end
index a144d04e01d689ce0129a1b073a22a3638cbc0dd..de5ad446d7299e6e64537d91ba9ca256463a063a 100644 (file)
     "organization": "= global.provider.name[global.provider.default_language]",
     "organizational_unit": "= 'https://' + global.common.domain.full_suffix",
     "bit_size": 4096,
+    "digest": "SHA256",
     "life_span": "10y",
     "server_certificates": {
       "bit_size": 3248,
+      "digest": "SHA256",
       "life_span": "1y"
     }
   },
   "vagrant":{
     "network":"10.5.5.0/24"
   }
-}
\ No newline at end of file
+}
diff --git a/vendor/certificate_authority/certificate_authority.gemspec b/vendor/certificate_authority/certificate_authority.gemspec
new file mode 100644 (file)
index 0000000..be8cd91
--- /dev/null
@@ -0,0 +1,88 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+  s.name = "certificate_authority"
+  s.version = "0.2.0"
+
+  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+  s.authors = ["Chris Chandler"]
+  s.date = "2012-09-16"
+  s.email = "chris@flatterline.com"
+  s.extra_rdoc_files = [
+    "README.rdoc"
+  ]
+  s.files = [
+    "Gemfile",
+    "Gemfile.lock",
+    "README.rdoc",
+    "Rakefile",
+    "VERSION.yml",
+    "certificate_authority.gemspec",
+    "lib/certificate_authority.rb",
+    "lib/certificate_authority/certificate.rb",
+    "lib/certificate_authority/certificate_revocation_list.rb",
+    "lib/certificate_authority/distinguished_name.rb",
+    "lib/certificate_authority/extensions.rb",
+    "lib/certificate_authority/key_material.rb",
+    "lib/certificate_authority/ocsp_handler.rb",
+    "lib/certificate_authority/pkcs11_key_material.rb",
+    "lib/certificate_authority/revocable.rb",
+    "lib/certificate_authority/serial_number.rb",
+    "lib/certificate_authority/signing_entity.rb",
+    "lib/certificate_authority/signing_request.rb",
+    "lib/tasks/certificate_authority.rake",
+    "spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem",
+    "spec/samples/certs/apple_wwdr_issued_cert.pem",
+    "spec/samples/certs/apple_wwdr_issuer.pem",
+    "spec/samples/certs/ca.crt",
+    "spec/samples/certs/ca.key",
+    "spec/samples/certs/client.crt",
+    "spec/samples/certs/client.csr",
+    "spec/samples/certs/client.key",
+    "spec/samples/certs/github.com.pem",
+    "spec/samples/certs/server.crt",
+    "spec/samples/certs/server.csr",
+    "spec/samples/certs/server.key",
+    "spec/spec_helper.rb",
+    "spec/units/certificate_authority_spec.rb",
+    "spec/units/certificate_revocation_list_spec.rb",
+    "spec/units/certificate_spec.rb",
+    "spec/units/distinguished_name_spec.rb",
+    "spec/units/extensions_spec.rb",
+    "spec/units/key_material_spec.rb",
+    "spec/units/ocsp_handler_spec.rb",
+    "spec/units/pkcs11_key_material_spec.rb",
+    "spec/units/serial_number_spec.rb",
+    "spec/units/signing_entity_spec.rb",
+    "spec/units/signing_request_spec.rb",
+    "spec/units/units_helper.rb",
+    "spec/units/working_with_openssl_spec.rb"
+  ]
+  s.homepage = "http://github.com/cchandler/certificate_authority"
+  s.licenses = ["MIT"]
+  s.require_paths = ["lib"]
+  s.rubygems_version = "1.8.15"
+  s.summary = "Ruby gem for managing the core functions outlined in RFC-3280 for PKI"
+
+  if s.respond_to? :specification_version then
+    s.specification_version = 3
+
+    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
+      s.add_runtime_dependency(%q<activemodel>, [">= 3.0.6"])
+      s.add_development_dependency(%q<rspec>, [">= 0"])
+      s.add_development_dependency(%q<jeweler>, [">= 1.5.2"])
+    else
+      s.add_dependency(%q<activemodel>, [">= 3.0.6"])
+      s.add_dependency(%q<rspec>, [">= 0"])
+      s.add_dependency(%q<jeweler>, [">= 1.5.2"])
+    end
+  else
+    s.add_dependency(%q<activemodel>, [">= 3.0.6"])
+    s.add_dependency(%q<rspec>, [">= 0"])
+    s.add_dependency(%q<jeweler>, [">= 1.5.2"])
+  end
+end
+
diff --git a/vendor/certificate_authority/lib/certificate_authority.rb b/vendor/certificate_authority/lib/certificate_authority.rb
new file mode 100644 (file)
index 0000000..a697c1b
--- /dev/null
@@ -0,0 +1,21 @@
+$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+
+#Exterior requirements
+require 'openssl'
+require 'active_model'
+
+#Internal modules
+require 'certificate_authority/signing_entity'
+require 'certificate_authority/revocable'
+require 'certificate_authority/distinguished_name'
+require 'certificate_authority/serial_number'
+require 'certificate_authority/key_material'
+require 'certificate_authority/pkcs11_key_material'
+require 'certificate_authority/extensions'
+require 'certificate_authority/certificate'
+require 'certificate_authority/certificate_revocation_list'
+require 'certificate_authority/ocsp_handler'
+require 'certificate_authority/signing_request'
+
+module CertificateAuthority
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate.rb b/vendor/certificate_authority/lib/certificate_authority/certificate.rb
new file mode 100644 (file)
index 0000000..ca8bc7c
--- /dev/null
@@ -0,0 +1,200 @@
+module CertificateAuthority
+  class Certificate
+    include ActiveModel::Validations
+    include Revocable
+
+    attr_accessor :distinguished_name
+    attr_accessor :serial_number
+    attr_accessor :key_material
+    attr_accessor :not_before
+    attr_accessor :not_after
+    attr_accessor :extensions
+    attr_accessor :openssl_body
+
+    alias :subject :distinguished_name #Same thing as the DN
+
+    attr_accessor :parent
+
+    validate do |certificate|
+      errors.add :base, "Distinguished name must be valid" unless distinguished_name.valid?
+      errors.add :base, "Key material must be valid" unless key_material.valid?
+      errors.add :base, "Serial number must be valid" unless serial_number.valid?
+      errors.add :base, "Extensions must be valid" unless extensions.each do |item|
+        unless item.respond_to?(:valid?)
+          true
+        else
+          item.valid?
+        end
+      end
+    end
+
+    def initialize
+      self.distinguished_name = DistinguishedName.new
+      self.serial_number = SerialNumber.new
+      self.key_material = MemoryKeyMaterial.new
+      self.not_before = Time.now
+      self.not_after = Time.now + 60 * 60 * 24 * 365 #One year
+      self.parent = self
+      self.extensions = load_extensions()
+
+      self.signing_entity = false
+
+    end
+
+    def sign!(signing_profile={})
+      raise "Invalid certificate #{self.errors.full_messages}" unless valid?
+      merge_profile_with_extensions(signing_profile)
+
+      openssl_cert = OpenSSL::X509::Certificate.new
+      openssl_cert.version    = 2
+      openssl_cert.not_before = self.not_before
+      openssl_cert.not_after = self.not_after
+      openssl_cert.public_key = self.key_material.public_key
+
+      openssl_cert.serial = self.serial_number.number
+
+      openssl_cert.subject = self.distinguished_name.to_x509_name
+      openssl_cert.issuer = parent.distinguished_name.to_x509_name
+
+      require 'tempfile'
+      t = Tempfile.new("bullshit_conf")
+      # t = File.new("/tmp/openssl.cnf")
+      ## The config requires a file even though we won't use it
+      openssl_config = OpenSSL::Config.new(t.path)
+
+      factory = OpenSSL::X509::ExtensionFactory.new
+      factory.subject_certificate = openssl_cert
+
+      #NB: If the parent doesn't have an SSL body we're making this a self-signed cert
+      if parent.openssl_body.nil?
+        factory.issuer_certificate = openssl_cert
+      else
+        factory.issuer_certificate = parent.openssl_body
+      end
+
+      self.extensions.keys.each do |k|
+        config_extensions = extensions[k].config_extensions
+        openssl_config = merge_options(openssl_config,config_extensions)
+      end
+
+      # p openssl_config.sections
+
+      factory.config = openssl_config
+
+      # Order matters: e.g. for self-signed, subjectKeyIdentifier must come before authorityKeyIdentifier
+      self.extensions.keys.sort{|a,b| b<=>a}.each do |k|
+        e = extensions[k]
+        next if e.to_s.nil? or e.to_s == "" ## If the extension returns an empty string we won't include it
+        ext = factory.create_ext(e.openssl_identifier, e.to_s)
+        openssl_cert.add_extension(ext)
+      end
+
+      if signing_profile["digest"].nil?
+        digest = OpenSSL::Digest::Digest.new("SHA512")
+      else
+        digest = OpenSSL::Digest::Digest.new(signing_profile["digest"])
+      end
+      self.openssl_body = openssl_cert.sign(parent.key_material.private_key,digest)
+      t.close! if t.is_a?(Tempfile)# We can get rid of the ridiculous temp file
+      self.openssl_body
+    end
+
+    def is_signing_entity?
+      self.extensions["basicConstraints"].ca
+    end
+
+    def signing_entity=(signing)
+      self.extensions["basicConstraints"].ca = signing
+    end
+
+    def revoked?
+      !self.revoked_at.nil?
+    end
+
+    def to_pem
+      raise "Certificate has no signed body" if self.openssl_body.nil?
+      self.openssl_body.to_pem
+    end
+
+    def is_root_entity?
+      self.parent == self && is_signing_entity?
+    end
+
+    def is_intermediate_entity?
+      (self.parent != self) && is_signing_entity?
+    end
+
+    private
+
+    def merge_profile_with_extensions(signing_profile={})
+      return self.extensions if signing_profile["extensions"].nil?
+      signing_config = signing_profile["extensions"]
+      signing_config.keys.each do |k|
+        extension = self.extensions[k]
+        items = signing_config[k]
+        items.keys.each do |profile_item_key|
+          if extension.respond_to?("#{profile_item_key}=".to_sym)
+            extension.send("#{profile_item_key}=".to_sym, items[profile_item_key] )
+          else
+            p "Tried applying '#{profile_item_key}' to #{extension.class} but it doesn't respond!"
+          end
+        end
+      end
+    end
+
+    def load_extensions
+      extension_hash = {}
+
+      temp_extensions = []
+      basic_constraints = CertificateAuthority::Extensions::BasicConstraints.new
+      temp_extensions << basic_constraints
+      crl_distribution_points = CertificateAuthority::Extensions::CrlDistributionPoints.new
+      temp_extensions << crl_distribution_points
+      subject_key_identifier = CertificateAuthority::Extensions::SubjectKeyIdentifier.new
+      temp_extensions << subject_key_identifier
+      authority_key_identifier = CertificateAuthority::Extensions::AuthorityKeyIdentifier.new
+      temp_extensions << authority_key_identifier
+      authority_info_access = CertificateAuthority::Extensions::AuthorityInfoAccess.new
+      temp_extensions << authority_info_access
+      key_usage = CertificateAuthority::Extensions::KeyUsage.new
+      temp_extensions << key_usage
+      extended_key_usage = CertificateAuthority::Extensions::ExtendedKeyUsage.new
+      temp_extensions << extended_key_usage
+      subject_alternative_name = CertificateAuthority::Extensions::SubjectAlternativeName.new
+      temp_extensions << subject_alternative_name
+      certificate_policies = CertificateAuthority::Extensions::CertificatePolicies.new
+      temp_extensions << certificate_policies
+
+      temp_extensions.each do |extension|
+        extension_hash[extension.openssl_identifier] = extension
+      end
+
+      extension_hash
+    end
+
+    def merge_options(config,hash)
+      hash.keys.each do |k|
+        config[k] = hash[k]
+      end
+      config
+    end
+
+    def self.from_openssl openssl_cert
+      unless openssl_cert.is_a? OpenSSL::X509::Certificate
+        raise "Can only construct from an OpenSSL::X509::Certificate"
+      end
+
+      certificate = Certificate.new
+      # Only subject, key_material, and body are used for signing
+      certificate.distinguished_name = DistinguishedName.from_openssl openssl_cert.subject
+      certificate.key_material.public_key = openssl_cert.public_key
+      certificate.openssl_body = openssl_cert
+      certificate.serial_number.number = openssl_cert.serial.to_i
+      certificate.not_before = openssl_cert.not_before
+      certificate.not_after = openssl_cert.not_after
+      # TODO extensions
+      certificate
+    end
+
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb b/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb
new file mode 100644 (file)
index 0000000..e222e26
--- /dev/null
@@ -0,0 +1,77 @@
+module CertificateAuthority
+  class CertificateRevocationList
+    include ActiveModel::Validations
+
+    attr_accessor :certificates
+    attr_accessor :parent
+    attr_accessor :crl_body
+    attr_accessor :next_update
+
+    validate do |crl|
+      errors.add :next_update, "Next update must be a positive value" if crl.next_update < 0
+      errors.add :parent, "A parent entity must be set" if crl.parent.nil?
+    end
+
+    def initialize
+      self.certificates = []
+      self.next_update = 60 * 60 * 4 # 4 hour default
+    end
+
+    def <<(revocable)
+      case revocable
+      when Revocable
+        raise "Only revoked entities can be added to a CRL" unless revocable.revoked?
+        self.certificates << revocable
+      when OpenSSL::X509::Certificate
+        raise "Not implemented yet"
+      else
+        raise "#{revocable.class} cannot be included in a CRL"
+      end
+    end
+
+    def sign!(signing_profile={})
+      raise "No parent entity has been set!" if self.parent.nil?
+      raise "Invalid CRL" unless self.valid?
+
+      revocations = self.certificates.collect do |revocable|
+        revocation = OpenSSL::X509::Revoked.new
+
+        ## We really just need a serial number, now we have to dig it out
+        case revocable
+        when Certificate
+          x509_cert = OpenSSL::X509::Certificate.new(revocable.to_pem)
+          revocation.serial = x509_cert.serial
+        when SerialNumber
+          revocation.serial = revocable.number
+        end
+        revocation.time = revocable.revoked_at
+        revocation
+      end
+
+      crl = OpenSSL::X509::CRL.new
+      revocations.each do |revocation|
+        crl.add_revoked(revocation)
+      end
+
+      crl.version = 1
+      crl.last_update = Time.now
+      crl.next_update = Time.now + self.next_update
+
+      signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem)
+      if signing_profile["digest"].nil?
+        digest = OpenSSL::Digest::Digest.new("SHA512")
+      else
+        digest = OpenSSL::Digest::Digest.new(signing_profile["digest"])
+      end
+      crl.issuer = signing_cert.subject
+      self.crl_body = crl.sign(self.parent.key_material.private_key, digest)
+
+      self.crl_body
+    end
+
+    def to_pem
+      raise "No signed CRL body" if self.crl_body.nil?
+      self.crl_body.to_pem
+    end
+  end#CertificateRevocationList
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb b/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb
new file mode 100644 (file)
index 0000000..165fe29
--- /dev/null
@@ -0,0 +1,97 @@
+module CertificateAuthority
+  class DistinguishedName
+    include ActiveModel::Validations
+
+    validates_presence_of :common_name
+
+    attr_accessor :common_name
+    alias :cn :common_name
+    alias :cn= :common_name=
+
+    attr_accessor :locality
+    alias :l :locality
+    alias :l= :locality=
+
+    attr_accessor :state
+    alias :s :state
+    alias :st= :state=
+
+    attr_accessor :country
+    alias :c :country
+    alias :c= :country=
+
+    attr_accessor :organization
+    alias :o :organization
+    alias :o= :organization=
+
+    attr_accessor :organizational_unit
+    alias :ou :organizational_unit
+    alias :ou= :organizational_unit=
+
+    attr_accessor :email_address
+    alias :emailAddress :email_address
+    alias :emailAddress= :email_address=
+
+    def to_x509_name
+      raise "Invalid Distinguished Name" unless valid?
+
+      # NB: the capitalization in the strings counts
+      name = OpenSSL::X509::Name.new
+      name.add_entry("C", country) unless country.blank?
+      name.add_entry("ST", state) unless state.blank?
+      name.add_entry("L", locality) unless locality.blank?
+      name.add_entry("O", organization) unless organization.blank?
+      name.add_entry("OU", organizational_unit) unless organizational_unit.blank?
+      name.add_entry("CN", common_name)
+      name.add_entry("emailAddress", email_address) unless email_address.blank?
+      name
+    end
+
+    def ==(other)
+      # Use the established OpenSSL comparison
+      self.to_x509_name() == other.to_x509_name()
+    end
+
+    def self.from_openssl openssl_name
+      unless openssl_name.is_a? OpenSSL::X509::Name
+        raise "Argument must be a OpenSSL::X509::Name"
+      end
+
+      WrappedDistinguishedName.new(openssl_name)
+    end
+  end
+
+  ## This is a significantly more complicated case. It's possible that
+  ## generically handled certificates will include custom OIDs in the
+  ## subject.
+  class WrappedDistinguishedName < DistinguishedName
+    attr_accessor :x509_name
+
+    def initialize(x509_name)
+      @x509_name = x509_name
+
+      subject = @x509_name.to_a
+      subject.each do |element|
+        field = element[0].downcase
+        value = element[1]
+        #type = element[2] ## -not used
+        method_sym = "#{field}=".to_sym
+        if self.respond_to?(method_sym)
+          self.send("#{field}=",value)
+        else
+          ## Custom OID
+          @custom_oids = true
+        end
+      end
+
+    end
+
+    def to_x509_name
+      @x509_name
+    end
+
+    def custom_oids?
+      @custom_oids
+    end
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/extensions.rb b/vendor/certificate_authority/lib/certificate_authority/extensions.rb
new file mode 100644 (file)
index 0000000..e5a8e85
--- /dev/null
@@ -0,0 +1,266 @@
+module CertificateAuthority
+  module Extensions
+    module ExtensionAPI
+      def to_s
+        raise "Implementation required"
+      end
+
+      def config_extensions
+        {}
+      end
+
+      def openssl_identifier
+        raise "Implementation required"
+      end
+    end
+
+    class BasicConstraints
+      include ExtensionAPI
+      include ActiveModel::Validations
+      attr_accessor :ca
+      attr_accessor :path_len
+      validates :ca, :inclusion => [true,false]
+
+      def initialize
+        self.ca = false
+      end
+
+      def is_ca?
+        self.ca
+      end
+
+      def path_len=(value)
+        raise "path_len must be a non-negative integer" if value < 0 or !value.is_a?(Fixnum)
+        @path_len = value
+      end
+
+      def openssl_identifier
+        "basicConstraints"
+      end
+
+      def to_s
+        result = ""
+        result += "CA:#{self.ca}"
+        result += ",pathlen:#{self.path_len}" unless self.path_len.nil?
+        result
+      end
+    end
+
+    class CrlDistributionPoints
+      include ExtensionAPI
+
+      attr_accessor :uri
+
+      def initialize
+        # self.uri = "http://moo.crlendPoint.example.com/something.crl"
+      end
+
+      def openssl_identifier
+        "crlDistributionPoints"
+      end
+
+      ## NB: At this time it seems OpenSSL's extension handlers don't support
+      ## any of the config options the docs claim to support... everything comes back
+      ## "missing value" on GENERAL NAME. Even if copied verbatim
+      def config_extensions
+        {
+          # "custom_crl_fields" => {"fullname" => "URI:#{fullname}"},
+          # "issuer_sect" => {"CN" => "crlissuer.com", "C" => "US", "O" => "shudder"}
+        }
+      end
+
+      def to_s
+        return "" if self.uri.nil?
+        "URI:#{self.uri}"
+      end
+    end
+
+    class SubjectKeyIdentifier
+      include ExtensionAPI
+      def openssl_identifier
+        "subjectKeyIdentifier"
+      end
+
+      def to_s
+        "hash"
+      end
+    end
+
+    class AuthorityKeyIdentifier
+      include ExtensionAPI
+
+      def openssl_identifier
+        "authorityKeyIdentifier"
+      end
+
+      def to_s
+        "keyid,issuer"
+      end
+    end
+
+    class AuthorityInfoAccess
+      include ExtensionAPI
+
+      attr_accessor :ocsp
+
+      def initialize
+        self.ocsp = []
+      end
+
+      def openssl_identifier
+        "authorityInfoAccess"
+      end
+
+      def to_s
+        return "" if self.ocsp.empty?
+        "OCSP;URI:#{self.ocsp}"
+      end
+    end
+
+    class KeyUsage
+      include ExtensionAPI
+
+      attr_accessor :usage
+
+      def initialize
+        self.usage = ["digitalSignature", "nonRepudiation"]
+      end
+
+      def openssl_identifier
+        "keyUsage"
+      end
+
+      def to_s
+        "#{self.usage.join(',')}"
+      end
+    end
+
+    class ExtendedKeyUsage
+      include ExtensionAPI
+
+      attr_accessor :usage
+
+      def initialize
+        self.usage = ["serverAuth","clientAuth"]
+      end
+
+      def openssl_identifier
+        "extendedKeyUsage"
+      end
+
+      def to_s
+        "#{self.usage.join(',')}"
+      end
+    end
+
+    class SubjectAlternativeName
+      include ExtensionAPI
+
+      attr_accessor :uris, :dns_names, :ips
+
+      def initialize
+        self.uris = []
+        self.dns_names = []
+        self.ips = []
+      end
+
+      def uris=(value)
+        raise "URIs must be an array" unless value.is_a?(Array)
+        @uris = value
+      end
+
+      def dns_names=(value)
+        raise "DNS names must be an array" unless value.is_a?(Array)
+        @dns_names = value
+      end
+
+      def ips=(value)
+        raise "IPs must be an array" unless value.is_a?(Array)
+        @ips = value
+      end
+
+      def openssl_identifier
+        "subjectAltName"
+      end
+
+      def to_s
+        res =  self.uris.map {|u| "URI:#{u}" }
+        res += self.dns_names.map {|d| "DNS:#{d}" }
+        res += self.ips.map {|i| "IP:#{i}" }
+
+        return res.join(',')
+      end
+    end
+
+    class CertificatePolicies
+      include ExtensionAPI
+
+      attr_accessor :policy_identifier
+      attr_accessor :cps_uris
+      ##User notice
+      attr_accessor :explicit_text
+      attr_accessor :organization
+      attr_accessor :notice_numbers
+
+      def initialize
+        @contains_data = false
+      end
+
+
+      def openssl_identifier
+        "certificatePolicies"
+      end
+
+      def user_notice=(value={})
+        value.keys.each do |key|
+          self.send("#{key}=".to_sym, value[key])
+        end
+      end
+
+      def config_extensions
+        config_extension = {}
+        custom_policies = {}
+        notice = {}
+        unless self.policy_identifier.nil?
+          custom_policies["policyIdentifier"] = self.policy_identifier
+        end
+
+        if !self.cps_uris.nil? and self.cps_uris.is_a?(Array)
+          self.cps_uris.each_with_index do |cps_uri,i|
+            custom_policies["CPS.#{i}"] = cps_uri
+          end
+        end
+
+        unless self.explicit_text.nil?
+          notice["explicitText"] = self.explicit_text
+        end
+
+        unless self.organization.nil?
+          notice["organization"] = self.organization
+        end
+
+        unless self.notice_numbers.nil?
+          notice["noticeNumbers"] = self.notice_numbers
+        end
+
+        if notice.keys.size > 0
+          custom_policies["userNotice.1"] = "@notice"
+          config_extension["notice"] = notice
+        end
+
+        if custom_policies.keys.size > 0
+          config_extension["custom_policies"] = custom_policies
+          @contains_data = true
+        end
+
+        config_extension
+      end
+
+      def to_s
+        return "" unless @contains_data
+        "ia5org,@custom_policies"
+      end
+    end
+
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/key_material.rb b/vendor/certificate_authority/lib/certificate_authority/key_material.rb
new file mode 100644 (file)
index 0000000..75ec62e
--- /dev/null
@@ -0,0 +1,148 @@
+module CertificateAuthority
+  module KeyMaterial
+    def public_key
+      raise "Required implementation"
+    end
+
+    def private_key
+      raise "Required implementation"
+    end
+
+    def is_in_hardware?
+      raise "Required implementation"
+    end
+
+    def is_in_memory?
+      raise "Required implementation"
+    end
+
+    def self.from_x509_key_pair(pair,password=nil)
+      if password.nil?
+        key = OpenSSL::PKey::RSA.new(pair)
+      else
+        key = OpenSSL::PKey::RSA.new(pair,password)
+      end
+      mem_key = MemoryKeyMaterial.new
+      mem_key.public_key = key.public_key
+      mem_key.private_key = key
+      mem_key
+    end
+
+    def self.from_x509_public_key(public_key_pem)
+      key = OpenSSL::PKey::RSA.new(public_key_pem)
+      signing_request_key = SigningRequestKeyMaterial.new
+      signing_request_key.public_key = key.public_key
+      signing_request_key
+    end
+  end
+
+  class MemoryKeyMaterial
+    include KeyMaterial
+    include ActiveModel::Validations
+
+    attr_accessor :keypair
+    attr_accessor :private_key
+    attr_accessor :public_key
+
+    def initialize
+    end
+
+    validates_each :private_key do |record, attr, value|
+        record.errors.add :private_key, "cannot be blank" if record.private_key.nil?
+    end
+    validates_each :public_key do |record, attr, value|
+      record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
+    end
+
+    def is_in_hardware?
+      false
+    end
+
+    def is_in_memory?
+      true
+    end
+
+    def generate_key(modulus_bits=2048)
+      self.keypair = OpenSSL::PKey::RSA.new(modulus_bits)
+      self.private_key = keypair
+      self.public_key = keypair.public_key
+      self.keypair
+    end
+
+    def private_key
+      @private_key
+    end
+
+    def public_key
+      @public_key
+    end
+  end
+
+  class SigningRequestKeyMaterial
+    include KeyMaterial
+    include ActiveModel::Validations
+
+    validates_each :public_key do |record, attr, value|
+      record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
+    end
+
+    attr_accessor :public_key
+
+    def initialize(request=nil)
+      if request.is_a? OpenSSL::X509::Request
+        raise "Invalid certificate signing request" unless request.verify request.public_key
+        self.public_key = request.public_key
+      end
+    end
+
+    def is_in_hardware?
+      false
+    end
+
+    def is_in_memory?
+      true
+    end
+
+    def private_key
+      nil
+    end
+
+    def public_key
+      @public_key
+    end
+  end
+
+  class SigningRequestKeyMaterial
+    include KeyMaterial
+    include ActiveModel::Validations
+
+    validates_each :public_key do |record, attr, value|
+      record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
+    end
+
+    attr_accessor :public_key
+
+    def initialize(request=nil)
+      if request.is_a? OpenSSL::X509::Request
+        raise "Invalid certificate signing request" unless request.verify request.public_key
+        self.public_key = request.public_key
+      end
+    end
+
+    def is_in_hardware?
+      false
+    end
+
+    def is_in_memory?
+      true
+    end
+
+    def private_key
+      nil
+    end
+
+    def public_key
+      @public_key
+    end
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb b/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb
new file mode 100644 (file)
index 0000000..e101f98
--- /dev/null
@@ -0,0 +1,144 @@
+module CertificateAuthority
+  class OCSPResponseBuilder
+    attr_accessor :ocsp_response
+    attr_accessor :verification_mechanism
+    attr_accessor :ocsp_request_reader
+    attr_accessor :parent
+    attr_accessor :next_update
+
+    GOOD = OpenSSL::OCSP::V_CERTSTATUS_GOOD
+    REVOKED = OpenSSL::OCSP::V_CERTSTATUS_REVOKED
+
+    NO_REASON=0
+    KEY_COMPROMISED=OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE
+    UNSPECIFIED=OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED
+
+    def build_response()
+      raise "Requires a parent for signing" if @parent.nil?
+      if @verification_mechanism.nil?
+        ## If no verification callback is provided we're marking it GOOD
+        @verification_mechanism = lambda {|cert_id| [GOOD,NO_REASON] }
+      end
+
+      @ocsp_request_reader.ocsp_request.certid.each do |cert_id|
+        result,reason = verification_mechanism.call(cert_id.serial)
+
+        ## cert_id, status, reason, rev_time, this update, next update, ext
+        ## - unit of time is seconds
+        ## - rev_time is currently set to "now"
+        @ocsp_response.add_status(cert_id,
+        result, reason,
+          0, 0, @next_update, nil)
+      end
+
+      @ocsp_response.sign(OpenSSL::X509::Certificate.new(@parent.to_pem), @parent.key_material.private_key, nil, nil)
+      OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, @ocsp_response)
+    end
+
+    def self.from_request_reader(request_reader,verification_mechanism=nil)
+      response_builder = OCSPResponseBuilder.new
+      response_builder.ocsp_request_reader = request_reader
+
+      ocsp_response = OpenSSL::OCSP::BasicResponse.new
+      ocsp_response.copy_nonce(request_reader.ocsp_request)
+      response_builder.ocsp_response = ocsp_response
+      response_builder.next_update = 60*15 #Default of 15 minutes
+      response_builder
+    end
+  end
+
+  class OCSPRequestReader
+    attr_accessor :raw_ocsp_request
+    attr_accessor :ocsp_request
+
+    def serial_numbers
+      @ocsp_request.certid.collect do |cert_id|
+        cert_id.serial
+      end
+    end
+
+    def self.from_der(request_body)
+      reader = OCSPRequestReader.new
+      reader.raw_ocsp_request = request_body
+      reader.ocsp_request = OpenSSL::OCSP::Request.new(request_body)
+
+      reader
+    end
+  end
+
+  ## DEPRECATED
+  class OCSPHandler
+    include ActiveModel::Validations
+
+    attr_accessor :ocsp_request
+    attr_accessor :certificate_ids
+
+    attr_accessor :certificates
+    attr_accessor :parent
+
+    attr_accessor :ocsp_response_body
+
+    validate do |crl|
+      errors.add :parent, "A parent entity must be set" if parent.nil?
+    end
+    validate :all_certificates_available
+
+    def initialize
+      self.certificates = {}
+    end
+
+    def <<(cert)
+      self.certificates[cert.serial_number.number.to_s] = cert
+    end
+
+    def extract_certificate_serials
+      openssl_request = OpenSSL::OCSP::Request.new(@ocsp_request)
+
+      self.certificate_ids = openssl_request.certid.collect do |cert_id|
+        cert_id.serial
+      end
+
+      self.certificate_ids
+    end
+
+
+    def response
+      raise "Invalid response" unless valid?
+
+      openssl_ocsp_response = OpenSSL::OCSP::BasicResponse.new
+      openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request)
+      openssl_ocsp_response.copy_nonce(openssl_ocsp_request)
+
+      openssl_ocsp_request.certid.each do |cert_id|
+        certificate = self.certificates[cert_id.serial.to_s]
+
+        openssl_ocsp_response.add_status(cert_id,
+        OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0,
+          0, 0, 30, nil)
+      end
+
+
+      openssl_ocsp_response.sign(OpenSSL::X509::Certificate.new(self.parent.to_pem), self.parent.key_material.private_key, nil, nil)
+      final_response = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, openssl_ocsp_response)
+      self.ocsp_response_body = final_response
+      self.ocsp_response_body
+    end
+
+    def to_der
+      raise "No signed OCSP response body available" if self.ocsp_response_body.nil?
+      self.ocsp_response_body.to_der
+    end
+
+    private
+
+    def all_certificates_available
+      openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request)
+
+      openssl_ocsp_request.certid.each do |cert_id|
+        certificate = self.certificates[cert_id.serial.to_s]
+        errors.add(:base, "Certificate #{cert_id.serial} has not been added yet") if certificate.nil?
+      end
+    end
+
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb b/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb
new file mode 100644 (file)
index 0000000..d4ebc47
--- /dev/null
@@ -0,0 +1,65 @@
+module CertificateAuthority
+  class Pkcs11KeyMaterial
+    include KeyMaterial
+    include ActiveModel::Validations
+    include ActiveModel::Serialization
+
+    attr_accessor :engine
+    attr_accessor :token_id
+    attr_accessor :pkcs11_lib
+    attr_accessor :openssl_pkcs11_engine_lib
+    attr_accessor :pin
+
+    def initialize(attributes = {})
+      @attributes = attributes
+      initialize_engine
+    end
+
+    def is_in_hardware?
+      true
+    end
+
+    def is_in_memory?
+      false
+    end
+
+    def generate_key(modulus_bits=1024)
+      puts "Key generation is not currently supported in hardware"
+      nil
+    end
+
+    def private_key
+      initialize_engine
+      self.engine.load_private_key(self.token_id)
+    end
+
+    def public_key
+      initialize_engine
+      self.engine.load_public_key(self.token_id)
+    end
+
+    private
+
+    def initialize_engine
+      ## We're going to return early and try again later if params weren't passed in
+      ## at initialization.  Any attempt at getting a public/private key will try
+      ## again.
+      return false if self.openssl_pkcs11_engine_lib.nil? or self.pkcs11_lib.nil?
+      return self.engine unless self.engine.nil?
+      OpenSSL::Engine.load
+
+      pkcs11 = OpenSSL::Engine.by_id("dynamic") do |e|
+        e.ctrl_cmd("SO_PATH",self.openssl_pkcs11_engine_lib)
+        e.ctrl_cmd("ID","pkcs11")
+        e.ctrl_cmd("LIST_ADD","1")
+        e.ctrl_cmd("LOAD")
+        e.ctrl_cmd("PIN",self.pin) unless self.pin.nil? or self.pin == ""
+        e.ctrl_cmd("MODULE_PATH",self.pkcs11_lib)
+      end
+
+      self.engine = pkcs11
+      pkcs11
+    end
+
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/revocable.rb b/vendor/certificate_authority/lib/certificate_authority/revocable.rb
new file mode 100644 (file)
index 0000000..eba5d98
--- /dev/null
@@ -0,0 +1,14 @@
+module CertificateAuthority
+  module Revocable
+    attr_accessor :revoked_at
+
+    def revoke!(time=Time.now)
+      @revoked_at = time
+    end
+
+    def revoked?
+      # If we have a time, then we're revoked
+      !@revoked_at.nil?
+    end
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/serial_number.rb b/vendor/certificate_authority/lib/certificate_authority/serial_number.rb
new file mode 100644 (file)
index 0000000..ec0b836
--- /dev/null
@@ -0,0 +1,10 @@
+module CertificateAuthority
+  class SerialNumber
+    include ActiveModel::Validations
+    include Revocable
+
+    attr_accessor :number
+
+    validates :number, :presence => true, :numericality => {:greater_than => 0}
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb b/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb
new file mode 100644 (file)
index 0000000..748350b
--- /dev/null
@@ -0,0 +1,16 @@
+module CertificateAuthority
+  module SigningEntity
+
+    def self.included(mod)
+      mod.class_eval do
+        attr_accessor :signing_entity
+      end
+    end
+
+    def signing_entity=(val)
+      raise "invalid param" unless [true,false].include?(val)
+      @signing_entity = val
+    end
+
+  end
+end
diff --git a/vendor/certificate_authority/lib/certificate_authority/signing_request.rb b/vendor/certificate_authority/lib/certificate_authority/signing_request.rb
new file mode 100644 (file)
index 0000000..590d5be
--- /dev/null
@@ -0,0 +1,56 @@
+module CertificateAuthority
+  class SigningRequest
+    attr_accessor :distinguished_name
+    attr_accessor :key_material
+    attr_accessor :raw_body
+    attr_accessor :openssl_csr
+    attr_accessor :digest
+
+    def to_cert
+      cert = Certificate.new
+      if !@distinguished_name.nil?
+        cert.distinguished_name = @distinguished_name
+      end
+      cert.key_material = @key_material
+      cert
+    end
+
+    def to_pem
+      to_x509_csr.to_pem
+    end
+
+    def to_x509_csr
+      raise "Must specify a DN/subject on csr" if @distinguished_name.nil?
+      raise "Invalid DN in request" unless @distinguished_name.valid?
+      raise "CSR must have key material" if @key_material.nil?
+      raise "CSR must include a public key on key material" if @key_material.public_key.nil?
+
+      opensslcsr = OpenSSL::X509::Request.new
+      opensslcsr.subject = @distinguished_name.to_x509_name
+      opensslcsr.public_key = @key_material.public_key
+      opensslcsr.sign @key_material.private_key, OpenSSL::Digest::Digest.new(@digest || "SHA512")
+      opensslcsr
+    end
+
+    def self.from_x509_csr(raw_csr)
+      csr = SigningRequest.new
+      openssl_csr = OpenSSL::X509::Request.new(raw_csr)
+      csr.distinguished_name = DistinguishedName.from_openssl openssl_csr.subject
+      csr.raw_body = raw_csr
+      csr.openssl_csr = openssl_csr
+      key_material = SigningRequestKeyMaterial.new
+      key_material.public_key = openssl_csr.public_key
+      csr.key_material = key_material
+      csr
+    end
+
+    def self.from_netscape_spkac(raw_spkac)
+      openssl_spkac = OpenSSL::Netscape::SPKI.new raw_spkac
+      csr = SigningRequest.new
+      csr.raw_body = raw_spkac
+      key_material = SigningRequestKeyMaterial.new
+      key_material.public_key = openssl_spkac.public_key
+      csr
+    end
+  end
+end
\ No newline at end of file
diff --git a/vendor/certificate_authority/lib/tasks/certificate_authority.rake b/vendor/certificate_authority/lib/tasks/certificate_authority.rake
new file mode 100644 (file)
index 0000000..e7d5bf9
--- /dev/null
@@ -0,0 +1,23 @@
+require 'certificate_authority'
+
+namespace :certificate_authority do
+  desc "Generate a quick self-signed cert"
+  task :self_signed do
+    
+    cn = "http://localhost"
+    cn = ENV['DOMAIN'] unless ENV['DOMAIN'].nil?
+    
+       root = CertificateAuthority::Certificate.new
+       root.subject.common_name= cn
+       root.key_material.generate_key
+       root.signing_entity = true
+       root.valid?
+       root.sign!
+       
+       print "Your cert for #{cn}\n"
+       print root.to_pem
+       
+       print "Your private key\n"
+       print root.key_material.private_key.to_pem
+  end
+end