diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
index 7807b699f2..a4ffc11733 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
@@ -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" +
"
I2P ERROR: PROXY AUTHENTICATION REQUIRED
" +
"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();
}
/**
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java
index f7eb3cdcb4..7b4e296bdc 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java
@@ -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);
}
}
}
diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java
index 24b2a392d5..9ea95faa73 100644
--- a/core/java/src/net/i2p/util/EepGet.java
+++ b/core/java/src/net/i2p/util/EepGet.java
@@ -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 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 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;
}
diff --git a/core/java/src/net/i2p/util/PasswordManager.java b/core/java/src/net/i2p/util/PasswordManager.java
index bec6ec7997..6c60cea440 100644
--- a/core/java/src/net/i2p/util/PasswordManager.java
+++ b/core/java/src/net/i2p/util/PasswordManager.java
@@ -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
diff --git a/history.txt b/history.txt
index fa96080b75..55ad8510d6 100644
--- a/history.txt
+++ b/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
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 6a9511fad2..f40f9f7a0c 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -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 = "";