/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.domain.http.server.security; import static org.wildfly.security.password.interfaces.DigestPassword.ALGORITHM_DIGEST_MD5; import static java.nio.charset.StandardCharsets.UTF_8; import static org.jboss.as.domain.http.server.logging.HttpServerLogger.ROOT_LOGGER; import static org.jboss.as.domain.management.RealmConfigurationConstants.DIGEST_PLAIN_TEXT; import io.undertow.security.idm.Account; import io.undertow.security.idm.Credential; import io.undertow.security.idm.DigestCredential; import io.undertow.security.idm.GSSContextCredential; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import io.undertow.security.idm.X509CertificateCredential; import io.undertow.util.HexConverter; import java.io.IOException; import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.util.Collection; import java.util.Collections; import java.util.Map; import javax.security.auth.callback.Callback; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.sasl.AuthorizeCallback; import javax.security.sasl.RealmCallback; import org.ietf.jgss.GSSException; import org.jboss.as.controller.security.InetAddressPrincipal; import org.jboss.as.core.security.SimplePrincipal; import org.jboss.as.core.security.SubjectUserInfo; import org.jboss.as.domain.http.server.logging.HttpServerLogger; import org.jboss.as.domain.management.AuthMechanism; import org.jboss.as.domain.management.AuthorizingCallbackHandler; import org.jboss.as.domain.management.SecurityRealm; import org.jboss.as.domain.management.SubjectIdentity; import org.wildfly.security.auth.callback.CredentialCallback; import org.wildfly.security.auth.callback.EvidenceVerifyCallback; import org.wildfly.security.evidence.PasswordGuessEvidence; import org.wildfly.security.password.interfaces.DigestPassword; import org.wildfly.security.util.ByteIterator; /** * {@link IdentityManager} implementation to wrap the current security realms. * * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> * @deprecated Elytron integration will make this obsolete. */ @Deprecated public class RealmIdentityManager implements IdentityManager { private static final ThreadLocal<ThreadLocalStore> requestSpecific = new ThreadLocal<ThreadLocalStore>(); static void setRequestSpecific(final AuthMechanism mechanism, final InetAddress clientAddress) { ThreadLocalStore store = new ThreadLocalStore(); store.requestMechanism = mechanism; store.inetAddress = clientAddress; requestSpecific.set(store); } static void clearRequestSpecific() { ThreadLocalStore store = requestSpecific.get(); if (store != null && store.subjectIdentity != null) { store.subjectIdentity.logout(); } requestSpecific.set(null); } private AuthMechanism getRequestMeschanism() { ThreadLocalStore store = requestSpecific.get(); return store == null ? null : store.requestMechanism; } private InetAddress getInetAddress() { ThreadLocalStore store = requestSpecific.get(); return store == null ? null : store.inetAddress; } void setCurrentSubjectIdentity(final SubjectIdentity subjectIdentity) { requestSpecific.get().subjectIdentity = subjectIdentity; } private final SecurityRealm securityRealm; public RealmIdentityManager(final SecurityRealm securityRealm) { this.securityRealm = securityRealm; } @Override public Account verify(Account account) { return account; } private boolean plainTextDigest() { Map<String, String> mechConfig = securityRealm.getMechanismConfig(AuthMechanism.DIGEST); boolean plainTextDigest = true; if (mechConfig.containsKey(DIGEST_PLAIN_TEXT)) { plainTextDigest = Boolean.parseBoolean(mechConfig.get(DIGEST_PLAIN_TEXT)); } return plainTextDigest; } /* * This verify method is used to verify both BASIC authentication and DIGEST authentication requests. */ @Override public Account verify(String id, Credential credential) { if (id == null || id.length() == 0) { HttpServerLogger.ROOT_LOGGER.debug("Missing or empty username received, aborting account verification."); return null; } if (credential instanceof PasswordCredential) { return verify(id, (PasswordCredential) credential); } else if (credential instanceof DigestCredential) { return verify(id, (DigestCredential) credential); } throw HttpServerLogger.ROOT_LOGGER.invalidCredentialType(credential.getClass().getName()); } private Account verify(String id, PasswordCredential credential) { assertMechanism(AuthMechanism.PLAIN); if (credential instanceof PasswordCredential == false) { return null; } AuthorizingCallbackHandler ach = securityRealm.getAuthorizingCallbackHandler(AuthMechanism.PLAIN); Callback[] callbacks = new Callback[3]; callbacks[0] = new RealmCallback("Realm", securityRealm.getName()); callbacks[1] = new NameCallback("Username", id); callbacks[2] = new EvidenceVerifyCallback(new PasswordGuessEvidence(credential.getPassword())); try { ach.handle(callbacks); } catch (Exception e) { ROOT_LOGGER.debug("Failure handling Callback(s) for BASIC authentication.", e); return null; } if (((EvidenceVerifyCallback) callbacks[2]).isVerified() == false) { return null; } Principal user = new SimplePrincipal(id); Collection<Principal> userCol = Collections.singleton(user); SubjectUserInfo supplemental; try { supplemental = ach.createSubjectUserInfo(userCol); } catch (IOException e) { return null; } addInetPrincipal(supplemental.getSubject().getPrincipals()); return new RealmIdentityAccount(supplemental.getSubject(), user); } private Account verify(String id, DigestCredential credential) { assertMechanism(AuthMechanism.DIGEST); AuthorizingCallbackHandler ach = securityRealm.getAuthorizingCallbackHandler(AuthMechanism.DIGEST); Callback[] callbacks = new Callback[3]; callbacks[0] = new RealmCallback("Realm", credential.getRealm()); callbacks[1] = new NameCallback("Username", id); boolean plainText = plainTextDigest(); if (plainText) { callbacks[2] = new PasswordCallback("Password", false); } else { callbacks[2] = new CredentialCallback(org.wildfly.security.credential.PasswordCredential.class, ALGORITHM_DIGEST_MD5); } try { ach.handle(callbacks); } catch (Exception e) { ROOT_LOGGER.debug("Failure handling Callback(s) for BASIC authentication.", e); return null; } byte[] ha1; if (plainText) { MessageDigest digest = null; try { digest = credential.getAlgorithm().getMessageDigest(); digest.update(id.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(credential.getRealm().getBytes(UTF_8)); digest.update((byte) ':'); digest.update(new String(((PasswordCallback) callbacks[2]).getPassword()).getBytes(UTF_8)); ha1 = HexConverter.convertToHexBytes(digest.digest()); } catch (NoSuchAlgorithmException e) { ROOT_LOGGER.debug("Unexpected authentication failure", e); return null; } finally { digest.reset(); } } else { org.wildfly.security.credential.PasswordCredential passwordCredential = (org.wildfly.security.credential.PasswordCredential) (((CredentialCallback)callbacks[2]).getCredential()); DigestPassword digestPassword = passwordCredential.getPassword(DigestPassword.class); ha1 = ByteIterator.ofBytes(digestPassword.getDigest()).hexEncode().drainToString().getBytes(StandardCharsets.US_ASCII); } try { if (credential.verifyHA1(ha1)) { Principal user = new SimplePrincipal(id); Collection<Principal> userCol = Collections.singleton(user); SubjectUserInfo supplemental = ach.createSubjectUserInfo(userCol); addInetPrincipal(supplemental.getSubject().getPrincipals()); return new RealmIdentityAccount(supplemental.getSubject(), user); } } catch (IOException e) { ROOT_LOGGER.debug("Unexpected authentication failure", e); } return null; } /* * The final single method is used for Client Cert style authentication only. */ @Override public Account verify(Credential credential) { assertMechanism(AuthMechanism.CLIENT_CERT, AuthMechanism.KERBEROS); final AuthorizingCallbackHandler ach; final Principal user; if (credential instanceof X509CertificateCredential) { X509CertificateCredential certCred = (X509CertificateCredential) credential; ach = securityRealm.getAuthorizingCallbackHandler(AuthMechanism.CLIENT_CERT); user = certCred.getCertificate().getSubjectDN(); } else if (credential instanceof GSSContextCredential) { GSSContextCredential gssCred = (GSSContextCredential) credential; try { user = new KerberosPrincipal(gssCred.getGssContext().getSrcName().toString()); } catch (GSSException e) { // By this point this should not be able to happen. ROOT_LOGGER.debug("Unexpected authentication failure", e); return null; } ach = securityRealm.getAuthorizingCallbackHandler(AuthMechanism.KERBEROS); } else { return null; } try { ach.handle(new Callback[] { new AuthorizeCallback(user.getName(), user.getName()) }); } catch (IOException e) { ROOT_LOGGER.debug("Unexpected authentication failure", e); return null; } catch (UnsupportedCallbackException e) { ROOT_LOGGER.debug("Unexpected authentication failure", e); return null; } Collection<Principal> userCol = Collections.singleton(user); SubjectUserInfo supplemental; try { supplemental = ach.createSubjectUserInfo(userCol); } catch (IOException e) { return null; } addInetPrincipal(supplemental.getSubject().getPrincipals()); return new RealmIdentityAccount(supplemental.getSubject(), user); } private void addInetPrincipal(final Collection<Principal> principals) { InetAddress address = getInetAddress(); if (address != null) { principals.add(new InetAddressPrincipal(address)); } } private void assertMechanism(final AuthMechanism... mechanisms) { AuthMechanism requested = getRequestMeschanism(); for (AuthMechanism current : mechanisms) { if (requested == current) { return; } } // This is impossible, only here for testing if someone messed up a change throw new IllegalStateException("Unexpected authentication mechanism executing."); } private static final class ThreadLocalStore { AuthMechanism requestMechanism; InetAddress inetAddress; SubjectIdentity subjectIdentity; } }