This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 84d5acca922e63ce1d85e655f54bfc88e70da31f Author: Benoit Tellier <[email protected]> AuthorDate: Fri Dec 3 14:07:34 2021 +0700 JAMES-3674 Document PBKDF2 usage for usersrepository.xml --- .../ROOT/pages/configure/usersrepository.adoc | 4 +- .../apache/james/user/lib/model/AlgorithmTest.java | 124 ++++++++++++++++++++- .../james/user/lib/model/DefaultUserTest.java | 120 -------------------- src/site/xdoc/server/config-users.xml | 4 +- 4 files changed, 128 insertions(+), 124 deletions(-) diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/usersrepository.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/usersrepository.adoc index 637955e..1921afa 100644 --- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/usersrepository.adoc +++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/usersrepository.adoc @@ -40,7 +40,9 @@ acting on the behalf of any user. | Delay after a failed authentication attempt with an invalid user name or password. Duration string defaulting to seconds, e.g. `2`, `2s`, `2000ms`. Default `0s` (disabled). | algorithm -| use a specific hash algorithm to compute passwords, with optional mode `plain` (default) or `salted`; e.g. `SHA-512` (default), `SHA-512/plain`, `SHA-512/salted` +| use a specific hash algorithm to compute passwords, with optional mode `plain` (default) or `salted`; e.g. `SHA-512`, `SHA-512/plain`, `SHA-512/salted`, `PBKDF2` (default). +Note: When using `PBKDF2` one can specify the iteration count and the key size in bytes. You can specify it as part of the algorithm. EG: `PBKDF2-2000-512` will use +2000 iterations with a key size of 512 bytes. | hashingMode | specify the hashing mode to use if there is none recorded in the database: `plain` (default) for newer installations or `legacy` for older ones diff --git a/server/data/data-library/src/test/java/org/apache/james/user/lib/model/AlgorithmTest.java b/server/data/data-library/src/test/java/org/apache/james/user/lib/model/AlgorithmTest.java index 5da7daa..3e2805e 100644 --- a/server/data/data-library/src/test/java/org/apache/james/user/lib/model/AlgorithmTest.java +++ b/server/data/data-library/src/test/java/org/apache/james/user/lib/model/AlgorithmTest.java @@ -22,8 +22,13 @@ package org.apache.james.user.lib.model; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.stream.Stream; + import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import nl.jqno.equalsverifier.EqualsVerifier; @@ -154,13 +159,13 @@ class AlgorithmTest { @Test void ofShouldParseIteration() { assertThat(Algorithm.of("PBKDF2-10", "plain").hasher()) - .isEqualTo(new Algorithm.PBKDF2Hasher(10, 1024)); + .isEqualTo(new Algorithm.PBKDF2Hasher(10, 512)); } @Test void ofShouldAcceptDefaultPBKDF2() { assertThat(Algorithm.of("PBKDF2", "plain").hasher()) - .isEqualTo(new Algorithm.PBKDF2Hasher(1000, 1024)); + .isEqualTo(new Algorithm.PBKDF2Hasher(1000, 512)); } @Test @@ -210,4 +215,119 @@ class AlgorithmTest { assertThatThrownBy(() -> Algorithm.of("PBKDF2-1--1", "plain")) .isInstanceOf(IllegalArgumentException.class); } + + + private static Stream<Arguments> sha1LegacyTestBed() { + return Stream.of( + Arguments.of("myPassword", "VBPuJHI7uixaa6LQGWx4s+5G"), + Arguments.of("otherPassword", "ks40t+AjBnHsMaC1Is/6+mtb"), + Arguments.of("", "2jmj7l5rSw0yVb/vlWAYkK/Y"), + Arguments.of("a", "hvfkN/qlp/zhXR3cuerq6jd2")); + } + + @ParameterizedTest + @MethodSource("sha1LegacyTestBed") + void testSha1Legacy(String password, String expectedHash) { + assertThat(Algorithm.of("SHA-1", "legacy").digest(password, "salt")) + .isEqualTo(expectedHash); + } + + private static Stream<Arguments> sha512LegacyTestBed() { + return Stream.of( + Arguments.of("myPassword", "RQrQPbk5XfzLXgMGb9fxbPuith4j1RY3NxRHFFkFLskKmkvzoVHmAOqKrtNuO4who9OKsXBYOXSd\r\nEw2kOA8U"), + Arguments.of("otherPassword", "6S2kG/b6oHgWBXQjKDKTayXWu2cs9374lxFrL9uVpmYUlq0lw/ZFU9svMtYVDV5aVjJqRbLWZ/df\r\neaaJwYxk"), + Arguments.of("", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGl\r\nODJ6+Sfa"), + Arguments.of("a", "H0D8ktokFpR1CXnubPWC8tXX0o4YM13gWrxU0FYOD1MChgxlK/CNVgJSql50IQVG82n7u86MEs/H\r\nlXsmUv6a")); + } + + @ParameterizedTest + @MethodSource("sha512LegacyTestBed") + void testSha512Legacy(String password, String expectedHash) { + assertThat(Algorithm.of("SHA-512", "legacy").digest(password, "salt")) + .isEqualTo(expectedHash); + } + + private static Stream<Arguments> sha1TestBed() { + return Stream.of( + Arguments.of("myPassword", "VBPuJHI7uixaa6LQGWx4s+5GKNE=\r\n"), + Arguments.of("otherPassword", "ks40t+AjBnHsMaC1Is/6+mtb05s=\r\n"), + Arguments.of("", "2jmj7l5rSw0yVb/vlWAYkK/YBwk=\r\n"), + Arguments.of("a", "hvfkN/qlp/zhXR3cuerq6jd2Z7g=\r\n")); + } + + @ParameterizedTest + @MethodSource("sha1TestBed") + void testSha1(String password, String expectedHash) { + assertThat(Algorithm.of("SHA-1").digest(password, "salt")) + .isEqualTo(expectedHash); + } + + private static Stream<Arguments> sha512TestBed() { + return Stream.of( + Arguments.of("myPassword", "RQrQPbk5XfzLXgMGb9fxbPuith4j1RY3NxRHFFkFLskKmkvzoVHmAOqKrtNuO4who9OKsXBYOXSd\r\nEw2kOA8USA==\r\n"), + Arguments.of("otherPassword", "6S2kG/b6oHgWBXQjKDKTayXWu2cs9374lxFrL9uVpmYUlq0lw/ZFU9svMtYVDV5aVjJqRbLWZ/df\r\neaaJwYxkhQ==\r\n"), + Arguments.of("", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGl\r\nODJ6+SfaPg==\r\n"), + Arguments.of("a", "H0D8ktokFpR1CXnubPWC8tXX0o4YM13gWrxU0FYOD1MChgxlK/CNVgJSql50IQVG82n7u86MEs/H\r\nlXsmUv6adQ==\r\n")); + } + + @ParameterizedTest + @MethodSource("sha512TestBed") + void testSha512(String password, String expectedHash) { + assertThat(Algorithm.of("SHA-512").digest(password, "salt")) + .isEqualTo(expectedHash); + } + + private static Stream<Arguments> PBKDF2TestBed() { + return Stream.of( + Arguments.of("myPassword", "Ag2g49zxor0w11yguLUMQ7EKBokU81LkvLyDqubWtQq7R5V21HVqZ+CEjEQxBLGfi35RFyesJtxb\r\n" + + "L5/VRCpI3g==\r\n"), + Arguments.of("otherPassword", "4KFfGIjbZqhaqZfr1rKWcoY5vkeps3/+x5BwU342kUbGGoW30kaP98R5iY6SNGg0yOaPBcB8EWqJ\r\n" + + "96RtIMnIYQ==\r\n"), + Arguments.of("", "6grdNX1hpxA5wJPXhBUJhz4qUoUSRZE0F3rqoPR+PYedDklDomJ0LPRV5f1SMNAX0fRgmQ8WDe6k\r\n" + + "2qr1Nc/orA==\r\n"), + Arguments.of("a", "WxpwqV5V9L3QR8xi8D8INuH0UH5oLeq+ZuXb6J1bAfhHp3urVOtAr+bwksC3JQRyC7QHE9MLfn61\r\n" + + "nTXo5johrQ==\r\n")); + } + + @ParameterizedTest + @MethodSource("PBKDF2TestBed") + void testPBKDF2(String password, String expectedHash) { + assertThat(Algorithm.of("PBKDF2").digest(password, "salt")) + .isEqualTo(expectedHash); + } + + private static Stream<Arguments> PBKDF210IterationTestBed() { + return Stream.of( + Arguments.of("myPassword", "AoNuFZ7ZI6vHU8obYASuuLPaQcr8fGentKWsOawBNTIc7MNJMbo4yNjo0pcCVK6J/XAfbISEKugt\r\n" + + "HwDeSUA10A==\r\n"), + Arguments.of("otherPassword", "e4+swbwo1s3X665INoRVsENXrgJtC7SMws9G3Y0GBoLZBkqZQzE2aT2WLd+hOlf3s/wwQe10MA0Q\r\n" + + "xMJQIcIosQ==\r\n"), + Arguments.of("", "ZBXj9rrLc4L9hHXOBPpDd5ot9DDB6qaq1g2mbAMOivpZe3eYw1ehdFXbU9pwpI4y/+MZlLkG3E1S\r\n" + + "WRQXuUZqag==\r\n"), + Arguments.of("a", "i1iWZzuaqsFotT998+stRqyrcyUrZ0diBJf9RJ52mUo0a074ykh8joWdrxhEsyd2Fh2DNO38TWxC\r\n" + + "KkIK6taLxA==\r\n")); + + } + + @ParameterizedTest + @MethodSource("PBKDF210IterationTestBed") + void testPBKDF210Iteration(String password, String expectedHash) { + assertThat(Algorithm.of("PBKDF2-10").digest(password, "salt")) + .isEqualTo(expectedHash); + } + + private static Stream<Arguments> PBKDF210Iteration128KeySizeTestBed() { + return Stream.of( + Arguments.of("myPassword", "AoNuFZ7ZI6vHU8obYASuuA==\r\n"), + Arguments.of("otherPassword", "e4+swbwo1s3X665INoRVsA==\r\n"), + Arguments.of("", "ZBXj9rrLc4L9hHXOBPpDdw==\r\n"), + Arguments.of("a", "i1iWZzuaqsFotT998+stRg==\r\n")); + } + + @ParameterizedTest + @MethodSource("PBKDF210Iteration128KeySizeTestBed") + void testPBKDF210Iteration128KeySize(String password, String expectedHash) { + assertThat(Algorithm.of("PBKDF2-10-128").digest(password, "salt")) + .isEqualTo(expectedHash); + } } \ No newline at end of file diff --git a/server/data/data-library/src/test/java/org/apache/james/user/lib/model/DefaultUserTest.java b/server/data/data-library/src/test/java/org/apache/james/user/lib/model/DefaultUserTest.java index 98b37f1..d900ef6 100644 --- a/server/data/data-library/src/test/java/org/apache/james/user/lib/model/DefaultUserTest.java +++ b/server/data/data-library/src/test/java/org/apache/james/user/lib/model/DefaultUserTest.java @@ -23,15 +23,9 @@ import static org.apache.james.user.lib.model.Algorithm.HashingMode.LEGACY; import static org.apache.james.user.lib.model.Algorithm.HashingMode.PLAIN; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Optional; -import java.util.stream.Stream; - import org.apache.james.core.Username; import org.junit.Before; import org.junit.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; public class DefaultUserTest { @@ -67,118 +61,4 @@ public class DefaultUserTest { assertThat(user.verifyPassword("secret2")).isTrue(); assertThat(user.verifyPassword("secret")).isFalse(); } - - private static Stream<Arguments> sha1LegacyTestBed() { - return Stream.of( - Arguments.of("myPassword", "VBPuJHI7uixaa6LQGWx4s+5G"), - Arguments.of("otherPassword", "ks40t+AjBnHsMaC1Is/6+mtb"), - Arguments.of("", "2jmj7l5rSw0yVb/vlWAYkK/Y"), - Arguments.of("a", "hvfkN/qlp/zhXR3cuerq6jd2")); - } - - @ParameterizedTest - @MethodSource("sha1LegacyTestBed") - void testSha1Legacy(String password, String expectedHash) throws Exception { - assertThat(DefaultUser.digestString(Optional.ofNullable(password).orElse(""), - Algorithm.of("SHA-1", "legacy"), "salt")) - .isEqualTo(expectedHash); - } - - private static Stream<Arguments> sha512LegacyTestBed() { - return Stream.of( - Arguments.of("myPassword", "RQrQPbk5XfzLXgMGb9fxbPuith4j1RY3NxRHFFkFLskKmkvzoVHmAOqKrtNuO4who9OKsXBYOXSd\r\nEw2kOA8U"), - Arguments.of("otherPassword", "6S2kG/b6oHgWBXQjKDKTayXWu2cs9374lxFrL9uVpmYUlq0lw/ZFU9svMtYVDV5aVjJqRbLWZ/df\r\neaaJwYxk"), - Arguments.of("", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGl\r\nODJ6+Sfa"), - Arguments.of("a", "H0D8ktokFpR1CXnubPWC8tXX0o4YM13gWrxU0FYOD1MChgxlK/CNVgJSql50IQVG82n7u86MEs/H\r\nlXsmUv6a")); - } - - @ParameterizedTest - @MethodSource("sha512LegacyTestBed") - void testSha512Legacy(String password, String expectedHash) throws Exception { - assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-512", "legacy"), "salt")) - .isEqualTo(expectedHash); - } - - private static Stream<Arguments> sha1TestBed() { - return Stream.of( - Arguments.of("myPassword", "VBPuJHI7uixaa6LQGWx4s+5GKNE=\r\n"), - Arguments.of("otherPassword", "ks40t+AjBnHsMaC1Is/6+mtb05s=\r\n"), - Arguments.of("", "2jmj7l5rSw0yVb/vlWAYkK/YBwk=\r\n"), - Arguments.of("a", "hvfkN/qlp/zhXR3cuerq6jd2Z7g=\r\n")); - } - - @ParameterizedTest - @MethodSource("sha1TestBed") - void testSha1(String password, String expectedHash) throws Exception { - assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-1"), "salt")) - .isEqualTo(expectedHash); - } - - private static Stream<Arguments> sha512TestBed() { - return Stream.of( - Arguments.of("myPassword", "RQrQPbk5XfzLXgMGb9fxbPuith4j1RY3NxRHFFkFLskKmkvzoVHmAOqKrtNuO4who9OKsXBYOXSd\r\nEw2kOA8USA==\r\n"), - Arguments.of("otherPassword", "6S2kG/b6oHgWBXQjKDKTayXWu2cs9374lxFrL9uVpmYUlq0lw/ZFU9svMtYVDV5aVjJqRbLWZ/df\r\neaaJwYxkhQ==\r\n"), - Arguments.of("", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGl\r\nODJ6+SfaPg==\r\n"), - Arguments.of("a", "H0D8ktokFpR1CXnubPWC8tXX0o4YM13gWrxU0FYOD1MChgxlK/CNVgJSql50IQVG82n7u86MEs/H\r\nlXsmUv6adQ==\r\n")); - } - - @ParameterizedTest - @MethodSource("sha512TestBed") - void testSha512(String password, String expectedHash) throws Exception { - assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-512"), "salt")) - .isEqualTo(expectedHash); - } - - private static Stream<Arguments> PBKDF2TestBed() { - return Stream.of( - Arguments.of("myPassword", "Ag2g49zxor0w11yguLUMQ7EKBokU81LkvLyDqubWtQq7R5V21HVqZ+CEjEQxBLGfi35RFyesJtxb\r\n" + - "L5/VRCpI3g==\r\n"), - Arguments.of("otherPassword", "4KFfGIjbZqhaqZfr1rKWcoY5vkeps3/+x5BwU342kUbGGoW30kaP98R5iY6SNGg0yOaPBcB8EWqJ\r\n" + - "96RtIMnIYQ==\r\n"), - Arguments.of("", "6grdNX1hpxA5wJPXhBUJhz4qUoUSRZE0F3rqoPR+PYedDklDomJ0LPRV5f1SMNAX0fRgmQ8WDe6k\r\n" + - "2qr1Nc/orA==\r\n"), - Arguments.of("a", "WxpwqV5V9L3QR8xi8D8INuH0UH5oLeq+ZuXb6J1bAfhHp3urVOtAr+bwksC3JQRyC7QHE9MLfn61\r\n" + - "nTXo5johrQ==\r\n")); - } - - @ParameterizedTest - @MethodSource("PBKDF2TestBed") - void testPBKDF2(String password, String expectedHash) { - assertThat(DefaultUser.digestString(password, Algorithm.of("PBKDF2"), "salt")) - .isEqualTo(expectedHash); - } - - private static Stream<Arguments> PBKDF210IterationTestBed() { - return Stream.of( - Arguments.of("myPassword", "AoNuFZ7ZI6vHU8obYASuuLPaQcr8fGentKWsOawBNTIc7MNJMbo4yNjo0pcCVK6J/XAfbISEKugt\r\n" + - "HwDeSUA10A==\r\n"), - Arguments.of("otherPassword", "e4+swbwo1s3X665INoRVsENXrgJtC7SMws9G3Y0GBoLZBkqZQzE2aT2WLd+hOlf3s/wwQe10MA0Q\r\n" + - "xMJQIcIosQ==\r\n"), - Arguments.of("", "ZBXj9rrLc4L9hHXOBPpDd5ot9DDB6qaq1g2mbAMOivpZe3eYw1ehdFXbU9pwpI4y/+MZlLkG3E1S\r\n" + - "WRQXuUZqag==\r\n"), - Arguments.of("a", "i1iWZzuaqsFotT998+stRqyrcyUrZ0diBJf9RJ52mUo0a074ykh8joWdrxhEsyd2Fh2DNO38TWxC\r\n" + - "KkIK6taLxA==\r\n")); - } - - @ParameterizedTest - @MethodSource("PBKDF210IterationTestBed") - void testPBKDF210Iteration(String password, String expectedHash) { - assertThat(DefaultUser.digestString(password, Algorithm.of("PBKDF2-10"), "salt")) - .isEqualTo(expectedHash); - } - - private static Stream<Arguments> PBKDF210Iteration128KeySizeTestBed() { - return Stream.of( - Arguments.of("myPassword", "AoNuFZ7ZI6vHU8obYASuuA==\r\n"), - Arguments.of("otherPassword", "e4+swbwo1s3X665INoRVsA==\r\n"), - Arguments.of("", "ZBXj9rrLc4L9hHXOBPpDdw==\r\n"), - Arguments.of("a", "i1iWZzuaqsFotT998+stRg==\r\n")); - } - - @ParameterizedTest - @MethodSource("PBKDF210Iteration128KeySizeTestBed") - void testPBKDF210Iteration128KeySize(String password, String expectedHash) { - assertThat(DefaultUser.digestString(password, Algorithm.of("PBKDF2-10-128"), "salt")) - .isEqualTo(expectedHash); - } } diff --git a/src/site/xdoc/server/config-users.xml b/src/site/xdoc/server/config-users.xml index 43dc856..58a7677 100644 --- a/src/site/xdoc/server/config-users.xml +++ b/src/site/xdoc/server/config-users.xml @@ -77,7 +77,9 @@ <dl> <dt><strong>algorithm</strong></dt> - <dd>Algorithm to hash passwords. Supported password algorithm are: MD5, SHA-256, SHA-512, NONE(then SHA-1 will be used).</dd> + <dd>Algorithm to hash passwords. Supported password algorithm are: MD5, SHA-256, SHA-512, NONE(then SHA-1 will be used), `PBKDF2` (default).<br/> + <b>Note</b>: When using `PBKDF2` one can specify the iteration count and the key size in bytes. You can specify it as part of the algorithm. EG: `PBKDF2-2000-512` will use + 2000 iterations with a key size of 512 bytes.</dd> <dd>MD5 and SHA-1 are deprecated.</dd> <dt><strong>enableVirtualHosting</strong></dt> <dd>true (default) or false. Defines if the usernames must (true) or may not contain (false) a domain part ([email protected]).</dd> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
