class Puppet::SSL::Host::CertificateRequest

This class creates and manages X509 certificate signing requests.

## CSR attributes

CSRs may contain a set of attributes that includes supplementary information about the CSR or information for the signed certificate.

PKCS#9/RFC 2985 section 5.4 formally defines the “Challenge password”, “Extension request”, and “Extended-certificate attributes”, but this implementation only handles the “Extension request” attribute. Other attributes may be defined on a CSR, but the RFC doesn’t define behavior for any other attributes so we treat them as only informational.

## CSR Extension request attribute

CSRs may contain an optional set of extension requests, which allow CSRs to include additional information that may be included in the signed certificate. Any additional information that should be copied from the CSR to the signed certificate MUST be included in this attribute.

This behavior is dictated by PKCS#9/RFC 2985 section 5.4.2.

@see tools.ietf.org/html/rfc2985 “RFC 2985 Section 5.4.2 Extension request”

Public Class Methods

supported_formats() click to toggle source

Because of how the format handler class is included, this can’t be in the base class.

# File lib/puppet/ssl/certificate_request.rb, line 52
def self.supported_formats
  [:s]
end

Public Instance Methods

custom_attributes() click to toggle source

Return all user specified attributes attached to this CSR as a hash. IF an OID has a single value it is returned as a string, otherwise all values are returned as an array.

The format of CSR attributes is specified in PKCS#10/RFC 2986

@see tools.ietf.org/html/rfc2986 “RFC 2986 Certification Request Syntax Specification”

@api public

@return [Hash<String, String>]

# File lib/puppet/ssl/certificate_request.rb, line 173
def custom_attributes
  x509_attributes = @content.attributes.reject do |attr|
    PRIVATE_CSR_ATTRIBUTES.include? attr.oid
  end

  x509_attributes.map do |attr|
    {"oid" => attr.oid, "value" => attr.value.first.value}
  end
end
extension_factory() click to toggle source
# File lib/puppet/ssl/certificate_request.rb, line 56
def extension_factory
  @ef ||= OpenSSL::X509::ExtensionFactory.new
end
generate(key, options = {}) click to toggle source

Create a certificate request with our system settings.

@param key [OpenSSL::X509::Key, Puppet::SSL::Key] The key pair associated

with this CSR.

@param options [Hash] @option options [String] :dns_alt_names A comma separated list of

Subject Alternative Names to include in the CSR extension request.

@option options [Hash<String, String, Array<String>>] :csr_attributes A hash

of OIDs and values that are either a string or array of strings.

@option options [Array<String, String>] :extension_requests A hash of

certificate extensions to add to the CSR extReq attribute, excluding
the Subject Alternative Names extension.

@raise [Puppet::Error] If the generated CSR signature couldn’t be verified

@return [OpenSSL::X509::Request] The generated CSR

# File lib/puppet/ssl/certificate_request.rb, line 76
def generate(key, options = {})
  Puppet.info "Creating a new SSL certificate request for #{name}"

  # Support either an actual SSL key, or a Puppet key.
  key = key.content if key.is_a?(Puppet::SSL::Key)

  # If we're a CSR for the CA, then use the real ca_name, rather than the
  # fake 'ca' name.  This is mostly for backward compatibility with 0.24.x,
  # but it's also just a good idea.
  common_name = name == Puppet::SSL::CA_NAME ? Puppet.settings[:ca_name] : name

  csr = OpenSSL::X509::Request.new
  csr.version = 0
  csr.subject = OpenSSL::X509::Name.new([["CN", common_name]])
  csr.public_key = key.public_key

  if options[:csr_attributes]
    add_csr_attributes(csr, options[:csr_attributes])
  end

  if (ext_req_attribute = extension_request_attribute(options))
    csr.add_attribute(ext_req_attribute)
  end

  signer = Puppet::SSL::CertificateSigner.new
  signer.sign(csr, key)

  raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key)

  @content = csr
  Puppet.info "Certificate Request fingerprint (#{digest.name}): #{digest.to_hex}"
  @content
end
request_extensions() click to toggle source

Return the set of extensions requested on this CSR, in a form designed to be useful to Ruby: an array of hashes. Which, not coincidentally, you can pass successfully to the OpenSSL constructor later, if you want.

@return [Array<Hash{String => String}>] An array of two or three element hashes, with key/value pairs for the extension’s oid, its value, and optionally its critical state.

# File lib/puppet/ssl/certificate_request.rb, line 117
def request_extensions
  raise Puppet::Error, "CSR needs content to extract fields" unless @content

  # Prefer the standard extReq, but accept the Microsoft specific version as
  # a fallback, if the standard version isn't found.
  attribute   = @content.attributes.find {|x| x.oid == "extReq" }
  attribute ||= @content.attributes.find {|x| x.oid == "msExtReq" }
  return [] unless attribute

  extensions = unpack_extension_request(attribute)

  index = -1
  extensions.map do |ext_values|
    index += 1
    context = "#{attribute.oid} extension index #{index}"

    # OK, turn that into an extension, to unpack the content.  Lovely that
    # we have to swap the order of arguments to the underlying method, or
    # perhaps that the ASN.1 representation chose to pack them in a
    # strange order where the optional component comes *earlier* than the
    # fixed component in the sequence.
    case ext_values.length
    when 2
      ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[1].value)
      { "oid" => ev.oid, "value" => ev.value }

    when 3
      ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[2].value, ext_values[1].value)
      { "oid" => ev.oid, "value" => ev.value, "critical" => ev.critical? }

    else
      raise Puppet::Error, "In #{attribute.oid}, expected extension record #{index} to have two or three items, but found #{ext_values.length}"
    end
  end
end
subject_alt_names() click to toggle source
# File lib/puppet/ssl/certificate_request.rb, line 153
def subject_alt_names
  @subject_alt_names ||= request_extensions.
    select {|x| x["oid"] == "subjectAltName" }.
    map {|x| x["value"].split(/\s*,\s*/) }.
    flatten.
    sort.
    uniq
end