Class: Puppet::SSL::Verifier Private

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/ssl/verifier.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Verify an SSL connection.

Constant Summary collapse

FIVE_MINUTES_AS_SECONDS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

5 * 60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hostname, ssl_context) ⇒ Verifier

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create a verifier using an ‘ssl_context`.

Parameters:

  • hostname (String)

    FQDN of the server we’re attempting to connect to

  • ssl_context (Puppet::SSL::SSLContext)

    ssl_context containing CA certs, CRLs, etc needed to verify the server’s certificate chain



19
20
21
22
# File 'lib/puppet/ssl/verifier.rb', line 19

def initialize(hostname, ssl_context)
  @hostname = hostname
  @ssl_context = ssl_context
end

Instance Attribute Details

#ssl_contextObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



11
12
13
# File 'lib/puppet/ssl/verifier.rb', line 11

def ssl_context
  @ssl_context
end

Instance Method Details

#call(preverify_ok, store_context) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.

Parameters:

  • preverify_ok (Boolean)

    if ‘true` the current certificate in `store_context` was verified. Otherwise, check for the current error in `store_context.error`

  • store_context (OpenSSL::X509::StoreContext)

    The context holding the verification result for one certificate

Returns:

  • (Boolean)

    If ‘true`, continue verifying the chain, even if that means ignoring the current verification error. If `false`, abort the connection.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/puppet/ssl/verifier.rb', line 105

def call(preverify_ok, store_context)
  return true if preverify_ok

  peer_cert = store_context.current_cert

  case store_context.error
  when OpenSSL::X509::V_OK
    # 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 peer_cert && chain_cert && peer_cert.to_der == chain_cert.to_der && !OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname)
      @last_error = Puppet::SSL::CertMismatchError.new(peer_cert, @hostname)
      return false
    end

  # ruby-openssl#74ef8c0cc56b840b772240f2ee2b0fc0aafa2743 now sets the
  # store_context error when the cert is mismatched
  when OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH
    @last_error = Puppet::SSL::CertMismatchError.new(peer_cert, @hostname)
    return false

  when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID
    crl = store_context.current_crl
    if crl && 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}")
      return true
    end
  end

  # 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
  @last_error = Puppet::SSL::CertVerifyError.new(
    _("certificate verify failed [%{error} for %{subject}]") %
    { error: store_context.error_string, subject: peer_cert.subject.to_utf8 },
    store_context.error, peer_cert
  )
  false
end

#handle_connection_error(http, error) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.

Parameters:

  • http (Net::HTTP)

    connection

  • error (OpenSSL::SSL::SSLError)

    connection error

Raises:

  • (Puppet::SSL::CertVerifyError)

    SSL connection failed due to a verification error with the server’s certificate or chain

  • (Puppet::Error)

    server hostname does not match certificate

  • (OpenSSL::SSL::SSLError)

    low-level SSL connection failure



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/puppet/ssl/verifier.rb', line 65

def handle_connection_error(http, error)
  raise @last_error if @last_error

  # ruby can pass SSL validation but fail post_connection_check
  peer_cert = http.peer_cert
  if peer_cert && !OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname)
    raise Puppet::SSL::CertMismatchError.new(peer_cert, @hostname)
  else
    raise error
  end
end

#reusable?(verifier) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.

Parameters:

Returns:

  • (Boolean)

    return true if a cached connection can be used, false otherwise



31
32
33
34
# File 'lib/puppet/ssl/verifier.rb', line 31

def reusable?(verifier)
  verifier.instance_of?(self.class) &&
    verifier.ssl_context.equal?(@ssl_context) # same object?
end

#setup_connection(http) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Configure the ‘http` connection based on the current `ssl_context`.

Parameters:

  • http (Net::HTTP)

    connection



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/puppet/ssl/verifier.rb', line 40

def setup_connection(http)
  http.cert_store = @ssl_context[:store]
  http.cert = @ssl_context[:client_cert]
  http.key = @ssl_context[:private_key]
  # default to VERIFY_PEER
  http.verify_mode = if !@ssl_context[:verify_peer]
                       OpenSSL::SSL::VERIFY_NONE
                     else
                       OpenSSL::SSL::VERIFY_PEER
                     end
  http.verify_callback = self
end