X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.enterprise%2Fsrc%2Forg%2Fargeo%2Fosgi%2Fuseradmin%2FLdifUser.java;h=392b17428b00b748be25de7f2c8b04000438c120;hb=088c1b517a543e935d8ab65c3b2fd2d0269b551d;hp=7cf416526ffdea417975c0cc9c3f704d564c8e2e;hpb=d2057396fab26e7b94e9d479d8429e0ed2487067;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 7cf416526..392b17428 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java @@ -1,12 +1,6 @@ package org.argeo.osgi.useradmin; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -16,9 +10,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; @@ -27,8 +19,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 { @@ -79,47 +72,73 @@ 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); + // 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 { + // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken); + return true; + } + // TODO delete expired tokens? + } else { + // TODO implement SHA + throw new UnsupportedOperationException( + "Unsupported authPassword scheme " + authPassword.getAuthScheme()); + } + } + + // Regular password byte[] hashedPassword = hash(password); if (hasCredential(LdapAttrs.userPassword.name(), hashedPassword)) return true; - if (hasCredential(LdapAttrs.authPassword.name(), pwd)) - 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) { - OffsetDateTime expiryOdt = NamingUtils.ldapDateToInstant(expiryTimestamp); - if (expiryOdt.isBefore(OffsetDateTime.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); - } - } - } + // 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; if (!(value instanceof String || value instanceof byte[])) @@ -133,35 +152,38 @@ class LdifUser implements DirectoryUser { /** Hash and clear the password */ private byte[] hash(char[] password) { - byte[] hashedPassword = ("{SHA}" + Base64.getEncoder().encodeToString(DigestUtils.sha1(toBytes(password)))) - .getBytes(StandardCharsets.UTF_8); + byte[] hashedPassword = ("{SHA}" + + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password)))) + .getBytes(StandardCharsets.UTF_8); // Arrays.fill(password, '\u0000'); 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; - } - + // 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; + // } + // @Override public LdapName getDn() { return dn; @@ -325,10 +347,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); + char[] password = DigestUtils.bytesToChars(value); byte[] hashedPassword = hash(password); return put(LdapAttrs.userPassword.name(), hashedPassword); } + if (key.startsWith("X-")) { + return put(LdapAttrs.authPassword.name(), value); + } userAdmin.checkEdit(); if (!isEditing()) @@ -344,6 +369,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));