/* * #%L * ScramSaslClientTest.java - mongodb-async-driver - Allanbank Consulting, Inc. * %% * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc. * %% * 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. * #L% */ package com.allanbank.mongodb.client.connection.auth; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.sameInstance; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; import java.text.Normalizer; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.SaslException; import org.easymock.Capture; import org.easymock.IAnswer; import org.junit.Test; import com.allanbank.mongodb.util.IOUtils; /** * ScramSaslClientTest provides tests for the {@link ScramSaslClient}. * * @copyright 2014, Allanbank Consulting, Inc., All Rights Reserved */ public class ScramSaslClientTest { /** * Test method for {@link ScramSaslClient#createInitialMessage()}. * * @throws UnsupportedCallbackException * On a test failure. * @throws IOException * On a test failure. */ @Test public void testCreateInitialMessageOnCallbackThrowingAnIOException() throws IOException, UnsupportedCallbackException { final CallbackHandler mockHandler = createMock(CallbackHandler.class); final IOException thrown = new IOException("Injected."); final Capture<Callback[]> callbackCapture = new Capture<Callback[]>(); mockHandler.handle(capture(callbackCapture)); expectLastCall().andThrow(thrown); replay(mockHandler); final ScramSaslClient client = new ScramSaslClient(mockHandler); try { client.evaluateChallenge(null); } catch (final SaslException expected) { assertThat(expected.getCause(), sameInstance((Throwable) thrown)); } verify(mockHandler); assertThat(callbackCapture.getValue().length, is(1)); assertThat(callbackCapture.getValue()[0], instanceOf(NameCallback.class)); } /** * Test method for {@link ScramSaslClient#createInitialMessage()}. * * @throws UnsupportedCallbackException * On a test failure. * @throws IOException * On a test failure. */ @Test public void testCreateInitialMessageOnCallbackThrowingAnUnsupportedCallbackException() throws IOException, UnsupportedCallbackException { final CallbackHandler mockHandler = createMock(CallbackHandler.class); final UnsupportedCallbackException thrown = new UnsupportedCallbackException( null); final Capture<Callback[]> callbackCapture = new Capture<Callback[]>(); mockHandler.handle(capture(callbackCapture)); expectLastCall().andThrow(thrown); replay(mockHandler); final ScramSaslClient client = new ScramSaslClient(mockHandler); try { client.evaluateChallenge(null); } catch (final SaslException expected) { assertThat(expected.getCause(), sameInstance((Throwable) thrown)); } verify(mockHandler); assertThat(callbackCapture.getValue().length, is(1)); assertThat(callbackCapture.getValue()[0], instanceOf(NameCallback.class)); } /** * Test method for {@link ScramSaslClient#createNonce()}. */ @Test public void testCreateNonce() { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); final String nonce = client.createNonce(); assertThat(nonce, notNullValue()); assertThat(IOUtils.base64ToBytes(nonce).length, is(ScramSaslClient.RANDOM_BYTES)); assertThat(client.createNonce(), not(is(nonce))); assertThat(client.createNonce(), not(is(nonce))); assertThat(client.createNonce(), not(is(nonce))); } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws SaslException * On a test failure. */ @Test public void testCreateProof() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); // Hard code the nonce for the test. final ScramSaslClient client = new TestRfcScramSaslClient(handler); assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); // Initialize the state. final byte[] firstMessage = client.evaluateChallenge(null); assertThat(client.isComplete(), is(false)); assertThat(firstMessage, notNullValue()); assertThat(new String(firstMessage, ScramSaslClient.UTF_8), is("n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL")); // Call createProof. final byte[] proof = client.createProof(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(false)); assertThat(proof, notNullValue()); assertThat(new String(proof, ScramSaslClient.UTF_8), is("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," + "p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=")); } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws UnsupportedCallbackException * On a test failure. * @throws IOException * On a test failure. */ @Test() public void testCreateProofOnAnIOException() throws IOException, UnsupportedCallbackException { final CallbackHandler mockHandler = createMock(CallbackHandler.class); final IOException thrown = new IOException("Injected."); final Capture<Callback[]> userCapture = new Capture<Callback[]>(); mockHandler.handle(capture(userCapture)); expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { assertThat(userCapture.getValue().length, is(1)); assertThat(userCapture.getValue()[0], instanceOf(NameCallback.class)); ((NameCallback) userCapture.getValue()[0]).setName("user"); return null; } }); final Capture<Callback[]> passwordCapture = new Capture<Callback[]>(); mockHandler.handle(capture(passwordCapture)); expectLastCall().andThrow(thrown); replay(mockHandler); final ScramSaslClient client = new ScramSaslClient(mockHandler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=" + clientNonce + "abcd,s=abcd,i=1") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getCause(), sameInstance((Throwable) thrown)); } verify(mockHandler); assertThat(passwordCapture.getValue().length, is(1)); assertThat(passwordCapture.getValue()[0], instanceOf(PasswordCallback.class)); } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws UnsupportedCallbackException * On a test failure. * @throws IOException * On a test failure. */ @Test() public void testCreateProofOnAnUnsupportedCallback() throws IOException, UnsupportedCallbackException { final CallbackHandler mockHandler = createMock(CallbackHandler.class); final UnsupportedCallbackException thrown = new UnsupportedCallbackException( null); final Capture<Callback[]> userCapture = new Capture<Callback[]>(); mockHandler.handle(capture(userCapture)); expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { assertThat(userCapture.getValue().length, is(1)); assertThat(userCapture.getValue()[0], instanceOf(NameCallback.class)); ((NameCallback) userCapture.getValue()[0]).setName("user"); return null; } }); final Capture<Callback[]> passwordCapture = new Capture<Callback[]>(); mockHandler.handle(capture(passwordCapture)); expectLastCall().andThrow(thrown); replay(mockHandler); final ScramSaslClient client = new ScramSaslClient(mockHandler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=" + clientNonce + "abcd,s=abcd,i=1") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getCause(), sameInstance((Throwable) thrown)); } verify(mockHandler); assertThat(passwordCapture.getValue().length, is(1)); assertThat(passwordCapture.getValue()[0], instanceOf(PasswordCallback.class)); } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws SaslException * On a test failure. */ @Test() public void testCreateProofWithBadIteration() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=" + clientNonce + "abcd,s=abcd,i=a") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("For input string: \"a\"")); assertThat(expected.getCause(), instanceOf(NumberFormatException.class)); } } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws SaslException * On a test failure. */ @Test() public void testCreateProofWithBadIteration2() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=" + clientNonce + "abcd,s=abcd,i=0") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("Iteration count 0 must be a positive integer.")); } } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws SaslException * On a test failure. */ @Test() public void testCreateProofWithBadSalt() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=" + clientNonce + "abcd,s=abc,i=4096") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("The server's salt is not a valid Base64 value: 'abc'.")); } } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws SaslException * On a test failure. */ @Test() public void testCreateProofWithBadServerNonce() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=server_nonce,s=abcd,i=4096") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat( expected.getMessage(), is("The server's nonce 'server_nonce' must start with the client's nonce '" + clientNonce + "'.")); } } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. */ @Test() public void testCreateProofWithMFieldThrows() { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); try { client.createProof(("m=is_not_allowed") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("The server required mandatory extension " + "is not supported: m=is_not_allowed")); } } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws SaslException * On a test failure. */ @Test() public void testCreateProofWithMissingIteration() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=" + clientNonce + "abcd,s=abcd") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("Could not find the iteration count: 'r=" + clientNonce + "abcd,s=abcd'.")); } } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. * * @throws SaslException * On a test failure. */ @Test() public void testCreateProofWithMissingSalt() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); // To create the client nonce. final byte[] initial = client.createInitialMessage(); final String initialMessage = new String(initial, ScramSaslClient.UTF_8); final String clientNonce = initialMessage.substring(initialMessage .indexOf(",r=") + 3); try { client.createProof(("r=" + clientNonce + "abcd,i=4096") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("Could not find the server's salt: 'r=" + clientNonce + "abcd,i=4096'.")); } } /** * Test method for {@link ScramSaslClient#createProof(byte[])}. */ @Test() public void testCreateProofWithMissingServerNonce() { final CallbackHandler handler = new TestHandler("user", "pencil"); final ScramSaslClient client = new ScramSaslClient(handler); try { client.createProof(("s=abcd,i=4096") .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("Could not find the server's nonce: 's=abcd,i=4096'.")); } } /** * Test method for {@link ScramSaslClient#dispose()}. * * @throws SaslException * On a test failure. */ @Test public void testDispose() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); // Hard code the nonce for the test. final ScramSaslClient client = new TestRfcScramSaslClient(handler); assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); final byte[] firstMessage = client.evaluateChallenge(null); assertThat(client.isComplete(), is(false)); assertThat(firstMessage, notNullValue()); assertThat(new String(firstMessage, ScramSaslClient.UTF_8), is("n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL")); final byte[] proof = client .evaluateChallenge(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(false)); assertThat(proof, notNullValue()); assertThat(new String(proof, ScramSaslClient.UTF_8), is("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," + "p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=")); final byte[] last = client .evaluateChallenge("v=rmF9pqV8S7suAoZWja4dJRkFsKQ=" .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(true)); assertThat(last, notNullValue()); assertThat(last.length, is(0)); // // Now Dispose. // client.dispose(); // And go again. assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); client.evaluateChallenge(null); client.evaluateChallenge(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); client.evaluateChallenge("v=rmF9pqV8S7suAoZWja4dJRkFsKQ=" .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(true)); } /** * Test method for {@link ScramSaslClient#evaluateChallenge(byte[])}. * * @throws SaslException * On a test failure. */ @Test public void testEvaluateChallenge() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); // Hard code the nonce for the test. final ScramSaslClient client = new TestRfcScramSaslClient(handler); assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); final byte[] firstMessage = client.evaluateChallenge(null); assertThat(client.isComplete(), is(false)); assertThat(firstMessage, notNullValue()); assertThat(new String(firstMessage, ScramSaslClient.UTF_8), is("n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL")); final byte[] proof = client .evaluateChallenge(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(false)); assertThat(proof, notNullValue()); assertThat(new String(proof, ScramSaslClient.UTF_8), is("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," + "p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=")); final byte[] last = client .evaluateChallenge("v=rmF9pqV8S7suAoZWja4dJRkFsKQ=" .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(true)); assertThat(last, notNullValue()); assertThat(last.length, is(0)); // Calling evaluate again throws an error. try { client.evaluateChallenge(null); fail("Should have thrown an SaslException."); } catch (final SaslException good) { // Good. assertThat(good.getMessage(), is("No challenge expected in state COMPLETE")); } } /** * Test method for {@link ScramSaslClient#evaluateFinalResult(byte[])}. * * @throws SaslException * On a test failure. */ @Test public void testEvaluateFinalResult() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); // Hard code the nonce for the test. final ScramSaslClient client = new TestRfcScramSaslClient(handler); assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); final byte[] firstMessage = client.evaluateChallenge(null); assertThat(client.isComplete(), is(false)); assertThat(firstMessage, notNullValue()); assertThat(new String(firstMessage, ScramSaslClient.UTF_8), is("n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL")); final byte[] proof = client .evaluateChallenge(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(false)); assertThat(proof, notNullValue()); assertThat(new String(proof, ScramSaslClient.UTF_8), is("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," + "p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=")); client.evaluateFinalResult("v=rmF9pqV8S7suAoZWja4dJRkFsKQ=" .getBytes(ScramSaslClient.UTF_8)); } /** * Test method for {@link ScramSaslClient#evaluateFinalResult(byte[])}. * * @throws SaslException * On a test failure. */ @Test public void testEvaluateFinalResultWithBadV() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); // Hard code the nonce for the test. final ScramSaslClient client = new TestRfcScramSaslClient(handler); assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); final byte[] firstMessage = client.evaluateChallenge(null); assertThat(client.isComplete(), is(false)); assertThat(firstMessage, notNullValue()); assertThat(new String(firstMessage, ScramSaslClient.UTF_8), is("n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL")); final byte[] proof = client .evaluateChallenge(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(false)); assertThat(proof, notNullValue()); assertThat(new String(proof, ScramSaslClient.UTF_8), is("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," + "p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=")); try { client.evaluateFinalResult("v=BADVALUES7suAoZWja4dJRkFsQ=" .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat( expected.getMessage(), is("The server's signature ('BADVALUES7suAoZWja4dJRkFsQ=') " + "does not match the expected signature: " + "'rmF9pqV8S7suAoZWja4dJRkFsKQ='.")); } } /** * Test method for {@link ScramSaslClient#evaluateFinalResult(byte[])}. * * @throws SaslException * On a test failure. */ @Test public void testEvaluateFinalResultWithMissingV() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); // Hard code the nonce for the test. final ScramSaslClient client = new TestRfcScramSaslClient(handler); assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); final byte[] firstMessage = client.evaluateChallenge(null); assertThat(client.isComplete(), is(false)); assertThat(firstMessage, notNullValue()); assertThat(new String(firstMessage, ScramSaslClient.UTF_8), is("n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL")); final byte[] proof = client .evaluateChallenge(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(false)); assertThat(proof, notNullValue()); assertThat(new String(proof, ScramSaslClient.UTF_8), is("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," + "p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=")); try { client.evaluateFinalResult("a=rmF9pqV8S7suAoZWja4dJRkFsKQ=" .getBytes(ScramSaslClient.UTF_8)); fail("Should have thrown a SaslException."); } catch (final SaslException expected) { assertThat(expected.getMessage(), is("The Server's final message did not contain " + "a verifier: 'a=rmF9pqV8S7suAoZWja4dJRkFsKQ='")); } } /** * Test method for {@link ScramSaslClient#getMechanismName()}. */ @Test public void testGetMechanismName() { final ScramSaslClient client = new ScramSaslClient(null); assertThat(client.getMechanismName(), is("SCRAM-SHA-1")); } /** * Test method for {@link ScramSaslClient#getNegotiatedProperty(String)}. */ @Test(expected = IllegalStateException.class) public void testGetNegotiatedProperty() { new ScramSaslClient(null).unwrap(null, 0, 0); } /** * Test method for {@link ScramSaslClient#hasInitialResponse()}. */ @Test public void testHasInitialResponse() { final ScramSaslClient client = new ScramSaslClient(null); assertThat(client.hasInitialResponse(), is(true)); } /** * Test based on the example in the RFC 5802, section 5. <blockquote> * * This is a simple example of a SCRAM-SHA-1 authentication exchange when * the client doesn't support channel bindings (username 'user' and password * 'pencil' are used): * * <pre> * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096 * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts= * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ= * </pre> * * </blockquote> * * @throws SaslException * On a test failure. */ @Test public void testRfcExample() throws SaslException { final CallbackHandler handler = new TestHandler("user", "pencil"); // Hard code the nonce for the test. final ScramSaslClient client = new TestRfcScramSaslClient(handler); assertThat(client.hasInitialResponse(), is(true)); assertThat(client.isComplete(), is(false)); final byte[] firstMessage = client.evaluateChallenge(null); assertThat(client.isComplete(), is(false)); assertThat(firstMessage, notNullValue()); assertThat(new String(firstMessage, ScramSaslClient.UTF_8), is("n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL")); final byte[] proof = client .evaluateChallenge(("r=fyko+d2lbbFgONRv9qkxdawL" + "3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096") .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(false)); assertThat(proof, notNullValue()); assertThat(new String(proof, ScramSaslClient.UTF_8), is("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," + "p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=")); final byte[] last = client .evaluateChallenge("v=rmF9pqV8S7suAoZWja4dJRkFsKQ=" .getBytes(ScramSaslClient.UTF_8)); assertThat(client.isComplete(), is(true)); assertThat(last, notNullValue()); assertThat(last.length, is(0)); } /** * Test method for {@link ScramSaslClient#saslName(String)}. * * @throws SaslException * On a test failure. */ @Test public void testSaslName() throws SaslException { final ScramSaslClient client = new ScramSaslClient(null); assertThat(client.saslName("abc"), is("abc")); assertThat(client.saslName("a=bc"), is("a=3Dbc")); assertThat(client.saslName("a,bc"), is("a=2Cbc")); assertThat(client.saslName("a=b,c"), is("a=3Db=2Cc")); } /** * Test method for {@link ScramSaslClient#saslPrep(char[])}. * * @throws SaslException * On a test failure. */ @Test public void testSaslPrep() throws SaslException { final ScramSaslClient client = new ScramSaslClient(null); assertThat(client.saslPrep("abc".toCharArray()), is("abc".toCharArray())); // Non-ASCII. assertThat(client.saslPrep("ab\u2345c".toCharArray()), is("ab\u2345c".toCharArray())); // Mappings. for (final Map.Entry<Character, String> mapping : ScramSaslClient.SASL_PREP_MAPPINGS .entrySet()) { assertThat(client.saslPrep(("ab" + String.valueOf(mapping.getKey().charValue()) + "c") .toCharArray()), is(("ab" + mapping.getValue() + "c").toCharArray())); } // Disallowed. final Set<Integer> values = new HashSet<Integer>( ScramSaslClient.SASL_PREP_DISALLOWED); // ((0xE000 <= codePoint) && (codePoint <= 0xF8FF)) // ((0xF0000 <= codePoint) && (codePoint <= 0xFFFFD)) // ((0x100000 <= codePoint) && (codePoint <= 0x10FFFD)) || // ((0xD800 <= codePoint) && (codePoint <= 0xDFFF))) { values.add(0xE000); values.add(0xF8FF); values.add(0xF0000); values.add(0xFFFFD); values.add(0x100000); values.add(0x10FFFD); values.add(0xD800); values.add(0xDFFF); for (final Integer disallowCodePoint : values) { final char[] chars = Character .toChars(disallowCodePoint.intValue()); if ((chars.length == 1) && ScramSaslClient.SASL_PREP_MAPPINGS.containsKey(Character .valueOf(chars[0]))) { // Mapping removes/replaces. continue; } final String disallowed = String.valueOf(chars); final String test = "ab" + disallowed + "c"; if (!Normalizer.normalize(test, Normalizer.Form.NFKC).equals(test)) { // Normalization clears. continue; } try { final char[] testChars = test.toCharArray(); final char[] result = client.saslPrep(testChars); fail("Should have thrown a SaslException: " + String.valueOf(result)); } catch (final SaslException expected) { assertThat( expected.getMessage(), is("SaslPrep disallowed character '" + disallowed + "' (0x" + Integer.toHexString(disallowCodePoint .intValue()) + "). See RFC 4013 section 2.3.")); } } // Bidi // DIRECTIONALITY_RIGHT_TO_LEFT ==> \u05be // DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC ==> \uFEFC // DIRECTIONALITY_LEFT_TO_RIGHT ==> \u0041 client.saslPrep("\u05be\uFEFC".toCharArray()); client.saslPrep("\uFEFC\u05be".toCharArray()); client.saslPrep("\u0041".toCharArray()); try { client.saslPrep("\u05be\u0041".toCharArray()); fail("Should have thrown a SaslException"); } catch (final SaslException expected) { assertThat(expected.getMessage(), startsWith("SaslPrep does not allow mixing left-to-right")); } try { client.saslPrep("\u05be\u0041\uFEFC".toCharArray()); fail("Should have thrown a SaslException"); } catch (final SaslException expected) { assertThat(expected.getMessage(), startsWith("SaslPrep does not allow mixing left-to-right")); } try { client.saslPrep("\u0041\uFEFC".toCharArray()); fail("Should have thrown a SaslException"); } catch (final SaslException expected) { assertThat(expected.getMessage(), startsWith("SaslPrep does not allow left-to-right")); } // Unassigned \uFDD0 try { client.saslPrep("\uFDD0".toCharArray()); fail("Should have thrown a SaslException"); } catch (final SaslException expected) { assertThat(expected.getMessage(), startsWith("SaslPrep disallowed character '\uFDD0' " + "(0xfdd0). See RFC 4013 section 2.3.")); } } /** * Test method for {@link ScramSaslClient#unwrap(byte[], int, int)}. */ @Test(expected = IllegalStateException.class) public void testUnwrap() { new ScramSaslClient(null).unwrap(null, 0, 0); } /** * Test method for {@link ScramSaslClient#wrap(byte[], int, int)}. */ @Test(expected = IllegalStateException.class) public void testWrap() { new ScramSaslClient(null).wrap(null, 0, 0); } /** * TestHandler provides a simple callback for user names and passwords for * use in the tests. * * @copyright 2014, Allanbank Consulting, Inc., All Rights Reserved */ protected static final class TestHandler implements CallbackHandler { /** The password to provide via the handler. */ private final String myPassword; /** The user name to provide via the handler. */ private final String myUserName; /** * Creates a new TestHandler. * * @param userName * The user name to provide via the handler. * @param password * The password to provide via the handler. */ public TestHandler(final String userName, final String password) { myUserName = userName; myPassword = password; } /** * {@inheritDoc} * <p> * Overridden to respond to the user name and password callbacks. * </p> */ @Override public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (final Callback cb : callbacks) { if (cb instanceof PasswordCallback) { ((PasswordCallback) cb).setPassword(myPassword .toCharArray()); } if (cb instanceof NameCallback) { ((NameCallback) cb).setName(myUserName); } } } } /** * TestRfcScramSaslClient provides a specialization of the * {@link ScramSaslClient} that returns a fixed nonce. * * @copyright 2014, Allanbank Consulting, Inc., All Rights Reserved */ protected static final class TestRfcScramSaslClient extends ScramSaslClient { /** * Creates a new TestRfcScramSaslClient. * * @param callbackHandler * The handler for the user name and password. */ protected TestRfcScramSaslClient(final CallbackHandler callbackHandler) { super(callbackHandler); } /** * {@inheritDoc} * <p> * Overridden to return the nonce from RFC 5802, section 5. * </p> */ @Override protected String createNonce() { return "fyko+d2lbbFgONRv9qkxdawL"; } } }