/*
 * 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
/** * Utility methods for handling SASL authentication using NIO-style programming methods. * * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */
public final class SaslUtils { private SaslUtils() { }
A zero-length byte array, useful for sending and receiving empty SASL messages.
/** * 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 SaslServerFactorys 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.
Params:
  • classLoader – the class loader to use
  • includeGlobal – true to include globally registered providers, false to exclude them
Returns:the Iterator of SaslServerFactorys
/** * 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<SaslServerFactory> getSaslServerFactories(ClassLoader classLoader, boolean includeGlobal) { return getFactories(SaslServerFactory.class, classLoader, includeGlobal); }
Returns an iterator of all of the registered SaslServerFactorys where the order is based on the order of the Provider registration and/or class path order.
Returns:the Iterator of SaslServerFactorys
/** * 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<SaslServerFactory> getSaslServerFactories() { return getFactories(SaslServerFactory.class, null, true); }
Returns an iterator of all of the registered SaslClientFactorys 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.
Params:
  • classLoader – the class loader to use
  • includeGlobal – true to include globally registered providers, false to exclude them
Returns:the Iterator of SaslClientFactorys
/** * 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<SaslClientFactory> getSaslClientFactories(ClassLoader classLoader, boolean includeGlobal) { return getFactories(SaslClientFactory.class, classLoader, includeGlobal); }
Returns an iterator of all of the registered SaslClientFactorys where the order is based on the order of the Provider registration and/or class path order.
Returns:the Iterator of SaslClientFactorys
/** * 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<SaslClientFactory> getSaslClientFactories() { return getFactories(SaslClientFactory.class, null, true); } private static <T> Iterator<T> getFactories(Class<T> type, ClassLoader classLoader, boolean includeGlobal) { Set<T> factories = new LinkedHashSet<T>(); final ServiceLoader<T> loader = ServiceLoader.load(type, classLoader); for (T factory : loader) { factories.add(factory); } if (includeGlobal) { Set<String> loadedClasses = new HashSet<String>(); 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<ClassLoader>() { 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 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 true then negotiation was successful and no response needs to be sent to the server.

The 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.

Params:
  • client – the SASL client to use to evaluate the challenge message
  • destination – the destination buffer into which the response message should be written, if any
  • source – the source buffer from which the challenge message should be read
Throws:
  • SaslException – if negotiation failed or another error occurred
Returns:true if negotiation is complete and successful, false otherwise
/** * 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. * <p> * 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-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 null then negotiation was successful and no response needs to be sent to the server.

The 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.

Params:
  • client – the SASL client to use to evaluate the challenge message
  • source – the source buffer from which the challenge message should be read
Throws:
  • SaslException – if negotiation failed or another error occurred
Returns:null if negotiation is complete and successful, or the response otherwise
/** * 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. * <p> * 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 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 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 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.

Params:
  • server – the SASL server to use to evaluate the response message
  • destination – the destination buffer into which the response message should be written, if any
  • source – the source buffer from which the response message should be read
Throws:
  • SaslException – if negotiation failed or another error occurred
Returns:true if negotiation is complete and successful, false otherwise
/** * 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). * <p> * 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-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 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 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.

Params:
  • server – the SASL server to use to evaluate the response message
  • source – the source buffer from which the response message should be read
Throws:
  • SaslException – if negotiation failed or another error occurred
Returns:true if negotiation is complete and successful, false otherwise
/** * 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). * <p> * 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 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.

Params:
  • client – the SASL client to wrap with
  • destination – the buffer into which bytes should be written
  • source – the buffers from which bytes should be read
Throws:
See Also:
/** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. * <p> * 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 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.

Params:
  • client – the SASL client to wrap with
  • source – the buffers from which bytes should be read
Throws:
See Also:
Returns:the wrap result
/** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. * <p> * 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 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.

Params:
  • server – the SASL server to wrap with
  • destination – the buffer into which bytes should be written
  • source – the buffers from which bytes should be read
Throws:
See Also:
/** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. * <p> * 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 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.

Params:
  • server – the SASL server to wrap with
  • source – the buffers from which bytes should be read
Throws:
See Also:
Returns:the wrap result
/** * Wrap a message. Wrapping occurs from the source buffer to the destination idea. * <p> * 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 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.

Params:
  • client – the SASL client to unwrap with
  • destination – the buffer into which bytes should be written
  • source – the buffers from which bytes should be read
Throws:
See Also:
/** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. * <p> * 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 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.

Params:
  • client – the SASL client to unwrap with
  • source – the buffers from which bytes should be read
Throws:
See Also:
Returns:the wrap result
/** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. * <p> * 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 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.

Params:
  • server – the SASL server to unwrap with
  • destination – the buffer into which bytes should be written
  • source – the buffers from which bytes should be read
Throws:
See Also:
/** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. * <p> * 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 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.

Params:
  • server – the SASL server to unwrap with
  • source – the buffers from which bytes should be read
Throws:
See Also:
Returns:the wrap result
/** * Unwrap a message. Unwrapping occurs from the source buffer to the destination idea. * <p> * 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.
Params:
  • optionMap – the option map
  • secure – true if the channel is secure, false otherwise
Returns:the property map
/** * 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<String, Object> createPropertyMap(final OptionMap optionMap, final boolean secure) { final Map<String,Object> propertyMap = new HashMap<String, Object>(); 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 <T> void add(OptionMap optionMap, Option<T> option, Map<String, Object> 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<Sequence<SaslQop>> option, Map<String, Object> map, String propName) { final Sequence<SaslQop> value = optionMap.get(option); if (value == null) return; final Sequence<SaslQop> seq = value; final StringBuilder builder = new StringBuilder(); final Iterator<SaslQop> 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<Sequence<Property>> option, Map<String, Object> map) { final Sequence<Property> value = optionMap.get(option); if (value == null) return; for (Property current : value) { map.put(current.getKey(), current.getValue()); } } }