From 6b578dfd8c4defb1439eaff551b2ed382af5915d Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 8 May 2016 19:49:14 +0000 Subject: [PATCH] Console: Fix UTF-8 passwords Partial fix for UTF-8 usernames Better input checking and help messages --- .../i2ptunnel/I2PTunnelHTTPClientBase.java | 1 + .../net/i2p/router/web/ConfigUIHandler.java | 14 +++++++ .../i2p/router/web/RouterConsoleRunner.java | 38 ++++++++++++++++++- core/java/src/net/i2p/data/DataHelper.java | 4 +- .../src/net/i2p/util/PasswordManager.java | 21 ++++++---- 5 files changed, 68 insertions(+), 10 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java index 3776d767b5..2d6e053b03 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java @@ -463,6 +463,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem " realm=\"" + getRealm() + '"' + (isDigest ? ", nonce=\"" + getNonce() + "\"," + " algorithm=MD5," + + " charset=UTF-8," + // RFC 7616/7617 " qop=\"auth\"" + (isStale ? ", stale=true" : "") : "") + diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHandler.java index b5b6a54375..afe897b3ba 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHandler.java @@ -5,6 +5,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import net.i2p.data.DataHelper; + /** set the theme */ public class ConfigUIHandler extends FormHandler { private boolean _shouldSave; @@ -80,6 +82,16 @@ public class ConfigUIHandler extends FormHandler { addFormError(_t("No user name entered")); return; } + // XSS filters # and ; but not = + // We store the username as the part of an option key, so we can't handle '=' + if (name.contains("=")) { + addFormError("User name may not contain '='"); + return; + } + byte[] b1 = DataHelper.getUTF8(name); + byte[] b2 = DataHelper.getASCII(name); + if (!DataHelper.eq(b1, b2)) + addFormError(_t("Warning: User names outside the ISO-8859-1 character set are not recommended. Support is not standardized and varies by browser.")); String pw = getJettyString("nofilter_pw"); if (pw == null || pw.length() <= 0) { addFormError(_t("No password entered")); @@ -91,6 +103,8 @@ public class ConfigUIHandler extends FormHandler { if (!_context.getBooleanProperty(RouterConsoleRunner.PROP_PW_ENABLE)) _context.router().saveConfig(RouterConsoleRunner.PROP_PW_ENABLE, "true"); addFormNotice(_t("Added user {0}", name)); + addFormNotice(_t("To recover from a forgotten or non-working password, stop I2P, edit the file {0}, delete the line {1}, and restart I2P.", + _context.router().getConfigFilename(), RouterConsoleRunner.PROP_PW_ENABLE + "=true")); addFormError(_t("Restart required to take effect")); } else { addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs.")); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index a88bbb86ab..0c8064d304 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.Inet4Address; import java.net.InetSocketAddress; @@ -838,16 +839,51 @@ public class RouterConsoleRunner implements RouterApp { HashLoginService realm = new HashLoginService(JETTY_REALM); sec.setLoginService(realm); sec.setAuthenticator(authenticator); + String[] role = new String[] {JETTY_ROLE}; for (Map.Entry<String, String> e : userpw.entrySet()) { String user = e.getKey(); String pw = e.getValue(); - realm.putUser(user, Credential.getCredential(MD5.__TYPE + pw), new String[] {JETTY_ROLE}); + Credential cred = Credential.getCredential(MD5.__TYPE + pw); + realm.putUser(user, cred, role); Constraint constraint = new Constraint(user, JETTY_ROLE); constraint.setAuthenticate(true); ConstraintMapping cm = new ConstraintMapping(); cm.setConstraint(constraint); cm.setPathSpec("/"); constraints.add(cm); + // Jetty does auth checking only with ISO-8859-1, + // so register a 2nd and 3rd user with different encodings if necessary. + // Might work, might not... + // There's no standard and browser behavior varies. + // Chrome sends UTF-8. Firefox doesn't send anything. + // https://bugzilla.mozilla.org/show_bug.cgi?id=41489 + // see also RFC 7616/7617 (late 2015) and PasswordManager.md5Hex() + byte[] b1 = DataHelper.getUTF8(user); + byte[] b2 = DataHelper.getASCII(user); + if (!DataHelper.eq(b1, b2)) { + try { + // each char truncated to 8 bytes + String user2 = new String(b2, "ISO-8859-1"); + realm.putUser(user2, cred, role); + constraint = new Constraint(user2, JETTY_ROLE); + constraint.setAuthenticate(true); + cm = new ConstraintMapping(); + cm.setConstraint(constraint); + cm.setPathSpec("/"); + constraints.add(cm); + + // each UTF-8 byte as a char + // this is what chrome does + String user3 = new String(b1, "ISO-8859-1"); + realm.putUser(user3, cred, role); + constraint = new Constraint(user3, JETTY_ROLE); + constraint.setAuthenticate(true); + cm = new ConstraintMapping(); + cm.setConstraint(constraint); + cm.setPathSpec("/"); + constraints.add(cm); + } catch (UnsupportedEncodingException uee) {} + } } } } diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 240ff42fbb..6ce1e07896 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -1873,7 +1873,9 @@ public class DataHelper { * Roughly the same as orig.getBytes("ISO-8859-1") but much faster and * will not throw an exception. * - * @param orig non-null, must be 7-bit chars + * Warning - misnamed, converts to ISO-8859-1. + * + * @param orig non-null, truncates to 8-bit chars * @since 0.9.5 */ public static byte[] getASCII(String orig) { diff --git a/core/java/src/net/i2p/util/PasswordManager.java b/core/java/src/net/i2p/util/PasswordManager.java index 5ca812b4e7..b324c95703 100644 --- a/core/java/src/net/i2p/util/PasswordManager.java +++ b/core/java/src/net/i2p/util/PasswordManager.java @@ -30,7 +30,7 @@ public class PasswordManager { protected static final String PROP_PW = ".password"; /** stored obfuscated as b64 of the UTF-8 bytes */ protected static final String PROP_B64 = ".b64"; - /** stored as the hex of the MD5 hash of the ISO-8859-1 bytes. Compatible with Jetty. */ + /** stored as the hex of the MD5 hash of the UTF-8 bytes. Compatible with Jetty. */ protected static final String PROP_MD5 = ".md5"; /** stored as a Unix crypt string */ protected static final String PROP_CRYPT = ".crypt"; @@ -185,6 +185,10 @@ public class PasswordManager { * Will return the MD5 sum of "user:subrealm:pw", compatible with Jetty * and RFC 2617. * + * Updated in 0.9.26 to use UTF-8, as implied in RFC 7616/7617 + * See also http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication + * http://stackoverflow.com/questions/702629/utf-8-characters-mangled-in-http-basic-auth-username + * * @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 @@ -200,17 +204,18 @@ public class PasswordManager { * Will return the MD5 sum of the data, compatible with Jetty * and RFC 2617. * + * Updated in 0.9.26 to use UTF-8, as implied in RFC 7616/7617 + * See also http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication + * * @param fullpw non-null, plain text, already trimmed * @return lower-case hex with leading zeros, 32 chars, or null on error */ public static String md5Hex(String fullpw) { - try { - byte[] data = fullpw.getBytes("ISO-8859-1"); - byte[] sum = md5Sum(data); - if (sum != null) - // adds leading zeros if necessary - return DataHelper.toString(sum); - } catch (UnsupportedEncodingException uee) {} + byte[] data = DataHelper.getUTF8(fullpw); + byte[] sum = md5Sum(data); + if (sum != null) + // adds leading zeros if necessary + return DataHelper.toString(sum); return null; } -- GitLab