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;
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;
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 {
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<String, List<String>> 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() {
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())
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));