Apache Directory Server Authentication methods
ADS follows the RFC 4513 which describes the authentication methods and security mechanisms of the LDAP protocol.
We will describe more specifically the implementation details of this RFC for documentation purposes.
Authentication methods
LDAP allows two authentication methods :
- Simple
- SASL
It is important to be aware that the Anonymous authentication is just a specific case of a Simple authentication.
Another important point is that neither Simple not SASL offer a protection for the exchanged data.This is handled on another layer.
Securing the communication
As communication are not protected by default, we have to implement one of the two following mechanisms in order to provide some secure communication :
- SSL
- TLS
Both mechanisms are available in Apache DS.
Authorization state
The default authorization state for a session is Anonymous until a BindRequest is successfully completed. An authorized session is immediately moved to an Anonymous state as soon as a BindRequest is received, whatever it's previous state was. That means
Authentication
Authentication is done through a BindRequest, which is described by this ASN.1 portion of the LDAP protocol grammar :
BindRequest ::= [APPLICATION 0] SEQUENCE { version INTEGER (1 .. 127), name LDAPDN, authentication AuthenticationChoice } AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, -- 1 and 2 reserved sasl [3] SaslCredentials, ... } SaslCredentials ::= SEQUENCE { mechanism LDAPString, credentials OCTET STRING OPTIONAL }
Simple authentication
The simple authentication is done through a Bind Request. It provides three authentication mechanisms :
- anonymous
- unauthenticated
- name/password
All those three mechanisms are deduced from the content of the BindRequest, when the Simple field for the AuthenticationChoice is set.
We have four different possibilities here :
BaseDN |
password |
Authentication |
comment |
empty |
empty |
Anonymous |
This is the default server state for any unauthenticated user |
"xyz" |
empty |
Unauthenticated |
For trace purpose only. The server should throw an UnwillingToPerform result |
"xyz" |
abc |
name/password |
Check for this user if it has the pasword in the set of passwords it stores |
empty |
abc |
undefined |
not accepted by ADS |
Implementation
For a simple authentication, the following algorithm is applied :
a BindRequest message is received if the version is not LDAP V3, error if the bindRequest is a simple authentication, then create a BindOperationContext, store the baseDN, the credentials, the controls call the OperationManager.bind() method
Tests
We need to cover all the different cases with the tests. Here are the list of possible tests :
- SimpleBindUserPassword : try to connect using a known user/password and read an entry.
- SimpleBindUserBadPassword : try to connect using a known user but with a bad password: we should get a invalidCredentials error.
- SimpleBindBadUserPassword : try to connect using a user with an invalid DN: we should get a invalidDNSyntax error.
- SimpleBindUnknowUserPassword : try to connect using a unknown user: we should get a invalidCredentials error.
- SimpleBindNoUserNoPassword : covers the anonymous authentication : we should be able to read the rootDSE, but that's it
- SimpleBindUserNoPassword : covers the Unauthenticated case : we should get a UnwillingToPerform error.
- SimpleBindNoUserPassword : not allowed by the server. We should get a invalidCredentials error.
SASL authentication
The SASL authentication is more complex. It's described in RFC 4513. It should be the prefered authentication mechanism used for every LDAP connection, a it guarantee some level of security Simple authentication can't offer.
SASL permits the client to negociate an authentication mechanism using the LDAP protocol during the handshake phase. It's important to understand that SASL by itself does not offer any kind of confidentiality, it's the negociated mechanism which does.
Last, not least, SASL and SSL/TLS can work in pair, in this case we perform SASL EXTERNAL authentication, EXTERNAL being a SASL mechanism.
SASL mechanisms
SASL existing mechanisms are listed here :
name |
Frequency |
link |
ADS implementation |
---|---|---|---|
KERBEROS_V4 |
OBSOLETE |
|
|
GSSAPI |
COMMON |
|
|
SKEY |
OBSOLETE |
|
|
EXTERNAL |
COMMON |
|
|
CRAM-MD5 |
LIMITED |
|
|
ANONYMOUS |
COMMON |
|
|
OTP |
COMMON |
May be implemented |
|
GSS-SPNEGO |
LIMITED |
Paul Leach |
Will be implemented |
PLAIN |
COMMON |
|
|
SECURID |
COMMON |
|
|
NTLM |
LIMITED |
Paul Leach |
|
NMAS_LOGIN |
LIMITED |
Mark G. Gayman |
|
NMAS_AUTHEN |
LIMITED |
Mark G. Gayman |
|
DIGEST-MD5 |
COMMON |
|
|
9798-U-RSA-SHA1-ENC |
COMMON |
|
|
9798-M-RSA-SHA1-ENC |
COMMON |
|
|
9798-U-DSA-SHA1 |
COMMON |
|
|
9798-M-DSA-SHA1 |
COMMON |
|
|
9798-U-ECDSA-SHA1 |
COMMON |
|
|
9798-M-ECDSA-SHA1 |
COMMON |
|
|
KERBEROS_V5 |
COMMON |
Simon Josefsson |
Will be implemented |
NMAS-SAMBA-AUTH |
LIMITED |
Vince Brimhall |
|
A client can request the list of the server supported list of mechanisms by readng the supportedSASLMechanisms attribute from the root DSE, this attribute being available even if the client use an anonymous authorization.
Implementation
In order to get SASL working for a specific mechanism XYZ, we must implement two or three interfaces :
- MechanismHandler, in charge of creating a SaslServer instance, or to get the current one for the ongoing session
- CallbackHandler, if we need to get some information like the Name, Password, etc...
- Optionally, SaslServer, if we need an instance which is not already provided by the underlying VM
The Java 5 JVM provides SaslServer instances for GSSAPI, CramMD5 and DigestMD5 mechanisms. For PLAIN mechanim, we will just implement our own SaslServer.
Client/Server dialog
SASL is a challenge/response system. A client initiate the communication, and the server replied either by validating the request, discarding it or asking for more information. The exchange goes on until we reach a state where the session is either validated or terminated.
From the server point of view, when it receives a BindRequest, it should check if it's a new request, or a reply to a challenge it has sent back to the client. We will store the current state in the LdapSession instance.
The Session state can be one of :
- anonymous
- bind in progress
- bound
The following schema shows the different possible transitions :
We have to store this state into the LdapSession so that we can determinate what to do with the incoming BindRequest received from a client.
Another important point is that each mechanism has to manage its own state machine : more than one Challenge/Response exchange can be necessary in order to get the authentication done. As this is an opaque mechanism from the server point of view, this has to be handled in a plugable mechanism (and this is why we have to implement the three former interfaces).
What is important to know is that if the server receives two consecutive BindRequests, if the second one's mechanism is empty or different from the first one, the Session state is set back to "not bound". Sending an empty mechanism is one way to stop a authentication being processed, if the client wants to redo the authentication with the same mechanism, the other one being to switch to Simple authentication.
In any case, the client can send more than just a BindRequest with the mechanism. In order to reduce the challenge/response roundtrip, the client can send some informations as if it responds to the next server challenge. For instance, if the client wants to use the PLAIN mechanism, it can feed the credentials with an opaque information, containing the authentication Id, the password, and optionally the authorization Id (this is described in RFC 4616) The server can then decipher this opaque data and do the authentication immediately.
ANONYMOUS SASL mechanism implementation
The ANONYMOUS mechanism, if used, will default to a simple bindRequest.
PLAIN SASL mechanism implementation
We don't have an implementation for PLAIN mechanism on the server side in the JDK, so we have to implement it ourself.
There are two possibilities :
- the client don't send any credentials within the first BindRequest : we will have a C/R requesting for these credentials
- the client send the initial BindRequest with a credentials containing the authcid and password information : we will use them to check if this user exists in the backend. Note that it fall back in the SimpleBind user/password authentication.
We don't currently support the authzid field, ie we don't proxy the authorization.
PLAN SaslServer implementation
GSSAPI SASL mechanism implementation
CRAM-MD5 SASL mechanism implementation
DIGEST-MD5 SASL mechanism implementation
NTLM SASL mechanism implementation
EXTERNAL SASL mechanism implementation
{}