module Fluent::PluginHelper::CertOption

def cert_option_add_extensions(cert, extensions)

def cert_option_add_extensions(cert, extensions)
  ef = OpenSSL::X509::ExtensionFactory.new
  extensions.each do |ext|
    oid, value = ext
    cert.add_extension ef.create_extension(oid, value)
  end
end

def cert_option_cert_generation_opts_from_conf(conf)

def cert_option_cert_generation_opts_from_conf(conf)
  {
    private_key_length: conf.generate_private_key_length,
    country: conf.generate_cert_country,
    state: conf.generate_cert_state,
    locality: conf.generate_cert_locality,
    common_name: conf.generate_cert_common_name || ::Socket.gethostname,
    expiration: conf.generate_cert_expiration,
    digest: conf.generate_cert_digest,
  }
end

def cert_option_certificates_from_file(path)

def cert_option_certificates_from_file(path)
  data = File.read(path)
  pattern = Regexp.compile('-+BEGIN CERTIFICATE-+\r?\n(?:[^-]*\r?\n)+-+END CERTIFICATE-+\r?\n?', Regexp::MULTILINE)
  list = []
  data.scan(pattern){|match| list << OpenSSL::X509::Certificate.new(match) }
  if list.length == 0
    raise Fluent::ConfigError, "cert_path does not contain a valid certificate"
  end
  list
end

def cert_option_create_context(version, insecure, ciphers, conf)

def cert_option_create_context(version, insecure, ciphers, conf)
  cert, key, extra = cert_option_server_validate!(conf)
  ctx = OpenSSL::SSL::SSLContext.new
  # inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
  # https://bugs.ruby-lang.org/issues/9424
  ctx.set_params({}) unless insecure
  if conf.client_cert_auth
    ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
  end
  ctx.ca_file = conf.ca_path
  ctx.cert = cert
  ctx.key = key
  if extra && !extra.empty?
    ctx.extra_chain_cert = extra
  end
  if conf.cert_verifier
    sandbox = Class.new
    ctx.verify_callback = if File.exist?(conf.cert_verifier)
                            verifier = File.read(conf.cert_verifier)
                            sandbox.instance_eval(verifier, File.basename(conf.cert_verifier))
                          else
                            sandbox.instance_eval(conf.cert_verifier)
                          end
  end
  Fluent::TLS.set_version_to_context(ctx, version, conf.min_version, conf.max_version)
  ctx.ciphers = ciphers unless insecure
  ctx
end

def cert_option_generate_ca_pair_self_signed(generate_opts)

def cert_option_generate_ca_pair_self_signed(generate_opts)
  cert, key = cert_option_generate_pair(generate_opts)
  cert_option_add_extensions(cert, [
    ['basicConstraints', 'CA:TRUE']
  ])
  cert.sign(key, generate_opts[:digest].to_s)
  return cert, key
end

def cert_option_generate_pair(opts, issuer = nil)

def cert_option_generate_pair(opts, issuer = nil)
  key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
  subject = OpenSSL::X509::Name.new
  subject.add_entry('C', opts[:country])
  subject.add_entry('ST', opts[:state])
  subject.add_entry('L', opts[:locality])
  subject.add_entry('CN', opts[:common_name])
  issuer ||= subject
  cert = OpenSSL::X509::Certificate.new
  cert.not_before = Time.at(0)
  cert.not_after = Time.now + opts[:expiration]
  cert.public_key = key
  cert.version = 2
  cert.serial = rand(2**(8*10))
  cert.issuer = issuer
  cert.subject  = subject
  return cert, key
end

def cert_option_generate_server_pair_by_ca(ca_cert_path, ca_key_path, ca_key_passphrase, generate_opts)

def cert_option_generate_server_pair_by_ca(ca_cert_path, ca_key_path, ca_key_passphrase, generate_opts)
  ca_key = OpenSSL::PKey::read(File.read(ca_key_path), ca_key_passphrase)
  ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_cert_path))
  cert, key = cert_option_generate_pair(generate_opts, ca_cert.subject)
  raise "BUG: certificate digest algorithm not set" unless generate_opts[:digest]
  cert_option_add_extensions(cert, [
    ['basicConstraints', 'CA:FALSE'],
    ['nsCertType', 'server'],
    ['keyUsage', 'digitalSignature,keyEncipherment'],
    ['extendedKeyUsage', 'serverAuth']
  ])
  cert.sign(ca_key, generate_opts[:digest].to_s)
  return cert, key, nil
end

def cert_option_generate_server_pair_self_signed(generate_opts)

def cert_option_generate_server_pair_self_signed(generate_opts)
  cert, key = cert_option_generate_pair(generate_opts)
  raise "BUG: certificate digest algorithm not set" unless generate_opts[:digest]
  cert_option_add_extensions(cert, [
    ['basicConstraints', 'CA:FALSE'],
    ['nsCertType', 'server']
  ])
  cert.sign(key, generate_opts[:digest].to_s)
  return cert, key, nil
end

def cert_option_load(cert_path, private_key_path, private_key_passphrase)

def cert_option_load(cert_path, private_key_path, private_key_passphrase)
  key = OpenSSL::PKey::read(File.read(private_key_path), private_key_passphrase)
  certs = cert_option_certificates_from_file(cert_path)
  cert = certs.shift
  return cert, key, certs
end

def cert_option_server_validate!(conf)

def cert_option_server_validate!(conf)
  case
  when conf.cert_path
    raise Fluent::ConfigError, "private_key_path is required when cert_path is specified" unless conf.private_key_path
    log.warn "For security reason, setting private_key_passphrase is recommended when cert_path is specified" unless conf.private_key_passphrase
    cert_option_load(conf.cert_path, conf.private_key_path, conf.private_key_passphrase)
  when conf.ca_cert_path
    raise Fluent::ConfigError, "ca_private_key_path is required when ca_cert_path is specified" unless conf.ca_private_key_path
    log.warn "For security reason, setting ca_private_key_passphrase is recommended when ca_cert_path is specified" unless conf.ca_private_key_passphrase
    generate_opts = cert_option_cert_generation_opts_from_conf(conf)
    cert_option_generate_server_pair_by_ca(
      conf.ca_cert_path,
      conf.ca_private_key_path,
      conf.ca_private_key_passphrase,
      generate_opts
    )
  when conf.insecure
    log.warn "insecure TLS communication server is configured (using 'insecure' mode)"
    generate_opts = cert_option_cert_generation_opts_from_conf(conf)
    cert_option_generate_server_pair_self_signed(generate_opts)
  else
    raise Fluent::ConfigError, "no valid cert options configured. specify either 'cert_path', 'ca_cert_path' or 'insecure'"
  end
end