/* * JBoss, Home of Professional Open Source. * Copyright 2015 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.otp; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.wildfly.security.password.interfaces.OneTimePassword.ALGORITHM_OTP_MD5; import static org.wildfly.security.password.interfaces.OneTimePassword.ALGORITHM_OTP_SHA1; import static org.wildfly.security.sasl.otp.OTP.DIRECT_OTP; import static org.wildfly.security.sasl.otp.OTP.HEX_RESPONSE; import static org.wildfly.security.sasl.otp.OTP.INIT_HEX_RESPONSE; import static org.wildfly.security.sasl.otp.OTP.INIT_WORD_RESPONSE; import static org.wildfly.security.sasl.otp.OTP.MATCH_NEW_PASSWORD_FORMAT_CHOICE; import static org.wildfly.security.sasl.otp.OTP.MATCH_PASSWORD_FORMAT_CHOICE; import static org.wildfly.security.sasl.otp.OTP.MATCH_RESPONSE_CHOICE; import static org.wildfly.security.sasl.otp.OTP.PASS_PHRASE; import static org.wildfly.security.sasl.otp.OTP.WORD_RESPONSE; import static org.wildfly.security.sasl.otp.OTP.getOTPParameterSpec; import java.io.Closeable; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; 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.junit.After; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.wildfly.security.auth.callback.CallbackUtil; import org.wildfly.security.auth.callback.PasswordResetCallback; import org.wildfly.security.auth.client.AuthenticationConfiguration; import org.wildfly.security.auth.client.AuthenticationContext; import org.wildfly.security.auth.client.CallbackKind; import org.wildfly.security.auth.client.ClientUtils; import org.wildfly.security.auth.client.MatchRule; import org.wildfly.security.auth.server.RealmIdentity; import org.wildfly.security.auth.server.RealmUnavailableException; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.password.Password; import org.wildfly.security.password.PasswordFactory; import org.wildfly.security.password.interfaces.OneTimePassword; import org.wildfly.security.password.spec.OneTimePasswordSpec; import org.wildfly.security.sasl.WildFlySasl; import org.wildfly.security.sasl.test.BaseTestCase; import org.wildfly.security.sasl.test.SaslServerBuilder; import org.wildfly.security.sasl.test.SaslServerBuilder.BuilderReference; import org.wildfly.security.sasl.util.SaslMechanismInformation; import org.wildfly.security.util.CodePointIterator; import mockit.Mock; import mockit.MockUp; import mockit.integration.junit4.JMockit; /** * Client and server side tests for the OTP SASL mechanism. The expected results for * these test cases were generated using the {@code python-otp} module. * * @author <a href="mailto:fjuma@redhat.com">Farah Juma</a> */ @RunWith(JMockit.class) public class OTPTest extends BaseTestCase { private long timeout; @After public void dispose() throws Exception { timeout = 0L; } // -- Successful authentication exchanges -- @Test public void testSimpleMD5AuthenticationWithPassPhrase() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("5bf075d9959d036f").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); assertNotNull(saslClient); assertTrue(saslClient instanceof OTPSaslClient); assertTrue(saslClient.hasInitialResponse()); assertFalse(saslClient.isComplete()); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("hex:5bf075d9959d036f", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testSimpleSHA1AuthenticationWithPassPhrase() throws Exception { final String algorithm = ALGORITHM_OTP_SHA1; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("103029b112deb117").hexDecode().drain(), "TeSt".getBytes(StandardCharsets.US_ASCII), 100)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("87fec7768b73ccf9").hexDecode().drain(), "TeSt".getBytes(StandardCharsets.US_ASCII), 99)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); assertNotNull(saslClient); assertTrue(saslClient instanceof OTPSaslClient); assertTrue(saslClient.hasInitialResponse()); assertFalse(saslClient.isComplete()); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-sha1 99 TeSt ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("hex:87fec7768b73ccf9", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testSimpleMD5AuthenticationWithMultiWordOTP() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("5bf075d9959d036f").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "BOND FOGY DRAB NE RISE MART", DIRECT_OTP, algorithm, WORD_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); assertNotNull(saslClient); assertTrue(saslClient instanceof OTPSaslClient); assertTrue(saslClient.hasInitialResponse()); assertFalse(saslClient.isComplete()); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("word:BOND FOGY DRAB NE RISE MART", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testSimpleSHA1AuthenticationWithMultiWordOTP() throws Exception { final String algorithm = ALGORITHM_OTP_SHA1; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("103029b112deb117").hexDecode().drain(), "TeSt".getBytes(StandardCharsets.US_ASCII), 100)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("87fec7768b73ccf9").hexDecode().drain(), "TeSt".getBytes(StandardCharsets.US_ASCII), 99)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "GAFF WAIT SKID GIG SKY EYED", DIRECT_OTP, algorithm, WORD_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); assertNotNull(saslClient); assertTrue(saslClient instanceof OTPSaslClient); assertTrue(saslClient.hasInitialResponse()); assertFalse(saslClient.isComplete()); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-sha1 99 TeSt ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("word:GAFF WAIT SKID GIG SKY EYED", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Ignore("ELY-777") @Test public void testAuthenticationWithInitHexResponse() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("3712dcb4aa5316c1").hexDecode().drain(), "ke1235".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, INIT_HEX_RESPONSE, "3712dcb4aa5316c1", algorithm, 499, "ke1235"); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); assertNotNull(saslClient); assertTrue(saslClient instanceof OTPSaslClient); assertTrue(saslClient.hasInitialResponse()); assertFalse(saslClient.isComplete()); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("init-hex:5bf075d9959d036f:md5 499 ke1235:3712dcb4aa5316c1", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Ignore("ELY-777") @Test public void testAuthenticationWithInitWordResponse() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("3712dcb4aa5316c1").hexDecode().drain(), "ke1235".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, INIT_WORD_RESPONSE, "RED HERD NOW BEAN PA BURG", algorithm, 499, "ke1235"); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); assertNotNull(saslClient); assertTrue(saslClient instanceof OTPSaslClient); assertTrue(saslClient.hasInitialResponse()); assertFalse(saslClient.isComplete()); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("init-word:BOND FOGY DRAB NE RISE MART:md5 499 ke1235:RED HERD NOW BEAN PA BURG", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Ignore("ELY-777") @Test public void testAuthenticationWithLowSequenceNumber() throws Exception { mockSeed("lr4321"); final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("eb65a876fd5e5e8e").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 10)); // Low sequence number, the sequence should be reset final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("870c2dcc4fd6b474").hexDecode().drain(), "lr4321".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, INIT_WORD_RESPONSE, "My new pass phrase"); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 9 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("init-word:HOYT ATE SARA DISH REED OUST:md5 499 lr4321:FULL BUSS DIET ITCH CORK SAM", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithMultiWordOTPWithAlternateDictionary() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("5bf075d9959d036f").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); Map<String, Object> props = new HashMap<String, Object>(); props.put(WildFlySasl.OTP_ALTERNATE_DICTIONARY, OTPSaslClientFactory.dictionaryArrayToProperty(ALTERNATE_DICTIONARY)); final CallbackHandler cbh = createClientCallbackHandler("userName", "sars zike zub sahn siar pft", DIRECT_OTP, algorithm, WORD_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", props, cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("word:sars zike zub sahn siar pft", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithPassPhraseWithAlternateDictionary() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslServerFactory serverFactory = obtainSaslServerFactory(OTPSaslServerFactory.class); assertNotNull(serverFactory); final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("5bf075d9959d036f").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); Map<String, Object> props = new HashMap<String, Object>(); props.put(WildFlySasl.OTP_ALTERNATE_DICTIONARY, OTPSaslClientFactory.dictionaryArrayToProperty(OTPTest.ALTERNATE_DICTIONARY)); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, WORD_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", props, cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertEquals("word:sars zike zub sahn siar pft", new String(message, StandardCharsets.UTF_8)); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertTrue(saslServer.isComplete()); assertNull(message); assertEquals("userName", saslServer.getAuthorizationID()); // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testMultipleSimultaneousAuthenticationSessions() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("5bf075d9959d036f").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder serverBuilder1 = createSaslServerBuilder(password, closeableReference, securityDomainReference); try { final SaslServer saslServer1 = serverBuilder1.build(); final SaslServer saslServer2 = serverBuilder1.copy(true).build(); final CallbackHandler cbh = createClientCallbackHandler("userName", "BOND FOGY DRAB NE RISE MART", DIRECT_OTP, algorithm, WORD_RESPONSE); final SaslClient saslClient1 = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); final SaslClient saslClient2 = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); final ExecutorService executorService = Executors.newFixedThreadPool(2); final Callable<Boolean> attempt1 = () -> { boolean attemptFailed = false; byte[] message1 = saslClient1.evaluateChallenge(new byte[0]); message1 = saslServer1.evaluateResponse(message1); message1 = saslClient1.evaluateChallenge(message1); try { saslServer1.evaluateResponse(message1); } catch (SaslException e) { attemptFailed = true; saslServer1.dispose(); } return attemptFailed; }; final Callable<Boolean> attempt2 = () -> { boolean attemptFailed = false; byte[] message2 = saslClient2.evaluateChallenge(new byte[0]); message2 = saslServer2.evaluateResponse(message2); message2 = saslClient2.evaluateChallenge(message2); try { saslServer2.evaluateResponse(message2); } catch (SaslException e) { attemptFailed = true; saslServer2.dispose(); } return attemptFailed; }; Future<Boolean> result1, result2; try { result1 = executorService.submit(attempt1); result2 = executorService.submit(attempt2); } finally { executorService.shutdown(); } boolean attempt1Failed = result1.get(); boolean attempt2Failed = result2.get(); if ((attempt1Failed && attempt2Failed) || (! attempt1Failed && ! attempt2Failed)) { fail("Exactly one of the authentication attempts should have succeeded"); } // Check the password is updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } // -- Unsuccessful authentication exchanges -- @Test public void testAuthenticationWithWrongPassword() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "TONE NELL RACY GRIN ROOM GELD", DIRECT_OTP, algorithm, WORD_RESPONSE); // Wrong password final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); assertFalse(saslClient.isComplete()); assertFalse(saslServer.isComplete()); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); message = saslClient.evaluateChallenge(message); assertTrue(saslClient.isComplete()); assertFalse(saslServer.isComplete()); try { saslServer.evaluateResponse(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should remain unchanged checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithWrongPasswordInInitResponse() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "GAFF WAIT SKID GIG SKY EYED", DIRECT_OTP, algorithm, INIT_WORD_RESPONSE, "RED HERD NOW BEAN PA BURG", algorithm, 500, "ke1235"); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); message = saslClient.evaluateChallenge(message); try { saslServer.evaluateResponse(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should remain unchanged checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithInvalidNewPasswordInInitResponse() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("5bf075d9959d036f").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 499)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "BOND FOGY DRAB NE RISE MART", DIRECT_OTP, algorithm, INIT_WORD_RESPONSE, "RED HERD NOW BEAN PA BURG", algorithm, 499, "ke1235"); final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); message = saslServer.evaluateResponse(message); assertEquals("otp-md5 499 ke1234 ext", new String(message, StandardCharsets.UTF_8)); // Construct an init-word response with a valid current OTP but an invalid new OTP message = "init-word:BOND FOGY DRAB NE RISE MART:md5 0 !ke1235$:RED".getBytes(StandardCharsets.UTF_8); try { saslServer.evaluateResponse(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should be updated to the current OTP checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithInvalidPassPhrase() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 500)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "tooShort", PASS_PHRASE, algorithm, HEX_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); message = saslServer.evaluateResponse(message); try { saslClient.evaluateChallenge(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should remain unchanged checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithLongSeed() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "thisSeedIsTooLong".getBytes(StandardCharsets.US_ASCII), 500)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); try { saslServer.evaluateResponse(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should remain unchanged checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithNonAlphanumericSeed() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "A seed!".getBytes(StandardCharsets.US_ASCII), 500)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); try { saslServer.evaluateResponse(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should remain unchanged checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testAuthenticationWithInvalidSequenceNumber() throws Exception { final String algorithm = ALGORITHM_OTP_MD5; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), "ke1234".getBytes(StandardCharsets.US_ASCII), 0)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); try { saslServer.evaluateResponse(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should remain unchanged checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); } finally { closeableReference.getReference().close(); } } @Test public void testUnauthorizedAuthorizationId() throws Exception { final String algorithm = ALGORITHM_OTP_SHA1; final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); assertNotNull(clientFactory); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("103029b112deb117").hexDecode().drain(), "TeSt".getBytes(StandardCharsets.US_ASCII), 100)); OneTimePassword expectedUpdatedPassword = (OneTimePassword) passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("87fec7768b73ccf9").hexDecode().drain(), "TeSt".getBytes(StandardCharsets.US_ASCII), 99)); final SaslServerBuilder.BuilderReference<SecurityDomain> securityDomainReference = new SaslServerBuilder.BuilderReference<>(); final SaslServerBuilder.BuilderReference<Closeable> closeableReference = new SaslServerBuilder.BuilderReference<>(); try { final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, "wrongName", "test", "testserver1.example.com", Collections.<String, Object>emptyMap(), cbh); byte[] message = saslClient.evaluateChallenge(new byte[0]); message = saslServer.evaluateResponse(message); message = saslClient.evaluateChallenge(message); try { saslServer.evaluateResponse(message); fail("Expected SaslException not thrown"); } catch (SaslException expected) { } saslClient.dispose(); saslServer.dispose(); // The password should be updated checkPassword(securityDomainReference, "userName", expectedUpdatedPassword, algorithm); } finally { closeableReference.getReference().close(); } } private SaslServerBuilder createSaslServerBuilder(Password password, BuilderReference<Closeable> closeableReference, BuilderReference<SecurityDomain> securityDomainReference) { SaslServerBuilder builder = new SaslServerBuilder(OTPSaslServerFactory.class, SaslMechanismInformation.Names.OTP) .setModifiableRealm() .setUserName("userName") .setPassword(password) .setModifiableRealm() .setProtocol("test") .setServerName("testserver1.example.com") .registerCloseableReference(closeableReference) .registerSecurityDomainReference(securityDomainReference); return builder; } private SaslServer createSaslServer(Password password, BuilderReference<Closeable> closeableReference, BuilderReference<SecurityDomain> securityDomainReference) throws IOException { SaslServer saslServer = createSaslServerBuilder(password, closeableReference, securityDomainReference) .build(); assertFalse(saslServer.isComplete()); return saslServer; } private void checkPassword(BuilderReference<SecurityDomain> domainReference, String userName, OneTimePassword expectedUpdatedPassword, String algorithmName) throws RealmUnavailableException { SecurityDomain securityDomain = domainReference.getReference(); RealmIdentity realmIdentity = securityDomain.getIdentity(userName); OneTimePassword updatedPassword = realmIdentity.getCredential(PasswordCredential.class, algorithmName).getPassword(OneTimePassword.class); assertEquals(expectedUpdatedPassword.getAlgorithm(), updatedPassword.getAlgorithm()); assertArrayEquals(expectedUpdatedPassword.getHash(), updatedPassword.getHash()); assertArrayEquals(expectedUpdatedPassword.getSeed(), updatedPassword.getSeed()); assertEquals(expectedUpdatedPassword.getSequenceNumber(), updatedPassword.getSequenceNumber()); realmIdentity.dispose(); } private void mockSeed(final String randomStr){ new MockUp<OTPUtil>(){ @Mock String generateRandomAlphanumericString(int length, Random random){ return randomStr; } }; } private CallbackHandler createClientCallbackHandler(String username, String password, String passwordFormatChoice, String algorithm, String responseChoice) throws Exception { final AuthenticationContext context = AuthenticationContext.empty() .with( MatchRule.ALL, AuthenticationConfiguration.EMPTY .useName(username) .useChoice(MATCH_RESPONSE_CHOICE, responseChoice) .useChoice(MATCH_PASSWORD_FORMAT_CHOICE, passwordFormatChoice) .usePassword(password) .allowSaslMechanisms(algorithm)); return ClientUtils.getCallbackHandler(new URI("remote://localhost"), context); } private CallbackHandler createClientCallbackHandler(String username, String password, String passwordFormatChoice, String algorithm, String responseChoice, String newPassword, String newAlgorithm, int newSequenceNumber, String newSeed) throws Exception { final AuthenticationContext context = AuthenticationContext.empty() .with( MatchRule.ALL, AuthenticationConfiguration.EMPTY .useName(username) .useChoice(MATCH_RESPONSE_CHOICE, responseChoice) .useChoice(MATCH_PASSWORD_FORMAT_CHOICE, passwordFormatChoice) .usePassword(password) .useChoice(MATCH_NEW_PASSWORD_FORMAT_CHOICE, DIRECT_OTP) .useParameterSpec(getOTPParameterSpec(newAlgorithm, newSeed, newSequenceNumber)) .useCallbackHandler(callbacks -> { if (callbacks.length > 0) { final Callback callback = callbacks[0]; if (callback instanceof PasswordResetCallback) { ((PasswordResetCallback) callback).setPassword(newPassword.toCharArray()); } else { CallbackUtil.unsupported(callback); } } }, EnumSet.of(CallbackKind.CREDENTIAL_RESET)) .allowSaslMechanisms(algorithm)); return ClientUtils.getCallbackHandler(new URI("remote://localhost"), context); } private CallbackHandler createClientCallbackHandler(String username, String password, String passwordFormatChoice, String algorithm, String responseChoice, String newPassPhrase) throws Exception { final AuthenticationContext context = AuthenticationContext.empty() .with( MatchRule.ALL, AuthenticationConfiguration.EMPTY .useName(username) .useChoice(MATCH_RESPONSE_CHOICE, responseChoice) .useChoice(MATCH_PASSWORD_FORMAT_CHOICE, passwordFormatChoice) .usePassword(password) .useChoice(MATCH_NEW_PASSWORD_FORMAT_CHOICE, PASS_PHRASE) .useCallbackHandler(callbacks -> { if (callbacks.length > 0) { final Callback callback = callbacks[0]; if (callback instanceof PasswordResetCallback) { ((PasswordResetCallback) callback).setPassword(newPassPhrase.toCharArray()); } else { CallbackUtil.unsupported(callback); } } }, EnumSet.of(CallbackKind.CREDENTIAL_RESET)) .allowSaslMechanisms(algorithm)); return ClientUtils.getCallbackHandler(new URI("remote://localhost"), context); } private static final String[] ALTERNATE_DICTIONARY = new String[] { "poel", "qewn", "xlob", "preg", "qome", "zarm", "sas", "oerk", "sct", "seb", "ilan", "wct", "bp", "sft", "beys", "rela", "iieu", "oive", "ncme", "xila", "znch", "zd", "zeaf", "oabe", "odge", "xmes", "zore", "xudo", "qial", "rmid", "pibe", "phud", "yife", "wund", "rjar", "qal", "zlma", "wgee", "wlma", "rids", "reak", "qoff", "xob", "abark", "zdge", "zdds", "zina", "zord", "qloe", "xeta", "qoke", "pcon", "qerg", "oide", "pvid", "plen", "yvid", "xers", "sart", "qden", "ffct", "qkat", "zage", "sbis", "zite", "slec", "poft", "ccugh", "zie", "ses", "wred", "pec", "abnt", "ohay", "rkay", "hab", "zake", "qar", "wcid", "oord", "xeed", "zumb", "prue", "zdit", "wrae", "pose", "zild", "iute", "iude", "sote", "qlan", "rway", "pao", "qach", "paas", "sye", "xahn", "zeys", "sued", "zbel", "xobo", "bbuns", "nhaw", "oad", "qlee", "xse", "pldy", "bord", "woke", "zcme", "zeam", "xobe", "xrub", "zaft", "yalk", "oida", "ccim", "zale", "ztc", "qiew", "zld", "qeek", "zyra", "peb", "sela", "haut", "ohar", "zad", "qund", "ooge", "saut", "pae", "qeo", "zddy", "oyed", "zsed", "zlse", "surd", "zire", "pava", "bct", "pham", "bem", "zem", "oile", "oarl", "zrb", "seda", "oule", "wde", "ierg", "zerk", "sens", "zag", "zibs", "sert", "zank", "perb", "qoal", "npa", "bloe", "sesk", "oao", "ske", "zwe", "rild", "onew", "qage", "xric", "rald", "zudd", "deent", "xsle", "abaul", "sean", "seth", "zic", "sayo", "qce", "qcme", "aarig", "peo", "xac", "slee", "qak", "abhan", "ccsks", "qlaf", "stah", "hed", "pobs", "qah", "pieu", "poat", "ooe", "wudy", "qiet", "owan", "xud", "wyde", "hcts", "quck", "iawd", "zure", "seir", "iudy", "paby", "hebt", "bbbe", "swe", "zrau", "yess", "xhub", "nwab", "zhat", "phoa", "xome", "zben", "zile", "pah", "soch", "bebe", "zids", "zaf", "srid", "sesh", "ccag", "qmra", "pife", "rtem", "qoer", "weny", "zap", "xabe", "pary", "rhoa", "sosa", "qhew", "xief", "sfar", "soat", "zila", "hval", "reaf", "zoof", "hatt", "pnee", "zam", "qeem", "pube", "xao", "zeat", "zans", "onch", "pide", "plod", "reys", "poff", "rawl", "peah", "zene", "pair", "zlec", "wbed", "oid", "zret", "bda", "rvan", "reir", "qda", "rtar", "zobo", "rte", "zlaw", "qmes", "saft", "aaont", "zame", "pobo", "sda", "plak", "xeat", "serm", "zole", "sind", "zang", "zcre", "pdgy", "iaft", "ccis", "qee", "xona", "xcta", "zial", "pait", "ziet", "peid", "ioad", "znna", "qmid", "haby", "ptag", "zeen", "pkat", "ssle", "hree", "zct", "bwk", "pade", "zeft", "peon", "zowe", "pyde", "pled", "purb", "roah", "zuod", "pram", "xasy", "pche", "xark", "shet", "sucy", "sbey", "abben", "xick", "zola", "wesh", "xuck", "hdna", "zalo", "rloe", "peat", "qona", "ooco", "slod", "poam", "zoft", "ynne", "pdin", "itab", "poga", "xlaw", "rair", "qret", "sahn", "xola", "xien", "xtay", "denus", "rrb", "zome", "xnna", "zran", "zwab", "deahn", "pmra", "xrid", "qumb", "zalt", "xulb", "sram", "pomb", "oawk", "zaas", "smra", "qine", "zoff", "zyle", "wnub", "zerg", "ztan", "rmma", "zeb", "xalk", "plad", "olad", "wuck", "qnee", "pem", "soer", "sgee", "znte", "pere", "sein", "psia", "xoan", "hrea", "sree", "neer", "zice", "pmes", "xery", "rard", "ilba", "zhay", "seft", "snca", "zond", "qhet", "slaw", "aberk", "qeak", "snee", "zelm", "xala", "plla", "rpe", "zoat", "poyd", "xale", "bec", "otab", "iuct", "abeta", "sibe", "znub", "soal", "puna", "byde", "xcar", "zift", "zurf", "xtag", "zace", "zrge", "sao", "zode", "bnee", "shem", "qver", "sray", "zlee", "qeam", "zeg", "pfar", "pass", "zuna", "xood", "xcid", "qeah", "xerr", "reet", "qlec", "okat", "aaray", "owag", "hmid", "xisa", "yild", "qben", "qoda", "zva", "sose", "qena", "qcho", "piern", "zne", "hlaw", "aaair", "sass", "phee", "sisc", "sies", "xrab", "wes", "pld", "smma", "qve", "nree", "pcat", "sed", "qed", "zoga", "zcts", "znag", "zloe", "xove", "sawd", "sawl", "aavow", "iait", "zomb", "sona", "quft", "hina", "oarm", "scho", "zven", "otem", "ibut", "sute", "sey", "bult", "xacy", "zrea", "pear", "zive", "xnee", "pace", "ztch", "hody", "igan", "soam", "zel", "sran", "qame", "slak", "qall", "nise", "surb", "qab", "zal", "qnds", "znee", "phoe", "zasy", "ccogo", "zcid", "zlba", "qarb", "iate", "salf", "pech", "reil", "rwen", "oant", "pre", "hase", "zham", "seef", "pald", "qoge", "perd", "zhub", "otah", "zlub", "sewt", "sask", "xike", "zse", "qoed", "plue", "pudy", "xold", "scy", "plaw", "peny", "pays", "zaid", "iray", "seno", "iome", "ggeys", "zcon", "sody", "oars", "zbey", "zep", "perg", "pbey", "xas", "pebt", "bkit", "xesh", "zvid", "zewn", "zke", "ztab", "poch", "smen", "qven", "sff", "zich", "snch", "oagy", "zkew", "xone", "abup", "pied", "zess", "nena", "ppe", "zabe", "purd", "haws", "oona", "zed", "zend", "xham", "prch", "sld", "ierr", "qrae", "zhem", "blga", "oery", "iien", "scon", "poge", "qima", "sant", "ccda", "zgan", "pate", "zeth", "iift", "phod", "zgee", "zoey", "bemo", "qran", "zamb", "zert", "hden", "pibs", "zane", "pain", "zbed", "ihef", "rola", "sany", "abkin", "ste", "pwam", "rran", "soid", "zlay", "xlea", "btab", "rarl", "hawl", "nerk", "zway", "zeef", "xlod", "zoad", "pord", "pab", "qell", "zuel", "zima", "zena", "hiew", "xota", "wice", "zuge", "zucy", "zady", "yaud", "owen", "zlue", "zeah", "sief", "pile", "zens", "abida", "zebe", "zaw", "iel", "qyed", "rra", "yone", "past", "simb", "rees", "soke", "pver", "hane", "pral", "xual", "plew", "oest", "zhef", "iiar", "zisc", "oddy", "rhed", "plid", "zyed", "siet", "yart", "sldy", "iurf", "sjar", "halk", "zee", "iual", "qhe", "roca", "soel", "pdd", "ouct", "qana", "poke", "sava", "xand", "sald", "xeir", "zood", "saf", "xebt", "ztub", "ymes", "wela", "puft", "qalt", "wman", "zary", "zain", "zayo", "humb", "odit", "xdle", "xear", "ielt", "qoat", "serb", "yats", "sola", "roga", "irag", "zule", "aat", "zlam", "pdds", "heno", "zey", "sboe", "pee", "xarm", "qive", "waby", "pova", "zoke", "sars", "qask", "xman", "xise", "pione", "hagy", "qhey", "oeed", "xtem", "sdea", "shen", "biet", "poes", "zude", "poah", "hde", "zib", "ooga", "oame", "rie", "hoat", "srch", "pota", "zatt", "pieef", "oena", "xess", "qawd", "zak", "sret", "ihay", "zhan", "zlan", "olba", "zime", "xoft", "xuel", "abiny", "zard", "icta", "zair", "sala", "zao", "bift", "zarb", "zlia", "yuds", "denes", "qoar", "rcts", "zant", "oawd", "zrey", "zoe", "pas", "swen", "xard", "qrib", "xcts", "ioid", "inub", "oelf", "zest", "peau", "zes", "zraw", "ohaw", "somb", "aaerb", "seto", "zlat", "pnce", "pids", "zhoa", "pven", "brit", "xowe", "sial", "pnds", "zarc", "oap", "qsle", "ikay", "oefy", "xawd", "phet", "phay", "sdd", "zewt", "zern", "zota", "zab", "oear", "snew", "hoam", "xel", "ccx", "solf", "zff", "rlan", "zuff", "rady", "rere", "sde", "qule", "xhic", "zove", "ddly", "sume", "patt", "qief", "xeth", "oero", "oute", "roak", "oary", "qoco", "zlen", "soof", "zlla", "bgan", "irue", "pift", "zeo", "qoft", "nuge", "sid", "zoar", "sble", "roge", "soed", "sagy", "oene", "pies", "zte", "wtag", "rlba", "zhet", "phag", "hven", "pboe", "yens", "xech", "qrau", "zibe", "nfro", "ccoat", "heak", "qloc", "iach", "sild", "zeed", "zhen", "pgan", "xue", "oeo", "ieek", "zser", "zobe", "syte", "zrib", "peil", "bbant", "oond", "zuke", "rhod", "zpe", "zaby", "suet", "hade", "seil", "zays", "aaabe", "slla", "nbut", "xnub", "zwan", "sien", "rmra", "pce", "aaulk", "qic", "oend", "zelf", "qays", "zea", "zube", "syde", "pild", "qlob", "rlub", "qact", "sebe", "aadds", "qdam", "sses", "xhef", "pesk", "znds", "zwam", "qobo", "abdds", "salm", "oeaf", "bhy", "xwan", "qat", "peen", "rnub", "qide", "qang", "xak", "zub", "zram", "zas", "xesk", "pard", "xldy", "qelt", "ohef", "poco", "iap", "zent", "xred", "xcme", "sdam", "xrc", "qeef", "sarn", "sobe", "pnes", "sudy", "sond", "zrae", "qval", "parb", "qobs", "zose", "zesk", "ccid", "iews", "zmes", "yoam", "qood", "puge", "iaas", "qway", "htan", "qlma", "buty", "zeil", "zae", "qne", "qaut", "zoel", "sone", "xoge", "zoid", "zenu", "pmen", "zoak", "pbut", "xret", "oarc", "pd", "qole", "zuct", "xein", "qift", "oaag", "part", "pben", "srue", "srod", "sait", "oays", "qbey", "seld", "zna", "wbe", "oove", "qd", "zrod", "zune", "serg", "yeth", "wao", "oich", "wmid", "bbris", "zied", "sird", "xlue", "zand", "pidy", "zise", "xrae", "xite", "scts", "zuad", "pang", "rara", "ze", "phad", "xbut", "xft", "sune", "xeg", "wero", "zafe", "qarr", "wass", "zldy", "qess", "boul", "sive", "peno", "sual", "zike", "rdge", "poof", "xea", "zoah", "rnee", "xode", "zlva", "pock", "bary", "ziar", "qvan", "zdgy", "zdle", "sawn", "deunt", "bnob", "qfro", "zmid", "zlga", "paff", "zoda", "iuge", "ploc", "zf", "sva", "ried", "zrue", "plaf", "oee", "huds", "pave", "pcho", "znne", "zets", "zast", "dero", "pye", "qurb", "xnch", "oaas", "seo", "zda", "srma", "pawn", "qibs", "xoca", "qume", "qnob", "zkid", "wora", "zued", "aaere", "zail", "prau", "hief", "orae", "pawk", "rlid", "ooda", "xwe", "oias", "znca", "sudo", "oea", "sawk", "pike", "qmen", "zeau", "qeon", "yich", "heer", "bosa", "bbour", "rlga", "qime", "zbe", "bim", "rvid", "seau", "oask", "abbed", "serr", "selt", "rna", "snds", "prey", "soud", "hdit", "sre", "zhaw", "yue", "zein", "zde", "oima", "pbet", "zfro", "oume", "ioge", "pare", "sreg", "woge", "sern", "qte", "oann", "zeno", "ptew", "qoey", "psed", "soge", "prae", "oora", "sebt", "orey", "yias", "qaud", "pees", "shan", "zpa", "zhod", "slad", "aaoin", "sndy", "zare", "qrc", "zlib", "plib", "slea", "zhar", "resh", "qarl", "sarc", "yven", "qash", "zyte", "zled", "zate", "buly", "zree", "zawl", "plab", "sdit", "qldy", "sord", "qbet", "ploe", "aarub", "sarb", "bbout", "zbut", "seg", "blo", "pisc", "xyed", "zhe", "indy", "xrb", "zffy", "zock", "hnna", "xcho", "ynca", "hasy", "qart", "puod", "zuch", "srew", "xask", "xche", "pote", "qain", "pess", "cctt", "hdds", "zela", "pke", "zman", "xram", "xeft", "suct", "xlec", "iood", "abasy", "pene", "icme", "slba", "hude", "xne", "stc", "zoch", "rsia", "bila", "xoe", "zias", "xmma", "ruba", "oce", "zhew", "zote", "yat", "sias", "qurf", "pdea", "aarow", "ound", "pute", "rimb", "zdna", "pake", "sral", "zdam", "rraw", "zye", "ywam", "zrad", "rota", "abutt", "qond", "aaure", "zra", "oeak", "oebe", "wnna", "sain", "pct", "pise", "peem", "ioal", "zagi", "pak", "zmma", "ycta", "zean", "qdea", "ress", "ipa", "zid", "slag", "prag", "oune", "qgee", "pebe", "pual", "qqua", "zyde", "oiew", "bbud", "pail", "rann", "sisa", "zud", "sady", "omma", "shic", "ouba", "zhic", "peck", "qet", "qtan", "pome", "pamb", "oobs", "nulf", "zone", "pask", "sess", "ccahn", "xval", "qife", "qrag", "ooud", "quff", "zawn", "seud", "zind", "bbcre", "zen", "oen", "mnath", "pefy", "beah", "irea", "hran", "slia", "samb", "xeto", "wib", "peek", "aaock", "oacy", "xurb", "sak", "zebt", "hucy", "qep", "wa", "ieat", "srea", "powa", "bucy", "senu", "zver", "ruft", "quch", "ryed", "pcy", "xreg", "heah", "zere", "parm", "zhag", "whic", "ylec", "selm", "xnds", "zero", "qawl", "peed", "skay", "qimb", "zndy", "ggale", "qwe", "pand", "xlag", "qest", "qre", "olam", "pcid", "zce", "abefy", "qurd", "soc", "perr", "pine", "ylaw", "zuba", "poad", "sdge", "rlob", "qoc", "sowe", "wabe", "pbis", "xake", "pnag", "bbdit", "rewt", "rve", "ptem", "qeb", "zash", "ove", "saws", "pase", "zray", "pier", "puke", "rrma", "zhad", "pwan", "xve", "qdna", "sene", "zurb", "xali", "bwo", "zble", "zve", "wive", "xank", "zuck", "rrab", "sce", "qrma", "bwry", "sven", "zear", "zews", "xrew", "oden", "zidy", "zqua", "zah", "rait", "prad", "pnd", "wite", "zew", "seon", "ooam", "pnna", "aargo", "zana", "ive", "hora", "suse", "zhee", "sdgy", "hft", "srib", "zrew", "zats", "zar", "zerb", "ywe", "ohen", "suck", "peet", "odds", "qkew", "xima", "oees", "pumb", "pwab", "ccrad", "oed", "pft", "sae", "okew", "znob", "pnne", "zlod", "zmra", "olen", "xody", "qpe", "sase", "zsia", "hlab", "qove", "zral", "zath", "puch", "zany", "hep", "savy", "zali", "qtew", "sark", "zagy", "soma", "oib", "qke", "peer", "rcta", "sall", "aareg", "aaie", "zeak", "peir", "slue", "shey", "sude", "squa", "whod", "qrew", "zata", "poma", "qtc", "zrag", "aawam", "pda", "sida", "zier", "pach", "xwag", "qnag", "yal", "qode", "qrch", "qeud", "zemo", "zead", "sdds", "plma", "sade", "qhay", "shar", "sata", "ooey", "xild", "iep", "qast", "shat", "qeto", "za", "qerk", "xlid", "yota", "shoa", "zcot", "wlag", "sast", "saff", "qeau", "oire", "qrud", "qhan", "zurd", "sate", "qlen", "pnew", "znce", "parr", "pcot", "srae", "xall", "zrid", "qaur", "zlob", "ooal", "ssia", "pack", "iah", "qual", "pree", "zcar", "xree", "pcre", "suod", "sche", "hace", "zalm", "zmen", "pali", "zick", "znd", "sova", "qudo", "zay", "xaft", "zudo", "qacy", "hhat", "zark", "pud", "slva", "xine", "ises", "ilad", "oact", "ocre", "zses", "nark", "sbel", "zjar", "bbar", "zeta", "xeld", "sloc", "hlec", "zeon", "rd", "powe", "pndy", "sats", "xase", "rowa", "webe", "xhey", "xure", "ouby", "zoca", "wlva", "nlam", "sair", "xoal", "xdge", "ycid", "qein", "seet", "ihoe", "xdgy", "bos", "qech", "rhey", "rask", "zida", "pnob", "abhe", "plva", "wse", "pude", "nefy", "zlid", "wlad", "poal", "oeam", "oofa", "ieaf", "pret", "salo", "oess", "saby", "qand", "qied", "xenu", "sulb", "yeer", "paur", "hbis", "renu", "qeld", "qard", "qava", "zerd", "spe", "hage", "qaag", "snne", "iudd", "siar", "satt", "suby", "qyra", "zfar", "hdea", "sic", "iell", "snte", "aaane", "zche", "qib", "zach", "oyle", "zase", "zcta", "iuna", "zden", "hte", "yoge", "zac", "sund", "pawd", "iab", "zalk", "qaws", "helt", "rbed", "qhee", "qune", "zec", "zeld", "wava", "zona", "rial", "inew", "yldy", "poer", "sf", "suna", "zell", "xway", "oech", "zald", "soah", "zold", "qafe", "zhey", "oeel", "zeer", "zdd", "xlew", "xraw", "zora", "hcot", "pdle", "peld", "rcy", "xff", "qtah", "src", "zawk", "qes", "aaata", "bban", "ptay", "zsle", "xune", "slma", "sube", "zann", "ser", "zoma", "xeda", "shee", "iman", "pody", "qeal", "suel", "iben", "qies", "slse", "prge", "zeir", "srb", "peft", "qf", "sep", "aaoma", "sah", "ylam", "rody", "soyd", "htew", "zaul", "zara", "rrae", "xair", "xule", "snce", "ilaw", "zwen", "qtem", "qoad", "seah", "sena", "pelm", "suad", "zack", "bbol", "aaear", "snna", "zoyd", "iarm", "saur", "zass", "zolf", "aaslo", "ielf", "zesh", "zoc", "zulf", "zeff", "sake", "pkid", "hoan", "zeel", "qate", "pcan", "zimb", "xarc", "zreg", "bbery", "zft", "oemo", "pens", "peaf", "yche", "path", "sval", "shub", "zade", "zeck", "rlam", "sove", "qyle", "soan", "ztah", "qnna", "zbis", "qlva", "zrc", "sne", "zuet", "aaid", "zide", "hibs", "ziew", "bars", "parn", "paws", "oep", "xiar", "qcre", "zlad", "ocid", "pure", "xag", "zre", "paud", "poc", "zhud", "ofro", "zhed", "zboe", "qct", "wtew", "ilub", "zeud", "pyte", "zuds", "xrch", "srek", "peef", "zcat", "obed", "zue", "zobs", "xate", "rhew", "zoaf", "pets", "zars", "qobe", "zody", "qhef", "oova", "pirab", "sna", "slub", "qtay", "sben", "zact", "zod", "sach", "qebt", "sarl", "qmma", "zute", "phew", "qeth", "zcy", "zan", "zrub", "oelt", "aary", "zred", "xlan", "qeed", "yeck", "abead", "payo", "qrea", "wcho", "zrud", "zavy", "rcar", "xume", "zarr", "sred", "irew", "qeg", "prid", "qde", "ptch", "bant", "wrud", "zala", "hime", "soca", "ygee", "pbed", "pwat", "hvid", "olaw", "sudd", "zura", "sagi", "zaws", "xmen", "xloc", "xra", "nnew", "rind", "oase", "zahn", "xond", "oas", "qem", "qath", "iobe", "peda", "zefy", "qses", "xeil", "bwat", "srag", "zlea", "zaud", "ptub", "sve", "zob", "qse", "zbet", "qike", "zall", "friend", "zeem", "zoam", "zrch", "soe", "zerr", "znew", "rune", "smes", "zerm", "oail", "zeek", "yurf", "pyed", "xalm", "yloc", "ondy", "pawl", "oudo", "zarn", "wtay", "pane", "zlew", "pudd", "svid", "hali", "ztar", "aayed", "sann", "olue", "puff", "oaur", "sarr", "hars", "ouds", "wbut", "oloc", "perm", "waws", "qid", "aaoud", "zer", "qina", "sib", "qeil", "zulb", "zhoe", "rld", "pata", "rac", "otub", "zelt", "zkat", "poud", "rerk", "zlab", "wble", "ccep", "plub", "zeet", "ztag", "snes", "qwag", "peud", "hoyd", "qcid", "pind", "boun", "rwe", "yoft", "zuby", "zaut", "para", "rnag", "pcts", "zees", "qert", "zoco", "hhad", "hello", "qcta", "zric", "xias", "qake", "ruds", "sie", "phem", "oent", "qlew", "pird", "oura", "zlak", "ieg", "pash", "hice", "qcts", "iet", "ioam", "zarl", "znes", "peak", "oeda", "xloe", "bm", "zudy", "nve", "qod"}; }