class Puppet::SSL::Verifier
Verify an SSL connection.
@api private
Constants
- FIVE_MINUTES_AS_SECONDS
Attributes
Public Class Methods
Create a verifier using an `ssl_context`.
@param hostname [String] FQDN of the server we're attempting to connect to @param ssl_context
[Puppet::SSL::SSLContext] ssl_context
containing CA certs,
CRLs, etc needed to verify the server's certificate chain
@api private
# File lib/puppet/ssl/verifier.rb 18 def initialize(hostname, ssl_context) 19 @hostname = hostname 20 @ssl_context = ssl_context 21 end
Public Instance Methods
OpenSSL
will call this method with the verification result for each cert in the server's chain, working from the root CA to the server's cert. If preverify_ok is `true`, then that cert passed verification. If it's `false` then the current verification error is contained in `store_context.error`. and the current cert is in `store_context.current_cert`.
If this method returns `false`, then verification stops and ruby will raise an `OpenSSL::SSL::Error` with “certificate verification failed”. If this method returns `true`, then verification continues.
If this method ignores a verification error, such as the cert's CRL will be valid within the next 5 minutes, then this method may be called with a different verification error for the same cert.
WARNING: If `store_context.error` returns `OpenSSL::X509::V_OK`, don't assume verification passed. Ruby 2.4+ implements certificate hostname checking by default, and if the cert doesn't match the hostname, then the error will be V_OK. Always use `preverify_ok` to determine if verification succeeded or not.
@param preverify_ok [Boolean] if `true` the current certificate in `store_context`
was verified. Otherwise, check for the current error in `store_context.error`
@param store_context [OpenSSL::X509::StoreContext] The context holding the
verification result for one certificate
@return [Boolean] If `true`, continue verifying the chain, even if that means
ignoring the current verification error. If `false`, abort the connection.
@api private
# File lib/puppet/ssl/verifier.rb 104 def call(preverify_ok, store_context) 105 return true if preverify_ok 106 107 peer_cert = store_context.current_cert 108 109 case store_context.error 110 when OpenSSL::X509::V_OK 111 # chain is from leaf to root, opposite of the order that `call` is invoked 112 chain_cert = store_context.chain.first 113 114 # ruby 2.4 doesn't compare certs based on value, so force to DER byte array 115 if peer_cert && chain_cert && peer_cert.to_der == chain_cert.to_der && !OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname) 116 @last_error = Puppet::SSL::CertMismatchError.new(peer_cert, @hostname) 117 return false 118 end 119 120 when OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH # new in ruby-openssl 2.2.0/ruby 3.0 121 @last_error = Puppet::SSL::CertMismatchError.new(peer_cert, @hostname) 122 return false 123 124 when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID 125 crl = store_context.current_crl 126 if crl && crl.last_update && crl.last_update < Time.now + FIVE_MINUTES_AS_SECONDS 127 Puppet.debug("Ignoring CRL not yet valid, current time #{Time.now.utc}, CRL last updated #{crl.last_update.utc}") 128 return true 129 end 130 end 131 132 # TRANSLATORS: `error` is an untranslated message from openssl describing why a certificate in the server's chain is invalid, and `subject` is the identity/name of the failed certificate 133 @last_error = Puppet::SSL::CertVerifyError.new( 134 _("certificate verify failed [%{error} for %{subject}]") % 135 { error: store_context.error_string, subject: peer_cert.subject.to_utf8 }, 136 store_context.error, peer_cert 137 ) 138 false 139 end
This method is called if `Net::HTTP#start` raises an exception, which could be a result of an openssl error during cert verification, due to ruby's `Socket#post_connection_check`, or general SSL connection error.
@param http [Net::HTTP] connection @param error [OpenSSL::SSL::SSLError] connection error @raise [Puppet::SSL::CertVerifyError] SSL connection failed due to a
verification error with the server's certificate or chain
@raise [Puppet::Error] server hostname does not match certificate @raise [OpenSSL::SSL::SSLError] low-level SSL connection failure @api private
# File lib/puppet/ssl/verifier.rb 64 def handle_connection_error(http, error) 65 raise @last_error if @last_error 66 67 # ruby can pass SSL validation but fail post_connection_check 68 peer_cert = http.peer_cert 69 if peer_cert && !OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname) 70 raise Puppet::SSL::CertMismatchError.new(peer_cert, @hostname) 71 else 72 raise error 73 end 74 end
Return true if `self` is reusable with `verifier` meaning they are using the same `ssl_context`, so there's no loss of security when using a cached connection.
@param verifier [Puppet::SSL::Verifier] the verifier to compare against @return [Boolean] return true if a cached connection can be used, false otherwise @api private
# File lib/puppet/ssl/verifier.rb 30 def reusable?(verifier) 31 verifier.instance_of?(self.class) && 32 verifier.ssl_context.object_id == @ssl_context.object_id 33 end
Configure the `http` connection based on the current `ssl_context`.
@param http [Net::HTTP] connection @api private
# File lib/puppet/ssl/verifier.rb 39 def setup_connection(http) 40 http.cert_store = @ssl_context[:store] 41 http.cert = @ssl_context[:client_cert] 42 http.key = @ssl_context[:private_key] 43 # default to VERIFY_PEER 44 http.verify_mode = if !@ssl_context[:verify_peer] 45 OpenSSL::SSL::VERIFY_NONE 46 else 47 OpenSSL::SSL::VERIFY_PEER 48 end 49 http.verify_callback = self 50 end