require 'puppet/ssl/openssl_loader'
require 'puppet/ssl'
# Perform peer certificate verification against the known CA.
# If there is no CA information known, then no verification is performed
#
# @deprecated
# @api private
#
class Puppet::SSL::Validator::DefaultValidator #< class Puppet::SSL::Validator
attr_reader :peer_certs
attr_reader :verify_errors
attr_reader :last_error
FIVE_MINUTES_AS_SECONDS = 5 * 60
# Creates a new DefaultValidator, optionally with an SSL Configuration and SSL Host.
#
# @param ca_path [String] Filepath for the cacert
#
# @api private
#
def initialize(
ca_path = Puppet[:ssl_client_ca_auth] || Puppet[:localcacert])
reset!
@ca_path = ca_path
end
# Resets this validator to its initial validation state. The ssl configuration is not changed.
#
# @api private
#
def reset!
@peer_certs = []
@verify_errors = []
@hostname = nil
@last_error = nil
end
# Performs verification of the SSL connection and collection of the
# certificates for use in constructing the error message if the verification
# failed. This callback will be executed once for each certificate in a
# chain being verified.
#
# From the [OpenSSL
# documentation](https://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html):
# The `verify_callback` function is used to control the behaviour when the
# SSL_VERIFY_PEER flag is set. It must be supplied by the application and
# receives two arguments: preverify_ok indicates, whether the verification of
# the certificate in question was passed (preverify_ok=1) or not
# (preverify_ok=0). x509_store_ctx is a pointer to the complete context used for
# the certificate chain verification.
#
# See {Puppet::Network::HTTP::Connection} for more information and where this
# class is intended to be used.
#
# @param [Boolean] preverify_ok indicates whether the verification of the
# certificate in question was passed (preverify_ok=true)
# @param [OpenSSL::X509::StoreContext] store_context holds the X509 store context
# for the chain being verified.
#
# @return [Boolean] false if the peer is invalid, true otherwise.
#
# @api private
#
def call(preverify_ok, store_context)
current_cert = store_context.current_cert
@peer_certs << current_cert
# We must make a copy since the scope of the store_context will be lost
# across invocations of this method.
if preverify_ok
# If we've copied all of the certs in the chain out of the SSL library
if @peer_certs.length == store_context.chain.length
# (#20027) The peer cert must be issued by a specific authority
preverify_ok = valid_peer?
end
else
error = store_context.error || 0
error_string = store_context.error_string || "OpenSSL error #{error}"
case error
when OpenSSL::X509::V_OK
if @hostname
# chain is from leaf to root, opposite of the order that `call` is invoked
chain_cert = store_context.chain.first
# ruby 2.4 doesn't compare certs based on value, so force to DER byte array
if current_cert && chain_cert && current_cert.to_der == chain_cert.to_der && !OpenSSL::SSL.verify_certificate_identity(current_cert, @hostname)
@last_error = Puppet::SSL::CertMismatchError.new(current_cert, @hostname)
return false
else
@verify_errors << "#{error_string} for #{current_cert.subject.to_utf8}"
end
else
@verify_errors << "#{error_string} for #{current_cert.subject.to_utf8}"
end
when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID
# current_crl can be nil
# https://github.com/ruby/ruby/blob/ruby_1_9_3/ext/openssl/ossl_x509store.c#L501-L510
crl = store_context.current_crl
if crl
if crl.last_update && crl.last_update < Time.now + FIVE_MINUTES_AS_SECONDS
Puppet.debug { "Ignoring CRL not yet valid, current time #{Time.now.utc}, CRL last updated #{crl.last_update.utc}" }
preverify_ok = true
else
@verify_errors << "#{error_string} for #{crl.issuer.to_utf8}"
end
else
@verify_errors << error_string
end
else
@verify_errors << "#{error_string} for #{current_cert.subject.to_utf8}"
end
end
preverify_ok
rescue => ex
@verify_errors << ex.message
false
end
# Registers the instance's call method with the connection.
#
# @param [Net::HTTP] connection The connection to validate
#
# @param [Puppet::SSL::Host] host The host object containing SSL data
# @return [void]
#
# @api private
#
def setup_connection(connection, ssl_host = Puppet.lookup(:ssl_host))
@hostname = connection.address
if ssl_certificates_are_present?
connection.cert_store = ssl_host.ssl_store
connection.ca_file = @ca_path
connection.cert = ssl_host.certificate.content
connection.key = ssl_host.key.content
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
connection.verify_callback = self
else
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end
##
# Decode a string of concatenated certificates
#
# @return [Array<OpenSSL::X509::Certificate>]
def decode_cert_bundle(bundle_str)
re = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m
pem_ary = bundle_str.scan(re)
pem_ary.map do |pem_str|
OpenSSL::X509::Certificate.new(pem_str)
end
end
# read_file makes testing easier.
def read_file(path)
# https://www.ietf.org/rfc/rfc2459.txt defines the x509 V3 certificate format
# CA bundles are concatenated X509 certificates, but may also include
# comments, which could have UTF-8 characters
Puppet::FileSystem.read(path, :encoding => Encoding::UTF_8)
end
# Validates the peer certificates against the authorized certificates.
#
# @api private
#
def valid_peer?
descending_cert_chain = @peer_certs.reverse
authz_ca_certs = decode_cert_bundle(read_file(@ca_path))
if not has_authz_peer_cert(descending_cert_chain, authz_ca_certs)
msg = "The server presented a SSL certificate chain which does not include a " <<
"CA listed in the ssl_client_ca_auth file. "
msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject.to_utf8}.join(', ')} " <<
"Peer Chain: #{descending_cert_chain.collect {|c| c.subject.to_utf8}.join(' => ')}"
@verify_errors << msg
false
else
true
end
end
# Checks if the set of peer_certs contains at least one certificate issued
# by a certificate listed in authz_certs
#
# @return [Boolean]
#
# @api private
#
def has_authz_peer_cert(peer_certs, authz_certs)
peer_certs.any? do |peer_cert|
authz_certs.any? do |authz_cert|
peer_cert.verify(authz_cert.public_key)
end
end
end
# @api private
#
def ssl_certificates_are_present?
Puppet::FileSystem.exist?(Puppet[:hostcert]) && Puppet::FileSystem.exist?(@ca_path)
end
end
Anons79 File Manager Version 1.0, Coded By Anons79
Email: [email protected]