/*
* 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.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import org.junit.Test;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.auth.client.ClientUtils;
import org.wildfly.security.auth.client.MatchRule;
import org.wildfly.security.sasl.localuser.LocalUserServerFactory;
import org.wildfly.security.util.CodePointIterator;
/**
* Test for the local user SASL mechanism, this will test both the client and server side.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
public class LocalUserTest extends BaseTestCase {
private static final String LOCAL_USER = "JBOSS-LOCAL-USER";
/*
* Normal SASL Client/Server interaction - Client First
*/
/**
* Test a successful exchange using the JBOSS-LOCAL-USER mechanism.
*/
@Test
public void testSuccessfulExchange_CF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[]{ LOCAL_USER }, "George", "TestProtocol", "TestServer", Collections.<String, Object>emptyMap(), clientCallback);
assertTrue(client.hasInitialResponse());
byte[] response = client.evaluateChallenge(new byte[0]);
byte[] challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
assertNull(challenge);
assertTrue(server.isComplete());
assertTrue(client.isComplete());
assertEquals("George", server.getAuthorizationID());
server.dispose();
}
/**
* Test a successful exchange using the JBOSS-LOCAL-USER mechanism with quiet client side
* and default user server side.
*/
@Test
public void testSuccessfulQuietExchange_CF() throws Exception {
Map<String, Object> serverOptions = new HashMap<>();
serverOptions.put("wildfly.sasl.local-user.default-user", "$local");
final Map<String, String> passwordMap = new HashMap<String, String>();
passwordMap.put("$local", null);
passwordMap.put("George", null);
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setPasswordMap(passwordMap)
.setProperties(serverOptions)
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
Map<String, String> clientOptions = new HashMap<String, String>();
clientOptions.put("wildfly.sasl.local-user.quiet-auth", "true");
SaslClient client = Sasl.createSaslClient(new String[]{ LOCAL_USER }, null, "TestProtocol", "TestServer", clientOptions, clientCallback);
assertTrue(client.hasInitialResponse());
byte[] response = client.evaluateChallenge(new byte[0]);
byte[] challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
assertNull(challenge);
assertTrue(server.isComplete());
assertTrue(client.isComplete());
assertEquals("$local", server.getAuthorizationID());
server.dispose();
}
/**
* Test an exchange where the client sends a bad response is correctly rejected.
*/
@Test
public void testBadExchange_CF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[]{LOCAL_USER}, "George", "TestProtocol", "TestServer", Collections.<String, Object>emptyMap(), clientCallback);
assertTrue(client.hasInitialResponse());
byte[] response = client.evaluateChallenge(new byte[0]);
byte[] challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
for (int i = 0; i < 8; i++) {
response[i] = 0x00;
}
try {
challenge = server.evaluateResponse(response);
fail("Expected SaslException not thrown.");
} catch (SaslException expected) {
}
assertFalse(server.isComplete());
try {
server.getAuthorizationID();
fail("Expected IllegalStateException not thrown");
} catch (IllegalStateException expected) {
}
server.dispose();
}
/**
* Test an exchange where the client is passed the path to a file that does not exist.
*/
@Test
public void testBadFile_CF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[]{LOCAL_USER}, "George", "TestProtocol", "TestServer",
Collections.<String, Object>emptyMap(), clientCallback);
assertTrue(client.hasInitialResponse());
byte[] response = client.evaluateChallenge(new byte[0]);
byte[] challenge = server.evaluateResponse(response);
File nonExistant = new File("nonExistant.txt");
String path = nonExistant.getAbsolutePath();
challenge = CodePointIterator.ofString(path).asUtf8(true).drain();
try {
response = client.evaluateChallenge(challenge);
} catch (SaslException expected) {
}
assertFalse(server.isComplete());
try {
server.getAuthorizationID();
fail("Expected IllegalStateException not thrown");
} catch (IllegalStateException expected) {
}
server.dispose();
}
/**
* Test an exchange where there is no authorization ID
*/
@Test
public void testNoAuthorizationId_CF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[] { LOCAL_USER }, null, "TestProtocol", "TestServer",
Collections.<String, Object> emptyMap(), clientCallback);
assertTrue(client.hasInitialResponse());
byte[] response = client.evaluateChallenge(new byte[0]);
byte[] challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
assertNull(challenge);
assertTrue(server.isComplete());
assertTrue(client.isComplete());
assertEquals("George", server.getAuthorizationID());
server.dispose();
}
/*
* Normal SASL Client/Server interaction - Server First
*/
/**
* Test a successful exchange using the JBOSS-LOCAL-USER mechanism.
*/
@Test
public void testSuccessfulExchange_SF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[]{ LOCAL_USER }, "George", "TestProtocol", "TestServer", Collections.<String, Object>emptyMap(), clientCallback);
byte[] challenge = server.evaluateResponse(new byte[0]);
byte[] response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
assertNull(challenge);
assertTrue(server.isComplete());
assertTrue(client.isComplete());
assertEquals("George", server.getAuthorizationID());
server.dispose();
}
/**
* Test a successful exchange using the JBOSS-LOCAL-USER mechanism with quiet client side
* and default user server side.
*/
@Test
public void testSuccessfulQuietExchange_SF() throws Exception {
Map<String, Object> serverOptions = new HashMap<>();
serverOptions.put("wildfly.sasl.local-user.default-user", "$local");
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("$local")
.setProperties(serverOptions)
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
Map<String, String> clientOptions = new HashMap<String, String>();
clientOptions.put("wildfly.sasl.local-user.quiet-auth", "true");
SaslClient client = Sasl.createSaslClient(new String[]{LOCAL_USER}, null, "TestProtocol", "TestServer", clientOptions, clientCallback);
byte[] challenge = server.evaluateResponse(new byte[0]);
byte[] response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
assertNull(challenge);
assertTrue(server.isComplete());
assertTrue(client.isComplete());
assertEquals("$local", server.getAuthorizationID());
server.dispose();
}
/**
* Test an exchange where the client sends a bad response is correctly rejected.
*/
@Test
public void testBadExchange_SF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[]{LOCAL_USER}, "George", "TestProtocol", "TestServer", Collections.<String, Object>emptyMap(), clientCallback);
byte[] challenge = server.evaluateResponse(new byte[0]);
byte[] response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
for (int i = 0; i < 8; i++) {
response[i] = 0x00;
}
try {
challenge = server.evaluateResponse(response);
fail("Expected SaslException not thrown.");
} catch (SaslException expected) {
}
assertFalse(server.isComplete());
try {
server.getAuthorizationID();
fail("Expected IllegalStateException not thrown");
} catch (IllegalStateException expected) {
}
server.dispose();
}
/**
* Test an exchange where the client is passed the path to a file that does not exist.
*/
@Test
public void testBadFile_SF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[]{LOCAL_USER}, "George", "TestProtocol", "TestServer",
Collections.<String, Object>emptyMap(), clientCallback);
byte[] challenge = server.evaluateResponse(new byte[0]);
byte[] response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
File nonExistant = new File("nonExistant.txt");
String path = nonExistant.getAbsolutePath();
challenge = CodePointIterator.ofString(path).asUtf8(true).drain();
try {
response = client.evaluateChallenge(challenge);
} catch (SaslException expected) {
}
assertFalse(server.isComplete());
try {
server.getAuthorizationID();
fail("Expected IllegalStateException not thrown");
} catch (IllegalStateException expected) {
}
server.dispose();
}
/**
* Test an exchange where there is no authorization ID
*/
@Test
public void testNoAuthorizationId_SF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
CallbackHandler clientCallback = createClientCallbackHandler("George");
SaslClient client = Sasl.createSaslClient(new String[] { LOCAL_USER }, null, "TestProtocol", "TestServer",
Collections.<String, Object> emptyMap(), clientCallback);
byte[] challenge = server.evaluateResponse(new byte[0]);
byte[] response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
response = client.evaluateChallenge(challenge);
challenge = server.evaluateResponse(response);
assertNull(challenge);
assertTrue(server.isComplete());
assertTrue(client.isComplete());
assertEquals("George", server.getAuthorizationID());
server.dispose();
}
/**
* Test that is a SaslServer is disposed of before the challenge is verified the temporary file is deleted.
*/
@Test
public void testTmpFileDeleted_SF() throws Exception {
SaslServer server = new SaslServerBuilder(LocalUserServerFactory.class, LOCAL_USER)
.setUserName("George")
.build();
byte[] challenge = server.evaluateResponse(new byte[0]);
challenge = server.evaluateResponse(new byte[]{0}); // Simulate initial message from client.
final String path = new String(challenge, StandardCharsets.UTF_8);
final File file = new File(path);
assertTrue("Temporary file was created.", file.exists());
server.dispose();
assertFalse("Temporary file was deleted.", file.exists());
}
private CallbackHandler createClientCallbackHandler(final String expectedUsername) throws Exception {
final AuthenticationContext context = AuthenticationContext.empty()
.with(
MatchRule.ALL,
AuthenticationConfiguration.EMPTY
.useName(expectedUsername)
.useRealm("mainRealm")
.allowSaslMechanisms(LOCAL_USER));
return ClientUtils.getCallbackHandler(new URI("doesnot://matter?"), context);
}
}