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 19 def initialize(hostname, ssl_context) 20 @hostname = hostname 21 @ssl_context = ssl_context 22 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 105 def call(preverify_ok, store_context) 106 return true if preverify_ok 107 108 peer_cert = store_context.current_cert 109 110 case store_context.error 111 when OpenSSL::X509::V_OK 112 # chain is from leaf to root, opposite of the order that `call` is invoked 113 chain_cert = store_context.chain.first 114 115 # ruby 2.4 doesn't compare certs based on value, so force to DER byte array 116 if peer_cert && chain_cert && peer_cert.to_der == chain_cert.to_der && !OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname) 117 @last_error = Puppet::SSL::CertMismatchError.new(peer_cert, @hostname) 118 return false 119 end 120 121 # ruby-openssl#74ef8c0cc56b840b772240f2ee2b0fc0aafa2743 now sets the 122 # store_context error when the cert is mismatched 123 when OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH 124 @last_error = Puppet::SSL::CertMismatchError.new(peer_cert, @hostname) 125 return false 126 127 when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID 128 crl = store_context.current_crl 129 if crl && crl.last_update && crl.last_update < Time.now + FIVE_MINUTES_AS_SECONDS 130 Puppet.debug("Ignoring CRL not yet valid, current time #{Time.now.utc}, CRL last updated #{crl.last_update.utc}") 131 return true 132 end 133 end 134 135 # 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 136 @last_error = Puppet::SSL::CertVerifyError.new( 137 _("certificate verify failed [%{error} for %{subject}]") % 138 { error: store_context.error_string, subject: peer_cert.subject.to_utf8 }, 139 store_context.error, peer_cert 140 ) 141 false 142 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 65 def handle_connection_error(http, error) 66 raise @last_error if @last_error 67 68 # ruby can pass SSL validation but fail post_connection_check 69 peer_cert = http.peer_cert 70 if peer_cert && !OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname) 71 raise Puppet::SSL::CertMismatchError.new(peer_cert, @hostname) 72 else 73 raise error 74 end 75 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 31 def reusable?(verifier) 32 verifier.instance_of?(self.class) && 33 verifier.ssl_context.object_id == @ssl_context.object_id 34 end
Configure the `http` connection based on the current `ssl_context`.
@param http [Net::HTTP] connection @api private
# File lib/puppet/ssl/verifier.rb 40 def setup_connection(http) 41 http.cert_store = @ssl_context[:store] 42 http.cert = @ssl_context[:client_cert] 43 http.key = @ssl_context[:private_key] 44 # default to VERIFY_PEER 45 http.verify_mode = if !@ssl_context[:verify_peer] 46 OpenSSL::SSL::VERIFY_NONE 47 else 48 OpenSSL::SSL::VERIFY_PEER 49 end 50 http.verify_callback = self 51 end