org.xnio.sasl.SaslUtils Maven / Gradle / Ivy
/*
* 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());
}
}
}