Fix for OPENDJ-9, Compatibility: Support SHA2 hashes with salts greater than 64bits.
The changes are providing compatibility with passwords hashed by other libraries and systems such as AIX Directory Server 6.3.
Specific tests have been added and could be extended with more varied passwords.
| | |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2010 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | |
| | | */ |
| | | private static final int NUM_SALT_BYTES = 8; |
| | | |
| | | |
| | | // Size of the dgiest in bytes. |
| | | private static final int SHA256_LENGTH = 256 / 8; |
| | | |
| | | // The message digest that will actually be used to generate the 256-bit SHA-2 |
| | | // hashes. |
| | |
| | | public boolean passwordMatches(ByteSequence plaintextPassword, |
| | | ByteSequence storedPassword) |
| | | { |
| | | // Base64-decode the stored value and take the last 8 bytes as the salt. |
| | | byte[] saltBytes = new byte[NUM_SALT_BYTES]; |
| | | byte[] digestBytes; |
| | | // Base64-decode the stored value and take the first 256 bits |
| | | // (SHA256_LENGTH) as the digest. |
| | | byte[] saltBytes; |
| | | byte[] digestBytes = new byte[SHA256_LENGTH]; |
| | | int saltLength = 0; |
| | | |
| | | try |
| | | { |
| | | byte[] decodedBytes = Base64.decode(storedPassword.toString()); |
| | | |
| | | int digestLength = decodedBytes.length - NUM_SALT_BYTES; |
| | | digestBytes = new byte[digestLength]; |
| | | System.arraycopy(decodedBytes, 0, digestBytes, 0, digestLength); |
| | | System.arraycopy(decodedBytes, digestLength, saltBytes, 0, |
| | | NUM_SALT_BYTES); |
| | | saltLength = decodedBytes.length - SHA256_LENGTH; |
| | | saltBytes = new byte[saltLength]; |
| | | System.arraycopy(decodedBytes, 0, digestBytes, 0, SHA256_LENGTH); |
| | | System.arraycopy(decodedBytes, SHA256_LENGTH, saltBytes, 0, |
| | | saltLength); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | |
| | | // Use the salt to generate a digest based on the provided plain-text value. |
| | | int plainBytesLength = plaintextPassword.length(); |
| | | byte[] plainPlusSalt = new byte[plainBytesLength + NUM_SALT_BYTES]; |
| | | byte[] plainPlusSalt = new byte[plainBytesLength + saltLength]; |
| | | plaintextPassword.copyTo(plainPlusSalt); |
| | | System.arraycopy(saltBytes, 0,plainPlusSalt, plainBytesLength, |
| | | NUM_SALT_BYTES); |
| | | saltLength); |
| | | |
| | | byte[] userDigestBytes; |
| | | |
| | |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2010 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | |
| | | private static final int NUM_SALT_BYTES = 8; |
| | | |
| | | |
| | | // The size of the digest in bytes. |
| | | private static final int SHA384_LENGTH = 384 / 8; |
| | | |
| | | // The message digest that will actually be used to generate the 384-bit SHA-2 |
| | | // hashes. |
| | |
| | | public boolean passwordMatches(ByteSequence plaintextPassword, |
| | | ByteSequence storedPassword) |
| | | { |
| | | // Base64-decode the stored value and take the last 8 bytes as the salt. |
| | | byte[] saltBytes = new byte[NUM_SALT_BYTES]; |
| | | byte[] digestBytes; |
| | | // Base64-decode the stored value and take the first 384 bits |
| | | // (SHA384_LENGTH) as the digest. |
| | | byte[] saltBytes; |
| | | byte[] digestBytes = new byte[SHA384_LENGTH]; |
| | | int saltLength = 0; |
| | | |
| | | try |
| | | { |
| | | byte[] decodedBytes = Base64.decode(storedPassword.toString()); |
| | | |
| | | int digestLength = decodedBytes.length - NUM_SALT_BYTES; |
| | | digestBytes = new byte[digestLength]; |
| | | System.arraycopy(decodedBytes, 0, digestBytes, 0, digestLength); |
| | | System.arraycopy(decodedBytes, digestLength, saltBytes, 0, |
| | | NUM_SALT_BYTES); |
| | | saltLength = decodedBytes.length - SHA384_LENGTH; |
| | | saltBytes = new byte[saltLength]; |
| | | System.arraycopy(decodedBytes, 0, digestBytes, 0, SHA384_LENGTH); |
| | | System.arraycopy(decodedBytes, SHA384_LENGTH, saltBytes, 0, |
| | | saltLength); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | |
| | | // Use the salt to generate a digest based on the provided plain-text value. |
| | | int plainBytesLength = plaintextPassword.length(); |
| | | byte[] plainPlusSalt = new byte[plainBytesLength + NUM_SALT_BYTES]; |
| | | byte[] plainPlusSalt = new byte[plainBytesLength + saltLength]; |
| | | plaintextPassword.copyTo(plainPlusSalt); |
| | | System.arraycopy(saltBytes, 0,plainPlusSalt, plainBytesLength, |
| | | NUM_SALT_BYTES); |
| | | saltLength); |
| | | |
| | | byte[] userDigestBytes; |
| | | |
| | |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2010 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | |
| | | private static final int NUM_SALT_BYTES = 8; |
| | | |
| | | |
| | | // The size of the digest in bytes. |
| | | private static final int SHA512_LENGTH = 512 / 8; |
| | | |
| | | // The message digest that will actually be used to generate the 512-bit SHA-2 |
| | | // hashes. |
| | |
| | | public boolean passwordMatches(ByteSequence plaintextPassword, |
| | | ByteSequence storedPassword) |
| | | { |
| | | // Base64-decode the stored value and take the last 8 bytes as the salt. |
| | | byte[] saltBytes = new byte[NUM_SALT_BYTES]; |
| | | byte[] digestBytes; |
| | | // Base64-decode the stored value and take the first 512 bits |
| | | // (SHA512_LENGTH) as the digest. |
| | | byte[] saltBytes; |
| | | byte[] digestBytes = new byte[SHA512_LENGTH]; |
| | | int saltLength = 0; |
| | | |
| | | try |
| | | { |
| | | byte[] decodedBytes = Base64.decode(storedPassword.toString()); |
| | | |
| | | int digestLength = decodedBytes.length - NUM_SALT_BYTES; |
| | | digestBytes = new byte[digestLength]; |
| | | System.arraycopy(decodedBytes, 0, digestBytes, 0, digestLength); |
| | | System.arraycopy(decodedBytes, digestLength, saltBytes, 0, |
| | | NUM_SALT_BYTES); |
| | | saltLength = decodedBytes.length - SHA512_LENGTH; |
| | | saltBytes = new byte[saltLength]; |
| | | System.arraycopy(decodedBytes, 0, digestBytes, 0, SHA512_LENGTH); |
| | | System.arraycopy(decodedBytes, SHA512_LENGTH, saltBytes, 0, |
| | | saltLength); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | |
| | | // Use the salt to generate a digest based on the provided plain-text value. |
| | | int plainBytesLength = plaintextPassword.length(); |
| | | byte[] plainPlusSalt = new byte[plainBytesLength + NUM_SALT_BYTES]; |
| | | byte[] plainPlusSalt = new byte[plainBytesLength + saltLength]; |
| | | plaintextPassword.copyTo(plainPlusSalt); |
| | | System.arraycopy(saltBytes, 0,plainPlusSalt, plainBytesLength, |
| | | NUM_SALT_BYTES); |
| | | saltLength); |
| | | |
| | | byte[] userDigestBytes; |
| | | |
| | |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2010 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | |
| | | * @param allowPreencoded whether or not to allow pre-encoded passwords |
| | | * @return the previous value for the allow preencoded passwords |
| | | */ |
| | | private boolean setAllowPreencodedPasswords(boolean allowPreencoded) |
| | | protected boolean setAllowPreencodedPasswords(boolean allowPreencoded) |
| | | throws Exception |
| | | { |
| | | // This code was borrowed from |
| | |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2010 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | | |
| | | import static org.testng.Assert.*; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.admin.server.AdminTestCaseUtils; |
| | | import org.opends.server.admin.std.meta. |
| | | SaltedSHA256PasswordStorageSchemeCfgDefn; |
| | | import org.opends.server.admin.std.server.SaltedSHA256PasswordStorageSchemeCfg; |
| | | import org.opends.server.api.PasswordStorageScheme; |
| | | import org.opends.server.types.Entry; |
| | | |
| | | |
| | | |
| | |
| | | scheme.initializePasswordStorageScheme(configuration); |
| | | return scheme; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves a set of passwords (plain and SSHA256 encrypted) that may |
| | | * be used to test the compatibility of SSHA256 passwords. |
| | | * The encrypted versions have been provided by external tools or |
| | | * users |
| | | * |
| | | * @return A set of couple (cleartext, encrypted) passwords that |
| | | * may be used to test the SSHA256 password storage scheme |
| | | */ |
| | | |
| | | @DataProvider(name = "testSSHA256Passwords") |
| | | public Object[][] getTestSSHA256Passwords() |
| | | throws Exception |
| | | { |
| | | return new Object[][] |
| | | { |
| | | new Object[] { "secret", "{SSHA256}xIar81hLva6DoMGVtk5WWfJTnBvkyAsYkj0phSdBBDW2DC1dXI79cw==" } |
| | | }; |
| | | } |
| | | |
| | | @Test(dataProvider = "testSSHA256Passwords") |
| | | public void testAuthSSHA256Passwords( |
| | | String plaintextPassword, |
| | | String encodedPassword) throws Exception |
| | | { |
| | | // Start/clear-out the memory backend |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | boolean allowPreencodedDefault = setAllowPreencodedPasswords(true); |
| | | |
| | | try { |
| | | |
| | | Entry userEntry = TestCaseUtils.makeEntry( |
| | | "dn: uid=testSSHA256.user,o=test", |
| | | "objectClass: top", |
| | | "objectClass: person", |
| | | "objectClass: organizationalPerson", |
| | | "objectClass: inetOrgPerson", |
| | | "uid: testSSHA256.user", |
| | | "givenName: TestSSHA256", |
| | | "sn: User", |
| | | "cn: TestSSHA256 User", |
| | | "userPassword: " + encodedPassword); |
| | | |
| | | |
| | | // Add the entry |
| | | TestCaseUtils.addEntry(userEntry); |
| | | |
| | | assertTrue(TestCaseUtils.canBind("uid=testSSHA256.user,o=test", |
| | | plaintextPassword), |
| | | "Failed to bind when pre-encoded password = \"" + |
| | | encodedPassword + "\" and " + |
| | | "plaintext password = \"" + |
| | | plaintextPassword + "\"" ); |
| | | } finally { |
| | | setAllowPreencodedPasswords(allowPreencodedDefault); |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2010 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | | |
| | | import static org.testng.Assert.*; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import org.opends.server.TestCaseUtils; |
| | | |
| | | import org.opends.server.admin.server.AdminTestCaseUtils; |
| | | import org.opends.server.admin.std.meta. |
| | | SaltedSHA512PasswordStorageSchemeCfgDefn; |
| | | import org.opends.server.admin.std.server.SaltedSHA512PasswordStorageSchemeCfg; |
| | | import org.opends.server.api.PasswordStorageScheme; |
| | | import org.opends.server.types.Entry; |
| | | |
| | | |
| | | |
| | |
| | | scheme.initializePasswordStorageScheme(configuration); |
| | | return scheme; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves a set of passwords (plain and SSHA512 encrypted) that may |
| | | * be used to test the compatibility of SSHA512 passwords. |
| | | * The encrypted versions have been provided by external tools or |
| | | * users |
| | | * |
| | | * @return A set of couple (cleartext, encrypted) passwords that |
| | | * may be used to test the SSHA512 password storage scheme |
| | | */ |
| | | |
| | | @DataProvider(name = "testSSHA512Passwords") |
| | | public Object[][] getTestSSHA512Passwords() |
| | | throws Exception |
| | | { |
| | | return new Object[][] |
| | | { |
| | | new Object[] { "secret", "{SSHA512}8gRXO3lD2fGN3JIhbNJOsh31IRFKnWbDNl+cPH3HoJCkUpxZPG617TnN6Nvl2mVMSBLlzPu2eMpOhCDKoolNG6QCsYf2hppQTAVaqfx25PUJ1ngbuBiNDCpK6Xj5PYZiFwa+cpkY/Pzs77bLn3VMxmHhwa+vowfGhy5RRW+6npQ=" } |
| | | }; |
| | | } |
| | | |
| | | @Test(dataProvider = "testSSHA512Passwords") |
| | | public void testAuthSSHA512Passwords( |
| | | String plaintextPassword, |
| | | String encodedPassword) throws Exception |
| | | { |
| | | // Start/clear-out the memory backend |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | boolean allowPreencodedDefault = setAllowPreencodedPasswords(true); |
| | | |
| | | try { |
| | | |
| | | Entry userEntry = TestCaseUtils.makeEntry( |
| | | "dn: uid=testSSHA512.user,o=test", |
| | | "objectClass: top", |
| | | "objectClass: person", |
| | | "objectClass: organizationalPerson", |
| | | "objectClass: inetOrgPerson", |
| | | "uid: testSSHA512.user", |
| | | "givenName: TestSSHA512", |
| | | "sn: User", |
| | | "cn: TestSSHA512 User", |
| | | "userPassword: " + encodedPassword); |
| | | |
| | | |
| | | // Add the entry |
| | | TestCaseUtils.addEntry(userEntry); |
| | | |
| | | assertTrue(TestCaseUtils.canBind("uid=testSSHA512.user,o=test", |
| | | plaintextPassword), |
| | | "Failed to bind when pre-encoded password = \"" + |
| | | encodedPassword + "\" and " + |
| | | "plaintext password = \"" + |
| | | plaintextPassword + "\"" ); |
| | | } finally { |
| | | setAllowPreencodedPasswords(allowPreencodedDefault); |
| | | } |
| | | } |
| | | |
| | | } |
| | | |