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
19 def initialize(hostname, ssl_context)
20   @hostname = hostname
21   @ssl_context = ssl_context
22 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
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
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
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
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
31 def reusable?(verifier)
32   verifier.instance_of?(self.class) &&
33     verifier.ssl_context.object_id == @ssl_context.object_id
34 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
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