/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., 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.wildfly.security.sasl.gssapi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.wildfly.security.sasl.gssapi.JaasUtil.loginClient;
import static org.wildfly.security.sasl.gssapi.JaasUtil.loginServer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslClientFactory;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import javax.security.sasl.SaslServerFactory;
import org.jboss.logging.Logger;
import org.junit.Test;
import org.wildfly.security.WildFlyElytronProvider;
import org.wildfly.security.sasl.test.BaseTestCase;
/**
* Test case for testing GSSAPI authentication.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public abstract class BaseGssapiTests extends BaseTestCase {
private static final String TEST_SERVER_1 = "test_server_1";
private static final String SASL_CLIENT_FACTORY_GSSAPI = "SaslClientFactory.GSSAPI";
private static final String SASL_SERVER_FACTORY_GSSAPI = "SaslServerFactory.GSSAPI";
private static Logger log = Logger.getLogger(BaseGssapiTests.class);
private static final String GSSAPI = "GSSAPI";
private static final String QOP_AUTH = "auth";
private static final String QOP_AUTH_INT = "auth-int";
private static final String QOP_AUTH_CONF = "auth-conf";
static final String SERVER_KEY_TAB = "serverKeyTab";
/*
* A pair of tests just to verify that a Subject can be obtained, in the event of a failure if these tests are failing focus
* here first.
*/
@Test
public void obtainServer1Subject() throws Exception {
Subject subject = loginServer(GssapiTestSuite.serverKeyTab);
assertNotNull(subject);
}
@Test
public void obtainClientSubject() throws Exception {
Subject subject = loginClient();
assertNotNull(subject);
}
@Test
public void authenticateClientServer1() throws Exception {
/*
* With the JDK supplied mechanism implementations this method can cause a NPE if logging for 'javax.security.sasl' is
* lower than DEBUG
*/
testSasl(false, VerificationMode.NONE);
}
@Test
public void authenticateClientAndServer() throws Exception {
testSasl(true, VerificationMode.NONE);
}
@Test
public void authenticateIntegrityQop() throws Exception {
testSasl(false, VerificationMode.INTEGRITY);
}
@Test
public void authenticateConfidentialityQop() throws Exception {
testSasl(false, VerificationMode.CONFIDENTIALITY);
}
private void testSasl(final boolean authServer, final VerificationMode mode) throws Exception {
SaslClient client = getSaslClient(authServer, mode);
SaslServer server = getSaslServer(mode);
try {
byte[] exchange = new byte[0];
while (client.isComplete() == false || server.isComplete() == false) {
exchange = client.evaluateChallenge(exchange);
if (server.isComplete() == false) {
exchange = server.evaluateResponse(exchange);
}
}
assertTrue(client.isComplete());
assertTrue(server.isComplete());
assertEquals("Authorization ID", "jduke@WILDFLY.ORG", server.getAuthorizationID());
assertEquals("Server QOP", mode.getQop(), server.getNegotiatedProperty(Sasl.QOP));
assertEquals("Client QOP", mode.getQop(), client.getNegotiatedProperty(Sasl.QOP));
/*
* In the case of the non-auth only modes verify the mode is operating.
*/
if (mode != VerificationMode.NONE) {
testDataExchange(client, server);
}
} finally {
try {
client.dispose();
server.dispose();
} catch (SaslException e) {
// Don't want disposal to mask any error in the test but do want it to happen.
e.printStackTrace();
}
}
}
private void testDataExchange(final SaslClient client, final SaslServer server) throws SaslException {
byte[] original = "Some Test Data".getBytes(StandardCharsets.UTF_8);
byte[] backup = "Some Test Data".getBytes(StandardCharsets.UTF_8);
byte[] wrappedFromClient = client.wrap(original, 0, original.length);
assertTrue("Original data unmodified", Arrays.equals(backup, original));
byte[] unwrappedFromClient = server.unwrap(wrappedFromClient, 0, wrappedFromClient.length);
assertTrue("Unwrapped (By Server) matched original", Arrays.equals(unwrappedFromClient, original));
byte[] wrappedFromServer = server.wrap(original, 0, original.length);
assertTrue("Original data unmodified", Arrays.equals(backup, original));
byte[] unwrappedFromServer = client.unwrap(wrappedFromServer, 0, wrappedFromServer.length);
assertTrue("Unwrapped (By Client) matched original", Arrays.equals(unwrappedFromServer, original));
}
/**
* @param authServer whether the server must authenticate to the client
* @param mode quality-of-protection to use
*/
protected abstract SaslClient getSaslClient(final boolean authServer, final VerificationMode mode) throws Exception;
/**
* @param mode quality-of-protection to use
*/
protected abstract SaslServer getSaslServer(final VerificationMode mode) throws Exception;
/*
* Client Creation Methods
*/
protected SaslClient createClient(final Subject subject, final boolean wildFlyProvider, final boolean authServer,
final VerificationMode mode, final Map<String, String> baseProps) throws SaslException {
try {
return Subject.doAs(subject, new PrivilegedExceptionAction<SaslClient>() {
@Override
public SaslClient run() throws SaslException {
return createClient(wildFlyProvider, authServer, mode, baseProps);
}
});
} catch (PrivilegedActionException e) {
if (e.getCause() instanceof SaslException) {
throw (SaslException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
private SaslClient createClient(final boolean wildFlyProvider, final boolean authServer, final VerificationMode mode, final Map<String, String> baseProps)
throws SaslException {
SaslClientFactory factory = findSaslClientFactory(wildFlyProvider);
Map<String, String> props = new HashMap<String, String>(baseProps);
props.put(Sasl.SERVER_AUTH, Boolean.toString(authServer));
props.put(Sasl.QOP, mode.getQop());
return factory.createSaslClient(new String[] { GSSAPI }, null, "sasl", TEST_SERVER_1, props, new NoCallbackHandler());
}
/*
* Server Creation Methods
*/
SaslServer createServer(final Subject subject, final boolean wildFlyProvider, final VerificationMode mode, final Map<String, String> baseProps)
throws SaslException {
try {
return Subject.doAs(subject, new PrivilegedExceptionAction<SaslServer>() {
@Override
public SaslServer run() throws Exception {
return createServer(wildFlyProvider, mode, baseProps);
}
});
} catch (PrivilegedActionException e) {
if (e.getCause() instanceof SaslException) {
throw (SaslException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
private SaslServer createServer(final boolean wildFlyProvider, final VerificationMode mode, final Map<String, String> baseProps) throws SaslException {
SaslServerFactory factory = findSaslServerFactory(wildFlyProvider);
Map<String, String> props = new HashMap<String, String>(baseProps);
props.put(Sasl.QOP, mode.getQop());
return factory.createSaslServer(GSSAPI, "sasl", TEST_SERVER_1, props, new AuthorizeOnlyCallbackHandler());
}
/*
* Provider Methods
*/
private SaslClientFactory findSaslClientFactory(final boolean wildFlyProvider) {
Provider p = findProvider(SASL_CLIENT_FACTORY_GSSAPI, wildFlyProvider);
String factoryName = (String) p.get(SASL_CLIENT_FACTORY_GSSAPI);
try {
return (SaslClientFactory) BaseGssapiTests.class.getClassLoader().loadClass(factoryName).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private SaslServerFactory findSaslServerFactory(final boolean wildFlyProvider) {
Provider p = findProvider(SASL_SERVER_FACTORY_GSSAPI, wildFlyProvider);
String factoryName = (String) p.get(SASL_SERVER_FACTORY_GSSAPI);
try {
return (SaslServerFactory) BaseGssapiTests.class.getClassLoader().loadClass(factoryName).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private Provider findProvider(final String filter, final boolean wildFlyProvider) {
Provider[] providers = Security.getProviders(filter);
for (Provider current : providers) {
if (current instanceof WildFlyElytronProvider) {
if (wildFlyProvider) {
return current;
}
} else if (wildFlyProvider == false) {
return current;
}
}
return null;
}
enum VerificationMode {
NONE(QOP_AUTH), INTEGRITY(QOP_AUTH_INT), CONFIDENTIALITY(QOP_AUTH_CONF);
private final String qop;
VerificationMode(final String qop) {
this.qop = qop;
}
String getQop() {
return qop;
}
}
private class NoCallbackHandler implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
throw new UnsupportedCallbackException(callbacks[0]);
}
}
private class AuthorizeOnlyCallbackHandler implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback current : callbacks) {
if (current instanceof AuthorizeCallback) {
AuthorizeCallback ac = (AuthorizeCallback) current;
ac.setAuthorized(ac.getAuthorizationID().equals(ac.getAuthenticationID()));
} else {
throw new UnsupportedCallbackException(current);
}
}
}
}
}