0

From documentation for UnicodePwd:

If the Modify request contains a delete operation containing a value Vdel for unicodePwd followed by an add operation containing a value Vadd for unicodePwd, the server considers the request to be a request to change the password. The server decodes Vadd and Vdel using the password decoding procedure documented later in this section. Vdel is the old password, while Vadd is the new password.

If the Modify request contains a single replace operation containing a value Vrep for unicodePwd, the server considers the request to be an administrative reset of the password, that is, a password modification without knowledge of the old password. The server decodes Vrep using the password decoding procedure documented later in this section and uses it as the new password.

I have not yet found way to let a user do a "forget password -> confirm email -> set new password" procedure on a webserver that uses AD as the user store while verifying the password according to PwdHistoryLength's check for password reuse. How should I do it?

public class Test {
    private static DirContext adminLdapContext;

    public static void main(String[] args) throws Exception {
        initLdapContext();
        createNewUser("user.x");
        assertThrows(() -> updateUserPasswordByAdmin("user.x", "X"), NamingException.class,
                "X should fail MinPwdLength");
        updateUserPasswordByAdmin("user.x", "good-Pwd$%^123");
        updateUserPassword("user.x", "good-Pwd$%^123", "good-Pwd$%^1237");
        updateUserPasswordByAdmin("user.x", "good-Pwd$%^1234");
        updateUserPasswordByAdmin("user.x", "good-Pwd$%^12345");
        updateUserPasswordByAdmin("user.x", "good-Pwd$%^123456");
        updateUserPassword("user.x", "good-Pwd$%^123456", "good-Pwd$%^1234567");
        assertThrows(() -> updateUserPassword("user.x", "good-Pwd$%^1234567", "good-Pwd$%^123456"),
                NamingException.class, "the password with 123456 should be rejected for reuse");
        assertThrows(() -> updateUserPasswordByAdmin("user.x", "good-Pwd$%^12345"), NamingException.class,
                "the password with 12345 should be rejected for reuse");
    }

    // static void initLdapContext(): initialize adminLdapContext
    // static void createNewUser(String uname): use adminLdapContext and create user with sAMAccountName = cn = uname
    // static void updateUserPassword(String uname, String oldpass, String newpass): Use Vdel+Vadd on UnicodePwd
    // static void updateUserPasswordByAdmin(String uname, String newpass): Use Vrep on UnicodePwd
    // static void assertThrows(RunnableThrows r, Class<? extends Throwable> ex, String message) throws AssertionError: throw AssertionError if r.run() doesn't throw ex

    // @FunctionalInterface static interface RunnableThrows: see https://stackoverflow.com/a/18198349/2185599
}

Output:

Exception in thread "main" java.lang.AssertionError: the password with 12345 should be rejected for reuse
    at sketchbook.Main.assertThrows(Main.java:97)
    at sketchbook.Main.main(Main.java:67)
Caused by: java.lang.AssertionError: r.run() didn't throw
    at sketchbook.Main.assertThrows(Main.java:94)
    ... 1 more

Workaround:

Double the password history count and generate random UUID's to be set into AD.

  • Change Password: oldPassword --vdel vadd--> UUID --vdel vadd--> newPassword
  • Reset Password: oldPassword (unknown) --vrep--> UUID --vdel vadd--> newPassword

The downside is the the password history would contain half generated UUIDs.

Community
  • 1
  • 1
T Tse
  • 786
  • 7
  • 19

1 Answers1

1

Your question isn't entirely clear, but I if I understand correctly, you are expecting that this should not work:

updateUserPasswordByAdmin("user.x", "good-Pwd$%^12345")

I assume that updateUserPasswordByAdmin does an administrative reset rather than a change.

If that is the case, then that will always work, since password history requirements are not enforced when doing a reset. I suspect that is a security feature, because it would reveal past passwords to someone who didn't already know them.

And there is no way for you to get the list of old passwords.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • The system I am developing is supposed to let user reset their own password (using alternative means for authorization), and so from the viewpoint for the whole integration, at no point would admin gain knowledge of user password. I get why it's designed this way, but I am disappointed that there's no option to change it. I just added a workaround in the question. How would you feel about it? – T Tse May 26 '20 at 03:55
  • Your understanding is correct though, that `updateUserPasswordByAdmin` utilizes Vrep in the modification under admin privileges. – T Tse May 26 '20 at 04:00
  • That's an interesting workaround. But why would you do that when changing the password? The users can't be forced to always use that tool when changing the password. – Gabriel Luci May 26 '20 at 13:04
  • Also, depending on the minimum password age set on the domain, you may not be able to change the password right away. – Gabriel Luci May 26 '20 at 13:04
  • In my case, the users can actually be forced to use the tool when changing the password because we have an interesting project where the client insists the user table that could be in the db be hosted by AD instead. – T Tse May 26 '20 at 13:12
  • Your users don't log into Windows with these accounts? – Gabriel Luci May 26 '20 at 13:45
  • Yeah... I don't think so. They are literally using it for a directory. – T Tse May 26 '20 at 15:24