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”
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
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
# File lib/puppet/ssl/certificate_request.rb, line 56 def extension_factory @ef ||= OpenSSL::X509::ExtensionFactory.new end
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
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
# 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