* Console: Fix Jetty digest auth bug causing repeated password requests

I2P fixes for out-of-order nonce counts.
   Based on DigestAuthenticator in Jetty 7.6.10.
   Includes the nonce count verification code from Tomcat 7.0.35.
   ref: http://jira.codehaus.org/browse/JETTY-1468 which was closed not-a-bug.
   ref: https://bugs.eclipse.org/bugs/show_bug.cgi?id=336443 in which the
   Jetty implementation was introduced.
This commit is contained in:
zzz
2013-04-23 18:22:48 +00:00
parent 4358d11191
commit 22025b0c3a
3 changed files with 97 additions and 43 deletions

View File

@ -202,6 +202,7 @@
<pathelement location="./jettylib/javax.servlet.jar" /> <pathelement location="./jettylib/javax.servlet.jar" />
<pathelement location="./jettylib/jetty-http.jar" /> <pathelement location="./jettylib/jetty-http.jar" />
<pathelement location="./jettylib/jetty-io.jar" /> <pathelement location="./jettylib/jetty-io.jar" />
<pathelement location="./jettylib/jetty-security.jar" />
<pathelement location="./jettylib/jetty-util.jar" /> <pathelement location="./jettylib/jetty-util.jar" />
<pathelement location="./jettylib/jetty-xml.jar" /> <pathelement location="./jettylib/jetty-xml.jar" />
</classpath> </classpath>
@ -216,7 +217,7 @@
debug="true" deprecation="on" source="1.5" target="1.5" debug="true" deprecation="on" source="1.5" target="1.5"
destdir="./build/obj" destdir="./build/obj"
includeAntRuntime="false" includeAntRuntime="false"
classpath="../../core/java/build/i2p.jar:./jettylib/commons-logging.jar:./jettylib/javax.servlet.jar:./jettylib/org.mortbay.jetty.jar:./jettylib/jetty-http.jar:./jettylib/jetty-io.jar:./jettylib/jetty-util.jar:./jettylib/jetty-xml.jar" > classpath="../../core/java/build/i2p.jar:./jettylib/commons-logging.jar:./jettylib/javax.servlet.jar:./jettylib/org.mortbay.jetty.jar:./jettylib/jetty-http.jar:./jettylib/jetty-io.jar:./jettylib/jetty-security.jar:./jettylib/jetty-util.jar:./jettylib/jetty-xml.jar" >
<compilerarg line="${javac.compilerargs}" /> <compilerarg line="${javac.compilerargs}" />
</javac> </javac>
</target> </target>

View File

@ -16,7 +16,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.security.authentication; package net.i2p.jetty;
import java.io.IOException; import java.io.IOException;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -25,7 +25,6 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
@ -36,6 +35,8 @@ import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
import org.eclipse.jetty.security.authentication.DigestAuthenticator;
import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
@ -50,74 +51,110 @@ import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.security.Credential;
/** /**
* I2P fixes for out-of-order nonce counts.
* Based on DigestAuthenticator in Jetty 7.6.10.
* Includes the nonce count verification code from Tomcat 7.0.35.
* ref: http://jira.codehaus.org/browse/JETTY-1468 which was closed not-a-bug.
* ref: https://bugs.eclipse.org/bugs/show_bug.cgi?id=336443 in which the
* Jetty implementation was introduced.
*
* @since 0.9.6
*
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
* *
* The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)} * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
* using the name "maxNonceAge" * using the name "maxNonceAge"
*/ */
public class DigestAuthenticator extends LoginAuthenticator public class I2PDigestAuthenticator extends DigestAuthenticator
{ {
private static final Logger LOG = Log.getLogger(DigestAuthenticator.class); // shadows super
private static final Logger LOG = Log.getLogger(I2PDigestAuthenticator.class);
SecureRandom _random = new SecureRandom(); SecureRandom _random = new SecureRandom();
private long _maxNonceAgeMs = 60*1000; // shadows super
private long _maxNonceAgeMs = 60*60*1000L;
private ConcurrentMap<String, Nonce> _nonceCount = new ConcurrentHashMap<String, Nonce>(); private ConcurrentMap<String, Nonce> _nonceCount = new ConcurrentHashMap<String, Nonce>();
// shadows super
private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>(); private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
/*
* Shadows super
*
* Contains code from Tomcat 7.0.35 DigestAuthenticator.NonceInfo
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
private static class Nonce private static class Nonce
{ {
final String _nonce; final String _nonce;
final long _ts; final long _ts;
AtomicInteger _nc=new AtomicInteger(); private volatile boolean seen[];
private volatile int offset;
private volatile int count = 0;
private static final int seenWindowSize = 100;
public Nonce(String nonce, long ts) public Nonce(String nonce, long ts)
{ {
_nonce=nonce; _nonce=nonce;
_ts=ts; _ts=ts;
seen = new boolean[seenWindowSize];
offset = seenWindowSize / 2;
}
public synchronized boolean nonceCountValid(long nonceCount) {
if ((count - offset) >= nonceCount ||
(nonceCount > count - offset + seen.length)) {
return false;
}
int checkIndex = (int) ((nonceCount + offset) % seen.length);
if (seen[checkIndex]) {
return false;
} else {
seen[checkIndex] = true;
seen[count % seen.length] = false;
count++;
return true;
}
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public DigestAuthenticator() public I2PDigestAuthenticator()
{ {
super(); super();
} }
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
*/
@Override
public void setConfiguration(AuthConfiguration configuration)
{
super.setConfiguration(configuration);
String mna=configuration.getInitParameter("maxNonceAge");
if (mna!=null)
{
synchronized (this)
{
_maxNonceAgeMs=Long.valueOf(mna);
}
}
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* Store local copy since field in super is private
*/
@Override
public synchronized void setMaxNonceAge(long maxNonceAgeInMillis) public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
{ {
super.setMaxNonceAge(maxNonceAgeInMillis);
_maxNonceAgeMs = maxNonceAgeInMillis; _maxNonceAgeMs = maxNonceAgeInMillis;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public String getAuthMethod()
{
return Constraint.__DIGEST_AUTH;
}
/* ------------------------------------------------------------ */ /**
public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException * No changes from super
{ */
return true; @Override
}
/* ------------------------------------------------------------ */
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
{ {
if (!mandatory) if (!mandatory)
@ -224,6 +261,11 @@ public class DigestAuthenticator extends LoginAuthenticator
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* No changes from super
*/
@Override
public String newNonce(Request request) public String newNonce(Request request)
{ {
Nonce nonce; Nonce nonce;
@ -247,6 +289,10 @@ public class DigestAuthenticator extends LoginAuthenticator
* @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
*/ */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* Contains fixes
*/
private int checkNonce(Digest digest, Request request) private int checkNonce(Digest digest, Request request)
{ {
// firstly let's expire old nonces // firstly let's expire old nonces
@ -274,12 +320,9 @@ public class DigestAuthenticator extends LoginAuthenticator
long count = Long.parseLong(digest.nc,16); long count = Long.parseLong(digest.nc,16);
if (count>Integer.MAX_VALUE) if (count>Integer.MAX_VALUE)
return 0; return 0;
int old=nonce._nc.get(); if (!nonce.nonceCountValid(count)) {
while (!nonce._nc.compareAndSet(old,(int)count))
old=nonce._nc.get();
if (count<=old)
return -1; return -1;
}
return 1; return 1;
} }
catch (Exception e) catch (Exception e)
@ -292,6 +335,11 @@ public class DigestAuthenticator extends LoginAuthenticator
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* Shadows super.
* No changes from super
*/
private static class Digest extends Credential private static class Digest extends Credential
{ {
private static final long serialVersionUID = -2484639019549527724L; private static final long serialVersionUID = -2484639019549527724L;

View File

@ -29,6 +29,7 @@ import static net.i2p.app.ClientAppState.*;
import net.i2p.apps.systray.SysTray; import net.i2p.apps.systray.SysTray;
import net.i2p.data.Base32; import net.i2p.data.Base32;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.jetty.I2PDigestAuthenticator;
import net.i2p.jetty.I2PLogger; import net.i2p.jetty.I2PLogger;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.update.ConsoleUpdateManager; import net.i2p.router.update.ConsoleUpdateManager;
@ -105,7 +106,11 @@ public class RouterConsoleRunner implements RouterApp {
private static final String DEFAULT_WEBAPP_CONFIG_FILENAME = "webapps.config"; private static final String DEFAULT_WEBAPP_CONFIG_FILENAME = "webapps.config";
// Jetty Auth // Jetty Auth
private static final DigestAuthenticator authenticator = new DigestAuthenticator(); private static final DigestAuthenticator authenticator = new I2PDigestAuthenticator();
static {
// default changed from 0 (forever) in Jetty 6 to 60*1000 ms in Jetty 7
authenticator.setMaxNonceAge(7*24*60*60*1000L);
}
public static final String JETTY_REALM = "i2prouter"; public static final String JETTY_REALM = "i2prouter";
private static final String JETTY_ROLE = "routerAdmin"; private static final String JETTY_ROLE = "routerAdmin";
public static final String PROP_CONSOLE_PW = "routerconsole.auth." + JETTY_REALM; public static final String PROP_CONSOLE_PW = "routerconsole.auth." + JETTY_REALM;