Skip to content

Commit 9bc5334

Browse files
author
blackhedd
committed
Added simple TLS encryption.
1 parent 43dcdec commit 9bc5334

File tree

3 files changed

+116
-2
lines changed

3 files changed

+116
-2
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
= Net::LDAP Changelog
22

3+
== Net::LDAP 0.0.3: July xx, 2006
4+
* Added simple TLS encryption.
5+
Thanks to Garett Shulman for suggestions and for helping test.
6+
37
== Net::LDAP 0.0.2: July 12, 2006
48
* Fixed malformation in distro tarball and gem.
59
* Improved documentation.

lib/net/ber.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,22 @@ class StringIO
139139
include Net::BER::BERParser
140140
end
141141

142+
begin
143+
require 'openssl'
144+
class OpenSSL::SSL::SSLSocket
145+
include Net::BER::BERParser
146+
end
147+
rescue LoadError
148+
# Ignore LoadError.
149+
# DON'T ignore NameError, which means the SSLSocket class
150+
# is somehow unavailable on this implementation of Ruby's openssl.
151+
# This may be WRONG, however, because we don't yet know how Ruby's
152+
# openssl behaves on machines with no OpenSSL library. I suppose
153+
# it's possible they do not fail to require 'openssl' but do not
154+
# create the classes. So this code is provisional.
155+
# Also, you might think that OpenSSL::SSL::SSLSocket inherits from
156+
# IO so we'd pick it up above. But you'd be wrong.
157+
end
142158

143159
class String
144160
def read_ber syntax=nil

lib/net/ldap.rb

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818

1919
require 'socket'
2020
require 'ostruct'
21+
22+
begin
23+
require 'openssl'
24+
$net_ldap_openssl_available = true
25+
rescue LoadError
26+
end
27+
2128
require 'net/ber'
2229
require 'net/ldap/pdu'
2330
require 'net/ldap/filter'
@@ -348,14 +355,16 @@ def LDAP::result2string code # :nodoc:
348355

349356

350357
# Instantiate an object of type Net::LDAP to perform directory operations.
351-
# This constructor takes a Hash containing arguments. The following arguments
358+
# This constructor takes a Hash containing arguments, all of which are either optional or may be specified later with other methods as described below. The following arguments
352359
# are supported:
353360
# * :host => the LDAP server's IP-address (default 127.0.0.1)
354361
# * :port => the LDAP server's TCP port (default 389)
355362
# * :auth => a Hash containing authorization parameters. Currently supported values include:
356363
# {:method => :anonymous} and
357364
# {:method => :simple, :username => your_user_name, :password => your_password }
358365
# The password parameter may be a Proc that returns a String.
366+
# * :base => a default treebase parameter for searches performed against the LDAP server. If you don't give this value, then each call to #search must specify a treebase parameter. If you do give this value, then it will be used in subsequent calls to #search that do not specify a treebase. If you give a treebase value in any particular call to #search, that value will override any treebase value you give here.
367+
# * :encryption => specifies the encryption to be used in communicating with the LDAP server. The value is either a Hash containing additional parameters, or the Symbol :simple_tls, which is equivalent to specifying the Hash {:method => :simple_tls}. There is a fairly large range of potential values that may be given for this parameter. See #encryption for details.
359368
#
360369
# Instantiating a Net::LDAP object does <i>not</i> result in network traffic to
361370
# the LDAP server. It simply stores the connection and binding parameters in the
@@ -367,6 +376,7 @@ def initialize args = {}
367376
@verbose = false # Make this configurable with a switch on the class.
368377
@auth = args[:auth] || DefaultAuth
369378
@base = args[:base] || DefaultTreebase
379+
encryption args[:encryption] # may be nil
370380

371381
if pr = @auth[:password] and pr.respond_to?(:call)
372382
@auth[:password] = pr.call
@@ -417,6 +427,51 @@ def authenticate username, password
417427

418428
alias_method :auth, :authenticate
419429

430+
# Convenience method to specify encryption characteristics for connections
431+
# to LDAP servers. Called implicitly by #new and #open, but may also be called
432+
# by user code if desired.
433+
# The single argument is generally a Hash (but see below for convenience alternatives).
434+
# This implementation is currently a stub, supporting only a few encryption
435+
# alternatives. As additional capabilities are added, more configuration values
436+
# will be added here.
437+
#
438+
# Currently, the only supported argument is {:method => :simple_tls}.
439+
# (Equivalently, you may pass the symbol :simple_tls all by itself, without
440+
# enclosing it in a Hash.)
441+
#
442+
# The :simple_tls encryption method encrypts <i>all</i> communications with the LDAP
443+
# server.
444+
# It completely establishes SSL/TLS encryption with the LDAP server
445+
# before any LDAP-protocol data is exchanged.
446+
# There is no plaintext negotiation and no special encryption-request controls
447+
# are sent to the server.
448+
# <i>The :simple_tls option is the simplest, easiest way to encrypt communications
449+
# between Net::LDAP and LDAP servers.</i>
450+
# It's intended for cases where you have an implicit level of trust in the authenticity
451+
# of the LDAP server. No validation of the LDAP server's SSL certificate is
452+
# performed. This means that :simple_tls will not produce errors if the LDAP
453+
# server's encryption certificate is not signed by a well-known Certification
454+
# Authority.
455+
# If you get communications or protocol errors when using this option, check
456+
# with your LDAP server administrator. Pay particular attention to the TCP port
457+
# you are connecting to. It's impossible for an LDAP server to support plaintext
458+
# LDAP communications and <i>simple TLS</i> connections on the same port.
459+
# The standard TCP port for unencrypted LDAP connections is 389, but the standard
460+
# port for simple-TLS encrypted connections is 636. Be sure you are using the
461+
# correct port.
462+
#
463+
# <i>[Note: a future version of Net::LDAP will support the STARTTLS LDAP control,
464+
# which will enable encrypted communications on the same TCP port used for
465+
# unencrypted connections.]</i>
466+
#
467+
def encryption args
468+
if args == :simple_tls
469+
args = {:method => :simple_tls}
470+
end
471+
@encryption = args
472+
end
473+
474+
420475
# #open takes the same parameters as #new. #open makes a network connection to the
421476
# LDAP server and then passes a newly-created Net::LDAP object to the caller-supplied block.
422477
# Within the block, you can call any of the instance methods of Net::LDAP to
@@ -484,7 +539,7 @@ def get_operation_result
484539
# if the bind was unsuccessful.
485540
def open
486541
raise LdapError.new( "open already in progress" ) if @open_connection
487-
@open_connection = Connection.new( :host => @host, :port => @port )
542+
@open_connection = Connection.new( :host => @host, :port => @port, :encryption => @encryption )
488543
@open_connection.bind @auth
489544
yield self
490545
@open_connection.close
@@ -908,10 +963,49 @@ def initialize server
908963
raise LdapError.new( "no connection to server" )
909964
end
910965

966+
if server[:encryption]
967+
setup_encryption server[:encryption]
968+
end
969+
911970
yield self if block_given?
912971
end
913972

914973

974+
#--
975+
# Helper method called only from new, and only after we have a successfully-opened
976+
# @conn instance variable, which is a TCP connection.
977+
# Depending on the received arguments, we establish SSL, potentially replacing
978+
# the value of @conn accordingly.
979+
# Don't generate any errors here if no encryption is requested.
980+
# DO raise LdapError objects if encryption is requested and we have trouble setting
981+
# it up. That includes if OpenSSL is not set up on the machine. (Question:
982+
# how does the Ruby OpenSSL wrapper react in that case?)
983+
# DO NOT filter exceptions raised by the OpenSSL library. Let them pass back
984+
# to the user. That should make it easier for us to debug the problem reports.
985+
# Presumably (hopefully?) that will also produce recognizable errors if someone
986+
# tries to use this on a machine without OpenSSL.
987+
#
988+
# The simple_tls method is intended as the simplest, stupidest, easiest solution
989+
# for people who want nothing more than encrypted comms with the LDAP server.
990+
# It doesn't do any server-cert validation and requires nothing in the way
991+
# of key files and root-cert files, etc etc.
992+
# OBSERVE: WE REPLACE the value of @conn, which is presumed to be a connected
993+
# TCPsocket object.
994+
#
995+
def setup_encryption args
996+
case args[:method]
997+
when :simple_tls
998+
raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available
999+
ctx = OpenSSL::SSL::SSLContext.new
1000+
@conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx)
1001+
@conn.connect
1002+
@conn.sync_close = true
1003+
# additional branches requiring server validation and peer certs, etc. go here.
1004+
else
1005+
raise LdapError.new( "unsupported encryption method #{args[:method]}" )
1006+
end
1007+
end
1008+
9151009
#--
9161010
# close
9171011
# This is provided as a convenience method to make

0 commit comments

Comments
 (0)