X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.enterprise%2Fsrc%2Forg%2Fargeo%2Fosgi%2Fuseradmin%2FLdifUser.java;h=b3e7f5955579bac5c53dc57c1b8453f0307fa01e;hb=f9efbe5228615951dd8482a4582aa24e00c10ce5;hp=90b8daa0eff0603c3f50e1398586a505e9dee192;hpb=2111a76162534100967eb0e7733632e80d9ddc5f;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java index 90b8daa0e..b3e7f5955 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java @@ -1,7 +1,8 @@ package org.argeo.osgi.useradmin; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; +import static java.nio.charset.StandardCharsets.US_ASCII; + +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -76,7 +77,7 @@ class LdifUser implements DirectoryUser { // TODO check other sources (like PKCS12) // String pwd = new String((char[]) value); // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112) - char[] password = toChars(value); + char[] password = DigestUtils.bytesToChars(value); AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password); if (authPassword != null) { if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) { @@ -97,49 +98,12 @@ class LdifUser implements DirectoryUser { } // Regular password - byte[] hashedPassword = hash(password); - if (hasCredential(LdapAttrs.userPassword.name(), hashedPassword)) +// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256); + if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password))) return true; - // if (hasCredential(LdapAttrs.authPassword.name(), pwd)) - // return true; return false; } - // authPassword (RFC 3112 https://tools.ietf.org/html/rfc3112) - // if (key.startsWith(ClientToken.X_CLIENT_TOKEN)) { - // return ClientToken.checkAttribute(getAttributes(), key, value); - // } else if (key.startsWith(OnceToken.X_ONCE_TOKEN)) { - // return OnceToken.checkAttribute(getAttributes(), key, value); - // } - // StringTokenizer st = new StringTokenizer((String) storedValue, "$ "); - // // TODO make it more robust, deal with bad formatting - // String authScheme = st.nextToken(); - // String authInfo = st.nextToken(); - // String authValue = st.nextToken(); - // if (authScheme.equals(UriToken.X_URI_TOKEN)) { - // UriToken token = new UriToken((String)storedValue); - // try { - // URI uri = new URI(authInfo); - // Map> query = NamingUtils.queryToMap(uri); - // String expiryTimestamp = NamingUtils.getQueryValue(query, - // LdapAttrs.modifyTimestamp.name()); - // if (expiryTimestamp != null) { - // Instant expiryOdt = NamingUtils.ldapDateToInstant(expiryTimestamp); - // if (expiryOdt.isBefore(Instant.now())) - // return false; - // } else { - // throw new UnsupportedOperationException("An expiry timestamp " - // + LdapAttrs.modifyTimestamp.name() + " must be set in the URI query"); - // } - // byte[] hash = Base64.getDecoder().decode(authValue); - // byte[] hashedInput = DigestUtils.sha1((authInfo + - // value).getBytes(StandardCharsets.US_ASCII)); - // return Arrays.equals(hash, hashedInput); - // } catch (URISyntaxException e) { - // throw new UserDirectoryException("Badly formatted " + authInfo, e); - // } - // } - Object storedValue = getCredentials().get(key); if (storedValue == null || value == null) return false; @@ -147,41 +111,59 @@ class LdifUser implements DirectoryUser { return false; if (storedValue instanceof String && value instanceof String) return storedValue.equals(value); - if (storedValue instanceof byte[] && value instanceof byte[]) - return Arrays.equals((byte[]) storedValue, (byte[]) value); + if (storedValue instanceof byte[] && value instanceof byte[]) { + String storedBase64 = new String((byte[]) storedValue, US_ASCII); + String passwordScheme = null; + if (storedBase64.charAt(0) == '{') { + int index = storedBase64.indexOf('}'); + if (index > 0) { + passwordScheme = storedBase64.substring(1, index); + String storedValueBase64 = storedBase64.substring(index + 1); + byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64); + char[] passwordValue = DigestUtils.bytesToChars((byte[]) value); + byte[] valueBytes; + if (DigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) { + valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, null); + } else if (DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) { + // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/ + byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4); + BigInteger iterations = new BigInteger(iterationsArr); + byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length, + iterationsArr.length + 64); + byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length, + storedValueBytes.length); + int keyLengthBits = keyArr.length * 8; + valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt, + iterations.intValue(), keyLengthBits); + } else { + throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme); + } + return Arrays.equals(storedValueBytes, valueBytes); + } + } + } +// if (storedValue instanceof byte[] && value instanceof byte[]) { +// return Arrays.equals((byte[]) storedValue, (byte[]) value); +// } return false; } - /** Hash and clear the password */ - private byte[] hash(char[] password) { - byte[] hashedPassword = ("{SHA}" + Base64.getEncoder().encodeToString(DigestUtils.sha1(toBytes(password)))) - .getBytes(StandardCharsets.UTF_8); - // Arrays.fill(password, '\u0000'); + /** Hash the password */ + byte[] sha1hash(char[] password) { + byte[] hashedPassword = ("{SHA}" + + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password)))) + .getBytes(StandardCharsets.UTF_8); return hashedPassword; } - private byte[] toBytes(char[] chars) { - CharBuffer charBuffer = CharBuffer.wrap(chars); - ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); - byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); - // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data - Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data - return bytes; - } - - private char[] toChars(Object obj) { - if (obj instanceof char[]) - return (char[]) obj; - if (!(obj instanceof byte[])) - throw new IllegalArgumentException(obj.getClass() + " is not a byte array"); - ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj); - CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer); - char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit()); - Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data - Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data - Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data - return res; - } +// byte[] hash(char[] password, String passwordScheme) { +// if (passwordScheme == null) +// passwordScheme = DigestUtils.PASSWORD_SCHEME_SHA; +// byte[] hashedPassword = ("{" + passwordScheme + "}" +// + Base64.getEncoder().encodeToString(DigestUtils.toPasswordScheme(passwordScheme, password))) +// .getBytes(US_ASCII); +// return hashedPassword; +// } @Override public LdapName getDn() { @@ -346,8 +328,8 @@ class LdifUser implements DirectoryUser { public Object put(String key, Object value) { if (key == null) { // TODO persist to other sources (like PKCS12) - char[] password = toChars(value); - byte[] hashedPassword = hash(password); + char[] password = DigestUtils.bytesToChars(value); + byte[] hashedPassword = sha1hash(password); return put(LdapAttrs.userPassword.name(), hashedPassword); } if (key.startsWith("X-")) { @@ -368,8 +350,8 @@ class LdifUser implements DirectoryUser { try { Attribute attribute = getModifiedAttributes().get(key.toString()); - if (attribute == null) - attribute = new BasicAttribute(key.toString()); + // if (attribute == null) // block unit tests + attribute = new BasicAttribute(key.toString()); if (value instanceof String && !isAsciiPrintable(((String) value))) attribute.add(((String) value).getBytes(StandardCharsets.UTF_8)); else