# The library for manipulating SSL certs.

require 'puppet'

raise Puppet::Error, "You must have the Ruby openssl library installed" unless Puppet.features.openssl?

module Puppet::SSLCertificates
  #def self.mkcert(type, name, dnsnames, ttl, issuercert, issuername, serial, publickey)
  def self.mkcert(hash)
    [:type, :name, :ttl, :issuer, :serial, :publickey].each { |param|
      raise ArgumentError, "mkcert called without #{param}" unless hash.include?(param)
    }

    cert = OpenSSL::X509::Certificate.new
    # Make the certificate valid as of yesterday, because
    # so many people's clocks are out of sync.
    from = Time.now - (60*60*24)

    cert.subject = hash[:name]
    if hash[:issuer]
      cert.issuer = hash[:issuer].subject
    else
      # we're a self-signed cert
      cert.issuer = hash[:name]
    end
    cert.not_before = from
    cert.not_after = from + hash[:ttl]
    cert.version = 2 # X509v3

    cert.public_key = hash[:publickey]
    cert.serial = hash[:serial]

    basic_constraint = nil
    key_usage = nil
    ext_key_usage = nil
    subject_alt_name = []

    ef = OpenSSL::X509::ExtensionFactory.new

    ef.subject_certificate = cert

    if hash[:issuer]
      ef.issuer_certificate = hash[:issuer]
    else
      ef.issuer_certificate = cert
    end

    ex = []
    case hash[:type]
    when :ca
      basic_constraint = "CA:TRUE"
      key_usage = %w{cRLSign keyCertSign}
    when :terminalsubca
      basic_constraint = "CA:TRUE,pathlen:0"
      key_usage = %w{cRLSign keyCertSign}
    when :server
      basic_constraint = "CA:FALSE"
      dnsnames = Puppet[:certdnsnames]
      name = hash[:name].to_s.sub(%r{/CN=},'')
      if dnsnames != ""
        dnsnames.split(':').each { |d| subject_alt_name << 'DNS:' + d }
        subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
      elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server
        subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias
        subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
        subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias
      end
      key_usage = %w{digitalSignature keyEncipherment}
      ext_key_usage = %w{serverAuth clientAuth emailProtection}
    when :ocsp
      basic_constraint = "CA:FALSE"
      key_usage = %w{nonRepudiation digitalSignature}
      ext_key_usage = %w{serverAuth OCSPSigning}
    when :client
      basic_constraint = "CA:FALSE"
      key_usage = %w{nonRepudiation digitalSignature keyEncipherment}
      ext_key_usage = %w{clientAuth emailProtection}
      ex << ef.create_extension("nsCertType", "client,email")
    else
      raise Puppet::Error, "unknown cert type '#{hash[:type]}'"
    end


      ex << ef.create_extension(
        "nsComment",

          "Puppet Ruby/OpenSSL Generated Certificate")
    ex << ef.create_extension("basicConstraints", basic_constraint, true)
    ex << ef.create_extension("subjectKeyIdentifier", "hash")

    ex << ef.create_extension("keyUsage", key_usage.join(",")) if key_usage
    ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(",")) if ext_key_usage
    ex << ef.create_extension("subjectAltName", subject_alt_name.join(",")) if ! subject_alt_name.empty?

    #if @ca_config[:cdp_location] then
    #  ex << ef.create_extension("crlDistributionPoints",
    #                            @ca_config[:cdp_location])
    #end

    #if @ca_config[:ocsp_location] then
    #  ex << ef.create_extension("authorityInfoAccess",
    #                            "OCSP;" << @ca_config[:ocsp_location])
    #end
    cert.extensions = ex

    # for some reason this _must_ be the last extension added
    ex << ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if hash[:type] == :ca

    cert
  end

  def self.mkhash(dir, cert, certfile)
    # Make sure the hash is zero-padded to 8 chars
    hash = "%08x" % cert.issuer.hash
    hashpath = nil
    10.times { |i|
      path = File.join(dir, "#{hash}.#{i}")
      if FileTest.exists?(path)
        if FileTest.symlink?(path)
          dest = File.readlink(path)
          if dest == certfile
            # the correct link already exists
            hashpath = path
            break
          else
            next
          end
        else
          next
        end
      end

      File.symlink(certfile, path)

      hashpath = path
      break
    }


    hashpath
  end
  require 'puppet/sslcertificates/certificate'
  require 'puppet/sslcertificates/inventory'
  require 'puppet/sslcertificates/ca'
end

