diff options
Diffstat (limited to 'qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl')
6 files changed, 606 insertions, 0 deletions
diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java new file mode 100644 index 0000000000..3c5ed1d6c2 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java @@ -0,0 +1,137 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import junit.framework.TestCase; +import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +/** + * These tests ensure that the Hex wrapping that the initialiser performs does actually operate when the handle method is called. + */ +public class CRAMMD5HexInitialiserTest extends TestCase +{ + public void testHex() + { + //Create User details for testing + String user = "testUser"; + String password = "testPassword"; + + perform(user, password); + } + + public void testHashedHex() + { + //Create User details for testing + String user = "testUser"; + String password = "testPassword"; + + //Create a hashed password that we then attempt to put through the call back mechanism. + try + { + password = new String(MessageDigest.getInstance("MD5").digest(password.getBytes())); + } + catch (NoSuchAlgorithmException e) + { + fail(e.getMessage()); + } + + perform(user, password); + } + + public void perform(String user, String password) + { + CRAMMD5HexInitialiser initialiser = new CRAMMD5HexInitialiser(); + + //Use properties to create a PrincipalDatabase + Properties users = new Properties(); + users.put(user, password); + + PropertiesPrincipalDatabase db = new PropertiesPrincipalDatabase(users); + + initialiser.initialise(db); + + //setup the callbacks + PasswordCallback passwordCallback = new PasswordCallback("password:", false); + NameCallback usernameCallback = new NameCallback("user:", user); + + Callback[] callbacks = new Callback[]{usernameCallback, passwordCallback}; + + //Check the + try + { + assertNull("The password was not null before the handle call.", passwordCallback.getPassword()); + initialiser.getCallbackHandler().handle(callbacks); + } + catch (IOException e) + { + fail(e.getMessage()); + } + catch (UnsupportedCallbackException e) + { + fail(e.getMessage()); + } + + //Hex the password we initialised with and compare it with the passwordCallback + assertArrayEquals(toHex(password.toCharArray()), passwordCallback.getPassword()); + } + + private void assertArrayEquals(char[] expected, char[] actual) + { + assertEquals("Arrays are not the same length", expected.length, actual.length); + + for (int index = 0; index < expected.length; index++) + { + assertEquals("Characters are not equal", expected[index], actual[index]); + } + } + + private char[] toHex(char[] password) + { + StringBuilder sb = new StringBuilder(); + for (char c : password) + { + //toHexString does not prepend 0 so we have to + if (((byte) c > -1) && (byte) c < 10) + { + sb.append(0); + } + + sb.append(Integer.toHexString(c & 0xFF)); + } + + //Extract the hex string as char[] + char[] hex = new char[sb.length()]; + + sb.getChars(0, sb.length(), hex, 0); + + return hex; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java new file mode 100644 index 0000000000..8b3f9c0622 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java @@ -0,0 +1,230 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import java.io.File; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.Principal; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import junit.framework.TestCase; + +import org.apache.commons.codec.binary.Hex; +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexSaslServer; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexServerFactory; + +/** + * Test for the CRAM-MD5-HEX SASL mechanism. + * + * This test case focuses on testing {@link CRAMMD5HexSaslServer} but also exercises + * collaborators {@link CRAMMD5HexInitialiser} and {@link Base64MD5PasswordFilePrincipalDatabase} + */ +public class CRAMMD5HexServerTest extends TestCase +{ + + private SaslServer _saslServer; // Class under test + private CRAMMD5HexServerFactory _saslFactory; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + CRAMMD5HexInitialiser _initializer = new CRAMMD5HexInitialiser(); + + //Use properties to create a PrincipalDatabase + Base64MD5PasswordFilePrincipalDatabase db = createTestPrincipalDatabase(); + assertEquals("Unexpected number of test users in the db", 2, db.getUsers().size()); + + _initializer.initialise(db); + + _saslFactory = new CRAMMD5HexServerFactory(); + + _saslServer = _saslFactory.createSaslServer(CRAMMD5HexSaslServer.MECHANISM, + "AMQP", + "localhost", + _initializer.getProperties(), + _initializer.getCallbackHandler()); + assertNotNull("Unable to create saslServer with mechanism type " + CRAMMD5HexSaslServer.MECHANISM, _saslServer); + + } + + public void testSuccessfulAuth() throws Exception + { + + final byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + // Generate client response + final byte[] clientResponse = generateClientResponse("knownuser", "guest", serverChallenge); + + + byte[] nextServerChallenge = _saslServer.evaluateResponse(clientResponse); + assertTrue("Exchange must be flagged as complete after successful authentication", _saslServer.isComplete()); + assertNull("Next server challenge must be null after successful authentication", nextServerChallenge); + + } + + public void testKnownUserPresentsWrongPassword() throws Exception + { + byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + + final byte[] clientResponse = generateClientResponse("knownuser", "wrong!", serverChallenge); + try + { + _saslServer.evaluateResponse(clientResponse); + fail("Exception not thrown"); + } + catch (SaslException se) + { + // PASS + } + assertFalse("Exchange must not be flagged as complete after unsuccessful authentication", _saslServer.isComplete()); + } + + public void testUnknownUser() throws Exception + { + final byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + + final byte[] clientResponse = generateClientResponse("unknownuser", "guest", serverChallenge); + + try + { + _saslServer.evaluateResponse(clientResponse); + fail("Exception not thrown"); + } + catch (SaslException se) + { + assertExceptionHasUnderlyingAsCause(AccountNotFoundException.class, se); + // PASS + } + assertFalse("Exchange must not be flagged as complete after unsuccessful authentication", _saslServer.isComplete()); + } + + /** + * + * Demonstrates QPID-3158. A defect meant that users with some valid password were failing to + * authenticate when using the .NET 0-8 client (uses this SASL mechanism). + * It so happens that password "guest2" was one of the affected passwords. + * + * @throws Exception + */ + public void testSuccessfulAuthReproducingQpid3158() throws Exception + { + byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + // Generate client response + byte[] resp = generateClientResponse("qpid3158user", "guest2", serverChallenge); + + byte[] nextServerChallenge = _saslServer.evaluateResponse(resp); + assertTrue("Exchange must be flagged as complete after successful authentication", _saslServer.isComplete()); + assertNull("Next server challenge must be null after successful authentication", nextServerChallenge); + } + + /** + * Since we don't have a CRAM-MD5-HEX implementation client implementation in Java, this method + * provides the implementation for first principals. + * + * @param userId user id + * @param clearTextPassword clear text password + * @param serverChallenge challenge from server + * + * @return challenge response + */ + private byte[] generateClientResponse(final String userId, final String clearTextPassword, final byte[] serverChallenge) throws Exception + { + byte[] digestedPasswordBytes = MessageDigest.getInstance("MD5").digest(clearTextPassword.getBytes()); + char[] hexEncodedDigestedPassword = Hex.encodeHex(digestedPasswordBytes); + byte[] hexEncodedDigestedPasswordBytes = new String(hexEncodedDigestedPassword).getBytes(); + + + Mac hmacMd5 = Mac.getInstance("HmacMD5"); + hmacMd5.init(new SecretKeySpec(hexEncodedDigestedPasswordBytes, "HmacMD5")); + final byte[] messageAuthenticationCode = hmacMd5.doFinal(serverChallenge); + + // Build client response + String responseAsString = userId + " " + new String(Hex.encodeHex(messageAuthenticationCode)); + byte[] resp = responseAsString.getBytes(); + return resp; + } + + /** + * Creates a test principal database. + * + * @return + * @throws IOException + */ + private Base64MD5PasswordFilePrincipalDatabase createTestPrincipalDatabase() throws IOException + { + Base64MD5PasswordFilePrincipalDatabase db = new Base64MD5PasswordFilePrincipalDatabase(); + File file = File.createTempFile("passwd", "db"); + file.deleteOnExit(); + db.setPasswordFile(file.getCanonicalPath()); + db.createPrincipal( createTestPrincipal("knownuser"), "guest".toCharArray()); + db.createPrincipal( createTestPrincipal("qpid3158user"), "guest2".toCharArray()); + return db; + } + + private Principal createTestPrincipal(final String name) + { + return new Principal() + { + + @Override + public String getName() + { + return name; + } + }; + } + + private void assertExceptionHasUnderlyingAsCause(final Class<? extends Throwable> expectedUnderlying, Throwable e) + { + assertNotNull(e); + int infiniteLoopGuard = 0; // Guard against loops in the cause chain + boolean foundExpectedUnderlying = false; + while (e.getCause() != null && infiniteLoopGuard++ < 10) + { + if (expectedUnderlying.equals(e.getCause().getClass())) + { + foundExpectedUnderlying = true; + break; + } + e = e.getCause(); + } + + if (!foundExpectedUnderlying) + { + fail("Not found expected underlying exception " + expectedUnderlying + " as underlying cause of " + e.getClass()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java new file mode 100644 index 0000000000..f80413d4f8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java @@ -0,0 +1,66 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import junit.framework.TestCase; + +public abstract class SaslServerTestCase extends TestCase +{ + protected SaslServer server; + protected String username = "u"; + protected String password = "p"; + protected String notpassword = "a"; + protected PrincipalDatabase db = new TestPrincipalDatabase(); + + protected byte[] correctresponse; + protected byte[] wrongresponse; + + public void testSucessfulAuth() throws SaslException + { + byte[] resp = this.server.evaluateResponse(correctresponse); + assertNull(resp); + } + + public void testFailAuth() + { + boolean exceptionCaught = false; + try + { + byte[] resp = this.server.evaluateResponse(wrongresponse); + } + catch (SaslException e) + { + assertEquals("Authentication failed", e.getCause().getMessage()); + exceptionCaught = true; + } + if (!exceptionCaught) + { + fail("Should have thrown SaslException"); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java new file mode 100644 index 0000000000..8507e49e17 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java @@ -0,0 +1,91 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import java.io.IOException; +import java.security.Principal; +import java.util.List; +import java.util.Map; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; + +public class TestPrincipalDatabase implements PrincipalDatabase +{ + + public boolean createPrincipal(Principal principal, char[] password) + { + // TODO Auto-generated method stub + return false; + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + // TODO Auto-generated method stub + return null; + } + + public Principal getUser(String username) + { + // TODO Auto-generated method stub + return null; + } + + public List<Principal> getUsers() + { + // TODO Auto-generated method stub + return null; + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, + AccountNotFoundException + { + callback.setPassword("p".toCharArray()); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public void reload() throws IOException + { + // TODO Auto-generated method stub + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java new file mode 100644 index 0000000000..6245064bf7 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.amqplain; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class AMQPlainSaslServerTest extends SaslServerTestCase +{ + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new AmqPlainInitialiser(); + handler.initialise(db); + this.server = new AmqPlainSaslServer(handler.getCallbackHandler()); + FieldTable table = FieldTableFactory.newFieldTable(); + table.setString("LOGIN", username); + table.setString("PASSWORD", password); + correctresponse = table.getDataAsBytes(); + table.setString("PASSWORD", notpassword); + wrongresponse = table.getDataAsBytes(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java new file mode 100644 index 0000000000..5dd51250dc --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.plain; + +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class PlainSaslServerTest extends SaslServerTestCase +{ + + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new PlainInitialiser(); + handler.initialise(db); + this.server = new PlainSaslServer(handler.getCallbackHandler()); + correctresponse = new byte[]{0x0, (byte) username.charAt(0), 0x0, (byte) password.charAt(0)}; + wrongresponse = new byte[]{0x0,(byte) username.charAt(0), 0x0, (byte) notpassword.charAt(0)}; + } + +} |