package org.wildfly.security.auth.realm.ldap;
import org.wildfly.common.Assert;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.OneTimePassword;
import org.wildfly.security.password.spec.OneTimePasswordSpec;
import org.wildfly.security.util.Alphabet;
import org.wildfly.security.util.ByteIterator;
import org.wildfly.security.util.CodePointIterator;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.NoSuchAttributeException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.spec.InvalidKeySpecException;
import java.util.Collection;
import java.util.function.Supplier;
import static org.wildfly.security._private.ElytronMessages.log;
/**
* A {@link CredentialLoader} for loading OTP credentials stored within defined attributes of LDAP entries.
*
* @author <a href="mailto:jkalina@redhat.com">Jan Kalina</a>
*/
class OtpCredentialLoader implements CredentialPersister {
private final String algorithmAttributeName;
private final String hashAttributeName;
private final String seedAttributeName;
private final String sequenceAttributeName;
OtpCredentialLoader(String algorithmAttributeName, String hashAttributeName, String seedAttributeName, String sequenceAttributeName) {
Assert.checkNotNullParam("algorithmAttributeName", algorithmAttributeName);
Assert.checkNotNullParam("hashAttributeName", hashAttributeName);
Assert.checkNotNullParam("seedAttributeName", seedAttributeName);
Assert.checkNotNullParam("sequenceAttributeName", sequenceAttributeName);
this.algorithmAttributeName = algorithmAttributeName;
this.hashAttributeName = hashAttributeName;
this.seedAttributeName = seedAttributeName;
this.sequenceAttributeName = sequenceAttributeName;
}
@Override
public SupportLevel getCredentialAcquireSupport(final Class<? extends Credential> credentialType, final String algorithmName) {
if (credentialType == PasswordCredential.class) {
if (algorithmName == null) {
return SupportLevel.SUPPORTED;
}
switch (algorithmName) {
case OneTimePassword.ALGORITHM_OTP_MD5: return SupportLevel.POSSIBLY_SUPPORTED;
case OneTimePassword.ALGORITHM_OTP_SHA1: return SupportLevel.POSSIBLY_SUPPORTED;
default: return SupportLevel.UNSUPPORTED;
}
}
return SupportLevel.UNSUPPORTED;
}
@Override
public ForIdentityLoader forIdentity(DirContext context, String distinguishedName, Attributes attributes) {
return new ForIdentityLoader(context, distinguishedName, attributes);
}
@Override
public void addRequiredIdentityAttributes(Collection<String> attributes) {
attributes.add(algorithmAttributeName);
attributes.add(hashAttributeName);
attributes.add(seedAttributeName);
attributes.add(sequenceAttributeName);
}
private class ForIdentityLoader implements IdentityCredentialPersister {
private final DirContext context;
private final String distinguishedName;
private final Attributes attributes;
public ForIdentityLoader(DirContext context, String distinguishedName, Attributes attributes) {
this.context = context;
this.distinguishedName = distinguishedName;
this.attributes = attributes;
}
@Override
public SupportLevel getCredentialAcquireSupport(final Class<? extends Credential> credentialType, final String algorithmName, final Supplier<Provider[]> providers) {
if (credentialType != PasswordCredential.class) {
return SupportLevel.UNSUPPORTED;
}
Attribute algorithmAttribute = attributes.get(algorithmAttributeName);
Attribute hashAttribute = attributes.get(hashAttributeName);
Attribute seedAttribute = attributes.get(seedAttributeName);
Attribute sequenceAttribute = attributes.get(sequenceAttributeName);
if (algorithmAttribute != null && hashAttribute != null && seedAttribute != null && sequenceAttribute != null && (algorithmName == null || algorithmAttribute.contains(algorithmName))) {
return SupportLevel.SUPPORTED;
}
return SupportLevel.UNSUPPORTED;
}
@Override
public <C extends Credential> C getCredential(final Class<C> credentialType, final String algorithmName, Supplier<Provider[]> providers) {
if (credentialType != PasswordCredential.class) {
return null;
}
try {
Attribute algorithmAttribute = attributes.get(algorithmAttributeName);
Attribute hashAttribute = attributes.get(hashAttributeName);
Attribute seedAttribute = attributes.get(seedAttributeName);
Attribute sequenceAttribute = attributes.get(sequenceAttributeName);
if (algorithmAttribute == null || algorithmName != null && ! algorithmAttribute.contains(algorithmName) || hashAttribute == null || seedAttribute == null || sequenceAttribute == null) {
return null;
}
PasswordFactory passwordFactory = PasswordFactory.getInstance((String) algorithmAttribute.get(), providers);
Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(
CodePointIterator.ofString((String) hashAttribute.get())
.base64Decode(Alphabet.Base64Alphabet.STANDARD, false).drain(),
CodePointIterator.ofString((String) seedAttribute.get())
.base64Decode(Alphabet.Base64Alphabet.STANDARD, false).drain(),
Integer.parseInt((String) sequenceAttribute.get())));
if (credentialType.isAssignableFrom(PasswordCredential.class)) {
return credentialType.cast(new PasswordCredential(password));
}
} catch (NamingException | InvalidKeySpecException | NoSuchAlgorithmException e) {
if (log.isTraceEnabled()) log.trace("Getting OTP credential of type "
+ credentialType.getName() + " failed. dn=" + distinguishedName, e);
}
return null;
}
@Override
public boolean getCredentialPersistSupport(final Class<? extends Credential> credentialType, final String algorithmName) {
return OtpCredentialLoader.this.getCredentialAcquireSupport(credentialType, algorithmName).mayBeSupported();
}
@Override
public void persistCredential(final Credential credential) throws RealmUnavailableException {
OneTimePassword password = credential.castAndApply(PasswordCredential.class, c -> c.getPassword(OneTimePassword.class));
try {
Attributes attributes = new BasicAttributes();
attributes.put(algorithmAttributeName, password.getAlgorithm());
attributes.put(hashAttributeName, ByteIterator.ofBytes(password.getHash()).base64Encode().drainToString());
attributes.put(seedAttributeName, ByteIterator.ofBytes(password.getSeed()).base64Encode().drainToString());
attributes.put(sequenceAttributeName, Integer.toString(password.getSequenceNumber()));
context.modifyAttributes(distinguishedName, DirContext.REPLACE_ATTRIBUTE, attributes);
} catch (NamingException e) {
throw log.ldapRealmCredentialPersistingFailed(credential.toString(), distinguishedName, e);
}
}
@Override public void clearCredentials() throws RealmUnavailableException {
try {
Attributes attributes = new BasicAttributes();
attributes.put(new BasicAttribute(algorithmAttributeName));
attributes.put(new BasicAttribute(hashAttributeName));
attributes.put(new BasicAttribute(seedAttributeName));
attributes.put(new BasicAttribute(sequenceAttributeName));
context.modifyAttributes(distinguishedName, DirContext.REMOVE_ATTRIBUTE, attributes);
} catch (NoSuchAttributeException e) {
// ignore if already clear
} catch (NamingException e) {
throw log.ldapRealmCredentialClearingFailed(distinguishedName, e);
}
}
}
}