forked from I2P_Developers/i2p.i2p
i2ptunnel, eepget: Add support for SHA-256 digest proxy auth (RFC 7616)
Requires re-saving user/pw on proxy side
This commit is contained in:
@ -92,11 +92,9 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
"Cache-Control: no-cache\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: ";
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n"; // try to get a UTF-8-encoded response back for the password
|
||||
// put the auth type and realm in between
|
||||
private static final String ERR_AUTH2 =
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>" +
|
||||
"This proxy is configured to require authentication.";
|
||||
@ -346,6 +344,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
/** new style MD5 auth */
|
||||
public static final String PROP_PROXY_DIGEST_PREFIX = "proxy.auth.";
|
||||
public static final String PROP_PROXY_DIGEST_SUFFIX = ".md5";
|
||||
public static final String PROP_PROXY_DIGEST_SHA256_SUFFIX = ".sha256";
|
||||
public static final String BASIC_AUTH = "basic";
|
||||
public static final String DIGEST_AUTH = "digest";
|
||||
|
||||
@ -526,6 +525,19 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
_log.info("Bad digest request: " + DataHelper.toString(args));
|
||||
return AuthResult.AUTH_BAD_REQ;
|
||||
}
|
||||
// RFC 7616
|
||||
String algorithm = args.get("algorithm");
|
||||
boolean isSHA256 = false;
|
||||
if (algorithm != null) {
|
||||
algorithm = algorithm.toLowerCase(Locale.US);
|
||||
if (algorithm.equals("sha-256")) {
|
||||
isSHA256 = true;
|
||||
} else if (!algorithm.equals("md5")) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Bad digest request: " + DataHelper.toString(args));
|
||||
return AuthResult.AUTH_BAD_REQ;
|
||||
}
|
||||
}
|
||||
// nonce check
|
||||
AuthResult check = verifyNonce(nonce, nc);
|
||||
if (check != AuthResult.AUTH_GOOD) {
|
||||
@ -535,17 +547,17 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
}
|
||||
// get H(A1) == stored password
|
||||
String ha1 = getTunnel().getClientOptions().getProperty(PROP_PROXY_DIGEST_PREFIX + user +
|
||||
PROP_PROXY_DIGEST_SUFFIX);
|
||||
(isSHA256 ? PROP_PROXY_DIGEST_SHA256_SUFFIX : PROP_PROXY_DIGEST_SUFFIX));
|
||||
if (ha1 == null) {
|
||||
_log.logAlways(Log.WARN, "HTTP proxy authentication failed, user: " + user);
|
||||
return AuthResult.AUTH_BAD;
|
||||
}
|
||||
// get H(A2)
|
||||
String a2 = method + ':' + uri;
|
||||
String ha2 = PasswordManager.md5Hex(a2);
|
||||
String ha2 = isSHA256 ? PasswordManager.sha256Hex(a2) : PasswordManager.md5Hex(a2);
|
||||
// response check
|
||||
String kd = ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2;
|
||||
String hkd = PasswordManager.md5Hex(kd);
|
||||
String hkd = isSHA256 ? PasswordManager.sha256Hex(kd) : PasswordManager.md5Hex(kd);
|
||||
if (!response.equals(hkd)) {
|
||||
_log.logAlways(Log.WARN, "HTTP proxy authentication failed, user: " + user);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -631,17 +643,43 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
*/
|
||||
protected String getAuthError(boolean isStale) {
|
||||
boolean isDigest = isDigestAuthRequired();
|
||||
return
|
||||
ERR_AUTH1 +
|
||||
(isDigest ? "Digest" : "Basic") +
|
||||
" realm=\"" + getRealm() + '"' +
|
||||
(isDigest ? ", nonce=\"" + getNonce() + "\"," +
|
||||
" algorithm=MD5," +
|
||||
" charset=UTF-8," + // RFC 7616/7617
|
||||
" qop=\"auth\"" +
|
||||
(isStale ? ", stale=true" : "")
|
||||
: "") +
|
||||
ERR_AUTH2;
|
||||
StringBuilder buf = new StringBuilder(512);
|
||||
buf.append(ERR_AUTH1)
|
||||
.append("Proxy-Authenticate: ")
|
||||
.append(isDigest ? "Digest" : "Basic")
|
||||
.append(" realm=\"" + getRealm() + '"');
|
||||
if (isDigest) {
|
||||
String nonce = getNonce();
|
||||
// RFC 7616 most-preferred first, client accepts first that he supports
|
||||
// This is also compatible with eepget < 0.9.56 that will use the last one
|
||||
// Do we have a SHA256 hash for any user?
|
||||
for (String k : getTunnel().getClientOptions().stringPropertyNames()) {
|
||||
if (k.startsWith(PROP_PROXY_DIGEST_PREFIX) &&
|
||||
k.endsWith(PROP_PROXY_DIGEST_SHA256_SUFFIX)) {
|
||||
// SHA-256, RFC 7616
|
||||
buf.append(", nonce=\"" + nonce + "\"," +
|
||||
" algorithm=SHA-256," +
|
||||
" charset=UTF-8," +
|
||||
" qop=\"auth\"");
|
||||
if (isStale)
|
||||
buf.append(", stale=true");
|
||||
buf.append("\r\n" +
|
||||
"Proxy-Authenticate: Digest" +
|
||||
" realm=\"" + getRealm() + '"');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf.append(", nonce=\"" + nonce + "\"," +
|
||||
" algorithm=MD5," +
|
||||
" charset=UTF-8," + // RFC 7616/7617
|
||||
" qop=\"auth\"");
|
||||
if (isStale)
|
||||
buf.append(", stale=true");
|
||||
}
|
||||
buf.append("\r\n")
|
||||
.append(ERR_AUTH2);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -774,7 +774,7 @@ public class TunnelConfig {
|
||||
config.setProperty(TunnelController.PROP_PROXIES, _proxyList);
|
||||
}
|
||||
|
||||
// Proxy auth including migration to MD5
|
||||
// Proxy auth including migration to MD5 and SHA256
|
||||
if (TunnelController.TYPE_HTTP_CLIENT.equals(_type) || TunnelController.TYPE_CONNECT.equals(_type)) {
|
||||
// Migrate even if auth is disabled
|
||||
// go get the old from custom options that updateConfigGeneric() put in there
|
||||
@ -796,6 +796,17 @@ public class TunnelConfig {
|
||||
config.remove(ppw);
|
||||
}
|
||||
}
|
||||
// SHA256 RFC 7616
|
||||
String psha256 = OPT + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
|
||||
user + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SHA256_SUFFIX;
|
||||
if (config.getProperty(psha256) == null) {
|
||||
// not in there, add it
|
||||
String realm = _type.equals(TunnelController.TYPE_HTTP_CLIENT) ? I2PTunnelHTTPClient.AUTH_REALM
|
||||
: I2PTunnelConnectClient.AUTH_REALM;
|
||||
String hex = PasswordManager.sha256Hex(realm, user, pw);
|
||||
if (hex != null)
|
||||
config.setProperty(psha256, hex);
|
||||
}
|
||||
}
|
||||
// New user/password
|
||||
String auth = _otherOptions.get(I2PTunnelHTTPClientBase.PROP_AUTH);
|
||||
@ -809,6 +820,11 @@ public class TunnelConfig {
|
||||
String hex = PasswordManager.md5Hex(realm, _newProxyUser, _newProxyPW);
|
||||
if (hex != null)
|
||||
config.setProperty(pmd5, hex);
|
||||
String psha256 = OPT + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
|
||||
_newProxyUser + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SHA256_SUFFIX;
|
||||
hex = PasswordManager.sha256Hex(realm, _newProxyUser, _newProxyPW);
|
||||
if (hex != null)
|
||||
config.setProperty(psha256, hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1785,20 +1785,38 @@ public class EepGet {
|
||||
// use standard alphabet
|
||||
authMode = AUTH_MODE.BASIC;
|
||||
authChallenge = auth.substring(6);
|
||||
args = parseAuthArgs(authChallenge);
|
||||
}
|
||||
} else if (authLC.startsWith("digest ")) {
|
||||
// better than anything
|
||||
authMode = AUTH_MODE.DIGEST;
|
||||
authChallenge = auth.substring(7);
|
||||
// RFC 7616 take the first one that we support
|
||||
if (authMode != AUTH_MODE.DIGEST) {
|
||||
String ac = auth.substring(7);
|
||||
Map<String, String> aargs = parseAuthArgs(ac);
|
||||
String algo = aargs.get("algorithm");
|
||||
if (algo != null) {
|
||||
algo = algo.toLowerCase(Locale.US);
|
||||
if (!algo.equals("sha-256") && !algo.equals("md5")) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unsupported auth algorithm " + algo);
|
||||
return;
|
||||
}
|
||||
}
|
||||
authMode = AUTH_MODE.DIGEST;
|
||||
authChallenge = ac;
|
||||
args = aargs;
|
||||
} else {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Ignoring auth algorithm " + auth);
|
||||
}
|
||||
} else {
|
||||
// better than NONE only
|
||||
if (authMode == AUTH_MODE.NONE) {
|
||||
authMode = AUTH_MODE.UNKNOWN;
|
||||
authChallenge = null;
|
||||
args = null;
|
||||
}
|
||||
}
|
||||
nonceCount = 0;
|
||||
args = null;
|
||||
}
|
||||
|
||||
public String getAuthHeader(String method, String uri) throws IOException {
|
||||
@ -1812,8 +1830,6 @@ public class EepGet {
|
||||
case DIGEST:
|
||||
if (authChallenge == null)
|
||||
throw new IOException("Bad proxy auth response");
|
||||
if (args == null)
|
||||
args = parseAuthArgs(authChallenge);
|
||||
Map<String, String> outArgs = generateAuthArgs(method, uri);
|
||||
if (outArgs == null)
|
||||
throw new IOException("Bad proxy auth response");
|
||||
@ -1842,7 +1858,14 @@ public class EepGet {
|
||||
String nonce = args.get("nonce");
|
||||
String qop = args.get("qop");
|
||||
String opaque = args.get("opaque");
|
||||
//String algorithm = args.get("algorithm");
|
||||
// RFC 7616
|
||||
String algorithm = args.get("algorithm");
|
||||
boolean isSHA256 = false;
|
||||
if (algorithm == null)
|
||||
algorithm = "MD5";
|
||||
else
|
||||
isSHA256 = algorithm.toLowerCase(Locale.US).equals("sha-256");
|
||||
rv.put("algorithm", algorithm);
|
||||
//String stale = args.get("stale");
|
||||
if (realm == null || nonce == null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -1872,13 +1895,14 @@ public class EepGet {
|
||||
}
|
||||
|
||||
// get H(A1)
|
||||
String ha1 = PasswordManager.md5Hex(username + ':' + realm + ':' + password);
|
||||
String a1 = username + ':' + realm + ':' + password;
|
||||
String ha1 = isSHA256 ? PasswordManager.sha256Hex(a1) : PasswordManager.md5Hex(a1);
|
||||
// get H(A2)
|
||||
String a2 = method + ':' + uri;
|
||||
String ha2 = PasswordManager.md5Hex(a2);
|
||||
String ha2 = isSHA256 ? PasswordManager.sha256Hex(a2) : PasswordManager.md5Hex(a2);
|
||||
// response
|
||||
String kd = ha1 + ':' + nonce + kdMiddle + ':' + ha2;
|
||||
rv.put("response", '"' + PasswordManager.md5Hex(kd) + '"');
|
||||
rv.put("response", '"' + (isSHA256 ? PasswordManager.sha256Hex(kd) : PasswordManager.md5Hex(kd)) + '"');
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.SessionKey;
|
||||
@ -232,6 +233,38 @@ public class PasswordManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Straight SHA256, no salt
|
||||
* Will return the SHA256 sum of "user:subrealm:pw", compatible with RFC 7616.
|
||||
* NOT currently supported by Jetty.
|
||||
*
|
||||
* @param subrealm to be used in creating the checksum
|
||||
* @param user non-null, non-empty, already trimmed
|
||||
* @param pw non-null, plain text, already trimmed
|
||||
* @return lower-case hex with leading zeros, 32 chars, or null on error
|
||||
* @since 0.9.56
|
||||
*/
|
||||
public static String sha256Hex(String subrealm, String user, String pw) {
|
||||
String fullpw = user + ':' + subrealm + ':' + pw;
|
||||
return sha256Hex(fullpw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SHA256 sum of the data, compatible with RFC 7616.
|
||||
* NOT currently supported by Jetty.
|
||||
*
|
||||
* @param fullpw non-null, plain text, already trimmed
|
||||
* @return lower-case hex with leading zeros, 64 chars, or null on error
|
||||
* @since 0.9.56
|
||||
*/
|
||||
public static String sha256Hex(String fullpw) {
|
||||
byte[] data = DataHelper.getUTF8(fullpw);
|
||||
byte[] sum = new byte[32];
|
||||
SHA256Generator.getInstance().calculateHash(data, 0, data.length, sum, 0);
|
||||
// adds leading zeros if necessary
|
||||
return DataHelper.toString(sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* speed/comparison test before removing BC version;
|
||||
* JVM was slightly faster
|
||||
|
13
history.txt
13
history.txt
@ -1,3 +1,6 @@
|
||||
2022-09-20 zzz
|
||||
* i2ptunnel, eepget: Add support for SHA-256 digest proxy auth (RFC 7616)
|
||||
|
||||
2022-09-09 zzz
|
||||
* SSU2: Enable handling of ack-immediate flag by default
|
||||
|
||||
@ -38,7 +41,7 @@
|
||||
2022-08-23 zzz
|
||||
* Router: Add deadlocks to event log
|
||||
|
||||
2022-08-22 1.9.0 released
|
||||
2022-08-22 1.9.0 (API 0.9.55) released
|
||||
|
||||
2022-08-10 zzz
|
||||
* SSU2: Enable for Android, ARM, and 2% of others
|
||||
@ -229,7 +232,7 @@
|
||||
* Crypto: Throw checked exception from ElGamal
|
||||
so console key import reports the correct error
|
||||
|
||||
2022-05-23 1.8.0 released
|
||||
2022-05-23 1.8.0 (API 0.9.54) released
|
||||
|
||||
2022-05-19 zzz
|
||||
* Pull translations from Transifex
|
||||
@ -377,7 +380,7 @@
|
||||
* Update: Add notification for new version
|
||||
* Util: Speed up PRNG nextInt() and nextLong()
|
||||
|
||||
2022-02-21 1.7.0 released
|
||||
2022-02-21 1.7.0 (API 0.9.53) released
|
||||
|
||||
2022-02-18 zzz
|
||||
* Update translations
|
||||
@ -492,7 +495,7 @@
|
||||
* Console: NetDB search form improvements
|
||||
* i2ptunnel: Increase priority for IRC and standard tunnels
|
||||
|
||||
2021-11-29 1.6.1 released
|
||||
2021-11-29 1.6.1 (API 0.9.52) released
|
||||
|
||||
2021-11-29 zzz
|
||||
* Tunnels: Fix NPE in BuildHandler
|
||||
@ -575,7 +578,7 @@
|
||||
* Router: Increase rekey probability
|
||||
* Tunnels: Enable sending short build messages
|
||||
|
||||
2021-08-23 1.5.0 released
|
||||
2021-08-23 1.5.0 (API 0.9.51) released
|
||||
|
||||
2021-08-20 zzz
|
||||
* Update GeoIP and translations
|
||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Git";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 8;
|
||||
public final static long BUILD = 9;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
Reference in New Issue
Block a user