verify LDAP user password in Java - java

I'm attempting to verify a user's password in some Java code:
private void bindToLdap(String un, String pw) throws NamingException, AuthenticationException {
log.debug ("Doing LDAP bind as user");
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.PROVIDER_URL, LdapConns.openLdapUrl);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "uid="+un+",ou=people,<base OU>");
env.put(Context.SECURITY_CREDENTIALS, pw);
DirContext dctx = new InitialDirContext(env);
log.debug("Context below:");
log.debug(dctx.getEnvironment().toString());
dctx.lookup("ou=people");
log.debug("connection context:");
log.debug(dctx.getEnvironment().toString());
log.debug ("Bound as user?");
}
Everything I've read so far indicates that I should take an exception when instantiating the DirContext if I've entered a bad password. This is not the case - I get a valid DirContext even if I enter a known bad password. I expect to catch an AuthenticationException at the dctx.lookup, I actually catch a NamingException.
In the debug log, I show:
[usermgmt.action.ChangeInternalPassword:bindToLdap:242] Context below:
[usermgmt.action.ChangeInternalPassword:bindToLdap:243] {java.naming.provider.url=ldaps://<ldap server>:636/, java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory, java.naming.security.authentication=simple, java.naming.security.principal=nononyen_test, java.naming.factory.url.pkgs=org.apache.naming, java.naming.security.credentials=fubar!}
How do I verify that the password is actually correct? Do I need to perform a search on the LDAP db after binding to take the exception?
The LDAP database is OpenLDAP.

To determine whether an authentication ID and password is correct, applications must establish a connection to the directory server and then transmit a bind request and examine the response. The bind response from the server will contain an integer result code indicating the success or failure of the bind request, and may also contain response controls with some extra information about the status of the user entry.

Related

See attributes of currently connected Ldap user

I connect to an LDAP server as Bob:
Hashtable props = new Hashtable();
props.put(Context.SECURITY_PRINCIPAL, "cn=Bob,cn=Users,dc=myCompany,dc=com");
props.put(Context.SECURITY_CREDENTIALS, "Password1");
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
props.put(Context.PROVIDER_URL, url);
InitialLdapContext context = new InitialLdapContext(props, null);
Now I want to see Bob's name and email address.
I was expecting to find a method on context to return the attributes of the currently connected user, but I can't find one.
Bob doesn't have permission to search the directory, so I can't use context.search after connecting.
Is there a way to get these attributes without making another call to the LDAP server, or having to connect as someone else first?
You need to use the RFC 4532 'whoami' extended operation, OID=1.3.6.1.4.1.4203.1.11.3. You'll need to write yourself an ExtendedRequest and ExtendedResponse class for that (sorry can't post mine, but it's simple), and use it as follows:
WhoAmIExtendedResponse response = (WhoAmIExtendedResponse)context.extendedOperation(new WhoAmIExtendedRequest());
and then get the authZId out of the response. That's the DN of the current user, prefixed by "dn:". You then get his attributes via context.getAttributes("...")

What are the accepted SECURITY_PRINCIPAL formats for LDAP Authentication against Active Directory?

I am trying to authenticate a user through LDAP against Active Directory. Following is the code snippet I use:
private DirContext bindAsUser(String bindPrincipal, String password) {
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
env.put(Context.PROVIDER_URL, bindUrl);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.REFERRAL, "follow");
try {
return new InitialLdapContext(env, null);
} catch (NamingException e) {
e.printStackTrace()
}
}
The code for binding works if I provide:
Down-Level Logon Name, i.e. NetBIOSDomainName\sAMAccountName (e.g. domain\username), or
userPrincipalName (e.g. username#abc.com), or
distinguishedName (e.g. CN=username,OU=xxx,DC=abc,DC=com), or
objectSid (e.g. S-1-5-21-3623811015-3361044348-30300820-1013)
as the SECURITY_PRINCIPAL, while it failed if sAMAccountName (e.g. username) was used (I guess only the names which are unique within the forest are valid).
So what are the accepted patterns for SECURITY_PRINCIPAL? I searched a few similar questions, but none provide reference to official AD/LDAP documents. Or is it a configuration which I could lookup somewhere? Thanks!
From [MS-ADTS: Active Directory Technical Specification], the official doc for AD I guess.
http://msdn.microsoft.com/en-us/library/cc223499.aspx
Section "5.1.1.1.1 Simple Authentication" lists all the name forms supported by simple authentication.
I think you need check LDAP Principal Template. It specifies the principal authentication template required by your LDAP server. The principal authentication template is the format in which the authentication information for the security principal (the person who is logging in) must be passed to the LDAP server. The default value is ${email}, which is the format required by Microsoft Active Directory. Other LDAP servers require different authentication templates. Check with your network administrator to learn more about your LDAP server.

Change AD user expired password in Java

I'm using JNDI to change LDAP user's password. In most cases (when user's password isn't expired) this code works just fine:
public InitialLdapContext connect(String url, String securityPrincipal, String password) throws AuthenticationException, NamingException {
System.setProperty("javax.net.ssl.trustStore", truststore);
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_PRINCIPAL, "EE\\" + securityPrincipal);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.SECURITY_PROTOCOL, "ssl");
env.put("java.naming.ldap.version", "3");
env.put(Context.REFERRAL, "follow");
return new InitialLdapContext(env,null);
}
But when user with expired password tries to change it my app throws:
Exception: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 773, v1db1 ]
com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3041)
com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2987)
com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2789)
com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2703)
com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:293)
com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:175)
com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:193)
com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:136)
com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:66)
So my question is: Is it possible to change LDAP expired passwords? If it's possible, then tell how.
Thanx for help!
If you're using the password policy overlay you have to use the change-password extended request. It's not supported in the JDK but I've posted code for it in the Oracle Java JNDI forum.
The problem was resolved by creating Super User in Ad, which has rights to change every AD password. And when AD user password is expired, then the Super User changes his password.

How to bind in Java using DN and Password in LDAP?

I want to search a user from LDAP and after getting the user I want to connect (validate)
that particular user using his DN and Password
I have successfully getting the DN but dont know how to bind it?
Here is an example that I took from the official documentation :
// Set up the environment for creating the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
DirContext ctx = new InitialDirContext(env);
You have to choose your right authentication model. I have tried it before and worked fine.
The LDAP bind() operation corresponds to the following in JNDI:
Constructing an InitialDirContext or InitialLdapContext with enough information in the environment to cause a login, i.e. a security principal and credentials, or
Calling reconnect() on an LdapContext initially obtained without any security information in the environment, or with security information relating to a different principal, but whose environment has subsequently been modified.
When a connection is made to a directory server using LDAP, the connection state is unauthenticated. Requests can be transmitted on an unauthenticated connection, assuming the server administrators permit unauthenticated requests. The BIND request is used to change authentication state of a connection.
Here is an example of searching and authenticating using the UnboundID LDAP SDK: SimpleBindExample.java. This example searches for an entry given a base object, naming attribute, and username, and then attempts to authenticate using a simple bind. Examples using a SASL bind could be constructed just as easily.
If you already have LdapContext opened using your credentials, you can copy it, change principal+credential in its environment and try reconnect:
LdapContext userContext = ldapContext.newInstance(null); // copy context
userContext.addToEnvironment(InitialDirContext.SECURITY_PRINCIPAL, userDn);
userContext.addToEnvironment(InitialDirContext.SECURITY_CREDENTIALS, password);
userContext.reconnect(null); // throws NamingException if creds wrong
userContext.close();
If it throws NamingException, credentials are wrong. It it is successful, credentials are ok. ;)
(This is useful if you have only LdapContext, but not the InitialDirContext, available.)

How to encrypt passwords for JConsole's password file

I am using the JConsole to access my application MBeans and i use the the password.properties file. But as per the Sun's specification this file contains passwords in clear text formats only.
com.sun.management.jmxremote.password.file=<someLocation>/password.properties
Now i would want to encrypt the password and use it for the JMX user authentication from JConsole (the username and password fields in Remote section). I could use any pre-defined encryption logic or my own encryption algorithms.
Does anyone know of any such interception to change the plain text password to encrypted one so that the JMX Framework too knows about the encrypted password?
My Current password file:
guest guest
admin admin
With Encryption it should look like:
guest ENC(RjqpRYbAOwbAfAEDBdHJ7Q4l/GO5IoJidZctNT5oG64=)
admin ENC(psg3EnDei6fVRuqHeLwOqNTgIWkwQTjI2+u2O7MXXWc=)
You can use the configuration parameter com.sun.management.jmxremote.login.config in the management.properties file (see %JAVA_HOME%/lib/management/management.properties) to configure which Authenticator and LoginModule to use.
The default is the following:
JMXPluggableAuthenticator {
com.sun.jmx.remote.security.FileLoginModule required;
};
which reads plain text password file jmxremote.password. Since the com.sun.jmx.remote.security.JMXPluggableAuthenticator can be reconfigured
to use any LoginModule implementation, you are free to either choose an existing LoginModule or to implement your own
which uses encrypted password files.
To reimplement FileLoginModule, you should have a look at the attemptAuthentication(boolean) method, which
actually performs the authentication and which you probably are going to replace. Implement the javax.security.auth.spi.LoginModule interface
and use the given CallbackHandler (you will get it from the init() method) to ask for a username and password. Encrypt/hash the received password and compare it against the one read from your encrypted password file. Pseudo code:
public class EncryptedFileLoginModule implements LoginModule {
#Override
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
}
public boolean login() throws LoginException {
attemptLogin();
if (username == null || password == null) {
throw new LoginException("Either no username or no password specified");
}
MessageDigest instance = MessageDigest.getInstance("SHA-1");
byte[] raw = new String(password).getBytes();
byte[] crypted = instance.digest(raw);
// TODO: Compare to the one stored locally
if (!authenticated) throw new LoginException();
return true;
}
private void attemptLogin() throws LoginException {
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("username");
callbacks[1] = new PasswordCallback("password", false);
callbackHandler.handle(callbacks);
username = ((NameCallback) callbacks[0]).getName();
user = new JMXPrincipal(username);
char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
((PasswordCallback) callbacks[1]).clearPassword();
}
However, as this is already the server-side, the password would afaik be still transferred in plain text if you don't enforce
JMX over SSL. So, either enforce SSL or use another transport protocol mechanism which encodes the credentials before
transmitting them over the wire.
To conclude, it's perhaps much better to rely on existing authentication mechanisms provided by JAAS. If, for example,
you're running in a local Windows environment, you can easily use the NTLoginModule for auto-login. But it only works on local machine.
Create a file c:/temp/mysecurity.cfg:
MyLoginModule {
com.sun.security.auth.module.NTLoginModule REQUIRED debug=true debugNative=true;
};
Next, configure the jmxremote.access file to contain the usernames or roles you wish to grant access to your JMX server:
monitorRole readonly
controlRole readwrite ...
mhaller readonly
(I recommend to enable debug mode until it works. You will see all the user names, domain names and group names when a user tries to log in)
Set the following JVM arguments for your server:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8686
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=true
-Djava.net.preferIPv4Stack=true
-Djava.security.auth.login.config=c:/temp/mysecurity.cfg
-Dcom.sun.management.jmxremote.login.config=MyLoginModule
Start up your application and try to connect using JConsole or VisualVM.
Note that JConsole, you will need to specify a username and a password, although it's not going to be used. Any password and any username will work.
The reason is because jconsole will try to authenticate with null username and null password, which is blocked explicitly.
VisualVM does a better job by using empty strings for username and password when none are entered by the user.
Also note that the NTLoginModule does not work when connecting remotely, i think you would have to use a more sophisticated login module,
but Sun already provides enough of them:
com.sun.security.auth.module.Krb5LoginModule: Authenticates users using the Kerberos protocols
com.sun.security.auth.module.LdapLoginModule: (new in Java 6): Performs authentication against an LDAP server by specifying technical connection user
com.sun.security.auth.module.JndiLoginModule: Performs authentication against an LDAP server registered in the JNDI context
com.sun.security.auth.module.KeyStoreLoginModule: Authenticates users by using a Java Keystore. Supports PIN or smart card authentication.
You will want to have a look at the LdapLoginModule

Categories

Resources