diff --git a/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java b/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java index 32a612f91776795a3c89f6b81335ce484f3006d5..16dfa3f314cfdcf72b8a860767254c7484989347 100644 --- a/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java +++ b/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java @@ -274,18 +274,19 @@ public class WebDAVAuthManager implements AuthenticationSPI { try { dbInstance.commit(); Identity reloadedIdentity = securityManager.loadIdentityByKey(identity.getKey()); - securityManager.createAndPersistAuthentication(reloadedIdentity, provider, authUsername, digestToken, Encoder.Algorithm.md5_noSalt); + securityManager.createAndPersistAuthentication(reloadedIdentity, provider, authUsername, digestToken, Encoder.Algorithm.md5_iso_8859_1); log.audit(doer.getKey() + " created new WebDAV (HA1) authentication for identity: " + identity.getKey() + " (" + authUsername + ")"); } catch(DBRuntimeException e) { log.error("Cannot create digest password with provider " + provider + " for identity:" + identity, e); dbInstance.commit(); } } else { - String md5DigestToken = Encoder.encrypt(digestToken, null, Encoder.Algorithm.md5_noSalt); + String md5DigestToken = Encoder.encrypt(digestToken, null, Encoder.Algorithm.md5_iso_8859_1); if (!md5DigestToken.equals(authHa1.getCredential()) || !authHa1.getAuthusername().equals(authUsername)) { try { authHa1.setCredential(md5DigestToken); authHa1.setAuthusername(authUsername); + authHa1.setAlgorithm(Encoder.Algorithm.md5_iso_8859_1.name()); securityManager.updateAuthentication(authHa1); log.audit(doer.getKey() + " set new WebDAV (HA1) password for identity: " + identity.getKey() + " (" + authUsername + ")"); } catch (DBRuntimeException e) { diff --git a/src/main/java/org/olat/core/util/Encoder.java b/src/main/java/org/olat/core/util/Encoder.java index 59e88a10a7c8cea7552f3025c6e2096db9157412..1856b19dd172b80edf310898005c1435101535c0 100644 --- a/src/main/java/org/olat/core/util/Encoder.java +++ b/src/main/java/org/olat/core/util/Encoder.java @@ -27,6 +27,7 @@ package org.olat.core.util; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -52,22 +53,49 @@ public class Encoder { private static final OLog log = Tracing.createLoggerFor(Encoder.class); public enum Algorithm { - md5("MD5", 1, true), - md5_noSalt("MD5", 1, false), - sha1("SHA-1", 100, true), - sha256("SHA-256", 100, true), - sha512("SHA-512", 100, true), - pbkdf2("PBKDF2WithHmacSHA1", 20000, true), - sha256Exam("SHA-256", 1, false); + /** + * md5 with one iteration and salted (conversion string to bytes made with UTF-8) + */ + md5("MD5", 1, true, null), + /** + * md5 with one iteration without any salt (conversion string to bytes made with UTF-8) + */ + md5_noSalt("MD5", 1, false, null), + /** + * md5 with one iteration and salted (conversion string to bytes made with ISO-8859-1) + */ + md5_iso_8859_1("MD5", 1, false, "ISO-8859-1"), + /** + * SHA-1 with 100 iterations and salted + */ + sha1("SHA-1", 100, true, null), + /** + * SHA-256 with 100 iterations and salted + */ + sha256("SHA-256", 100, true, null), + /** + * SHA-512 with 100 iterations and salted + */ + sha512("SHA-512", 100, true, null), + /** + * PBKDF2 with 20'000 iterations and salted, made to be slow to prevent brute force attack + */ + pbkdf2("PBKDF2WithHmacSHA1", 20000, true, null), + /** + * SHA-256 with one iteration no salted + */ + sha256Exam("SHA-256", 1, false, null); private final boolean salted; private final int iterations; private final String algorithm; + private final Charset charset; - private Algorithm(String algorithm, int iterations, boolean salted) { + private Algorithm(String algorithm, int iterations, boolean salted, String charsetName) { this.algorithm = algorithm; this.iterations = iterations; this.salted = salted; + charset = charsetName == null ? null : Charset.forName(charsetName); } public boolean isSalted() { @@ -82,6 +110,10 @@ public class Encoder { return iterations; } + public Charset getCharset() { + return charset; + } + public static final Algorithm find(String str) { if(StringHelper.containsNonWhitespace(str)) { for(Algorithm value:values()) { @@ -106,7 +138,7 @@ public class Encoder { * @return MD5 encrypted string */ public static String md5hash(String s) { - return md5(s, null); + return md5(s, null, null); } public static String sha256Exam(String s) { @@ -128,24 +160,30 @@ public class Encoder { public static String encrypt(String s, String salt, Algorithm algorithm) { switch(algorithm) { - case md5: return md5(s, salt); + case md5: + return md5(s, salt, algorithm.getCharset()); + case md5_noSalt: + return md5(s, null, algorithm.getCharset()); + case md5_iso_8859_1: + return md5(s, salt, algorithm.getCharset()); case sha1: case sha256: case sha512: return digest(s, salt, algorithm); case pbkdf2: return secretKey(s, salt, algorithm); - default: return md5(s, salt); + default: return md5(s, salt, algorithm.getCharset()); } } - protected static String md5(String s, String salt) { + protected static String md5(String s, String salt, Charset charset) { try { - byte[] inbytes = s.getBytes(); + byte[] inbytes = charset == null ? s.getBytes() : s.getBytes(charset); MessageDigest digest = MessageDigest.getInstance(Algorithm.md5.algorithm); digest.reset(); if(salt != null) { - digest.update(salt.getBytes()); + byte[] saltbytes = charset == null ? salt.getBytes() : salt.getBytes(charset); + digest.update(saltbytes); } byte[] outbytes = digest.digest(inbytes); return md5Encoder.encode(outbytes); diff --git a/src/test/java/org/olat/core/util/EncoderTest.java b/src/test/java/org/olat/core/util/EncoderTest.java index de7bb013083623cce7800e11154533b5798cb43d..e0180c0ad6790bc1bb146fe91c9cfebb6ebac649 100644 --- a/src/test/java/org/olat/core/util/EncoderTest.java +++ b/src/test/java/org/olat/core/util/EncoderTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; +import org.olat.core.commons.services.webdav.manager.WebDAVManagerImpl; import org.olat.core.util.Encoder.Algorithm; /** @@ -41,7 +42,7 @@ public class EncoderTest { //the openolat password as saved on our database String openolat = "c14c4d01c090a065eaa619ca92f8cbc0"; - String hashedOpenolat_1 = Encoder.md5("openolat", null); + String hashedOpenolat_1 = Encoder.md5("openolat", null, null); Assert.assertEquals(openolat, hashedOpenolat_1); String encryptedOpenolat_1 = Encoder.md5hash("openolat"); @@ -51,12 +52,35 @@ public class EncoderTest { Assert.assertEquals(openolat, encryptedOpenolat_2); } + /** + * The test check the use of the UTF-8 or the ISO-8859-1 + * on the hashing algorithm used by the digest authentication. + */ + @Test + public void testDigestLikeCompatibility() { + // UTF-8 encoded of standard username + String rawDigest = digest("myUsername@openolat.org", "de#34KL"); + String ha1_utf8 = Encoder.encrypt(rawDigest, null, Encoder.Algorithm.md5_noSalt); + String ha1_iso = Encoder.encrypt(rawDigest, null, Encoder.Algorithm.md5_iso_8859_1); + Assert.assertEquals(ha1_utf8, ha1_iso); + + // ISO-8859-1 difference with Umlaut + String rawUmlautDigest = digest("myUsern\u00E4me@openolat.org", "de#34KL"); + String ha1_umlaut_utf8 = Encoder.encrypt(rawUmlautDigest, null, Encoder.Algorithm.md5_noSalt); + String ha1_umlaut_iso = Encoder.encrypt(rawUmlautDigest, null, Encoder.Algorithm.md5_iso_8859_1); + Assert.assertNotEquals(ha1_umlaut_utf8, ha1_umlaut_iso); + } + + private String digest(String authUsername, String password) { + return authUsername + ":" + WebDAVManagerImpl.BASIC_AUTH_REALM + ":" + password; + } + /** * Dummy test which check that the salts are not always equals. */ @Test public void testSalt() { - Set<String> history = new HashSet<String>(); + Set<String> history = new HashSet<>(); for(int i=0; i<100; i++) { String salt = Encoder.getSalt(); Assert.assertFalse(history.contains(salt)); diff --git a/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java b/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java index eb6ba5527b085090702c8f11fcdf04431a7477da..8ac186f96499df318c9419becad751b067b9b749 100644 --- a/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java +++ b/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java @@ -30,25 +30,34 @@ import org.junit.Test; */ public class UserNameAndPasswordSyntaxCheckerWithRegexpTest { + @Test + public void defaultPasswordCheck() { + UserNameAndPasswordSyntaxCheckerWithRegexp checker = new UserNameAndPasswordSyntaxCheckerWithRegexp(); + Assert.assertFalse(checker.syntaxCheckOlatPassword("Kan")); + Assert.assertTrue(checker.syntaxCheckOlatPassword("Kanu#01")); + Assert.assertFalse(checker.syntaxCheckOlatPassword("Kan\u00FC#01")); + } + /** * Min. 7 characters, one uppercase, one lowercase, one number */ @Test - public void testCustomPasswordCheck_upperLowerCase_number() { + public void customPasswordCheck_upperLowerCase_number() { UserNameAndPasswordSyntaxCheckerWithRegexp checker = new UserNameAndPasswordSyntaxCheckerWithRegexp(); checker.setPasswordRegExp("(?=^.{7,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$"); Assert.assertTrue(checker.syntaxCheckOlatPassword("Kanu#01")); Assert.assertTrue(checker.syntaxCheckOlatPassword("Kanuunc1")); Assert.assertFalse(checker.syntaxCheckOlatPassword("Kanu#1"));//less than 7 characters - Assert.assertFalse(checker.syntaxCheckOlatPassword("Kanuunch"));//no number + Assert.assertFalse(checker.syntaxCheckOlatPassword("Kanuunch"));//no number Kan\u00FC + Assert.assertTrue(checker.syntaxCheckOlatPassword("Kan\u00FCunc1"));// Umlaut allowed } /** * Min. 8 characters, one uppercase, one lowercase, one number, one special character */ @Test - public void testCustomPasswordCheck_upperLowerCase_number_special() { + public void customPasswordCheck_upperLowerCase_number_special() { UserNameAndPasswordSyntaxCheckerWithRegexp checker = new UserNameAndPasswordSyntaxCheckerWithRegexp(); checker.setPasswordRegExp("(?=^.{8,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z])(?=.*[$@$!%*#?&]).*$");