Skip to main content

Mobomo webinars-now on demand! | learn more.

.ping_graph_img { -moz-box-shadow: 0px 2px 7px #aaa; border: 1px solid #fff; width: 600px; margin: 12px 0; }

I recently had the opportunity to investigate LDAP authentication and particularly SASL (Simple Authentication and Security Layer) binding with DIGEST-MD5 (“SASL/DIGEST-MD5”), because this approach provides network communication security with little sever configuration.

Currently, there are two popular LDAP gems ruby-ldap and ruby-net-ldap that provide API for simple binding on plain or SSL connections; however, in my test, both gems failed to create SASL/DIGEST-MD5 binding correctly.

Summarized below are some of my observations during the test:

  1. In my test with ruby-ldap, I tried several types of SASL binding mechanisms (i.e. DIGEST-MD5, GSS-SPNEGO, CRAM-MD5, etc.). No matter which type I chose, the SASL binding method always returned “Local Error” message. As we know, ruby-ldap is a wrapper for the libldap c library, which redirects all SASL calls to Cyrus SASL library. Since there is no easy way to debug the error, I used wireshark packet sniffer to examine the packets sequences, and found out that the SASL binding request is never sent out after the initial TCP syn/ack/syn-ack packets. It appears that the wrapper for the libldap c library is not working properly.

  2. A closer examination at the ruby-net-ldap reveals that the gem has implemented the full LDAP stack in Ruby even though it does not support SASL binding. The gem utilizes Ruby TCPSocket class to communicate with the LDAP server, and provides LDAP packet parser and constructor. ruby-net-ldap has recently been upgraded to net-ldap, which provides a framework for SASL binding.

Clearly, net-ldap is the only option for the SASL/DIGEST-MD5 implementation. To complete a SASL/DIGEST-MD5 authentication, the client needs to communicate with sever in the following sequence:

  1. The client sends an LDAP binding request with authentication=SASL and mechanism=DIGEST-MD5.
  2. The server generates an initial “digest challenge”, and sends it to the client.
  3. The client calculates “digest response” based on username, password, a random key and some information from the server’s challenge packet and returns the result to the server.
  4. The server validates the hash value and responses with a binding success packet.

The following net-ldap code ensures the correct SASL binding sequence and LDAP packet format:

  #msgid should always start with 0  #LDAP uses BER format to denote data, http://www.vijaymukhi.com/vmis/berldap.htm  #challenge_response is the Proc object which will parse the #digest challenge and provide the correct response to the server.    def bind_sasl auth    mech,cred,chall = auth[:mechanism],auth[:initial_credential],auth[:challenge_response]    raise LdapError.new( "invalid binding information" ) unless (mech && cred && chall)    n = 0    loop {       #start with initial credential for binding request       msgid = next_msgid.to_ber       #construct the LDAP payload from bottom up       sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)       request = [LdapVersion.to_ber, "".to_ber, sasl].to_ber_appsequence(0)       request_pkt = [msgid, request].to_ber_sequence       #send out the packet through tcp socket       @conn.write request_pkt       #read server challenge from the socket and parse out the LDAP payload       (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" )       return pdu.result_code unless pdu.result_code == 14 # saslBindInProgress       raise LdapError.new("sasl challenge overflow") if ((n+= 1) > MaxSaslChallenges)       #decode the LDAP payload and pass it to challenlge_response proc       cred = chall.call( pdu.result_server_sasl_creds )       #this credential will be send to server as the challenge response     }  end  

In addition to implementing communication sequence, I forked a gem pyu-ruby-sasl from ruby-sasl to parse server challenge and generate the correct digest response and fixing several issues while connecting to Active Directory. The following example shows how to parse server challenge and generate client digest response.

  def sasl_digest_md5(bind_dn, password, host)        challenge_response = Proc.new do |cred|      pref = SASL::Preferences.new :digest_uri =>  "ldap/#{host}", :username => bind_dn, :has_password? => true, :password =>password         sasl = SASL.new("DIGEST-MD5", pref)         response = sasl.receive("challenge", cred)         response[1]      end      {:mechanism => “DIGEST-MD5”, :initial_credential => ‘’, :challenge_response => challenge_response}  end  

After I implemented the above methods, I only need following lines to perform a SASL/DIGEST-MD5 binding to Active Directory server:

  conn = Net::LDAP::Connection.new(:host => “pyub8bb.score.local”, :port => 389)  result = conn.bind_sasl(sasl_digest_md5(‘SCORE\pyu’, ‘password’, ‘pyub8bb.score.local’)) == 0  

Graph 1

image
*Graph 1 demonstrates the SASL biding packet sequence between client and server:

Graph 2

image

Graph 3

image
*Graph 2 shows the server digest-change packet format, and *Graph 3 shows the content.

Graph 4

image

Graph 5

image
*Graphs 4 and 5 show the client challenge response packet format and content.

In our latest OSS gem OmniAuth, we included several implementations for different LDAP authentication mechanisms in oa-enterprise sub-gem.

Thanks to all the good gems, implementing LDAP authentication is still fairly easy in Ruby. However, we should not forget about Java; its JNDI package provides all LDAP authentication mechanisms including much more complicated GSSAPI/Kerberos authentication. We should seriously consider using JRuby.

Categories
Author

If you are an administrator of a large enterprise with thousands of users and hundreds of business units, one of the most challenging tasks you are facing is how to manage those users and groups across hundreds of applications. It would be a nightmare to create users and groups one-by-one in your presently account, not to mention associating all the users with the right groups.

If you are an administrator who worries about sensitive login credentials duplicated everywhere, or you are a user who hates to remember different user names and passwords for all the applications your company has, you might be hesitate to create yet another account for presently.

Now those worries are over, Presently proudly announce that it is LDAP ready. Since most large enterprises already has directory services in place that supports LDAP, while for smaller organizations, setting up an OpenLDAP server or Microsoft Active Directory is quite easy.

Just in case you are hearing about LDAP for the first time, the Lightweight Directory Access Protocol (LDAP) is an open standard that provides an extendable architecture for storage and management of directory information. Widely accepted and fast-growing, LDAP has become the de facto industry standard for accessing directory information over a TCP/IP network. For more details, please visit "LDAP Wiki":http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol .

Let us see what you can achieve by integrating your existing LDAP service with Presently:

* Account admin can easily batch import users and groups from LDAP
directory.
* Users are associated with groups automatically during the import
process according to your organization structure.
* Utilize LDAP to authenticate users directly instead of using local
accont credentials. So no sensitive login credentials are stored locally. And for users it means no more extra logins to remember.
* New user will be added on-the-fly during the authentication process, if his account information has not yet been imported.
These features help to eliminate all the administrator's headaches , and improve users' experience with Presently.

Here is how you can setup LDAP for your account:

1. Login as an admin user of your account.
2. Go to admin tab and click LDAP Settings on the right menu bar.
3. Enter your LDAP information in the following LDAP Settings Form.
4. Save the settings after you finish.

test [Present.ly]

Definition of terms in the LDAP Settings Form:

* __Enable__: Enable LDAP on your account.
* __Server__: Type the IP address of the LDAP directory. Use either the host name or dotted decimal format.
* __Port__: Type the TCP/IP port on which the LDAP server will accept a connection from an LDAP client.
* __Encryption__: Select the communcation encryption method, can be "No Encryption", "SSL" or "StartTLS"
* __Authentication__: If your LDAP server allow anonymous access select "None", otherwise select "Simple" and provide Bind DN and password.
* __Bind DN__: Type the distinguished name (DN) of the directory administrator that allows presently to update information. You must use the LDAP string representation for distinguished names (for example, cn=Chris Smith,dc=intridea,dc=com ).
* __Password__: Type the directory administrator's password.
* __LDAP User Auth.__: Enable LDAP user authenticate for this account.
* __User search base__: Type the distinguished name (DN) of the entry in the directory information tree (DIT) under which user information is stored. You must use the LDAP string representation for distinguished names (for example, ou=people, dc=intridea,dc=com ).
* __Group search base__: Type the distinguished name (DN) of the entry in the directory information tree (DIT) under which group information is stored. You must use the LDAP string representation for distinguished names (for example, ou=groups, dc=intridea,dc=com ).
* __User unique ID name__: Type the user id name defined in LDAP user object schema. Usually it's 'uid', or 'sAMAccountName' for Microsoft Active Directory.
* __Group member ID name__: Type the group member name defined in LDAP group object schema. Usually it's 'member'.

Please email ping_at_intridea_dot_com for questions, suggestions or comments.

Categories
Author
1
Subscribe to Ldap