class Puppet::SSL::Verifier

Verify an SSL connection.

@api private

Constants

FIVE_MINUTES_AS_SECONDS

Attributes

ssl_context[R]

Public Class Methods

new(hostname, ssl_context) click to toggle source

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

call(preverify_ok, store_context) click to toggle source

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
handle_connection_error(http, error) click to toggle source

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
reusable?(verifier) click to toggle source

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
setup_connection(http) click to toggle source

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