class Acme::Client::CertificateRequest

def add_extension(csr)

def add_extension(csr)
  extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
    'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
  )
  csr.add_attribute(
    OpenSSL::X509::Attribute.new(
      'extReq',
      OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])])
    )
  )
end

def csr

def csr
  @csr ||= generate
end

def generate

def generate
  OpenSSL::X509::Request.new.tap do |csr|
    if @private_key.is_a?(OpenSSL::PKey::EC) && RbConfig::CONFIG['MAJOR'] == '2' &&
       RbConfig::CONFIG['MINOR'].to_i < 4
      # OpenSSL::PKey::EC does not respect classic PKey interface (as defined by
      # PKey::RSA and PKey::DSA) until ruby 2.4.
      # Supporting this interface needs monkey patching of OpenSSL:PKey::EC, or
      # subclassing it. Here, use a subclass.
      @private_key = ECKeyPatch.new(@private_key)
    end
    csr.public_key = @private_key
    csr.subject = generate_subject
    csr.version = 0
    add_extension(csr)
    csr.sign @private_key, @digest
  end
end

def generate_private_key

def generate_private_key
  OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH)
end

def generate_subject

def generate_subject
  OpenSSL::X509::Name.new(
    @subject.map {|name, value|
      [name, value, SUBJECT_TYPES[name]]
    }
  )
end

def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new)

def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new)
  @digest = digest
  @private_key = private_key
  @subject = normalize_subject(subject)
  @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name]
  @names = names.to_a.dup
  normalize_names
  @subject[SUBJECT_KEYS[:common_name]] ||= @common_name
  validate_subject
end

def normalize_names

def normalize_names
  if @common_name
    @names.unshift(@common_name) unless @names.include?(@common_name)
  else
    raise ArgumentError, 'No common name and no list of names given' if @names.empty?
    @common_name = @names.first
  end
end

def normalize_subject(subject)

def normalize_subject(subject)
  @subject = subject.each_with_object({}) do |(key, value), hash|
    hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s
  end
end

def validate_subject

def validate_subject
  validate_subject_attributes
  validate_subject_common_name
end

def validate_subject_attributes

def validate_subject_attributes
  extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values
  return if extra_keys.empty?
  raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}"
end

def validate_subject_common_name

def validate_subject_common_name
  return if @common_name == @subject[SUBJECT_KEYS[:common_name]]
  raise ArgumentError, 'Conflicting common name given in arguments and subject'
end