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=4eab8cd87023a0cd5b3f791979af0b1e70ef52ce;hpb=9c1e0062044a1dcf34d34c7cda840334e56a289c;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 4eab8cd87..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,11 +1,9 @@ package org.argeo.osgi.useradmin; -import java.net.URI; -import java.net.URISyntaxException; -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.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -15,9 +13,7 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.StringTokenizer; import javax.naming.NamingEnumeration; import javax.naming.NamingException; @@ -26,8 +22,9 @@ import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.ldap.LdapName; +import org.argeo.naming.AuthPassword; import org.argeo.naming.LdapAttrs; -import org.argeo.naming.NamingUtils; +import org.argeo.naming.SharedSecret; /** Directory user implementation */ class LdifUser implements DirectoryUser { @@ -78,88 +75,95 @@ class LdifUser implements DirectoryUser { public boolean hasCredential(String key, Object value) { if (key == null) { // TODO check other sources (like PKCS12) - String pwd = new String((char[]) value); - char[] password = toChars(value); - byte[] hashedPassword = hash(password); - if (hasCredential(LdapAttrs.userPassword.name(), hashedPassword)) - return true; - if (hasCredential(LdapAttrs.authPassword.name(), pwd)) - return true; - return false; - } - - Object storedValue = getCredentials().get(key); - - // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112) - if (LdapAttrs.authPassword.name().equals(key)) { - 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("X-Node-Token")) { - 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; + // String pwd = new String((char[]) value); + // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112) + char[] password = DigestUtils.bytesToChars(value); + AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password); + if (authPassword != null) { + if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) { + SharedSecret onceToken = new SharedSecret(authPassword); + if (onceToken.isExpired()) { + // AuthPassword.remove(getAttributes(), onceToken); + return false; } else { - throw new UnsupportedOperationException("An expiry timestamp " - + LdapAttrs.modifyTimestamp.name() + " must be set in the URI query"); + // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken); + return true; } - 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); + // TODO delete expired tokens? + } else { + // TODO implement SHA + throw new UnsupportedOperationException( + "Unsupported authPassword scheme " + authPassword.getAuthScheme()); } } + + // Regular password +// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256); + if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password))) + return true; + return false; } + Object storedValue = getCredentials().get(key); if (storedValue == null || value == null) return false; if (!(value instanceof String || value instanceof byte[])) 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() { @@ -324,10 +328,13 @@ 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-")) { + return put(LdapAttrs.authPassword.name(), value); + } userAdmin.checkEdit(); if (!isEditing()) @@ -343,6 +350,7 @@ class LdifUser implements DirectoryUser { try { Attribute attribute = getModifiedAttributes().get(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));