1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
require 'puppet/ssl'
# This class encapsulates the logic of creating and adding extensions to X509
# certificates.
#
# @api private
module Puppet::CertificateFactory
# Create a new X509 certificate and add any needed extensions to the cert.
#
# @param cert_type [Symbol] The certificate type to create, which specifies
# what extensions are added to the certificate.
# One of (:ca, :terminalsubca, :server, :ocsp, :client)
# @param csr [Puppet::SSL::CertificateRequest] The signing request associated with
# the certificate being created.
# @param issuer [OpenSSL::X509::Certificate, OpenSSL::X509::Request] An X509 CSR
# if this is a self signed certificate, or the X509 certificate of the CA if
# this is a CA signed certificate.
# @param serial [Integer] The serial number for the given certificate, which
# MUST be unique for the given CA.
# @param ttl [String] The duration of the validity for the given certificate.
#
# @api public
#
# @return [OpenSSL::X509::Certificate]
def self.build(cert_type, csr, issuer, serial, ttl = 3600)
# Work out if we can even build the requested type of certificate.
build_extensions = "build_#{cert_type}_extensions"
respond_to?(build_extensions) or
raise ArgumentError, _("%{cert_type} is an invalid certificate type!") % { cert_type: cert_type }
raise ArgumentError, _("Certificate TTL must be an integer") unless ttl.nil? || ttl.is_a?(Integer)
# set up the certificate, and start building the content.
cert = OpenSSL::X509::Certificate.new
cert.version = 2 # X509v3
cert.subject = csr.content.subject
cert.issuer = issuer.subject
cert.public_key = csr.content.public_key
cert.serial = serial
# Make the certificate valid as of yesterday, because so many people's
# clocks are out of sync. This gives one more day of validity than people
# might expect, but is better than making every person who has a messed up
# clock fail, and better than having every cert we generate expire a day
# before the user expected it to when they asked for "one year".
cert.not_before = Time.now - (60*60*24)
cert.not_after = Time.now + ttl
add_extensions_to(cert, csr, issuer, send(build_extensions))
return cert
end
# Add X509v3 extensions to the given certificate.
#
# @param cert [OpenSSL::X509::Certificate] The certificate to add the
# extensions to.
# @param csr [OpenSSL::X509::Request] The CSR associated with the given
# certificate, which may specify requested extensions for the given cert.
# See https://tools.ietf.org/html/rfc2985 Section 5.4.2 Extension request
# @param issuer [OpenSSL::X509::Certificate, OpenSSL::X509::Request] An X509 CSR
# if this is a self signed certificate, or the X509 certificate of the CA if
# this is a CA signed certificate.
# @param extensions [Hash<String, Array<String> | String>] The extensions to
# add to the certificate, based on the certificate type being created (CA,
# server, client, etc)
#
# @api private
#
# @return [void]
def self.add_extensions_to(cert, csr, issuer, extensions)
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = issuer.is_a?(OpenSSL::X509::Request) ? cert : issuer
# Extract the requested extensions from the CSR.
requested_exts = csr.request_extensions.inject({}) do |hash, re|
hash[re["oid"]] = [re["value"], re["critical"]]
hash
end
# Produce our final set of extensions. We deliberately order these to
# build the way we want:
# 1. "safe" default values, like the comment, that no one cares about.
# 2. request extensions, from the CSR
# 3. extensions based on the type we are generating
# 4. overrides, which we always want to have in their form
#
# This ordering *is* security-critical, but we want to allow the user
# enough rope to shoot themselves in the foot, if they want to ignore our
# advice and externally approve a CSR that sets the basicConstraints.
#
# Swapping the order of 2 and 3 would ensure that you couldn't slip a
# certificate through where the CA constraint was true, though, if
# something went wrong up there. --daniel 2011-10-11
defaults = { "nsComment" => "Puppet Ruby/OpenSSL Internal Certificate" }
# See https://www.openssl.org/docs/apps/x509v3_config.html
# for information about the special meanings of 'hash', 'keyid', 'issuer'
override = {
"subjectKeyIdentifier" => "hash",
"authorityKeyIdentifier" => "keyid,issuer"
}
exts = [defaults, requested_exts, extensions, override].
inject({}) {|ret, val| ret.merge(val) }
cert.extensions = exts.map do |oid, val|
generate_extension(ef, oid, *val)
end
end
private_class_method :add_extensions_to
# Woot! We're a CA.
def self.build_ca_extensions
{
# This was accidentally omitted in the previous version of this code: an
# effort was made to add it last, but that actually managed to avoid
# adding it to the certificate at all.
#
# We have some sort of bug, which means that when we add it we get a
# complaint that the issuer keyid can't be fetched, which breaks all
# sorts of things in our test suite and, e.g., bootstrapping the CA.
#
# https://tools.ietf.org/html/rfc5280#section-4.2.1.1 says that, to be a
# conforming CA we MAY omit the field if we are self-signed, which I
# think gives us a pass in the specific case.
#
# It also notes that we MAY derive the ID from the subject and serial
# number of the issuer, or from the key ID, and we definitely have the
# former data, should we want to restore this...
#
# Anyway, preserving this bug means we don't risk breaking anything in
# the field, even though it would be nice to have. --daniel 2011-10-11
#
# "authorityKeyIdentifier" => "keyid:always,issuer:always",
"keyUsage" => [%w{cRLSign keyCertSign}, true],
"basicConstraints" => ["CA:TRUE", true],
}
end
# We're a terminal CA, probably not self-signed.
def self.build_terminalsubca_extensions
{
"keyUsage" => [%w{cRLSign keyCertSign}, true],
"basicConstraints" => ["CA:TRUE,pathlen:0", true],
}
end
# We're a normal server.
def self.build_server_extensions
{
"keyUsage" => [%w{digitalSignature keyEncipherment}, true],
"extendedKeyUsage" => [%w{serverAuth clientAuth}, true],
"basicConstraints" => ["CA:FALSE", true],
}
end
# Um, no idea.
def self.build_ocsp_extensions
{
"keyUsage" => [%w{nonRepudiation digitalSignature}, true],
"extendedKeyUsage" => [%w{serverAuth OCSPSigning}, true],
"basicConstraints" => ["CA:FALSE", true],
}
end
# Normal client.
def self.build_client_extensions
{
"keyUsage" => [%w{nonRepudiation digitalSignature keyEncipherment}, true],
# We don't seem to use this, but that seems much more reasonable here...
"extendedKeyUsage" => [%w{clientAuth emailProtection}, true],
"basicConstraints" => ["CA:FALSE", true],
"nsCertType" => "client,email",
}
end
# Generate an extension with the given OID, value, and critical state
#
# @param oid [String] The numeric value or short name of a given OID. X509v3
# extensions must be passed by short name or long name, while custom
# extensions may be passed by short name, long name, oid numeric OID.
# @param ef [OpenSSL::X509::ExtensionFactory] The extension factory to use
# when generating the extension.
# @param val [String, Array<String>] The extension value.
# @param crit [true, false] Whether the given extension is critical, defaults
# to false.
#
# @return [OpenSSL::X509::Extension]
#
# @api private
def self.generate_extension(ef, oid, val, crit = false)
val = val.join(', ') unless val.is_a? String
# Enforce the X509v3 rules about subjectAltName being critical:
# specifically, it SHOULD NOT be critical if we have a subject, which we
# always do. --daniel 2011-10-18
crit = false if oid == "subjectAltName"
if Puppet::SSL::Oids.subtree_of?('id-ce', oid) or Puppet::SSL::Oids.subtree_of?('id-pkix', oid)
# Attempt to create a X509v3 certificate extension. Standard certificate
# extensions may need access to the associated subject certificate and
# issuing certificate, so must be created by the OpenSSL::X509::ExtensionFactory
# which provides that context.
ef.create_ext(oid, val, crit)
else
# This is not an X509v3 extension which means that the extension
# factory cannot generate it. We need to generate the extension
# manually.
OpenSSL::X509::Extension.new(oid, OpenSSL::ASN1::UTF8String.new(val).to_der, crit)
end
end
private_class_method :generate_extension
end
|