All Downloads are FREE. Search and download functionalities are using the official Maven repository.

blazingcache.security.sasl.SaslNettyClient Maven / Gradle / Ivy

There is a newer version: 3.3.1
Show newest version
/*
 Licensed to Diennea S.r.l. under one
 or more contributor license agreements. See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership. Diennea S.r.l. licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.

 */
package blazingcache.security.sasl;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.RealmCallback;
import javax.security.sasl.RealmChoiceCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import org.apache.zookeeper.server.auth.KerberosName;

/**
 * Sasl Client
 *
 * @author enrico.olivelli
 */
public class SaslNettyClient {

    private static final Logger LOG = Logger
        .getLogger(SaslNettyClient.class.getName());

    /**
     * Used to respond to server's counterpart, SaslServer with SASL tokens represented as byte arrays.
     */
    private SaslClient saslClient;
    private Subject clientSubject;

    /**
     * Create a SaslNettyClient for authentication with servers.
     */
    public SaslNettyClient(String username, String password, String serverHostname) throws Exception {
        String serverPrincipal = "blazingcache/" + serverHostname;
        clientSubject = loginClient();

        if (clientSubject == null) {
            LOG.log(Level.INFO, "Using plain SASL/DIGEST-MD5 auth to connect to " + serverHostname);
            saslClient = Sasl.createSaslClient(
                new String[]{SaslUtils.AUTH_DIGEST_MD5}, null, null,
                SaslUtils.DEFAULT_REALM, SaslUtils.getSaslProps(),
                new SaslClientCallbackHandler(username, password.toCharArray()));
        } else if (clientSubject.getPrincipals().isEmpty()) {
            LOG.log(Level.INFO, "Using JAAS/SASL/DIGEST-MD5 auth to connect to " + serverPrincipal);
            String[] mechs = {"DIGEST-MD5"};
            username = (String) (clientSubject.getPublicCredentials().toArray()[0]);
            password = (String) (clientSubject.getPrivateCredentials().toArray()[0]);
            saslClient = Sasl.createSaslClient(mechs, username, "blazingcache", "blazingcache", null, new ClientCallbackHandler(password));
        } else { // GSSAPI.
            final Object[] principals = clientSubject.getPrincipals().toArray();
            // determine client principal from subject.
            final Principal clientPrincipal = (Principal) principals[0];
            final KerberosName clientKerberosName = new KerberosName(clientPrincipal.getName());
            KerberosName serviceKerberosName = new KerberosName(serverPrincipal + "@" + clientKerberosName.getRealm());
            final String serviceName = serviceKerberosName.getServiceName();
            final String serviceHostname = serviceKerberosName.getHostName();
            final String clientPrincipalName = clientKerberosName.toString();
            LOG.log(Level.INFO, "Using JAAS/SASL/GSSAPI auth to connect to server Principal " + serverPrincipal);
            saslClient = Subject.doAs(clientSubject, new PrivilegedExceptionAction() {
                @Override
                public SaslClient run() throws SaslException {
                    String[] mechs = {"GSSAPI"};
                    return Sasl.createSaslClient(mechs, clientPrincipalName, serviceName, serviceHostname, null, new ClientCallbackHandler(null));
                }
            });
        }
        if (saslClient == null) {
            throw new IOException("Cannot create JVM SASL Client");
        }

    }

    public byte[] evaluateChallenge(final byte[] saslToken) throws SaslException {
        if (saslToken == null) {
            throw new SaslException("saslToken is null.");
        }

        if (clientSubject != null) {
            try {
                final byte[] retval
                    = Subject.doAs(clientSubject, new PrivilegedExceptionAction() {
                        public byte[] run() throws SaslException {
                            return saslClient.evaluateChallenge(saslToken);
                        }
                    });
                return retval;
            } catch (PrivilegedActionException e) {
                e.printStackTrace();
                throw new SaslException("SASL/JAAS error", e);
            }
        } else {
            return saslClient.evaluateChallenge(saslToken);
        }
    }

    private Subject loginClient() throws SaslException, PrivilegedActionException, LoginException {
        String clientSection = "BlazingCacheClient";
        AppConfigurationEntry[] entries = Configuration.getConfiguration().getAppConfigurationEntry(clientSection);
        if (entries == null) {
            LOG.log(Level.INFO, "No JAAS Configuration found with section BlazingCacheClient");
            return null;
        }
        try {
            LoginContext loginContext = new LoginContext(clientSection, new ClientCallbackHandler(null));
            loginContext.login();
            LOG.log(Level.FINEST, "Using JAAS Configuration subject: " + loginContext.getSubject());
            return loginContext.getSubject();
        } catch (LoginException error) {
            LOG.log(Level.SEVERE, "Error JAAS Configuration subject: " + error, error);
            return null;
        }
    }

    public boolean hasInitialResponse() {
        return saslClient.hasInitialResponse();
    }

    private static class ClientCallbackHandler implements CallbackHandler {

        private String password = null;

        public ClientCallbackHandler(String password) {
            this.password = password;
        }

        @Override
        public void handle(Callback[] callbacks) throws
            UnsupportedCallbackException {
            for (Callback callback : callbacks) {
                if (callback instanceof NameCallback) {
                    NameCallback nc = (NameCallback) callback;
                    nc.setName(nc.getDefaultName());
                } else {
                    if (callback instanceof PasswordCallback) {
                        PasswordCallback pc = (PasswordCallback) callback;
                        if (password != null) {
                            pc.setPassword(this.password.toCharArray());
                        }
                    } else {
                        if (callback instanceof RealmCallback) {
                            RealmCallback rc = (RealmCallback) callback;
                            rc.setText(rc.getDefaultText());
                        } else {
                            if (callback instanceof AuthorizeCallback) {
                                AuthorizeCallback ac = (AuthorizeCallback) callback;
                                String authid = ac.getAuthenticationID();
                                String authzid = ac.getAuthorizationID();
                                if (authid.equals(authzid)) {
                                    ac.setAuthorized(true);
                                } else {
                                    ac.setAuthorized(false);
                                }
                                if (ac.isAuthorized()) {
                                    ac.setAuthorizedID(authzid);
                                }
                            } else {
                                throw new UnsupportedCallbackException(callback, "Unrecognized SASL ClientCallback");
                            }
                        }
                    }
                }
            }
        }
    }

    public boolean isComplete() {
        return saslClient.isComplete();
    }

    /**
     * Respond to server's SASL token.
     *
     * @param saslTokenMessage contains server's SASL token
     * @return client's response SASL token
     */
    public byte[] saslResponse(byte[] saslTokenMessage) {
        try {
            byte[] retval = saslClient.evaluateChallenge(saslTokenMessage);
            return retval;
        } catch (SaslException e) {
            LOG.log(Level.SEVERE,
                "saslResponse: Failed to respond to SASL server's token:",
                e);
            return null;
        }
    }

    private static class SaslClientCallbackHandler implements CallbackHandler {

        private final String userName;

        private final char[] userPassword;

        public SaslClientCallbackHandler(String username, char[] token) {
            this.userName = username;
            this.userPassword = token;
        }

        /**
         * Implementation used to respond to SASL tokens from server.
         *
         * @param callbacks objects that indicate what credential information the server's SaslServer requires from the
         * client.
         * @throws UnsupportedCallbackException
         */
        public void handle(Callback[] callbacks)
            throws UnsupportedCallbackException {
            NameCallback nc = null;
            PasswordCallback pc = null;
            RealmCallback rc = null;
            for (Callback callback : callbacks) {
                if (callback instanceof RealmChoiceCallback) {
                    continue;
                } else if (callback instanceof NameCallback) {
                    nc = (NameCallback) callback;
                } else if (callback instanceof PasswordCallback) {
                    pc = (PasswordCallback) callback;
                } else if (callback instanceof RealmCallback) {
                    rc = (RealmCallback) callback;
                } else {
                    throw new UnsupportedCallbackException(callback,
                        "handle: Unrecognized SASL client callback");
                }
            }
            if (nc != null) {
                nc.setName(userName);
            }
            if (pc != null) {
                pc.setPassword(userPassword);
            }
            if (rc != null) {
                rc.setText(rc.getDefaultText());
            }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy