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

org.xnio.sasl.SaslUtils Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * JBoss, Home of Professional Open Source
 *
 * Copyright 2011 Red Hat, Inc. and/or its affiliates, and individual
 * contributors as indicated by the @author tags.
 *
 * Licensed 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 org.xnio.sasl;

import static java.security.AccessController.doPrivileged;
import static org.xnio._private.Messages.msg;

import java.nio.ByteBuffer;
import java.security.PrivilegedAction;
import java.security.Provider;
import java.security.Security;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import org.xnio.Buffers;
import org.xnio.Option;
import org.xnio.OptionMap;
import org.xnio.Options;
import org.xnio.Property;
import org.xnio.Sequence;

import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslClientFactory;
import javax.security.sasl.SaslServer;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServerFactory;

/**
 * Utility methods for handling SASL authentication using NIO-style programming methods.
 *
 * @author David M. Lloyd
 */
public final class SaslUtils {

    private SaslUtils() {
    }

    /**
     * A zero-length byte array, useful for sending and receiving empty SASL messages.
     */
    public static final byte[] EMPTY_BYTES = new byte[0];

    /**
     * Returns an iterator of all of the registered {@code SaslServerFactory}s where the order is based on the
     * order of the Provider registration and/or class path order.  Class path providers are listed before
     * global providers; in the event of a name conflict, the class path provider is preferred.
     *
     * @param classLoader the class loader to use
     * @param includeGlobal {@code true} to include globally registered providers, {@code false} to exclude them
     * @return the {@code Iterator} of {@code SaslServerFactory}s
     */
    public static Iterator getSaslServerFactories(ClassLoader classLoader, boolean includeGlobal) {
        return getFactories(SaslServerFactory.class, classLoader, includeGlobal);
    }

    /**
     * Returns an iterator of all of the registered {@code SaslServerFactory}s where the order is based on the
     * order of the Provider registration and/or class path order.
     *
     * @return the {@code Iterator} of {@code SaslServerFactory}s
     */
    public static Iterator getSaslServerFactories() {
        return getFactories(SaslServerFactory.class, null, true);
    }

    /**
     * Returns an iterator of all of the registered {@code SaslClientFactory}s where the order is based on the
     * order of the Provider registration and/or class path order.  Class path providers are listed before
     * global providers; in the event of a name conflict, the class path provider is preferred.
     *
     * @param classLoader the class loader to use
     * @param includeGlobal {@code true} to include globally registered providers, {@code false} to exclude them
     * @return the {@code Iterator} of {@code SaslClientFactory}s
     */
    public static Iterator getSaslClientFactories(ClassLoader classLoader, boolean includeGlobal) {
        return getFactories(SaslClientFactory.class, classLoader, includeGlobal);
    }

    /**
     * Returns an iterator of all of the registered {@code SaslClientFactory}s where the order is based on the
     * order of the Provider registration and/or class path order.
     *
     * @return the {@code Iterator} of {@code SaslClientFactory}s
     */
    public static Iterator getSaslClientFactories() {
        return getFactories(SaslClientFactory.class, null, true);
    }

    private static  Iterator getFactories(Class type, ClassLoader classLoader, boolean includeGlobal) {
        Set factories = new LinkedHashSet();
        final ServiceLoader loader = ServiceLoader.load(type, classLoader);
        for (T factory : loader) {
            factories.add(factory);
        }
        if (includeGlobal) {
            Set loadedClasses = new HashSet();
            final String filter = type.getSimpleName() + ".";

            Provider[] providers = Security.getProviders();
            final SecurityManager sm = System.getSecurityManager();
            for (final Provider currentProvider : providers) {
                final ClassLoader cl = sm == null ? currentProvider.getClass().getClassLoader() : doPrivileged(new PrivilegedAction() {
                    public ClassLoader run() {
                        return currentProvider.getClass().getClassLoader();
                    }
                });
                for (Object currentKey : currentProvider.keySet()) {
                    if (currentKey instanceof String &&
                            ((String) currentKey).startsWith(filter) &&
                            ((String) currentKey).indexOf(' ') < 0) {
                        String className = currentProvider.getProperty((String) currentKey);
                        if (className != null && loadedClasses.add(className)) {
                            try {
                                factories.add(Class.forName(className, true, cl).asSubclass(type).newInstance());
                            } catch (ClassNotFoundException e) {
                            } catch (ClassCastException e) {
                            } catch (InstantiationException e) {
                            } catch (IllegalAccessException e) {
                            }
                        }
                    }
                }
            }
        }
        return factories.iterator();
    }

    /**
     * Evaluate a sasl challenge.  If the result is {@code false} then the negotiation is not yet complete and the data
     * written into the destination buffer needs to be sent to the server as a response.  If the result is {@code true}
     * then negotiation was successful and no response needs to be sent to the server.
     * 

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message. The SASL message itself does not encode any length information so it is up to the protocol implementer * to ensure that the message is properly framed. * * @param client the SASL client to use to evaluate the challenge message * @param destination the destination buffer into which the response message should be written, if any * @param source the source buffer from which the challenge message should be read * @return {@code true} if negotiation is complete and successful, {@code false} otherwise * @throws SaslException if negotiation failed or another error occurred */ public static boolean evaluateChallenge(SaslClient client, ByteBuffer destination, ByteBuffer source) throws SaslException { final byte[] result; result = evaluateChallenge(client, source); if (result != null) { if (destination == null) { throw msg.extraChallenge(); } destination.put(result); return false; } else { return true; } } /** * Evaluate a sasl challenge. If the result is non-{@code null} then the negotiation is not yet complete and the data * returned needs to be sent to the server as a response. If the result is {@code null} * then negotiation was successful and no response needs to be sent to the server. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message. The SASL message itself does not encode any length information so it is up to the protocol implementer * to ensure that the message is properly framed. * * @param client the SASL client to use to evaluate the challenge message * @param source the source buffer from which the challenge message should be read * @return {@code null} if negotiation is complete and successful, or the response otherwise * @throws SaslException if negotiation failed or another error occurred */ public static byte[] evaluateChallenge(SaslClient client, ByteBuffer source) throws SaslException { return client.evaluateChallenge(Buffers.take(source)); } /** * Evaluate a sasl response. If the result is {@code false} then the negotiation is not yet complete and the data * written into the destination buffer needs to be sent to the server as a response. If the result is {@code true} * then negotiation was successful and no response needs to be sent to the client (other than a successful completion * message, depending on the protocol). *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message. The SASL message itself does not encode any length information so it is up to the protocol implementer * to ensure that the message is properly framed. * * @param server the SASL server to use to evaluate the response message * @param destination the destination buffer into which the response message should be written, if any * @param source the source buffer from which the response message should be read * @return {@code true} if negotiation is complete and successful, {@code false} otherwise * @throws SaslException if negotiation failed or another error occurred */ public static boolean evaluateResponse(SaslServer server, ByteBuffer destination, ByteBuffer source) throws SaslException { final byte[] result; result = evaluateResponse(server, source); if (result != null) { if (destination == null) { throw msg.extraResponse(); } destination.put(result); return server.isComplete(); } else { return true; } } /** * Evaluate a sasl response. If the result is non-{@code null} then the negotiation is not yet complete and the data * returned needs to be sent to the server as a response. If the result is {@code null} * then negotiation was successful and no response needs to be sent to the client (other than a successful completion * message, depending on the protocol). *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message. The SASL message itself does not encode any length information so it is up to the protocol implementer * to ensure that the message is properly framed. * * @param server the SASL server to use to evaluate the response message * @param source the source buffer from which the response message should be read * @return {@code true} if negotiation is complete and successful, {@code false} otherwise * @throws SaslException if negotiation failed or another error occurred */ public static byte[] evaluateResponse(final SaslServer server, final ByteBuffer source) throws SaslException { return server.evaluateResponse(source.hasRemaining() ? Buffers.take(source) : EMPTY_BYTES); } /** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param client the SASL client to wrap with * @param destination the buffer into which bytes should be written * @param source the buffers from which bytes should be read * @throws SaslException if a SASL error occurs * @see SaslClient#wrap(byte[], int, int) */ public static void wrap(SaslClient client, ByteBuffer destination, ByteBuffer source) throws SaslException { destination.put(wrap(client, source)); } /** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param client the SASL client to wrap with * @param source the buffers from which bytes should be read * @return the wrap result * @throws SaslException if a SASL error occurs * @see SaslClient#wrap(byte[], int, int) */ public static byte[] wrap(final SaslClient client, final ByteBuffer source) throws SaslException { final byte[] result; final int len = source.remaining(); if (len == 0) { result = client.wrap(EMPTY_BYTES, 0, len); } else if (source.hasArray()) { final byte[] array = source.array(); final int offs = source.arrayOffset(); source.position(source.position() + len); result = client.wrap(array, offs, len); } else { result = client.wrap(Buffers.take(source, len), 0, len); } return result; } /** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param server the SASL server to wrap with * @param destination the buffer into which bytes should be written * @param source the buffers from which bytes should be read * @throws SaslException if a SASL error occurs * @see SaslServer#wrap(byte[], int, int) */ public static void wrap(SaslServer server, ByteBuffer destination, ByteBuffer source) throws SaslException { destination.put(wrap(server, source)); } /** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param server the SASL server to wrap with * @param source the buffers from which bytes should be read * @return the wrap result * @throws SaslException if a SASL error occurs * @see SaslServer#wrap(byte[], int, int) */ public static byte[] wrap(final SaslServer server, final ByteBuffer source) throws SaslException { final byte[] result; final int len = source.remaining(); if (len == 0) { result = server.wrap(EMPTY_BYTES, 0, len); } else if (source.hasArray()) { final byte[] array = source.array(); final int offs = source.arrayOffset(); source.position(source.position() + len); result = server.wrap(array, offs, len); } else { result = server.wrap(Buffers.take(source, len), 0, len); } return result; } /** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param client the SASL client to unwrap with * @param destination the buffer into which bytes should be written * @param source the buffers from which bytes should be read * @throws SaslException if a SASL error occurs * @see SaslClient#unwrap(byte[], int, int) */ public static void unwrap(SaslClient client, ByteBuffer destination, ByteBuffer source) throws SaslException { destination.put(unwrap(client, source)); } /** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param client the SASL client to unwrap with * @param source the buffers from which bytes should be read * @return the wrap result * @throws SaslException if a SASL error occurs * @see SaslClient#unwrap(byte[], int, int) */ public static byte[] unwrap(final SaslClient client, final ByteBuffer source) throws SaslException { final byte[] result; final int len = source.remaining(); if (len == 0) { result = client.unwrap(EMPTY_BYTES, 0, len); } else if (source.hasArray()) { final byte[] array = source.array(); final int offs = source.arrayOffset(); source.position(source.position() + len); result = client.unwrap(array, offs, len); } else { result = client.unwrap(Buffers.take(source, len), 0, len); } return result; } /** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param server the SASL server to unwrap with * @param destination the buffer into which bytes should be written * @param source the buffers from which bytes should be read * @throws SaslException if a SASL error occurs * @see SaslServer#unwrap(byte[], int, int) */ public static void unwrap(SaslServer server, ByteBuffer destination, ByteBuffer source) throws SaslException { destination.put(unwrap(server, source)); } /** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. *

* The {@code source} buffer should have its position and remaining length set to encompass exactly one SASL * message (without the length field). The SASL message itself does not encode any length information so it is up * to the protocol implementer to ensure that the message is properly framed. * * @param server the SASL server to unwrap with * @param source the buffers from which bytes should be read * @return the wrap result * @throws SaslException if a SASL error occurs * @see SaslServer#unwrap(byte[], int, int) */ public static byte[] unwrap(final SaslServer server, final ByteBuffer source) throws SaslException { final byte[] result; final int len = source.remaining(); if (len == 0) { result = server.unwrap(EMPTY_BYTES, 0, len); } else if (source.hasArray()) { final byte[] array = source.array(); final int offs = source.arrayOffset(); source.position(source.position() + len); result = server.unwrap(array, offs, len); } else { result = server.unwrap(Buffers.take(source, len), 0, len); } return result; } /** * Create a SASL property map from an XNIO option map. * * @param optionMap the option map * @param secure {@code true} if the channel is secure, {@code false} otherwise * @return the property map */ public static Map createPropertyMap(final OptionMap optionMap, final boolean secure) { final Map propertyMap = new HashMap(); add(optionMap, Options.SASL_POLICY_FORWARD_SECRECY, propertyMap, Sasl.POLICY_FORWARD_SECRECY, null); add(optionMap, Options.SASL_POLICY_NOACTIVE, propertyMap, Sasl.POLICY_NOACTIVE, null); add(optionMap, Options.SASL_POLICY_NOANONYMOUS, propertyMap, Sasl.POLICY_NOANONYMOUS, Boolean.TRUE); add(optionMap, Options.SASL_POLICY_NODICTIONARY, propertyMap, Sasl.POLICY_NODICTIONARY, null); add(optionMap, Options.SASL_POLICY_NOPLAINTEXT, propertyMap, Sasl.POLICY_NOPLAINTEXT, Boolean.valueOf(! secure)); add(optionMap, Options.SASL_POLICY_PASS_CREDENTIALS, propertyMap, Sasl.POLICY_PASS_CREDENTIALS, null); add(optionMap, Options.SASL_REUSE, propertyMap, Sasl.REUSE, null); add(optionMap, Options.SASL_SERVER_AUTH, propertyMap, Sasl.SERVER_AUTH, null); addQopList(optionMap, Options.SASL_QOP, propertyMap, Sasl.QOP); add(optionMap, Options.SASL_STRENGTH, propertyMap, Sasl.STRENGTH, null); addSaslProperties(optionMap, Options.SASL_PROPERTIES, propertyMap); return propertyMap; } private static void add(OptionMap optionMap, Option option, Map map, String propName, T defaultVal) { final Object value = optionMap.get(option, defaultVal); if (value != null) map.put(propName, value.toString().toLowerCase(Locale.US)); } private static void addQopList(OptionMap optionMap, Option> option, Map map, String propName) { final Sequence value = optionMap.get(option); if (value == null) return; final Sequence seq = value; final StringBuilder builder = new StringBuilder(); final Iterator iterator = seq.iterator(); if (!iterator.hasNext()) { return; } else do { builder.append(iterator.next().getString()); if (iterator.hasNext()) { builder.append(','); } } while (iterator.hasNext()); map.put(propName, builder.toString()); } private static void addSaslProperties(OptionMap optionMap, Option> option, Map map) { final Sequence value = optionMap.get(option); if (value == null) return; for (Property current : value) { map.put(current.getKey(), current.getValue()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy