Class: Puppet::SSL::Validator::DefaultValidator Deprecated Private

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/ssl/validator/default_validator.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.

Deprecated.

Perform peer certificate verification against the known CA. If there is no CA information known, then no verification is performed

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(ca_path = Puppet[:ssl_client_ca_auth] || Puppet[:localcacert]) ⇒ DefaultValidator

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.

Creates a new DefaultValidator, optionally with an SSL Configuration and SSL Host.


23
24
25
26
27
28
# File 'lib/puppet/ssl/validator/default_validator.rb', line 23

def initialize(
  ca_path = Puppet[:ssl_client_ca_auth] || Puppet[:localcacert])

  reset!
  @ca_path = ca_path
end

Instance Attribute Details

#last_errorObject (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.


13
14
15
# File 'lib/puppet/ssl/validator/default_validator.rb', line 13

def last_error
  @last_error
end

#peer_certsObject (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/validator/default_validator.rb', line 11

def peer_certs
  @peer_certs
end

#verify_errorsObject (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.


12
13
14
# File 'lib/puppet/ssl/validator/default_validator.rb', line 12

def verify_errors
  @verify_errors
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.

Performs verification of the SSL connection and collection of the certificates for use in constructing the error message if the verification failed. This callback will be executed once for each certificate in a chain being verified.

From the [OpenSSL documentation](www.openssl.org/docs/ssl/SSL_CTX_set_verify.html): The `verify_callback` function is used to control the behaviour when the SSL_VERIFY_PEER flag is set. It must be supplied by the application and receives two arguments: preverify_ok indicates, whether the verification of the certificate in question was passed (preverify_ok=1) or not (preverify_ok=0). x509_store_ctx is a pointer to the complete context used for the certificate chain verification.

See Network::HTTP::Connection for more information and where this class is intended to be used.


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/puppet/ssl/validator/default_validator.rb', line 68

def call(preverify_ok, store_context)
  current_cert = store_context.current_cert
  @peer_certs << current_cert

  # We must make a copy since the scope of the store_context will be lost
  # across invocations of this method.
  if preverify_ok
    # If we've copied all of the certs in the chain out of the SSL library
    if @peer_certs.length == store_context.chain.length
      # (#20027) The peer cert must be issued by a specific authority
      preverify_ok = valid_peer?
    end
  else
    error = store_context.error || 0
    error_string = store_context.error_string || "OpenSSL error #{error}"

    case error
    when OpenSSL::X509::V_OK
      if @hostname
        # 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 current_cert && chain_cert && current_cert.to_der == chain_cert.to_der && !OpenSSL::SSL.verify_certificate_identity(current_cert, @hostname)
          @last_error = Puppet::SSL::CertMismatchError.new(current_cert, @hostname)
          return false
        else
          @verify_errors << "#{error_string} for #{current_cert.subject.to_utf8}"
        end
      else
        @verify_errors << "#{error_string} for #{current_cert.subject.to_utf8}"
      end

    when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID
      # current_crl can be nil
      # https://github.com/ruby/ruby/blob/ruby_1_9_3/ext/openssl/ossl_x509store.c#L501-L510
      crl = store_context.current_crl
      if crl
        if 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}")
          preverify_ok = true
        else
          @verify_errors << "#{error_string} for #{crl.issuer.to_utf8}"
        end
      else
        @verify_errors << error_string
      end
    else
      @verify_errors << "#{error_string} for #{current_cert.subject.to_utf8}"
    end
  end
  preverify_ok
rescue => ex
  @verify_errors << ex.message
  false
end

#decode_cert_bundle(bundle_str) ⇒ Array<OpenSSL::X509::Certificate>

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.

Decode a string of concatenated certificates


153
154
155
156
157
158
159
# File 'lib/puppet/ssl/validator/default_validator.rb', line 153

def decode_cert_bundle(bundle_str)
  re = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m
  pem_ary = bundle_str.scan(re)
  pem_ary.map do |pem_str|
    OpenSSL::X509::Certificate.new(pem_str)
  end
end

#has_authz_peer_cert(peer_certs, authz_certs) ⇒ 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.

Checks if the set of peer_certs contains at least one certificate issued by a certificate listed in authz_certs


196
197
198
199
200
201
202
# File 'lib/puppet/ssl/validator/default_validator.rb', line 196

def has_authz_peer_cert(peer_certs, authz_certs)
  peer_certs.any? do |peer_cert|
    authz_certs.any? do |authz_cert|
      peer_cert.verify(authz_cert.public_key)
    end
  end
end

#read_file(path) ⇒ 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.

read_file makes testing easier.


162
163
164
165
166
167
# File 'lib/puppet/ssl/validator/default_validator.rb', line 162

def read_file(path)
  # https://www.ietf.org/rfc/rfc2459.txt defines the x509 V3 certificate format
  # CA bundles are concatenated X509 certificates, but may also include
  # comments, which could have UTF-8 characters
  Puppet::FileSystem.read(path, :encoding => Encoding::UTF_8)
end

#reset!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.

Resets this validator to its initial validation state. The ssl configuration is not changed.


35
36
37
38
39
40
# File 'lib/puppet/ssl/validator/default_validator.rb', line 35

def reset!
  @peer_certs = []
  @verify_errors = []
  @hostname = nil
  @last_error = nil
end

#setup_connection(connection, ssl_host = Puppet.lookup(:ssl_host)) ⇒ void

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 returns an undefined value.

Registers the instance's call method with the connection.


134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/puppet/ssl/validator/default_validator.rb', line 134

def setup_connection(connection, ssl_host = Puppet.lookup(:ssl_host))
  @hostname = connection.address

  if ssl_certificates_are_present?
    connection.cert_store = ssl_host.ssl_store
    connection.ca_file = @ca_path
    connection.cert = ssl_host.certificate.content
    connection.key = ssl_host.key.content
    connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
    connection.verify_callback = self
  else
    connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
end

#ssl_certificates_are_present?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.


206
207
208
# File 'lib/puppet/ssl/validator/default_validator.rb', line 206

def ssl_certificates_are_present?
  Puppet::FileSystem.exist?(Puppet[:hostcert]) && Puppet::FileSystem.exist?(@ca_path)
end

#valid_peer?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.

Validates the peer certificates against the authorized certificates.


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/puppet/ssl/validator/default_validator.rb', line 173

def valid_peer?
  descending_cert_chain = @peer_certs.reverse
  authz_ca_certs = decode_cert_bundle(read_file(@ca_path))

  if not has_authz_peer_cert(descending_cert_chain, authz_ca_certs)
    msg = "The server presented a SSL certificate chain which does not include a " <<
      "CA listed in the ssl_client_ca_auth file.  "
    msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject.to_utf8}.join(', ')}  " <<
      "Peer Chain: #{descending_cert_chain.collect {|c| c.subject.to_utf8}.join(' => ')}"
    @verify_errors << msg
    false
  else
    true
  end
end