Merge remote-tracking branch 'origin/jetty-7' into jetty-8
Conflicts: jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
This commit is contained in:
commit
5306c540bf
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.security.authentication;
|
|||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.BitSet;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
@ -60,17 +61,32 @@ public class DigestAuthenticator extends LoginAuthenticator
|
|||
private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
|
||||
SecureRandom _random = new SecureRandom();
|
||||
private long _maxNonceAgeMs = 60*1000;
|
||||
private ConcurrentMap<String, Nonce> _nonceCount = new ConcurrentHashMap<String, Nonce>();
|
||||
private int _maxNC=1024;
|
||||
private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
|
||||
private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
|
||||
private static class Nonce
|
||||
{
|
||||
final String _nonce;
|
||||
final long _ts;
|
||||
AtomicInteger _nc=new AtomicInteger();
|
||||
public Nonce(String nonce, long ts)
|
||||
final BitSet _seen;
|
||||
|
||||
public Nonce(String nonce, long ts, int size)
|
||||
{
|
||||
_nonce=nonce;
|
||||
_ts=ts;
|
||||
_seen = new BitSet(size);
|
||||
}
|
||||
|
||||
public boolean seen(int count)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
if (count>=_seen.size())
|
||||
return true;
|
||||
boolean s=_seen.get(count);
|
||||
_seen.set(count);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,19 +108,35 @@ public class DigestAuthenticator extends LoginAuthenticator
|
|||
String mna=configuration.getInitParameter("maxNonceAge");
|
||||
if (mna!=null)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_maxNonceAgeMs=Long.valueOf(mna);
|
||||
}
|
||||
_maxNonceAgeMs=Long.valueOf(mna);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public int getMaxNonceCount()
|
||||
{
|
||||
return _maxNC;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setMaxNonceCount(int maxNC)
|
||||
{
|
||||
_maxNC = maxNC;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
|
||||
public void setMaxNonceAge(long maxNonceAgeInMillis)
|
||||
{
|
||||
_maxNonceAgeMs = maxNonceAgeInMillis;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getMaxNonceAge()
|
||||
{
|
||||
return _maxNonceAgeMs;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String getAuthMethod()
|
||||
{
|
||||
|
@ -235,9 +267,9 @@ public class DigestAuthenticator extends LoginAuthenticator
|
|||
byte[] nounce = new byte[24];
|
||||
_random.nextBytes(nounce);
|
||||
|
||||
nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp());
|
||||
nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
|
||||
}
|
||||
while (_nonceCount.putIfAbsent(nonce._nonce,nonce)!=null);
|
||||
while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
|
||||
_nonceQueue.add(nonce);
|
||||
|
||||
return nonce._nonce;
|
||||
|
@ -252,36 +284,27 @@ public class DigestAuthenticator extends LoginAuthenticator
|
|||
private int checkNonce(Digest digest, Request request)
|
||||
{
|
||||
// firstly let's expire old nonces
|
||||
long expired;
|
||||
synchronized (this)
|
||||
{
|
||||
expired = request.getTimeStamp()-_maxNonceAgeMs;
|
||||
}
|
||||
|
||||
long expired = request.getTimeStamp()-_maxNonceAgeMs;
|
||||
Nonce nonce=_nonceQueue.peek();
|
||||
while (nonce!=null && nonce._ts<expired)
|
||||
{
|
||||
_nonceQueue.remove(nonce);
|
||||
_nonceCount.remove(nonce._nonce);
|
||||
_nonceMap.remove(nonce._nonce);
|
||||
nonce=_nonceQueue.peek();
|
||||
}
|
||||
|
||||
|
||||
// Now check the requested nonce
|
||||
try
|
||||
{
|
||||
nonce = _nonceCount.get(digest.nonce);
|
||||
nonce = _nonceMap.get(digest.nonce);
|
||||
if (nonce==null)
|
||||
return 0;
|
||||
|
||||
|
||||
long count = Long.parseLong(digest.nc,16);
|
||||
if (count>Integer.MAX_VALUE)
|
||||
if (count>=_maxNC)
|
||||
return 0;
|
||||
int old=nonce._nc.get();
|
||||
while (!nonce._nc.compareAndSet(old,(int)count))
|
||||
old=nonce._nc.get();
|
||||
if (count<=old)
|
||||
if (nonce.seen((int)count))
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -380,6 +403,7 @@ public class DigestAuthenticator extends LoginAuthenticator
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return username + "," + response;
|
||||
|
|
|
@ -23,18 +23,22 @@ import static org.junit.Assert.assertTrue;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.DigestAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
|
@ -46,7 +50,10 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
|||
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.util.security.Credential;
|
||||
import org.eclipse.jetty.util.security.Password;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -245,14 +252,12 @@ public class ConstraintTest
|
|||
_server.start();
|
||||
|
||||
String response;
|
||||
/*
|
||||
response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
*/
|
||||
|
||||
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
|
||||
/*
|
||||
|
||||
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
|
||||
assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
|
||||
|
@ -267,8 +272,7 @@ public class ConstraintTest
|
|||
"Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
|
||||
"\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
*/
|
||||
/*
|
||||
|
||||
// test admin
|
||||
response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
|
||||
|
@ -317,11 +321,130 @@ public class ConstraintTest
|
|||
response = _connector.getResponses("HEAD /ctx/omit/x HTTP/1.0\r\n" +
|
||||
"Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
|
||||
"\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));*/
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
}
|
||||
|
||||
|
||||
private static String CNONCE="1234567890";
|
||||
private String digest(String nonce, String username,String password,String uri,String nc) throws Exception
|
||||
{
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] ha1;
|
||||
// calc A1 digest
|
||||
md.update(username.getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update("TestRealm".getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update(password.getBytes(StringUtil.__ISO_8859_1));
|
||||
ha1 = md.digest();
|
||||
// calc A2 digest
|
||||
md.reset();
|
||||
md.update("GET".getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update(uri.getBytes(StringUtil.__ISO_8859_1));
|
||||
byte[] ha2 = md.digest();
|
||||
|
||||
// calc digest
|
||||
// request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
|
||||
// nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
|
||||
// <">
|
||||
// request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
|
||||
// ) > <">
|
||||
|
||||
md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update(nc.getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update(CNONCE.getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update("auth".getBytes(StringUtil.__ISO_8859_1));
|
||||
md.update((byte) ':');
|
||||
md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
|
||||
byte[] digest = md.digest();
|
||||
|
||||
// check digest
|
||||
return TypeUtil.toString(digest, 16);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigest() throws Exception
|
||||
{
|
||||
_security.setAuthenticator(new DigestAuthenticator());
|
||||
_security.setStrict(false);
|
||||
_server.start();
|
||||
|
||||
String response;
|
||||
response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
|
||||
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
|
||||
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
|
||||
assertTrue(response.contains("WWW-Authenticate: Digest realm=\"TestRealm\""));
|
||||
|
||||
Pattern nonceP = Pattern.compile("nonce=\"([^\"]*)\",");
|
||||
Matcher matcher = nonceP.matcher(response);
|
||||
assertTrue(matcher.find());
|
||||
String nonce=matcher.group(1);
|
||||
|
||||
|
||||
//wrong password
|
||||
String digest= digest(nonce,"user","WRONG","/ctx/auth/info","1");
|
||||
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
|
||||
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
|
||||
"nc=1, "+
|
||||
"nonce=\""+nonce+"\", "+
|
||||
"response=\""+digest+"\"\r\n"+
|
||||
"\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
|
||||
|
||||
// right password
|
||||
digest= digest(nonce,"user","password","/ctx/auth/info","2");
|
||||
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
|
||||
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
|
||||
"nc=2, "+
|
||||
"nonce=\""+nonce+"\", "+
|
||||
"response=\""+digest+"\"\r\n"+
|
||||
"\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
|
||||
|
||||
// once only
|
||||
digest= digest(nonce,"user","password","/ctx/auth/info","2");
|
||||
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
|
||||
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
|
||||
"nc=2, "+
|
||||
"nonce=\""+nonce+"\", "+
|
||||
"response=\""+digest+"\"\r\n"+
|
||||
"\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
|
||||
|
||||
// increasing
|
||||
digest= digest(nonce,"user","password","/ctx/auth/info","4");
|
||||
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
|
||||
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
|
||||
"nc=4, "+
|
||||
"nonce=\""+nonce+"\", "+
|
||||
"response=\""+digest+"\"\r\n"+
|
||||
"\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
|
||||
// out of order
|
||||
digest= digest(nonce,"user","password","/ctx/auth/info","3");
|
||||
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
|
||||
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
|
||||
"nc=3, "+
|
||||
"nonce=\""+nonce+"\", "+
|
||||
"response=\""+digest+"\"\r\n"+
|
||||
"\r\n");
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFormDispatch() throws Exception
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue