include OpenSSLCookbook::Helpers

property :name,             String, name_property: true
property :owner,            String
property :group,            String
property :expire,           Integer
property :mode,             [Integer, String]
property :org,              String, required: true
property :org_unit,         String, required: true
property :country,          String, required: true
property :common_name,      String, required: true
property :subject_alt_name, Array, default: []
property :key_file,         String
property :key_pass,         String
property :key_length,       equal_to: [1024, 2048, 4096, 8192], default: 2048

action :create do
  unless ::File.exist? new_resource.name
    converge_by("Create #{@new_resource}") do
      create_keys
      cert_content = cert.to_pem
      key_content = key.to_pem

      file new_resource.name do
        action :create_if_missing
        mode new_resource.mode
        owner new_resource.owner
        group new_resource.group
        sensitive true
        content cert_content
      end

      file new_resource.key_file do
        action :create_if_missing
        mode new_resource.mode
        owner new_resource.owner
        group new_resource.group
        sensitive true
        content key_content
      end
    end
  end
end

action_class.class_eval do
  def generate_key_file
    unless new_resource.key_file
      path, file = ::File.split(new_resource.name)
      filename = ::File.basename(file, ::File.extname(file))
      new_resource.key_file path + '/' + filename + '.key'
    end
    new_resource.key_file
  end

  def key
    @key ||= if key_file_valid?(generate_key_file, new_resource.key_pass)
               OpenSSL::PKey::RSA.new ::File.read(generate_key_file), new_resource.key_pass
             else
               OpenSSL::PKey::RSA.new(new_resource.key_length)
             end
    @key
  end

  def cert
    @cert ||= OpenSSL::X509::Certificate.new
  end

  def gen_cert
    cert
    cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
    cert.not_before = Time.now
    cert.not_after = Time.now + (new_resource.expire.to_i * 24 * 60 * 60)
    cert.public_key = key.public_key
    cert.serial = 0x0
    cert.version = 2
  end

  def subject
    @subject ||= '/C=' + new_resource.country +
                 '/O=' + new_resource.org +
                 '/OU=' + new_resource.org_unit +
                 '/CN=' + new_resource.common_name
  end

  def extensions
    exts = []
    exts << @ef.create_extension('basicConstraints', 'CA:TRUE', true)
    exts << @ef.create_extension('subjectKeyIdentifier', 'hash')

    unless new_resource.subject_alt_name.empty?
      san = {}
      counters = {}
      new_resource.subject_alt_name.each do |an|
        kind, value = an.split(':', 2)
        counters[kind] ||= 0
        counters[kind] += 1
        san["#{kind}.#{counters[kind]}"] = value
      end
      @ef.config['alt_names'] = san
      exts << @ef.create_extension('subjectAltName', '@alt_names')
    end

    exts
  end

  def create_keys
    gen_cert
    @ef ||= OpenSSL::X509::ExtensionFactory.new
    @ef.subject_certificate = cert
    @ef.issuer_certificate = cert
    @ef.config = OpenSSL::Config.load(OpenSSL::Config::DEFAULT_CONFIG_FILE)

    cert.extensions = extensions
    cert.add_extension @ef.create_extension('authorityKeyIdentifier',
                                           'keyid:always,issuer:always')
    cert.sign key, OpenSSL::Digest::SHA256.new
  end
end
