Skip to content

Commit 1f632ae

Browse files
committed
(PUP-8213) Display correct message when certname is mismatched
Previously, if the server's cert did not match the hostname we tried to connect to, puppet would output a confusing message: certificate verify failed: [ok for /CN=XXX] This occurs because ruby 2.4 introduced a new security feature whereby the cert is automatically verified during the call to `SSLSocket#connect`[1]. In earlier ruby versions, the application had to call `SSLSocket#post_connection_check`, but of course, many people forgot to, or didn't know they had to, leading to MITM vulnerabilities. However, when a mismatch occurs, ruby 2.4 invokes our `verify_callback` with `preverify_ok=false`, but `store_context.error=0` which is `OpenSSL::SSL::V_OK`. Ruby then raises an `SSLError` whose message is 'certificate verify failed', which matches the first "if" statement in our error handler. This commit changes the order so that if an SSLError is rescued, we check to see if there's a host mismatch first. If not, we check if there were *any* verify errors, or raise the original error. This change is compatible with ruby versions prior to 2.4, because both `SSLSocket#post_connection_check` and our error handler use `OpenSSL::SSL.verify_certificate_identity` to detect the certname mismatch. [1] ruby/openssl#60
1 parent 5efd824 commit 1f632ae

File tree

2 files changed

+26
-8
lines changed

2 files changed

+26
-8
lines changed

lib/puppet/network/http/connection.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,7 @@ def with_connection(site, &block)
318318
# can be nil
319319
peer_cert = @verify.peer_certs.last
320320

321-
if error.message.include? "certificate verify failed"
322-
msg = error.message
323-
msg << ": [" + @verify.verify_errors.join('; ') + "]"
324-
raise Puppet::Error, msg, error.backtrace
325-
elsif peer_cert && !OpenSSL::SSL.verify_certificate_identity(peer_cert.content, site.host)
321+
if peer_cert && !OpenSSL::SSL.verify_certificate_identity(peer_cert.content, site.host)
326322
valid_certnames = [peer_cert.name, *peer_cert.subject_alt_names].uniq
327323
if valid_certnames.size > 1
328324
expected_certnames = _("expected one of %{certnames}") % { certnames: valid_certnames.join(', ') }
@@ -332,6 +328,10 @@ def with_connection(site, &block)
332328

333329
msg = _("Server hostname '%{host}' did not match server certificate; %{expected_certnames}") % { host: site.host, expected_certnames: expected_certnames }
334330
raise Puppet::Error, msg, error.backtrace
331+
elsif !@verify.verify_errors.empty?
332+
msg = error.message
333+
msg << ": [" + @verify.verify_errors.join('; ') + "]"
334+
raise Puppet::Error, msg, error.backtrace
335335
else
336336
raise
337337
end

spec/unit/network/http/connection_spec.rb

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def verify_errors
113113
WebMock.enable!
114114
end
115115

116-
it "should provide a useful error message when one is available and certificate validation fails", :unless => Puppet.features.microsoft_windows? do
116+
it "should provide a useful error message when one is available and certificate validation fails in ruby 2.4 and up", :unless => Puppet.features.microsoft_windows? do
117117
connection = Puppet::Network::HTTP::Connection.new(
118118
host, port,
119119
:verify => ConstantErrorValidator.new(:fails_with => 'certificate verify failed',
@@ -124,13 +124,13 @@ def verify_errors
124124
end.to raise_error(Puppet::Error, "certificate verify failed: [shady looking signature]")
125125
end
126126

127-
it "should provide a helpful error message when hostname was not match with server certificate", :unless => Puppet.features.microsoft_windows? do
127+
it "should provide a helpful error message when hostname does not match server certificate before ruby 2.4", :unless => Puppet.features.microsoft_windows? do
128128
Puppet[:confdir] = tmpdir('conf')
129129

130130
connection = Puppet::Network::HTTP::Connection.new(
131131
host, port,
132132
:verify => ConstantErrorValidator.new(
133-
:fails_with => 'hostname was not match with server certificate',
133+
:fails_with => "hostname 'myserver' does not match the server certificate",
134134
:peer_certs => [Puppet::SSL::CertificateAuthority.new.generate(
135135
'not_my_server', :dns_alt_names => 'foo,bar,baz')]))
136136

@@ -142,6 +142,24 @@ def verify_errors
142142
end
143143
end
144144

145+
it "should provide a helpful error message when hostname does not match server certificate in ruby 2.4 or greater", :unless => Puppet.features.microsoft_windows? do
146+
Puppet[:confdir] = tmpdir('conf')
147+
148+
connection = Puppet::Network::HTTP::Connection.new(
149+
host, port,
150+
:verify => ConstantErrorValidator.new(
151+
:fails_with => "certificate verify failed",
152+
:peer_certs => [Puppet::SSL::CertificateAuthority.new.generate(
153+
'not_my_server', :dns_alt_names => 'foo,bar,baz')]))
154+
155+
expect do
156+
connection.get('request')
157+
end.to raise_error(Puppet::Error) do |error|
158+
error.message =~ /\AServer hostname 'my_server' did not match server certificate; expected one of (.+)/
159+
expect($1.split(', ')).to match_array(%w[DNS:foo DNS:bar DNS:baz DNS:not_my_server not_my_server])
160+
end
161+
end
162+
145163
it "should pass along the error message otherwise" do
146164
connection = Puppet::Network::HTTP::Connection.new(
147165
host, port,

0 commit comments

Comments
 (0)