You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/12/08 03:39:53 UTC

[james-project] branch master updated (b5f3e0b -> 77bb17b)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from b5f3e0b  JAMES-3636 Unit tests for plainAuthDisallowed (#770)
     new ae2cc2b  JAMES-3674 Move DigestUtil into DefaultUser
     new 72f570b  JAMES-3674 DefaultUser.digestString should take salt into account
     new 43647d0  JAMES-3674 DefaultUser.digestString: extract base64 encoding to a sub-method
     new d0e76a3  JAMES-3674 Have hashing logic encapsulated with algorithm
     new bc10bab  JAMES-3674 PBKDF2 hashing
     new 36c06cd  JAMES-3674 Adopt PBKDF2 in default configuration
     new c7a132c  JAMES-3674 PBKDF2 should be the default choice
     new 84d5acc  JAMES-3674 Document PBKDF2 usage for usersrepository.xml
     new 77bb17b  JAMES-3674 PBKDF2 extract default constants

The 9 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../sample-configuration/usersrepository.xml       |   2 +-
 .../ROOT/pages/configure/usersrepository.adoc      |   4 +-
 .../sample-configuration/usersrepository.xml       |   2 +-
 .../sample-configuration/usersrepository.xml       |   2 +-
 .../sample-configuration/usersrepository.xml       |   2 +-
 .../sample-configuration/usersrepository.xml       |   2 +-
 .../sample-configuration/usersrepository.xml       |   2 +-
 .../CassandraRepositoryConfiguration.java          |   2 +-
 .../org/apache/james/user/jpa/JPAUsersDAO.java     |   2 +-
 .../org/apache/james/user/jpa/model/JPAUser.java   |   3 +
 .../org/apache/james/user/lib/model/Algorithm.java | 139 +++++++++++++++
 .../apache/james/user/lib/model/DefaultUser.java   |  42 ++---
 .../org/apache/james/user/lib/util/DigestUtil.java | 163 -----------------
 .../apache/james/user/lib/model/AlgorithmTest.java | 193 ++++++++++++++++++++-
 .../james/user/lib/model/DefaultUserTest.java      |   8 +-
 .../apache/james/user/lib/util/DigestUtilTest.java |  92 ----------
 .../apache/james/user/memory/MemoryUsersDAO.java   |   4 +-
 src/site/xdoc/server/config-users.xml              |   4 +-
 18 files changed, 372 insertions(+), 296 deletions(-)
 delete mode 100644 server/data/data-library/src/main/java/org/apache/james/user/lib/util/DigestUtil.java
 delete mode 100644 server/data/data-library/src/test/java/org/apache/james/user/lib/util/DigestUtilTest.java

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 08/09: JAMES-3674 Document PBKDF2 usage for usersrepository.xml

Posted by bt...@apache.org.
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 <bt...@linagora.com>
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 (user@domain.tld).</dd>

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 01/09: JAMES-3674 Move DigestUtil into DefaultUser

Posted by bt...@apache.org.
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 ae2cc2b1ecc50b75849fc6169ae5267a5a2d6fba
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 09:55:30 2021 +0700

    JAMES-3674 Move DigestUtil into DefaultUser
---
 .../apache/james/user/lib/model/DefaultUser.java   |  46 +++++-
 .../org/apache/james/user/lib/util/DigestUtil.java | 163 ---------------------
 .../james/user/lib/model/DefaultUserTest.java      |  74 +++++++++-
 .../apache/james/user/lib/util/DigestUtilTest.java |  92 ------------
 4 files changed, 113 insertions(+), 262 deletions(-)

diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
index 4212e9a..164ff05 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
@@ -19,12 +19,20 @@
 
 package org.apache.james.user.lib.model;
 
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.io.Serializable;
+import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeUtility;
+
 import org.apache.james.core.Username;
 import org.apache.james.user.api.model.User;
-import org.apache.james.user.lib.util.DigestUtil;
 
 /**
  * Implementation of User Interface. Instances of this class do not allow the
@@ -84,7 +92,7 @@ public class DefaultUser implements User, Serializable {
     public boolean verifyPassword(String pass) {
         try {
             String credentials = getCredentials(currentAlgorithm, pass);
-            String hashGuess = DigestUtil.digestString(credentials, currentAlgorithm);
+            String hashGuess = digestString(credentials, currentAlgorithm);
             return hashedPassword.equals(hashGuess);
         } catch (NoSuchAlgorithmException nsae) {
             throw new RuntimeException("Security error: " + nsae);
@@ -95,7 +103,7 @@ public class DefaultUser implements User, Serializable {
     public boolean setPassword(String newPass) {
         try {
             String newCredentials = getCredentials(preferredAlgorithm, newPass);
-            hashedPassword = DigestUtil.digestString(newCredentials, preferredAlgorithm);
+            hashedPassword = digestString(newCredentials, preferredAlgorithm);
             currentAlgorithm = preferredAlgorithm;
             return true;
         } catch (NoSuchAlgorithmException nsae) {
@@ -128,4 +136,36 @@ public class DefaultUser implements User, Serializable {
     public Algorithm getHashAlgorithm() {
         return currentAlgorithm;
     }
+
+    /**
+     * Calculate digest of given String using given algorithm. Encode digest in
+     * MIME-like base64.
+     *
+     * @param pass
+     *            the String to be hashed
+     * @param algorithm
+     *            the algorithm to be used
+     * @return String Base-64 encoding of digest
+     *
+     * @throws NoSuchAlgorithmException
+     *             if the algorithm passed in cannot be found
+     */
+    static String digestString(String pass, Algorithm algorithm) throws NoSuchAlgorithmException {
+        MessageDigest md;
+        ByteArrayOutputStream bos;
+
+        try {
+            md = MessageDigest.getInstance(algorithm.getName());
+            byte[] digest = md.digest(pass.getBytes(ISO_8859_1));
+            bos = new ByteArrayOutputStream();
+            OutputStream encodedStream = MimeUtility.encode(bos, "base64");
+            encodedStream.write(digest);
+            if (!algorithm.isLegacy()) {
+                encodedStream.close();
+            }
+            return bos.toString(ISO_8859_1);
+        } catch (IOException | MessagingException e) {
+            throw new RuntimeException("Fatal error", e);
+        }
+    }
 }
diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/util/DigestUtil.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/util/DigestUtil.java
deleted file mode 100644
index b0a2257..0000000
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/util/DigestUtil.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.user.lib.util;
-
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Locale;
-
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeUtility;
-
-import org.apache.james.user.lib.model.Algorithm;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Computes and verifies digests of files and strings
- */
-public class DigestUtil {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DigestUtil.class);
-
-    /**
-     * Command line interface. Use -help for arguments.
-     * 
-     * @param args
-     *            the arguments passed in on the command line
-     */
-    public static void main(String[] args) {
-
-        String alg = "SHA";
-        boolean file = false;
-
-        if (args.length == 0 || args.length > 4) {
-            printUsage();
-            return;
-        }
-
-        for (int i = 0; i < args.length; i++) {
-            String currArg = args[i].toLowerCase(Locale.US);
-            if (currArg.equals("-help") || currArg.equals("-usage")) {
-                printUsage();
-                return;
-            }
-            if (currArg.equals("-alg")) {
-                alg = args[i + 1];
-            }
-            if (currArg.equals("-file")) {
-                file = true;
-            }
-        }
-
-        if (file) {
-            digestFile(args[args.length - 1], alg);
-        } else {
-            try {
-                String hash = digestString(args[args.length - 1], Algorithm.of(alg));
-                System.out.println("Hash is: " + hash);
-            } catch (NoSuchAlgorithmException nsae) {
-                System.out.println("No such algorithm available");
-            }
-        }
-    }
-
-    /**
-     * Print the command line usage string.
-     */
-    public static void printUsage() {
-        System.out.println("Usage: " + "java org.apache.james.security.DigestUtil" + " [-alg algorithm]" + " [-file] filename|string");
-    }
-
-    /**
-     * Calculate digest of given file with given algorithm. Writes digest to
-     * file named filename.algorithm .
-     * 
-     * @param filename
-     *            the String name of the file to be hashed
-     * @param algorithm
-     *            the algorithm to be used to compute the digest
-     */
-    public static void digestFile(String filename, String algorithm) {
-        byte[] b = new byte[65536];
-        int read;
-        try (FileInputStream fis = new FileInputStream(filename)) {
-            MessageDigest md = MessageDigest.getInstance(algorithm);
-            while (fis.available() > 0) {
-                read = fis.read(b);
-                md.update(b, 0, read);
-            }
-            byte[] digest = md.digest();
-            String fileNameBuffer = filename + "." + algorithm;
-            try (FileOutputStream fos = new FileOutputStream(fileNameBuffer)) {
-                OutputStream encodedStream = MimeUtility.encode(fos, "base64");
-                encodedStream.write(digest);
-                fos.flush();
-            }
-        } catch (Exception e) {
-            LOGGER.error("Error computing Digest", e);
-        }
-    }
-
-    /**
-     * Calculate digest of given String using given algorithm. Encode digest in
-     * MIME-like base64.
-     * 
-     * @param pass
-     *            the String to be hashed
-     * @param algorithm
-     *            the algorithm to be used
-     * @return String Base-64 encoding of digest
-     * 
-     * @throws NoSuchAlgorithmException
-     *             if the algorithm passed in cannot be found
-     */
-    public static String digestString(String pass, Algorithm algorithm) throws NoSuchAlgorithmException {
-
-        MessageDigest md;
-        ByteArrayOutputStream bos;
-
-        try {
-            md = MessageDigest.getInstance(algorithm.getName());
-            byte[] digest = md.digest(pass.getBytes(ISO_8859_1));
-            bos = new ByteArrayOutputStream();
-            OutputStream encodedStream = MimeUtility.encode(bos, "base64");
-            encodedStream.write(digest);
-            if (!algorithm.isLegacy()) {
-                encodedStream.close();
-            }
-            return bos.toString(ISO_8859_1);
-        } catch (IOException | MessagingException e) {
-            throw new RuntimeException("Fatal error", e);
-        }
-    }
-
-    /**
-     * Private constructor to prevent instantiation of the class
-     */
-    private DigestUtil() {
-    }
-}
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 1a11d3d..3a79d64 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
@@ -19,14 +19,20 @@
 
 package org.apache.james.user.lib.model;
 
-import org.apache.james.core.Username;
-import org.junit.Before;
-import org.junit.Test;
-
 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 {
 
     private DefaultUser user;
@@ -61,4 +67,64 @@ 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")))
+            .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")))
+            .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(Optional.ofNullable(password).orElse(""), Algorithm.of("SHA-1")))
+            .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")))
+            .isEqualTo(expectedHash);
+    }
 }
diff --git a/server/data/data-library/src/test/java/org/apache/james/user/lib/util/DigestUtilTest.java b/server/data/data-library/src/test/java/org/apache/james/user/lib/util/DigestUtilTest.java
deleted file mode 100644
index 1825fc6..0000000
--- a/server/data/data-library/src/test/java/org/apache/james/user/lib/util/DigestUtilTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.user.lib.util;
-
-import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
-
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import org.apache.james.user.lib.model.Algorithm;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-class DigestUtilTest {
-    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(DigestUtil.digestString(Optional.ofNullable(password).orElse(""), Algorithm.of("SHA-1", "legacy")))
-            .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(DigestUtil.digestString(password, Algorithm.of("SHA-512", "legacy")))
-            .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(DigestUtil.digestString(Optional.ofNullable(password).orElse(""), Algorithm.of("SHA-1")))
-            .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(DigestUtil.digestString(password, Algorithm.of("SHA-512")))
-            .isEqualTo(expectedHash);
-    }
-}
\ No newline at end of file

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 05/09: JAMES-3674 PBKDF2 hashing

Posted by bt...@apache.org.
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 bc10bab19ea489f6146d4c735c9df42ecec23852
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 11:29:53 2021 +0700

    JAMES-3674 PBKDF2 hashing
---
 .../org/apache/james/user/jpa/model/JPAUser.java   |  3 +
 .../org/apache/james/user/lib/model/Algorithm.java | 96 +++++++++++++++++++++-
 .../apache/james/user/lib/model/DefaultUser.java   | 58 ++-----------
 .../apache/james/user/lib/model/AlgorithmTest.java | 73 +++++++++++++++-
 .../james/user/lib/model/DefaultUserTest.java      | 55 ++++++++++++-
 5 files changed, 233 insertions(+), 52 deletions(-)

diff --git a/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/model/JPAUser.java b/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/model/JPAUser.java
index c906b39..dad1fa0 100644
--- a/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/model/JPAUser.java
+++ b/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/model/JPAUser.java
@@ -60,6 +60,9 @@ public class JPAUser implements User {
     @VisibleForTesting
     static String hashPassword(String password, String nullableSalt, String nullableAlgorithm) {
         Algorithm algorithm = Algorithm.of(Optional.ofNullable(nullableAlgorithm).orElse("SHA-512"));
+        if (algorithm.isPBKDF2()) {
+            return algorithm.digest(password, nullableSalt);
+        }
         String credentials = password;
         if (algorithm.isSalted() && nullableSalt != null) {
             credentials = nullableSalt + password;
diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
index 6e0222d..5c01c68 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
@@ -21,21 +21,32 @@ package org.apache.james.user.lib.model;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeUtility;
+
+import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 
 public class Algorithm {
     public interface Hasher {
         static Hasher from(Algorithm algorithm) {
-            return new RegularHashingSpec(algorithm);
+            return PBKDF2Hasher.from(algorithm)
+                .orElseGet(() -> new RegularHashingSpec(algorithm));
         }
 
         byte[] digestString(String pass, String salt) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException;
@@ -64,6 +75,67 @@ public class Algorithm {
         }
     }
 
+    public static class PBKDF2Hasher implements Hasher {
+        public static Optional<Hasher> from(Algorithm algorithm) {
+            if (algorithm.getName().startsWith("PBKDF2")) {
+                List<String> parts = Splitter.on('-').splitToList(algorithm.getName());
+                return Optional.of(new PBKDF2Hasher(parseIterationCount(parts), parseKeySize(parts)));
+            } else {
+                return Optional.empty();
+            }
+        }
+
+        private static int parseKeySize(List<String> parts) {
+            if (parts.size() >= 3) {
+                return Integer.parseInt(parts.get(2));
+            } else {
+                return 512;
+            }
+        }
+
+        private static int parseIterationCount(List<String> parts) {
+            if (parts.size() >= 2) {
+                return Integer.parseInt(parts.get(1));
+            } else {
+                return 1000;
+            }
+        }
+
+        private final int iterationCount;
+        private final int keySize;
+
+        public PBKDF2Hasher(int iterationCount, int keySize) {
+            Preconditions.checkArgument(iterationCount > 0, "'iterationCount' should be greater than 0");
+            Preconditions.checkArgument(keySize > 0, "'keySize' should be greater than 0");
+
+            this.iterationCount = iterationCount;
+            this.keySize = keySize;
+        }
+
+        public byte[] digestString(String pass, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
+            KeySpec spec = new PBEKeySpec(pass.toCharArray(), salt.getBytes(ISO_8859_1), iterationCount, keySize);
+            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+
+            return factory.generateSecret(spec).getEncoded();
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof PBKDF2Hasher) {
+                PBKDF2Hasher that = (PBKDF2Hasher) o;
+
+                return Objects.equals(this.iterationCount, that.iterationCount)
+                    && Objects.equals(this.keySize, that.keySize);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(iterationCount, keySize);
+        }
+    }
+
     public enum HashingMode {
         PLAIN,
         SALTED,
@@ -121,6 +193,10 @@ public class Algorithm {
         return hashingMode == HashingMode.LEGACY || hashingMode == HashingMode.LEGACY_SALTED;
     }
 
+    public boolean isPBKDF2() {
+        return hasher instanceof PBKDF2Hasher;
+    }
+
     public boolean isSalted() {
         return hashingMode == HashingMode.SALTED || hashingMode == HashingMode.LEGACY_SALTED;
     }
@@ -129,6 +205,24 @@ public class Algorithm {
         return hasher;
     }
 
+    public String digest(String pass, String salt) {
+        try {
+            return encodeInBase64(hasher().digestString(pass, salt));
+        } catch (MessagingException | IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
+            throw new RuntimeException("Fatal error when hashing password", e);
+        }
+    }
+
+    public String encodeInBase64(byte[] digest) throws MessagingException, IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        OutputStream encodedStream = MimeUtility.encode(bos, "base64");
+        encodedStream.write(digest);
+        if (!isLegacy()) {
+            encodedStream.close();
+        }
+        return bos.toString(ISO_8859_1);
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof Algorithm) {
diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
index 4a802de..9be1106 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
@@ -19,17 +19,7 @@
 
 package org.apache.james.user.lib.model;
 
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
 import java.io.Serializable;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeUtility;
 
 import org.apache.james.core.Username;
 import org.apache.james.user.api.model.User;
@@ -90,23 +80,15 @@ public class DefaultUser implements User, Serializable {
 
     @Override
     public boolean verifyPassword(String pass) {
-        try {
-            String hashGuess = digestString(pass, currentAlgorithm, userName.asString());
-            return hashedPassword.equals(hashGuess);
-        } catch (NoSuchAlgorithmException nsae) {
-            throw new RuntimeException("Security error: " + nsae);
-        }
+        String hashGuess = digestString(pass, currentAlgorithm, userName.asString());
+        return hashedPassword.equals(hashGuess);
     }
 
     @Override
     public boolean setPassword(String newPass) {
-        try {
-            hashedPassword = digestString(newPass, preferredAlgorithm, userName.asString());
-            currentAlgorithm = preferredAlgorithm;
-            return true;
-        } catch (NoSuchAlgorithmException nsae) {
-            throw new RuntimeException("Security error: " + nsae);
-        }
+        hashedPassword = digestString(newPass, preferredAlgorithm, userName.asString());
+        currentAlgorithm = preferredAlgorithm;
+        return true;
     }
 
 
@@ -129,37 +111,15 @@ public class DefaultUser implements User, Serializable {
         return currentAlgorithm;
     }
 
-
-
     /**
      * Calculate digest of given String using given algorithm. Encode digest in
      * MIME-like base64.
      *
-     * @param pass
-     *            the String to be hashed
-     * @param algorithm
-     *            the algorithm to be used
+     * @param pass the String to be hashed
+     * @param algorithm the algorithm to be used
      * @return String Base-64 encoding of digest
-     *
-     * @throws NoSuchAlgorithmException
-     *             if the algorithm passed in cannot be found
      */
-    static String digestString(String pass, Algorithm algorithm, String salt) throws NoSuchAlgorithmException {
-        try {
-            byte[] digest = algorithm.hasher().digestString(pass, salt);
-            return encodeInBase64(algorithm, digest);
-        } catch (IOException | MessagingException | InvalidKeySpecException e) {
-            throw new RuntimeException("Fatal error", e);
-        }
-    }
-
-    private static String encodeInBase64(Algorithm algorithm, byte[] digest) throws MessagingException, IOException {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        OutputStream encodedStream = MimeUtility.encode(bos, "base64");
-        encodedStream.write(digest);
-        if (!algorithm.isLegacy()) {
-            encodedStream.close();
-        }
-        return bos.toString(ISO_8859_1);
+    static String digestString(String pass, Algorithm algorithm, String salt) {
+        return algorithm.digest(pass, salt);
     }
 }
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 0498583..5da7daa 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
@@ -19,6 +19,9 @@
 
 package org.apache.james.user.lib.model;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
@@ -27,7 +30,9 @@ import nl.jqno.equalsverifier.EqualsVerifier;
 class AlgorithmTest {
     @Test
     void shouldMatchBeanContract() {
-        EqualsVerifier.forClass(Algorithm.class).verify();
+        EqualsVerifier.forClass(Algorithm.class)
+            .withIgnoredFields("hasher")
+            .verify();
     }
 
     @Test
@@ -139,4 +144,70 @@ class AlgorithmTest {
             softly.assertThat(Algorithm.of("SHA-1/legacy_salted", "plain").isSalted()).isTrue();
         });
     }
+
+    @Test
+    void ofShouldParseIterationAndKeySize() {
+        assertThat(Algorithm.of("PBKDF2-10-20", "plain").hasher())
+            .isEqualTo(new Algorithm.PBKDF2Hasher(10, 20));
+    }
+
+    @Test
+    void ofShouldParseIteration() {
+        assertThat(Algorithm.of("PBKDF2-10", "plain").hasher())
+            .isEqualTo(new Algorithm.PBKDF2Hasher(10, 1024));
+    }
+
+    @Test
+    void ofShouldAcceptDefaultPBKDF2() {
+        assertThat(Algorithm.of("PBKDF2", "plain").hasher())
+            .isEqualTo(new Algorithm.PBKDF2Hasher(1000, 1024));
+    }
+
+    @Test
+    void ofShouldThrowOnBadIteration() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2-bad", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowOnEmptyIteration() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2-", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowOnNegativeIteration() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2--1", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowOnEmptyKeySize() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2-1-", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowOnBadKeySize() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2-1-bad", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowOnZeroIteration() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2-0", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowOnZeroKeySize() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2-1-0", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void ofShouldThrowOnNegativeKeySize() {
+        assertThatThrownBy(() -> Algorithm.of("PBKDF2-1--1", "plain"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
 }
\ 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 87f9e2a..98b37f1 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
@@ -110,7 +110,7 @@ public class DefaultUserTest {
     @ParameterizedTest
     @MethodSource("sha1TestBed")
     void testSha1(String password, String expectedHash) throws Exception {
-        assertThat(DefaultUser.digestString(Optional.ofNullable(password).orElse(""), Algorithm.of("SHA-1"), "salt"))
+        assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-1"), "salt"))
             .isEqualTo(expectedHash);
     }
 
@@ -128,4 +128,57 @@ public class DefaultUserTest {
         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);
+    }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 03/09: JAMES-3674 DefaultUser.digestString: extract base64 encoding to a sub-method

Posted by bt...@apache.org.
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 43647d0c72cc624b24056f3befd728ef4a082bef
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 10:07:18 2021 +0700

    JAMES-3674 DefaultUser.digestString: extract base64 encoding to a sub-method
---
 .../apache/james/user/lib/model/DefaultUser.java   | 23 +++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
index c63f1cd..96740ab 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
@@ -143,25 +143,26 @@ public class DefaultUser implements User, Serializable {
      *             if the algorithm passed in cannot be found
      */
     static String digestString(String pass, Algorithm algorithm, String salt) throws NoSuchAlgorithmException {
-        MessageDigest md;
-        ByteArrayOutputStream bos;
-
         try {
-            md = MessageDigest.getInstance(algorithm.getName());
+            MessageDigest md = MessageDigest.getInstance(algorithm.getName());
             String saltedPass = applySalt(algorithm, pass, salt);
             byte[] digest = md.digest(saltedPass.getBytes(ISO_8859_1));
-            bos = new ByteArrayOutputStream();
-            OutputStream encodedStream = MimeUtility.encode(bos, "base64");
-            encodedStream.write(digest);
-            if (!algorithm.isLegacy()) {
-                encodedStream.close();
-            }
-            return bos.toString(ISO_8859_1);
+            return encodeInBase64(algorithm, digest);
         } catch (IOException | MessagingException e) {
             throw new RuntimeException("Fatal error", e);
         }
     }
 
+    private static String encodeInBase64(Algorithm algorithm, byte[] digest) throws MessagingException, IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        OutputStream encodedStream = MimeUtility.encode(bos, "base64");
+        encodedStream.write(digest);
+        if (!algorithm.isLegacy()) {
+            encodedStream.close();
+        }
+        return bos.toString(ISO_8859_1);
+    }
+
     static String applySalt(Algorithm algorithm, String pass, String salt) {
         if (algorithm.isSalted()) {
             return salt + pass;

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 04/09: JAMES-3674 Have hashing logic encapsulated with algorithm

Posted by bt...@apache.org.
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 d0e76a3ab1fa2bb851048f3cc20a04dcbcd8acd7
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 11:28:57 2021 +0700

    JAMES-3674 Have hashing logic encapsulated with algorithm
---
 .../org/apache/james/user/lib/model/Algorithm.java | 42 ++++++++++++++++++++++
 .../apache/james/user/lib/model/DefaultUser.java   | 18 +++-------
 2 files changed, 47 insertions(+), 13 deletions(-)

diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
index 3cca98e..6e0222d 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
@@ -19,6 +19,12 @@
 
 package org.apache.james.user.lib.model;
 
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -27,6 +33,36 @@ import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 
 public class Algorithm {
+    public interface Hasher {
+        static Hasher from(Algorithm algorithm) {
+            return new RegularHashingSpec(algorithm);
+        }
+
+        byte[] digestString(String pass, String salt) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException;
+    }
+
+    public static class RegularHashingSpec implements Hasher {
+        private final Algorithm algorithm;
+
+        public RegularHashingSpec(Algorithm algorithm) {
+            this.algorithm = algorithm;
+        }
+
+        @Override
+        public byte[] digestString(String pass, String salt) throws NoSuchAlgorithmException {
+            MessageDigest md = MessageDigest.getInstance(algorithm.getName());
+            String saltedPass = applySalt(algorithm, pass, salt);
+            return md.digest(saltedPass.getBytes(ISO_8859_1));
+        }
+
+        private String applySalt(Algorithm algorithm, String pass, String salt) {
+            if (algorithm.isSalted()) {
+                return salt + pass;
+            } else {
+                return pass;
+            }
+        }
+    }
 
     public enum HashingMode {
         PLAIN,
@@ -61,10 +97,12 @@ public class Algorithm {
 
     private final String rawValue;
     private final HashingMode hashingMode;
+    private final Hasher hasher;
 
     private Algorithm(String rawValue, HashingMode hashingMode) {
         this.rawValue = rawValue;
         this.hashingMode = hashingMode;
+        this.hasher = Hasher.from(this);
     }
 
     public String asString() {
@@ -87,6 +125,10 @@ public class Algorithm {
         return hashingMode == HashingMode.SALTED || hashingMode == HashingMode.LEGACY_SALTED;
     }
 
+    public Hasher hasher() {
+        return hasher;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof Algorithm) {
diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
index 96740ab..4a802de 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
@@ -25,8 +25,8 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.Serializable;
-import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
 
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeUtility;
@@ -129,6 +129,8 @@ public class DefaultUser implements User, Serializable {
         return currentAlgorithm;
     }
 
+
+
     /**
      * Calculate digest of given String using given algorithm. Encode digest in
      * MIME-like base64.
@@ -144,11 +146,9 @@ public class DefaultUser implements User, Serializable {
      */
     static String digestString(String pass, Algorithm algorithm, String salt) throws NoSuchAlgorithmException {
         try {
-            MessageDigest md = MessageDigest.getInstance(algorithm.getName());
-            String saltedPass = applySalt(algorithm, pass, salt);
-            byte[] digest = md.digest(saltedPass.getBytes(ISO_8859_1));
+            byte[] digest = algorithm.hasher().digestString(pass, salt);
             return encodeInBase64(algorithm, digest);
-        } catch (IOException | MessagingException e) {
+        } catch (IOException | MessagingException | InvalidKeySpecException e) {
             throw new RuntimeException("Fatal error", e);
         }
     }
@@ -162,12 +162,4 @@ public class DefaultUser implements User, Serializable {
         }
         return bos.toString(ISO_8859_1);
     }
-
-    static String applySalt(Algorithm algorithm, String pass, String salt) {
-        if (algorithm.isSalted()) {
-            return salt + pass;
-        } else {
-            return pass;
-        }
-    }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 06/09: JAMES-3674 Adopt PBKDF2 in default configuration

Posted by bt...@apache.org.
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 36c06cdb1a59c402d5b762eaa21ed3e96a528831
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 12:07:21 2021 +0700

    JAMES-3674 Adopt PBKDF2 in default configuration
---
 server/apps/cassandra-app/sample-configuration/usersrepository.xml      | 2 +-
 server/apps/distributed-app/sample-configuration/usersrepository.xml    | 2 +-
 .../apps/distributed-pop3-app/sample-configuration/usersrepository.xml  | 2 +-
 server/apps/jpa-app/sample-configuration/usersrepository.xml            | 2 +-
 server/apps/jpa-smtp-app/sample-configuration/usersrepository.xml       | 2 +-
 server/apps/memory-app/sample-configuration/usersrepository.xml         | 2 +-
 6 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/server/apps/cassandra-app/sample-configuration/usersrepository.xml b/server/apps/cassandra-app/sample-configuration/usersrepository.xml
index eb84cba..375e386 100644
--- a/server/apps/cassandra-app/sample-configuration/usersrepository.xml
+++ b/server/apps/cassandra-app/sample-configuration/usersrepository.xml
@@ -21,7 +21,7 @@
 <!-- Read https://james.apache.org/server/config-users.html for further details -->
 
 <usersrepository name="LocalUsers">
-    <algorithm>SHA-512/salted</algorithm>
+    <algorithm>PBKDF2</algorithm>
     <enableVirtualHosting>true</enableVirtualHosting>    
     <enableForwarding>true</enableForwarding>
 </usersrepository>
diff --git a/server/apps/distributed-app/sample-configuration/usersrepository.xml b/server/apps/distributed-app/sample-configuration/usersrepository.xml
index 7a4a4d7..b69346a 100644
--- a/server/apps/distributed-app/sample-configuration/usersrepository.xml
+++ b/server/apps/distributed-app/sample-configuration/usersrepository.xml
@@ -21,7 +21,7 @@
 <!-- Read https://james.apache.org/server/config-users.html for further details -->
 
 <usersrepository name="LocalUsers">
-    <algorithm>SHA-512/salted</algorithm>
+    <algorithm>PBKDF2</algorithm>
     <enableVirtualHosting>true</enableVirtualHosting>    
     <enableForwarding>true</enableForwarding>
 </usersrepository>
diff --git a/server/apps/distributed-pop3-app/sample-configuration/usersrepository.xml b/server/apps/distributed-pop3-app/sample-configuration/usersrepository.xml
index 7a4a4d7..b69346a 100644
--- a/server/apps/distributed-pop3-app/sample-configuration/usersrepository.xml
+++ b/server/apps/distributed-pop3-app/sample-configuration/usersrepository.xml
@@ -21,7 +21,7 @@
 <!-- Read https://james.apache.org/server/config-users.html for further details -->
 
 <usersrepository name="LocalUsers">
-    <algorithm>SHA-512/salted</algorithm>
+    <algorithm>PBKDF2</algorithm>
     <enableVirtualHosting>true</enableVirtualHosting>    
     <enableForwarding>true</enableForwarding>
 </usersrepository>
diff --git a/server/apps/jpa-app/sample-configuration/usersrepository.xml b/server/apps/jpa-app/sample-configuration/usersrepository.xml
index 9c2d27e..88eb8b4 100644
--- a/server/apps/jpa-app/sample-configuration/usersrepository.xml
+++ b/server/apps/jpa-app/sample-configuration/usersrepository.xml
@@ -21,7 +21,7 @@
 <!-- Read https://james.apache.org/server/config-users.html for further details -->
 
 <usersrepository name="LocalUsers">
-    <algorithm>SHA-512/salted</algorithm>
+    <algorithm>PBKDF2</algorithm>
     <enableVirtualHosting>true</enableVirtualHosting>    
     <enableForwarding>true</enableForwarding>
 </usersrepository>
diff --git a/server/apps/jpa-smtp-app/sample-configuration/usersrepository.xml b/server/apps/jpa-smtp-app/sample-configuration/usersrepository.xml
index cc6f95e..f5ed7b2 100644
--- a/server/apps/jpa-smtp-app/sample-configuration/usersrepository.xml
+++ b/server/apps/jpa-smtp-app/sample-configuration/usersrepository.xml
@@ -22,7 +22,7 @@
 
 <usersrepository name="LocalUsers" class="org.apache.james.user.jpa.JPAUsersRepository">
     <destination URL="file://users/"/>
-    <algorithm>SHA-512/salted</algorithm>
+    <algorithm>PBKDF2</algorithm>
     <enableVirtualHosting>true</enableVirtualHosting>
     <enableForwarding>true</enableForwarding>
 </usersrepository>
diff --git a/server/apps/memory-app/sample-configuration/usersrepository.xml b/server/apps/memory-app/sample-configuration/usersrepository.xml
index 9c2d27e..88eb8b4 100644
--- a/server/apps/memory-app/sample-configuration/usersrepository.xml
+++ b/server/apps/memory-app/sample-configuration/usersrepository.xml
@@ -21,7 +21,7 @@
 <!-- Read https://james.apache.org/server/config-users.html for further details -->
 
 <usersrepository name="LocalUsers">
-    <algorithm>SHA-512/salted</algorithm>
+    <algorithm>PBKDF2</algorithm>
     <enableVirtualHosting>true</enableVirtualHosting>    
     <enableForwarding>true</enableForwarding>
 </usersrepository>

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 02/09: JAMES-3674 DefaultUser.digestString should take salt into account

Posted by bt...@apache.org.
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 72f570bcf88ece0ccd56308e6831c24de4453a8d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 10:00:18 2021 +0700

    JAMES-3674 DefaultUser.digestString should take salt into account
---
 .../apache/james/user/lib/model/DefaultUser.java   | 27 +++++++++++-----------
 .../james/user/lib/model/DefaultUserTest.java      |  9 ++++----
 2 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
index 164ff05..c63f1cd 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/DefaultUser.java
@@ -91,8 +91,7 @@ public class DefaultUser implements User, Serializable {
     @Override
     public boolean verifyPassword(String pass) {
         try {
-            String credentials = getCredentials(currentAlgorithm, pass);
-            String hashGuess = digestString(credentials, currentAlgorithm);
+            String hashGuess = digestString(pass, currentAlgorithm, userName.asString());
             return hashedPassword.equals(hashGuess);
         } catch (NoSuchAlgorithmException nsae) {
             throw new RuntimeException("Security error: " + nsae);
@@ -102,8 +101,7 @@ public class DefaultUser implements User, Serializable {
     @Override
     public boolean setPassword(String newPass) {
         try {
-            String newCredentials = getCredentials(preferredAlgorithm, newPass);
-            hashedPassword = digestString(newCredentials, preferredAlgorithm);
+            hashedPassword = digestString(newPass, preferredAlgorithm, userName.asString());
             currentAlgorithm = preferredAlgorithm;
             return true;
         } catch (NoSuchAlgorithmException nsae) {
@@ -111,13 +109,7 @@ public class DefaultUser implements User, Serializable {
         }
     }
 
-    private String getCredentials(Algorithm algorithm, String pass) {
-        if (algorithm.isSalted()) {
-            return userName.asString() + pass;
-        } else {
-            return pass;
-        }
-    }
+
 
     /**
      * Method to access hash of password
@@ -150,13 +142,14 @@ public class DefaultUser implements User, Serializable {
      * @throws NoSuchAlgorithmException
      *             if the algorithm passed in cannot be found
      */
-    static String digestString(String pass, Algorithm algorithm) throws NoSuchAlgorithmException {
+    static String digestString(String pass, Algorithm algorithm, String salt) throws NoSuchAlgorithmException {
         MessageDigest md;
         ByteArrayOutputStream bos;
 
         try {
             md = MessageDigest.getInstance(algorithm.getName());
-            byte[] digest = md.digest(pass.getBytes(ISO_8859_1));
+            String saltedPass = applySalt(algorithm, pass, salt);
+            byte[] digest = md.digest(saltedPass.getBytes(ISO_8859_1));
             bos = new ByteArrayOutputStream();
             OutputStream encodedStream = MimeUtility.encode(bos, "base64");
             encodedStream.write(digest);
@@ -168,4 +161,12 @@ public class DefaultUser implements User, Serializable {
             throw new RuntimeException("Fatal error", e);
         }
     }
+
+    static String applySalt(Algorithm algorithm, String pass, String salt) {
+        if (algorithm.isSalted()) {
+            return salt + pass;
+        } else {
+            return pass;
+        }
+    }
 }
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 3a79d64..87f9e2a 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
@@ -79,7 +79,8 @@ public class DefaultUserTest {
     @ParameterizedTest
     @MethodSource("sha1LegacyTestBed")
     void testSha1Legacy(String password, String expectedHash) throws Exception {
-        assertThat(DefaultUser.digestString(Optional.ofNullable(password).orElse(""), Algorithm.of("SHA-1", "legacy")))
+        assertThat(DefaultUser.digestString(Optional.ofNullable(password).orElse(""),
+            Algorithm.of("SHA-1", "legacy"), "salt"))
             .isEqualTo(expectedHash);
     }
 
@@ -94,7 +95,7 @@ public class DefaultUserTest {
     @ParameterizedTest
     @MethodSource("sha512LegacyTestBed")
     void testSha512Legacy(String password, String expectedHash) throws Exception {
-        assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-512", "legacy")))
+        assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-512", "legacy"), "salt"))
             .isEqualTo(expectedHash);
     }
 
@@ -109,7 +110,7 @@ public class DefaultUserTest {
     @ParameterizedTest
     @MethodSource("sha1TestBed")
     void testSha1(String password, String expectedHash) throws Exception {
-        assertThat(DefaultUser.digestString(Optional.ofNullable(password).orElse(""), Algorithm.of("SHA-1")))
+        assertThat(DefaultUser.digestString(Optional.ofNullable(password).orElse(""), Algorithm.of("SHA-1"), "salt"))
             .isEqualTo(expectedHash);
     }
 
@@ -124,7 +125,7 @@ public class DefaultUserTest {
     @ParameterizedTest
     @MethodSource("sha512TestBed")
     void testSha512(String password, String expectedHash) throws Exception {
-        assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-512")))
+        assertThat(DefaultUser.digestString(password, Algorithm.of("SHA-512"), "salt"))
             .isEqualTo(expectedHash);
     }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 09/09: JAMES-3674 PBKDF2 extract default constants

Posted by bt...@apache.org.
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 77bb17b7c4fd5b62aa2e7a1463ef21ac909a4eb2
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 14:17:03 2021 +0700

    JAMES-3674 PBKDF2 extract default constants
---
 .../src/main/java/org/apache/james/user/lib/model/Algorithm.java   | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
index 5c01c68..7b24adc 100644
--- a/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
+++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/model/Algorithm.java
@@ -76,6 +76,9 @@ public class Algorithm {
     }
 
     public static class PBKDF2Hasher implements Hasher {
+        public static final int DEFAULT_ITERATION_COUNT = 1000;
+        public static final int DEFAULT_KEY_SIZE = 512;
+
         public static Optional<Hasher> from(Algorithm algorithm) {
             if (algorithm.getName().startsWith("PBKDF2")) {
                 List<String> parts = Splitter.on('-').splitToList(algorithm.getName());
@@ -89,7 +92,7 @@ public class Algorithm {
             if (parts.size() >= 3) {
                 return Integer.parseInt(parts.get(2));
             } else {
-                return 512;
+                return DEFAULT_KEY_SIZE;
             }
         }
 
@@ -97,7 +100,7 @@ public class Algorithm {
             if (parts.size() >= 2) {
                 return Integer.parseInt(parts.get(1));
             } else {
-                return 1000;
+                return DEFAULT_ITERATION_COUNT;
             }
         }
 

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 07/09: JAMES-3674 PBKDF2 should be the default choice

Posted by bt...@apache.org.
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 c7a132c4557b4c2c919f21e17015c851acaae854
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 3 12:09:06 2021 +0700

    JAMES-3674 PBKDF2 should be the default choice
---
 .../apache/james/user/cassandra/CassandraRepositoryConfiguration.java | 2 +-
 .../data-jpa/src/main/java/org/apache/james/user/jpa/JPAUsersDAO.java | 2 +-
 .../src/main/java/org/apache/james/user/memory/MemoryUsersDAO.java    | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/user/cassandra/CassandraRepositoryConfiguration.java b/server/data/data-cassandra/src/main/java/org/apache/james/user/cassandra/CassandraRepositoryConfiguration.java
index 8c3c8d1..8d96edd 100644
--- a/server/data/data-cassandra/src/main/java/org/apache/james/user/cassandra/CassandraRepositoryConfiguration.java
+++ b/server/data/data-cassandra/src/main/java/org/apache/james/user/cassandra/CassandraRepositoryConfiguration.java
@@ -26,7 +26,7 @@ import org.apache.james.user.lib.model.Algorithm;
 import org.apache.james.user.lib.model.Algorithm.HashingMode;
 
 public class CassandraRepositoryConfiguration {
-    public static final String DEFAULT_ALGORITHM = "SHA-512";
+    public static final String DEFAULT_ALGORITHM = "PBKDF2";
     public static final String DEFAULT_HASHING_MODE = HashingMode.PLAIN.name();
 
     public static final CassandraRepositoryConfiguration DEFAULT = new CassandraRepositoryConfiguration(
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/JPAUsersDAO.java b/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/JPAUsersDAO.java
index d50ded0..fc12e0e 100644
--- a/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/JPAUsersDAO.java
+++ b/server/data/data-jpa/src/main/java/org/apache/james/user/jpa/JPAUsersDAO.java
@@ -56,7 +56,7 @@ public class JPAUsersDAO implements UsersDAO, Configurable {
 
     @Override
     public void configure(HierarchicalConfiguration<ImmutableNode> config) {
-        algo = config.getString("algorithm", "SHA-512");
+        algo = config.getString("algorithm", "PBKDF2");
     }
 
     /**
diff --git a/server/data/data-memory/src/main/java/org/apache/james/user/memory/MemoryUsersDAO.java b/server/data/data-memory/src/main/java/org/apache/james/user/memory/MemoryUsersDAO.java
index 0f93a90..91daeb6 100644
--- a/server/data/data-memory/src/main/java/org/apache/james/user/memory/MemoryUsersDAO.java
+++ b/server/data/data-memory/src/main/java/org/apache/james/user/memory/MemoryUsersDAO.java
@@ -42,12 +42,12 @@ public class MemoryUsersDAO implements UsersDAO, Configurable {
 
     MemoryUsersDAO() {
         this.userByName = new HashMap<>();
-        this.algo = Algorithm.of("SHA-512");
+        this.algo = Algorithm.of("PBKDF2");
     }
 
     @Override
     public void configure(HierarchicalConfiguration<ImmutableNode> config) {
-        algo = Algorithm.of(config.getString("algorithm", "SHA-512"), config.getString("hashingMode", PLAIN.name()));
+        algo = Algorithm.of(config.getString("algorithm", "PBKDF2"), config.getString("hashingMode", PLAIN.name()));
     }
 
     public void clear() {

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org