From d7d2c5234fba8583b005cca58f77e1929b0b7efb Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 11 Nov 2014 21:19:50 +0100 Subject: [PATCH 1/3] Implement custom tls options This allows users of the API to either specify a simple CA-File for certificate verification, or a custom SSLContext in order for a more fine-grained control of the TLS options they want to use. As of now, no additional tests were added, but the existing tests were changed to reflect the changes in internal methods, so that they can still pass --- lib/net/ldap.rb | 15 +++++++++++---- lib/net/ldap/connection.rb | 19 +++++++++++++++---- test/test_ldap_connection.rb | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index 87c7eccb..fb24383e 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -537,10 +537,6 @@ def authenticate(username, password) # additional capabilities are added, more configuration values will be # added here. # - # Currently, the only supported argument is { :method => :simple_tls }. - # (Equivalently, you may pass the symbol :simple_tls all by itself, - # without enclosing it in a Hash.) - # # The :simple_tls encryption method encrypts all communications # with the LDAP server. It completely establishes SSL/TLS encryption with # the LDAP server before any LDAP-protocol data is exchanged. There is no @@ -563,6 +559,17 @@ def authenticate(username, password) # The :start_tls like the :simple_tls encryption method also encrypts all # communcations with the LDAP server. With the exception that it operates # over the standard TCP port. + # + # In order to allow verification of server certificates and other TLS-related + # options, the keys :cafile and :ssl_context can be used. + # + # The :cafile option is a single filename that points to one or more + # PEM-encoded certificates. These certificates are used as a certificate auhority + # to verify the server certificates. + # + # For fine-grained control of the TLS settings, it is also possible to use the + # :ssl_context option to pass a custom OpenSSL::SSL::SSLContext. Consult the + # OpenSSL documentation for more information on the available options. def encryption(args) case args when :simple_tls, :start_tls diff --git a/lib/net/ldap/connection.rb b/lib/net/ldap/connection.rb index 508e37ac..987d5500 100644 --- a/lib/net/ldap/connection.rb +++ b/lib/net/ldap/connection.rb @@ -41,9 +41,20 @@ def close end end - def self.wrap_with_ssl(io) + def self.wrap_with_ssl(io, ssl_context = nil, cafile = nil) raise Net::LDAP::LdapError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL - ctx = OpenSSL::SSL::SSLContext.new + if (ssl_context && cafile) + raise Net::LDAP::LdapError, "Please specify only one of ssl_context or cafile" + end + + ctx = ssl_context ? ssl_context : OpenSSL::SSL::SSLContext.new + + # OpenSSL automatically merges the given parameters with the default parameters + # These include verification and some common workarounds + if cafile + ctx.set_params({:ca_file => cafile}) + end + conn = OpenSSL::SSL::SSLSocket.new(io, ctx) conn.connect @@ -85,7 +96,7 @@ def self.wrap_with_ssl(io) def setup_encryption(args) case args[:method] when :simple_tls - @conn = self.class.wrap_with_ssl(@conn) + @conn = self.class.wrap_with_ssl(@conn, args[:ssl_context], args[:cafile]) # additional branches requiring server validation and peer certs, etc. # go here. when :start_tls @@ -102,7 +113,7 @@ def setup_encryption(args) end if pdu.result_code.zero? - @conn = self.class.wrap_with_ssl(@conn) + @conn = self.class.wrap_with_ssl(@conn, args[:ssl_context], args[:cafile]) else raise Net::LDAP::LdapError, "start_tls failed: #{pdu.result_code}" end diff --git a/test/test_ldap_connection.rb b/test/test_ldap_connection.rb index 56dfe813..583a815a 100644 --- a/test/test_ldap_connection.rb +++ b/test/test_ldap_connection.rb @@ -202,7 +202,7 @@ def test_queued_read_setup_encryption_with_start_tls and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) - flexmock(Net::LDAP::Connection).should_receive(:wrap_with_ssl).with(mock). + flexmock(Net::LDAP::Connection).should_receive(:wrap_with_ssl).with(mock, nil, nil). and_return(mock) conn.next_msgid # simulates ongoing query From fd78782fc64ea5c1081c3185f630cc2787a8d442 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 13 Nov 2014 16:27:45 +0100 Subject: [PATCH 2/3] Modify parameters to reflect set_params hash By default, we don't set any parameters (empty or no :tls_options). If the hash is non-empty, we pass it directly to set_params and use the resulting context for creating the TLS socket. --- lib/net/ldap.rb | 31 ++++++++++++++++++++----------- lib/net/ldap/connection.rb | 18 +++++------------- test/test_ldap_connection.rb | 2 +- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index fb24383e..b181d83d 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -560,20 +560,29 @@ def authenticate(username, password) # communcations with the LDAP server. With the exception that it operates # over the standard TCP port. # - # In order to allow verification of server certificates and other TLS-related - # options, the keys :cafile and :ssl_context can be used. - # - # The :cafile option is a single filename that points to one or more - # PEM-encoded certificates. These certificates are used as a certificate auhority - # to verify the server certificates. - # - # For fine-grained control of the TLS settings, it is also possible to use the - # :ssl_context option to pass a custom OpenSSL::SSL::SSLContext. Consult the - # OpenSSL documentation for more information on the available options. + # In order to verify certificates and enable other TLS options, the + # :tls_options hash can be passed alongside :simple_tls or :start_tls. + # This hash contains any options that can be passed to + # OpenSSL::SSL::SSLContext#set_params(). The most common options passed + # should be OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, or the :ca_file option, + # which contains a path to a Certificate Authority file (PEM-encoded). + # + # Example for a default setup without custom settings: + # { + # :method => :simple_tls, + # :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS + # } + # + # Example for specifying a CA-File and only allowing TLSv1.1 connections: + # + # { + # :method => :start_tls, + # :tls_options => { :ca_file => "/etc/cafile.pem", :ssl_version => "TLSv1_1" } + # } def encryption(args) case args when :simple_tls, :start_tls - args = { :method => args } + args = { :method => args, :tls_options => {} } end @encryption = args end diff --git a/lib/net/ldap/connection.rb b/lib/net/ldap/connection.rb index 987d5500..e45e75a6 100644 --- a/lib/net/ldap/connection.rb +++ b/lib/net/ldap/connection.rb @@ -41,19 +41,11 @@ def close end end - def self.wrap_with_ssl(io, ssl_context = nil, cafile = nil) + def self.wrap_with_ssl(io, tls_options = {}) raise Net::LDAP::LdapError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL - if (ssl_context && cafile) - raise Net::LDAP::LdapError, "Please specify only one of ssl_context or cafile" - end - - ctx = ssl_context ? ssl_context : OpenSSL::SSL::SSLContext.new - # OpenSSL automatically merges the given parameters with the default parameters - # These include verification and some common workarounds - if cafile - ctx.set_params({:ca_file => cafile}) - end + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params(tls_options) unless tls_options.empty? conn = OpenSSL::SSL::SSLSocket.new(io, ctx) conn.connect @@ -96,7 +88,7 @@ def self.wrap_with_ssl(io, ssl_context = nil, cafile = nil) def setup_encryption(args) case args[:method] when :simple_tls - @conn = self.class.wrap_with_ssl(@conn, args[:ssl_context], args[:cafile]) + @conn = self.class.wrap_with_ssl(@conn, args[:tls_options]) # additional branches requiring server validation and peer certs, etc. # go here. when :start_tls @@ -113,7 +105,7 @@ def setup_encryption(args) end if pdu.result_code.zero? - @conn = self.class.wrap_with_ssl(@conn, args[:ssl_context], args[:cafile]) + @conn = self.class.wrap_with_ssl(@conn, args[:tls_options]) else raise Net::LDAP::LdapError, "start_tls failed: #{pdu.result_code}" end diff --git a/test/test_ldap_connection.rb b/test/test_ldap_connection.rb index 583a815a..0bffb66a 100644 --- a/test/test_ldap_connection.rb +++ b/test/test_ldap_connection.rb @@ -202,7 +202,7 @@ def test_queued_read_setup_encryption_with_start_tls and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) - flexmock(Net::LDAP::Connection).should_receive(:wrap_with_ssl).with(mock, nil, nil). + flexmock(Net::LDAP::Connection).should_receive(:wrap_with_ssl).with(mock, nil). and_return(mock) conn.next_msgid # simulates ongoing query From acffb16a0e7de49e911a949fb1cb9eef0caaf9fd Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 18 Nov 2014 14:41:31 +0100 Subject: [PATCH 3/3] Add comment for TLS verification in 1.0 release --- lib/net/ldap/connection.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/net/ldap/connection.rb b/lib/net/ldap/connection.rb index e45e75a6..6371f636 100644 --- a/lib/net/ldap/connection.rb +++ b/lib/net/ldap/connection.rb @@ -45,6 +45,9 @@ def self.wrap_with_ssl(io, tls_options = {}) raise Net::LDAP::LdapError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL ctx = OpenSSL::SSL::SSLContext.new + + # By default, we do not verify certificates. For a 1.0 release, this should probably be changed at some point. + # See discussion in https://github.com/ruby-ldap/ruby-net-ldap/pull/161 ctx.set_params(tls_options) unless tls_options.empty? conn = OpenSSL::SSL::SSLSocket.new(io, ctx)