/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.sasl.digest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
import org.wildfly.security.sasl.test.BaseTestCase;
import org.wildfly.security.sasl.test.SaslServerBuilder;
import org.wildfly.security.sasl.util.SaslMechanismInformation;
import org.wildfly.security.util.ByteIterator;
import org.wildfly.security.util.CodePointIterator;
import mockit.Mock;
import mockit.MockUp;
import mockit.integration.junit4.JMockit;
/**
* Test of server side of the Digest mechanism.
* JMockit ensure same generated nonce in every test run.
*
* @author <a href="mailto:jkalina@redhat.com">Jan Kalina</a>
*/
@RunWith(JMockit.class)
public class CompatibilityServerTest extends BaseTestCase {
protected static final String REALM_PROPERTY = "com.sun.security.sasl.digest.realm";
protected static final String QOP_PROPERTY = "javax.security.sasl.qop";
private void mockNonce(final String nonce){
new MockUp<DigestSaslServer>(){
@Mock
byte[] generateNonce(){
return nonce.getBytes(StandardCharsets.UTF_8);
}
};
}
/**
* Test communication by first example in RFC 2831 [page 18]
*/
@Test
public void testRfc2831example1() throws Exception {
mockNonce("OA6MG9tEQGm2hh");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("imap").setServerName("elwood.innosoft.com")
.addMechanismRealm("elwood.innosoft.com")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=ea40f60335c427b5527b84dbabcdfffd", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
}
/**
* Test communication by second example in RFC 2831 [page 19]
*/
@Test
public void testRfc2831example2() throws Exception {
mockNonce("OA9BSXrbuRhWay");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.addMechanismRealm("elwood.innosoft.com")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",response=6084c6db3fede7352c551284490fd0fc,qop=auth".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=2f0b3d7c3c2e486600ef710726aa2eae", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
}
/**
* Test with authorization ID (authzid) of other user
*/
@Test
public void testUnauthorizedAuthorizationId() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(REALM_PROPERTY, "elwood.innosoft.com");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=0d071450228e395e2c0999e02b6aa665,qop=auth,authzid=\"george\"".getBytes(StandardCharsets.UTF_8);
try {
server.evaluateResponse(message2);
fail("Not throwed SaslException!");
} catch (SaslException e) {}
assertFalse(server.isComplete());
}
/**
* Test with authorization ID (authzid) - authorized
*/
@Test
public void testAuthorizedAuthorizationId() throws Exception {
mockNonce("OA9BSXrbuRhWay");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.addMechanismRealm("elwood.innosoft.com")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",response=aa4e81f1c6656350f7bce05d436665de,qop=auth,authzid=\"chris\"".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=af3ca83a805d4cfa00675a17315475c4", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
}
/**
* Test with authentication plus integrity protection (qop=auth-int)
*/
@Test
public void testQopAuthInt() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(QOP_PROPERTY, "auth-int");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.setProperties(serverProps)
.addMechanismRealm("elwood.innosoft.com")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",qop=\"auth-int\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=d8b17f55b410208c6ebb22f89f9d6cbb,qop=auth-int,authzid=\"chris\"".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=7a8794654d6d6de607e9143d52b554a8", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
byte[] incoming1 = CodePointIterator.ofString("1122334499191be7952a49d8549b000100000000").hexDecode().drain();
byte[] incoming1unwrapped = server.unwrap(incoming1, 0, incoming1.length);
assertEquals("11223344", ByteIterator.ofBytes(incoming1unwrapped).hexEncode().drainToString());
byte[] outcoming1 = CodePointIterator.ofString("55667788").hexDecode().drain();
byte[] outcoming1wrapped = server.wrap(outcoming1, 0, outcoming1.length);
assertEquals("55667788cf5e02ad15987d9076b8000100000000", ByteIterator.ofBytes(outcoming1wrapped).hexEncode().drainToString());
byte[] incoming2 = CodePointIterator.ofString("aabbcc7e845ed48b0474447543000100000001").hexDecode().drain();
byte[] incoming2unwrapped = server.unwrap(incoming2, 0, incoming2.length);
assertEquals("aabbcc", ByteIterator.ofBytes(incoming2unwrapped).hexEncode().drainToString());
byte[] outcoming2 = new byte[0];
byte[] outcoming2wrapped = server.wrap(outcoming2, 0, outcoming2.length);
assertEquals("", ByteIterator.ofBytes(outcoming2wrapped).hexEncode().drainToString());
// MAC not corresponds to message and sequence number
byte[] incoming3 = CodePointIterator.ofString("0188034ce1b414194c1c822a55000100000002").hexDecode().drain();
byte[] incoming3unwrapped = server.unwrap(incoming3, 0, incoming3.length);
assertEquals("", ByteIterator.ofBytes(incoming3unwrapped).hexEncode().drainToString());
// bad sequence number
try {
byte[] incoming4 = CodePointIterator.ofString("0102032cf12c67e4318ebd624e000100000003").hexDecode().drain();
server.unwrap(incoming4, 0, incoming4.length);
fail("Out of order sequencing SaslException expected!");
} catch(SaslException e){}
}
/**
* Test with authentication plus integrity and confidentiality protection (qop=auth-conf, cipher=default=3des)
*/
@Test
public void testQopAuthConf() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(QOP_PROPERTY, "auth-conf");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.setProperties(serverProps)
.addMechanismRealm("elwood.innosoft.com")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",qop=\"auth-conf\",charset=utf-8,cipher=\"3des,rc4,des,rc4-56,rc4-40\",algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=4520cf48234bb93b95548a25cd56601b,qop=auth-conf,cipher=\"3des\",authzid=\"chris\"".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=a804fda66588e2d911bbacd1b1163bc1", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
byte[] incoming1 = CodePointIterator.ofString("13f7644f8c783501177522c1a455cb1f000100000000").hexDecode().drain();
byte[] incoming1unwrapped = server.unwrap(incoming1, 0, incoming1.length);
assertEquals("11223344", ByteIterator.ofBytes(incoming1unwrapped).hexEncode().drainToString());
byte[] outcoming1 = CodePointIterator.ofString("55667788").hexDecode().drain();
byte[] outcoming1wrapped = server.wrap(outcoming1, 0, outcoming1.length);
assertEquals("93ce33409e0fe5187e07c16fc3041f64000100000000", ByteIterator.ofBytes(outcoming1wrapped).hexEncode().drainToString());
byte[] incoming2 = CodePointIterator.ofString("ec426d9cd3276f22285ab5da8df8f26b000100000001").hexDecode().drain();
byte[] incoming2unwrapped = server.unwrap(incoming2, 0, incoming2.length);
assertEquals("aabbcc", ByteIterator.ofBytes(incoming2unwrapped).hexEncode().drainToString());
byte[] outcoming2 = new byte[0];
byte[] outcoming2wrapped = server.wrap(outcoming2, 0, outcoming2.length);
assertEquals("", ByteIterator.ofBytes(outcoming2wrapped).hexEncode().drainToString());
// MAC not corresponds to message and sequence number
byte[] incoming3 = CodePointIterator.ofString("b0acad3c969d091251666f91070166f5000100000002").hexDecode().drain();
byte[] incoming3unwrapped = server.unwrap(incoming3, 0, incoming3.length);
assertEquals("", ByteIterator.ofBytes(incoming3unwrapped).hexEncode().drainToString());
// bad sequence number
try {
byte[] incoming4 = CodePointIterator.ofString("2cfa9bced5b960763953c4f9838b7022000100000003").hexDecode().drain();
server.unwrap(incoming4, 0, incoming4.length);
fail("Out of order sequencing SaslException expected!");
} catch(SaslException e){}
}
/**
* Test with authentication plus integrity and confidentiality protection (qop=auth-conf, cipher=rc4)
*/
@Test
public void testQopAuthConfRc4() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(QOP_PROPERTY, "auth-conf");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.addMechanismRealm("elwood.innosoft.com")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",qop=\"auth-conf\",charset=utf-8,cipher=\"3des,rc4,des,rc4-56,rc4-40\",algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=4520cf48234bb93b95548a25cd56601b,qop=auth-conf,cipher=\"rc4\",authzid=\"chris\"".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=a804fda66588e2d911bbacd1b1163bc1", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
byte[] incoming1 = CodePointIterator.ofString("6a9328ca634e47c8d1ecc3c3f6e6000100000000").hexDecode().drain();
byte[] incoming1unwrapped = server.unwrap(incoming1, 0, incoming1.length);
assertEquals("11223344", ByteIterator.ofBytes(incoming1unwrapped).hexEncode().drainToString());
byte[] outcoming1 = CodePointIterator.ofString("55667788").hexDecode().drain();
byte[] outcoming1wrapped = server.wrap(outcoming1, 0, outcoming1.length);
assertEquals("9fc7eb1c3c9e04b52df6e347a389000100000000", ByteIterator.ofBytes(outcoming1wrapped).hexEncode().drainToString());
byte[] incoming2 = CodePointIterator.ofString("7e15b940fccbb58a5612f54da7000100000001").hexDecode().drain();
byte[] incoming2unwrapped = server.unwrap(incoming2, 0, incoming2.length);
assertEquals("aabbcc", ByteIterator.ofBytes(incoming2unwrapped).hexEncode().drainToString());
byte[] outcoming2 = new byte[0];
byte[] outcoming2wrapped = server.wrap(outcoming2, 0, outcoming2.length);
assertEquals("", ByteIterator.ofBytes(outcoming2wrapped).hexEncode().drainToString());
// bad message
byte[] incoming3 = CodePointIterator.ofString("84468595614f4ac73fabe47cc4000100000002").hexDecode().drain();
byte[] incoming3unwrapped = server.unwrap(incoming3, 0, incoming3.length);
assertEquals("", ByteIterator.ofBytes(incoming3unwrapped).hexEncode().drainToString());
// bad sequence number
try {
byte[] incoming4 = CodePointIterator.ofString("4a206df4178a9b7d091dec3527000100000003").hexDecode().drain();
server.unwrap(incoming4, 0, incoming4.length);
fail("Out of order sequencing SaslException expected!");
} catch(SaslException e){}
}
/**
* Test with authentication plus integrity and confidentiality protection (qop=auth-conf, cipher=des)
*/
@Test
public void testQopAuthConfDes() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(QOP_PROPERTY, "auth-conf");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.addMechanismRealm("elwood.innosoft.com")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",qop=\"auth-conf\",charset=utf-8,cipher=\"3des,rc4,des,rc4-56,rc4-40\",algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=4520cf48234bb93b95548a25cd56601b,qop=auth-conf,cipher=\"des\",authzid=\"chris\"".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=a804fda66588e2d911bbacd1b1163bc1", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
byte[] incoming1 = CodePointIterator.ofString("b2a12ba8ccd1030e7da4bac57a224197000100000000").hexDecode().drain();
byte[] incoming1unwrapped = server.unwrap(incoming1, 0, incoming1.length);
assertEquals("11223344", ByteIterator.ofBytes(incoming1unwrapped).hexEncode().drainToString());
byte[] outcoming1 = CodePointIterator.ofString("55667788").hexDecode().drain();
byte[] outcoming1wrapped = server.wrap(outcoming1, 0, outcoming1.length);
assertEquals("8bc1267e71a769456f0c60f030e13f32000100000000", ByteIterator.ofBytes(outcoming1wrapped).hexEncode().drainToString());
byte[] incoming2 = CodePointIterator.ofString("13144fc90ca65d3838d3547cca43e8ad000100000001").hexDecode().drain();
byte[] incoming2unwrapped = server.unwrap(incoming2, 0, incoming2.length);
assertEquals("aabbcc", ByteIterator.ofBytes(incoming2unwrapped).hexEncode().drainToString());
byte[] outcoming2 = new byte[0];
byte[] outcoming2wrapped = server.wrap(outcoming2, 0, outcoming2.length);
assertEquals("", ByteIterator.ofBytes(outcoming2wrapped).hexEncode().drainToString());
// bad message
byte[] incoming3 = CodePointIterator.ofString("7022412985dbee1d261ecb8850486c6e000100000002").hexDecode().drain();
byte[] incoming3unwrapped = server.unwrap(incoming3, 0, incoming3.length);
assertEquals("", ByteIterator.ofBytes(incoming3unwrapped).hexEncode().drainToString());
// bad sequence number
try {
byte[] incoming4 = CodePointIterator.ofString("3457275b036fa15042e41aeda83a563b000100000003").hexDecode().drain();
server.unwrap(incoming4, 0, incoming4.length);
fail("Out of order sequencing SaslException expected!");
} catch(SaslException e){}
}
/**
* Test with authentication plus integrity and confidentiality protection (qop=auth-conf, cipher=rc4-56)
*/
@Test
public void testQopAuthConfRc456() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(QOP_PROPERTY, "auth-conf");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.setProperties(serverProps)
.addMechanismRealm("elwood.innosoft.com")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",qop=\"auth-conf\",charset=utf-8,cipher=\"3des,rc4,des,rc4-56,rc4-40\",algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=4520cf48234bb93b95548a25cd56601b,qop=auth-conf,cipher=\"rc4-56\",authzid=\"chris\"".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=a804fda66588e2d911bbacd1b1163bc1", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
byte[] incoming1 = CodePointIterator.ofString("7a77c4b8b20208e502e5dc09bbfc000100000000").hexDecode().drain();
byte[] incoming1unwrapped = server.unwrap(incoming1, 0, incoming1.length);
assertEquals("11223344", ByteIterator.ofBytes(incoming1unwrapped).hexEncode().drainToString());
byte[] outcoming1 = CodePointIterator.ofString("55667788").hexDecode().drain();
byte[] outcoming1wrapped = server.wrap(outcoming1, 0, outcoming1.length);
assertEquals("c10acbf737cdebf2298df53417bc000100000000", ByteIterator.ofBytes(outcoming1wrapped).hexEncode().drainToString());
byte[] incoming2 = CodePointIterator.ofString("efcb8662925427788b0ffeab2c000100000001").hexDecode().drain();
byte[] incoming2unwrapped = server.unwrap(incoming2, 0, incoming2.length);
assertEquals("aabbcc", ByteIterator.ofBytes(incoming2unwrapped).hexEncode().drainToString());
byte[] outcoming2 = new byte[0];
byte[] outcoming2wrapped = server.wrap(outcoming2, 0, outcoming2.length);
assertEquals("", ByteIterator.ofBytes(outcoming2wrapped).hexEncode().drainToString());
// bad message
byte[] incoming3 = CodePointIterator.ofString("03c8fa9cb28ecf4a99561e5ac3000100000002").hexDecode().drain();
byte[] incoming3unwrapped = server.unwrap(incoming3, 0, incoming3.length);
assertEquals("", ByteIterator.ofBytes(incoming3unwrapped).hexEncode().drainToString());
// bad sequence number
try {
byte[] incoming4 = CodePointIterator.ofString("4daa261a6afb77f0b1d1d3d4eb000100000003").hexDecode().drain();
server.unwrap(incoming4, 0, incoming4.length);
fail("Out of order sequencing SaslException expected!");
} catch(SaslException e){}
}
/**
* Test with authentication plus integrity and confidentiality protection (qop=auth-conf, cipher=rc4-40)
*/
@Test
public void testQopAuthConfRc440() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(QOP_PROPERTY, "auth-conf");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.addMechanismRealm("elwood.innosoft.com")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",qop=\"auth-conf\",charset=utf-8,cipher=\"3des,rc4,des,rc4-56,rc4-40\",algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=4520cf48234bb93b95548a25cd56601b,qop=auth-conf,cipher=\"rc4-40\",authzid=\"chris\"".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=a804fda66588e2d911bbacd1b1163bc1", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
byte[] incoming1 = CodePointIterator.ofString("ed46c6b0d38acb719aad661f9625000100000000").hexDecode().drain();
byte[] incoming1unwrapped = server.unwrap(incoming1, 0, incoming1.length);
assertEquals("11223344", ByteIterator.ofBytes(incoming1unwrapped).hexEncode().drainToString());
byte[] outcoming1 = CodePointIterator.ofString("55667788").hexDecode().drain();
byte[] outcoming1wrapped = server.wrap(outcoming1, 0, outcoming1.length);
assertEquals("44aca6145a89353d26258e524724000100000000", ByteIterator.ofBytes(outcoming1wrapped).hexEncode().drainToString());
byte[] incoming2 = CodePointIterator.ofString("b7bdc8f08733182154289e7f3d000100000001").hexDecode().drain();
byte[] incoming2unwrapped = server.unwrap(incoming2, 0, incoming2.length);
assertEquals("aabbcc", ByteIterator.ofBytes(incoming2unwrapped).hexEncode().drainToString());
byte[] outcoming2 = new byte[0];
byte[] outcoming2wrapped = server.wrap(outcoming2, 0, outcoming2.length);
assertEquals("", ByteIterator.ofBytes(outcoming2wrapped).hexEncode().drainToString());
// bad message
byte[] incoming3 = CodePointIterator.ofString("34968ede3148eb0d3affe15656000100000002").hexDecode().drain();
byte[] incoming3unwrapped = server.unwrap(incoming3, 0, incoming3.length);
assertEquals("", ByteIterator.ofBytes(incoming3unwrapped).hexEncode().drainToString());
// bad sequence number
try {
byte[] incoming4 = CodePointIterator.ofString("8e497ee789076071cf3b5bb9e1000100000003").hexDecode().drain();
server.unwrap(incoming4, 0, incoming4.length);
fail("Out of order sequencing SaslException expected!");
} catch(SaslException e){}
}
/**
* Replay attack (different nonce than sent by server)
*/
@Test
public void testReplayAttack() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(REALM_PROPERTY, "elwood.innosoft.com");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8);
try{
server.evaluateResponse(message2);
fail("Not throwed SaslException!");
} catch (SaslException e) {}
assertFalse(server.isComplete());
}
/**
* Bad response
*/
@Test
public void testBadResponse() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(REALM_PROPERTY, "elwood.innosoft.com");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8);
try{
server.evaluateResponse(message2);
fail("Not throwed SaslException!");
} catch (SaslException e) {}
assertFalse(server.isComplete());
}
/**
* More realms from server (realm="other-realm",realm="elwood.innosoft.com",realm="next-realm" -> elwood.innosoft.com)
*/
@Test
public void testMoreRealmsFromServer() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(REALM_PROPERTY, "other-realm elwood.innosoft.com next-realm");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"other-realm\",realm=\"elwood.innosoft.com\",realm=\"next-realm\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8);
try{
server.evaluateResponse(message2);
fail("Not throwed SaslException!");
} catch (SaslException e) {}
assertFalse(server.isComplete());
}
/**
* Blank nonce from client (connection with naughty client)
*/
@Test
public void testBlankClientNonce() throws Exception {
mockNonce("OA9BSXrbuRhWay");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("acap").setServerName("elwood.innosoft.com")
.addMechanismRealm("other-realm")
.addMechanismRealm("elwood.innosoft.com")
.addMechanismRealm("next-realm")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"other-realm\",realm=\"elwood.innosoft.com\",realm=\"next-realm\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"\",digest-uri=\"acap/elwood.innosoft.com\",response=0ca21eafddf586f954909d2fd95b1ee7,qop=auth".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=2bf631e48acb9863e9f5518ccc804b3b", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
}
/**
* Test successful authentication with Unicode chars (UTF-8 encoding)
*/
@Test
public void testUtf8Charset() throws Exception {
mockNonce("sn\u0438\u4F60\uD83C\uDCA1");
Map<String, Object> serverProps = new HashMap<String, Object>();
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("\u0438\u4F60\uD83C\uDCA1")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("\u0438\u4F60\uD83C\uDCA1".toCharArray()))
.setProtocol("\u0438\u4F60\uD83C\uDCA1").setServerName("realm.\u0438\u4F60\uD83C\uDCA1.com")
.setProperties(serverProps)
.addMechanismRealm("realm.\u0438\u4F60\uD83C\uDCA1.com")
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"realm.\u0438\u4F60\uD83C\uDCA1.com\",nonce=\"sn\u0438\u4F60\uD83C\uDCA1\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"\u0438\u4F60\uD83C\uDCA1\",realm=\"realm.\u0438\u4F60\uD83C\uDCA1.com\",nonce=\"sn\u0438\u4F60\uD83C\uDCA1\",nc=00000001,cnonce=\"cn\u0438\u4F60\uD83C\uDCA1\",digest-uri=\"\u0438\u4F60\uD83C\uDCA1/realm.\u0438\u4F60\uD83C\uDCA1.com\",maxbuf=65536,response=420939e06d2d748c157c5e33499b41a9,qop=auth".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=9c4d137545617ba98c11aaea939b4381", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("\u0438\u4F60\uD83C\uDCA1", server.getAuthorizationID());
}
/**
* More realms from server (realm="other-realm",realm="elwood.innosoft.com",realm="next-realm" -> elwood.innosoft.com)
*/
@Test
public void testMoreRealmsWithEscapedDelimiters() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map<String, Object> serverProps = new HashMap<String, Object>();
serverProps.put(REALM_PROPERTY, "first\\ realm second\\\\\\ realm \\ with\\ spaces\\ \\ ");
SaslServer server =
new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
.setUserName("chris")
.setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
.setProtocol("protocol name").setServerName("server name")
.addMechanismRealm("first realm")
.addMechanismRealm("second\\ realm")
.addMechanismRealm(" with spaces ")
.addMechanismRealm(" ")
.setProperties(serverProps)
.build();
assertFalse(server.isComplete());
byte[] message1 = server.evaluateResponse(new byte[0]);
assertEquals("realm=\"first realm\",realm=\"second\\\\ realm\",realm=\" with spaces \",realm=\" \",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"first realm\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"protocol name/server name\",maxbuf=65536,response=bf3dd710ee08b05c663456975c156075,qop=auth".getBytes(StandardCharsets.UTF_8);
byte[] message3 = server.evaluateResponse(message2);
assertEquals("rspauth=05a18aff49b22e373bb91af7396ce345", new String(message3, "UTF-8"));
assertTrue(server.isComplete());
assertEquals("chris", server.getAuthorizationID());
}
}