2016-02-19

Changing Samba4 (AWS Simple AD) user password using Java


I recently had to setup a AWS Simple AD to replace an old OpenLDAP that served us for years. I may post more about this in the following days, but if you have interest in any specific topic, please post here in the comments and I can speed it up ;)

Before


During this migration, I had to adjust a simple app I did in the past to reset and change users' passwords. To set a user's password, this application was just calculating the new password and setting in the userPassword attribute, like this:


LdapConnection connection = new LdapNetworkConnection( ldapserver, 389, false )) {
connection.bind(user, pass);

EntryCursor cursor = connection.search( baseDN, "(uid=" + user.getUsername() + ")", SearchScope.ONELEVEL, "*" );

if ( !cursor.next() ) {
  logger.debug("no user with uid '{}' found.", user.getUsername());
  return false;
}

Entry entry = cursor.get();

byte[] newPass = PasswordUtil.createStoragePassword(user.getPassword(), LdapSecurityConstants.HASH_METHOD_SSHA);

entry.put("userPassword", newPass);
entry.put("shadowLastChange", new Date().getTime() / (1000 * 60 * 60 * 24) + "");

logger.debug("changing password for user '{}'...", user.getUsername());
connection.modify(entry.getDn(), 
  extractModification("userPassword", entry), 
  extractModification("shadowLastChange", entry)
); 
 
connection.unBind();

The problem


When checking the Simple AD user's attributes, there was no userPassword anymore, so I tried to find a way to change the password. As I was already using de Apache LDAP API, and it supports LDAP Password Modify Extended Operation, it seemed a good way to go as I wouldn't need to worry about implementation-specific problems.

I just forgot to check if Simple AD/Samba4 supports it :( And it didn't, AFAICS.

Believe me, I tried a lot of code combinations to use the 1.3.6.1.4.1.4203.1.11.1 extended operation as there is no doc (and even reported a bug), but could not make it work.

There is hope


After spending a lot of time trying to figure it out, I found this archive:

http://linux.samba.narkive.com/3nPys6p5/samba-sambar4-user-creation-with-ldap-and-initial-password

There, Tomas Mueller says:
I do not have a AD available today , i'll try tomorrow. i've found this
about the userPassword attribute on msdn:
http://msdn.microsoft.com/en-us/library/cc223249(prot.20).aspx
<http://msdn.microsoft.com/en-us/library/cc223249%28prot.20%29.aspx>

searching the sourcecode about userPassword i've found this comment in
password_hash.c:

* Notice: unlike the real AD which only supports the UTF16 special based
* 'unicodePwd' and the UTF8 based 'userPassword' plaintext attribute we
* understand also a UTF16 based 'clearTextPassword' one.
* The latter is also accessible through LDAP so it can also be set by
external
* tools and scripts. But be aware that this isn't portable on non
SAMBA 4 ADs!

"The latter is also accessible through LDAP" implies that unicodePwd and
userPassword aren't.

- Thomas

So, after reading this, I tried clearTextPassword and it worked!!! It seems that Samba4 doesn't implement the extended operation, but created this fake attribute to allow password set through LDAP protocol.

Follows the base of the final code:
LdapConnection connection = new LdapNetworkConnection( ldapserver, 389, false )) {
connection.bind(user, pass);

SearchRequest searchRequest = getUserSearch(user);
SearchCursor cursor = connection.search( searchRequest );
if ( !cursor.next() ) {
  logger.debug("no user with sAMAccountName '{}' found.", user.getUsername());
  throw new InvalidCredentialsException();
}

Entry entry = cursor.getEntry();
Dn userBeingChangedDN = entry.getDn();

logger.debug("changing password for user '{}'...", user.getUsername());
entry.put("clearTextPassword", newPassword.getBytes("UTF-16LE"));
connection.modify(entry.getDn(), extractModification("clearTextPassword", entry));

try (LdapConnection connectionForChange = getConnection()) {
  connectionForChange.bind(userBeingChangedDN, user.getPassword());
  connectionForChange.unBind();
} catch (LdapException e) {
  throw new InvalidCredentialsException();
}

connection.unBind();


Hope it helps!

If you want to know more about the whole Simple AD setup, please comment asking what you want.

Extras



private static Modification extractModification(String attrAlias, Entry modifiedEntry) {
  Modification mod = new DefaultModification();
  mod.setOperation(ModificationOperation.REPLACE_ATTRIBUTE);
  Attribute attribute = modifiedEntry.get(attrAlias);
  mod.setAttribute(attribute);
  return mod; 
}

private SearchRequest getUserSearch(User user) throws LdapInvalidDnException, LdapException {
  SearchRequest searchRequest = new SearchRequestImpl();
  searchRequest.setBase( new Dn("CN=Users,DC=visagio,DC=company") );
  searchRequest.setFilter( "(sAMAccountName=" + user.getUsername() + ")" );
  searchRequest.setScope( SearchScope.ONELEVEL );
  searchRequest.addAttributes( "*" );
  return searchRequest;
}


2 comments:

R Meske said...

Hello Bruno,

Thank you for posting this, it did answer some questions I had about modifying an existing password change function to integrate with Simple AD. I now know it is possible.

I would like to discuss your utility and using it in another application. What is the best way to contact you?

Best regards,
Ron

Jorge Garcia said...

Uploaded code here. Thanks! Thanks! Thanks!

https://gist.github.com/jorgegarciaperez/8642728df8829d845e1ca6c88f767c46