Changeset a9e1862


Ignore:
Timestamp:
Oct 16, 2012 7:17:06 PM (7 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
cbc9165
Parents:
613dd77
Message:
  • Convert HTTP and CONNECT proxies to MD5 authentication
  • Allow multiple users
  • Migrate passwords on first save
Location:
apps/i2ptunnel
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java

    r613dd77 ra9e1862  
    5959public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements Runnable {
    6060
    61     private static final String AUTH_REALM = "I2P SSL Proxy";
     61    public static final String AUTH_REALM = "I2P SSL Proxy";
    6262
    6363    private final static byte[] ERR_DESTINATION_UNKNOWN =
     
    278278           
    279279            // Authorization
    280             if (!authorize(s, requestId, method, authorization)) {
     280            AuthResult result = authorize(s, requestId, method, authorization);
     281            if (result != AuthResult.AUTH_GOOD) {
    281282                if (_log.shouldLog(Log.WARN)) {
    282283                    if (authorization != null)
     
    285286                        _log.warn(getPrefix(requestId) + "Auth required, sending 407");
    286287                }
    287                 out.write(getAuthError(false).getBytes());
     288                out.write(getAuthError(result == AuthResult.AUTH_STALE).getBytes());
    288289                s.close();
    289290                return;
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java

    r613dd77 ra9e1862  
    7878    private final String _proxyNonce;
    7979
    80     private static final String AUTH_REALM = "I2P HTTP Proxy";
     80    public static final String AUTH_REALM = "I2P HTTP Proxy";
    8181
    8282    /**
     
    847847
    848848            // Authorization
    849             if(!authorize(s, requestId, method, authorization)) {
     849            AuthResult result = authorize(s, requestId, method, authorization);
     850            if (result != AuthResult.AUTH_GOOD) {
    850851                if(_log.shouldLog(Log.WARN)) {
    851852                    if(authorization != null) {
     
    855856                    }
    856857                }
    857                 out.write(getAuthError(false).getBytes());
     858                out.write(getAuthError(result == AuthResult.AUTH_STALE).getBytes());
    858859                writeFooter(out);
    859860                s.close();
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java

    r613dd77 ra9e1862  
    123123    public static final String PROP_OUTPROXY_USER_PREFIX = PROP_OUTPROXY_USER + '.';
    124124    public static final String PROP_OUTPROXY_PW_PREFIX = PROP_OUTPROXY_PW + '.';
     125    /** new style MD5 auth */
     126    public static final String PROP_PROXY_DIGEST_PREFIX = "proxy.auth.";
     127    public static final String PROP_PROXY_DIGEST_SUFFIX = ".md5";
     128    public static final String BASIC_AUTH = "basic";
     129    public static final String DIGEST_AUTH = "digest";
    125130
    126131    protected abstract String getRealm();
     132
     133    protected enum AuthResult {AUTH_BAD_REQ, AUTH_BAD, AUTH_STALE, AUTH_GOOD}
    127134
    128135    /**
     
    145152     *  @return success
    146153     */
    147     protected boolean authorize(Socket s, long requestId, String method, String authorization) {
     154    protected AuthResult authorize(Socket s, long requestId, String method, String authorization) {
    148155        String authRequired = getTunnel().getClientOptions().getProperty(PROP_AUTH);
    149156        if (authRequired == null)
    150             return true;
     157            return AuthResult.AUTH_GOOD;
    151158        authRequired = authRequired.toLowerCase(Locale.US);
    152159        if (authRequired.equals("false"))
    153             return true;
     160            return AuthResult.AUTH_GOOD;
    154161        if (s instanceof InternalSocket) {
    155162            if (_log.shouldLog(Log.INFO))
    156163                _log.info(getPrefix(requestId) + "Internal access, no auth required");
    157             return true;
     164            return AuthResult.AUTH_GOOD;
    158165        }
    159166        if (authorization == null)
    160             return false;
     167            return AuthResult.AUTH_BAD;
    161168        if (_log.shouldLog(Log.INFO))
    162169            _log.info(getPrefix(requestId) + "Auth: " + authorization);
    163170        String authLC = authorization.toLowerCase(Locale.US);
    164         if (authRequired.equals("true") || authRequired.equals("basic")) {
     171        if (authRequired.equals("true") || authRequired.equals(BASIC_AUTH)) {
    165172            if (!authLC.startsWith("basic "))
    166                 return false;
     173                return AuthResult.AUTH_BAD;
    167174            authorization = authorization.substring(6);
    168175
    169                 // hmm safeDecode(foo, true) to use standard alphabet is private in Base64
    170                 byte[] decoded = Base64.decode(authorization.replace("/", "~").replace("+", "="));
    171                 if (decoded != null) {
    172                     // We send Accept-Charset: UTF-8 in the 407 so hopefully it comes back that way inside the B64 ?
    173                     try {
    174                         String dec = new String(decoded, "UTF-8");
    175                         String[] parts = dec.split(":");
    176                         String user = parts[0];
    177                         String pw = parts[1];
    178                         // first try pw for that user
    179                         String configPW = getTunnel().getClientOptions().getProperty(PROP_PW_PREFIX + user);
    180                         if (configPW == null) {
    181                             // if not, look at default user and pw
    182                             String configUser = getTunnel().getClientOptions().getProperty(PROP_USER);
    183                             if (user.equals(configUser))
    184                                 configPW = getTunnel().getClientOptions().getProperty(PROP_PW);
     176            // hmm safeDecode(foo, true) to use standard alphabet is private in Base64
     177            byte[] decoded = Base64.decode(authorization.replace("/", "~").replace("+", "="));
     178            if (decoded != null) {
     179                // We send Accept-Charset: UTF-8 in the 407 so hopefully it comes back that way inside the B64 ?
     180                try {
     181                    String dec = new String(decoded, "UTF-8");
     182                    String[] parts = dec.split(":");
     183                    String user = parts[0];
     184                    String pw = parts[1];
     185                    // first try pw for that user
     186                    String configPW = getTunnel().getClientOptions().getProperty(PROP_PW_PREFIX + user);
     187                    if (configPW == null) {
     188                        // if not, look at default user and pw
     189                        String configUser = getTunnel().getClientOptions().getProperty(PROP_USER);
     190                        if (user.equals(configUser))
     191                            configPW = getTunnel().getClientOptions().getProperty(PROP_PW);
     192                    }
     193                    if (configPW != null) {
     194                        if (pw.equals(configPW)) {
     195                            if (_log.shouldLog(Log.INFO))
     196                                _log.info(getPrefix(requestId) + "Good auth - user: " + user + " pw: " + pw);
     197                            return AuthResult.AUTH_GOOD;
    185198                        }
    186                         if (configPW != null) {
    187                             if (pw.equals(configPW)) {
    188                                 if (_log.shouldLog(Log.INFO))
    189                                     _log.info(getPrefix(requestId) + "Good auth - user: " + user + " pw: " + pw);
    190                                 return true;
    191                             } else {
    192                                 if (_log.shouldLog(Log.WARN))
    193                                     _log.warn(getPrefix(requestId) + "Bad auth, pw mismatch - user: " + user + " pw: " + pw + " expected: " + configPW);
    194                             }
    195                         } else {
    196                             if (_log.shouldLog(Log.WARN))
    197                                 _log.warn(getPrefix(requestId) + "Bad auth, no stored pw for user: " + user + " pw: " + pw);
    198                         }
    199                     } catch (UnsupportedEncodingException uee) {
    200                         _log.error(getPrefix(requestId) + "No UTF-8 support? B64: " + authorization, uee);
    201                     } catch (ArrayIndexOutOfBoundsException aioobe) {
    202                         // no ':' in response
    203                         if (_log.shouldLog(Log.WARN))
    204                             _log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization, aioobe);
    205199                    }
    206                 } else {
     200                    _log.logAlways(Log.WARN, "PROXY AUTH FAILURE: user " + user);
     201                } catch (UnsupportedEncodingException uee) {
     202                    _log.error(getPrefix(requestId) + "No UTF-8 support? B64: " + authorization, uee);
     203                } catch (ArrayIndexOutOfBoundsException aioobe) {
     204                    // no ':' in response
    207205                    if (_log.shouldLog(Log.WARN))
    208                         _log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization);
     206                        _log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization, aioobe);
     207                    return AuthResult.AUTH_BAD_REQ;
    209208                }
    210 
    211             return false;
    212         } else if (authRequired.equals("digest")) {
     209                return AuthResult.AUTH_BAD;
     210            } else {
     211                if (_log.shouldLog(Log.WARN))
     212                    _log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization);
     213                return AuthResult.AUTH_BAD_REQ;
     214            }
     215        } else if (authRequired.equals(DIGEST_AUTH)) {
    213216            if (!authLC.startsWith("digest "))
    214                 return false;
     217                return AuthResult.AUTH_BAD;
    215218            authorization = authorization.substring(7);
    216219            Map<String, String> args = parseArgs(authorization);
    217220            AuthResult rv = validateDigest(method, args);
    218             return rv == AuthResult.AUTH_GOOD;
     221            return rv;
    219222        } else {
    220223            _log.error("Unknown proxy authorization type configured: " + authRequired);
    221             return true;
     224            return AuthResult.AUTH_BAD_REQ;
    222225        }
    223226    }
     
    251254        }
    252255        // get H(A1) == stored password
    253         String ha1 = getTunnel().getClientOptions().getProperty(PROP_PW_PREFIX + user);
     256        String ha1 = getTunnel().getClientOptions().getProperty(PROP_PROXY_DIGEST_PREFIX + user +
     257                                                                PROP_PROXY_DIGEST_SUFFIX);
    254258        if (ha1 == null) {
    255             if (_log.shouldLog(Log.INFO))
    256                 _log.info("Bad digest auth - no stored pw for user: " + user);
     259            _log.logAlways(Log.WARN, "PROXY AUTH FAILURE: user " + user);
    257260            return AuthResult.AUTH_BAD;
    258261        }
     
    264267        String hkd = PasswordManager.md5Hex(kd);
    265268        if (!response.equals(hkd)) {
     269            _log.logAlways(Log.WARN, "PROXY AUTH FAILURE: user " + user);
    266270            if (_log.shouldLog(Log.INFO))
    267                 _log.info("Bad digest auth - user: " + user);
     271                _log.info("Bad digest auth: " + DataHelper.toString(args));
    268272            return AuthResult.AUTH_BAD;
    269273        }
     
    288292        return Base64.encode(n);
    289293    }
    290 
    291     protected enum AuthResult {AUTH_BAD_REQ, AUTH_BAD, AUTH_STALE, AUTH_GOOD}
    292294
    293295    /**
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java

    r613dd77 ra9e1862  
    222222    /** all proxy auth @since 0.8.2 */
    223223    public boolean getProxyAuth(int tunnel) {
    224         return getBooleanProperty(tunnel, I2PTunnelHTTPClientBase.PROP_AUTH) &&
    225                getProxyUsername(tunnel).length() > 0 &&
    226                getProxyPassword(tunnel).length() > 0;
    227     }
    228    
    229     public String getProxyUsername(int tunnel) {
    230         return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_USER, "");
    231     }
    232    
    233     public String getProxyPassword(int tunnel) {
    234         if (getProxyUsername(tunnel).length() <= 0)
    235             return "";
    236         return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_PW, "");
     224        return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_AUTH, "false") != "false";
    237225    }
    238226   
     
    355343            StringBuilder buf = new StringBuilder(64);
    356344            int i = 0;
     345            boolean isMD5Proxy = "httpclient".equals(tun.getType()) ||
     346                                 "connectclient".equals(tun.getType());
    357347            for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
    358348                String key = (String)iter.next();
    359349                if (_noShowSet.contains(key))
     350                    continue;
     351                // leave in for HTTP and Connect so it can get migrated to MD5
     352                // hide for SOCKS until migrated to MD5
     353                if ((!isMD5Proxy) &&
     354                    _nonProxyNoShowSet.contains(key))
    360355                    continue;
    361356                String val = opts.getProperty(key);
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java

    r613dd77 ra9e1862  
    2828import net.i2p.data.PrivateKeyFile;
    2929import net.i2p.data.SessionKey;
     30import net.i2p.i2ptunnel.I2PTunnelConnectClient;
    3031import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
    3132import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
     
    3738import net.i2p.util.FileUtil;
    3839import net.i2p.util.Log;
     40import net.i2p.util.PasswordManager;
    3941
    4042/**
     
    8486    private int _certType;
    8587    private String _certSigner;
     88    private String _newProxyUser;
     89    private String _newProxyPW;
    8690   
    8791    public static final int RUNNING = 1;
     
    225229        if (_tunnel < 0) return "Invalid tunnel";
    226230       
    227         List controllers = _group.getControllers();
     231        List<TunnelController> controllers = _group.getControllers();
    228232        if (_tunnel >= controllers.size()) return "Invalid tunnel";
    229         TunnelController controller = (TunnelController)controllers.get(_tunnel);
     233        TunnelController controller = controllers.get(_tunnel);
    230234        controller.startTunnelBackground();
    231235        // give the messages a chance to make it to the window
     
    238242        if (_tunnel < 0) return "Invalid tunnel";
    239243       
    240         List controllers = _group.getControllers();
     244        List<TunnelController> controllers = _group.getControllers();
    241245        if (_tunnel >= controllers.size()) return "Invalid tunnel";
    242         TunnelController controller = (TunnelController)controllers.get(_tunnel);
     246        TunnelController controller = controllers.get(_tunnel);
    243247        controller.stopTunnel();
    244248        // give the messages a chance to make it to the window
     
    269273        if (Boolean.parseBoolean(cur.getSharedClient()) && isClient(cur.getType())) {
    270274            // all clients use the same I2CP session, and as such, use the same I2CP options
    271             List controllers = _group.getControllers();
     275            List<TunnelController> controllers = _group.getControllers();
    272276
    273277            for (int i = 0; i < controllers.size(); i++) {
    274                 TunnelController c = (TunnelController)controllers.get(i);
     278                TunnelController c = controllers.get(i);
    275279
    276280                // Current tunnel modified by user, skip
     
    805809    /** all proxy auth @since 0.8.2 */
    806810    public void setProxyAuth(String s) {
    807         _booleanOptions.add(I2PTunnelHTTPClientBase.PROP_AUTH);
     811        if (s != null)
     812            _otherOptions.put(I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
    808813    }
    809814   
    810815    public void setProxyUsername(String s) {
    811816        if (s != null)
    812             _otherOptions.put(I2PTunnelHTTPClientBase.PROP_USER, s.trim());
     817            _newProxyUser = s.trim();
    813818    }
    814819   
    815820    public void setProxyPassword(String s) {
    816821        if (s != null)
    817             _otherOptions.put(I2PTunnelHTTPClientBase.PROP_PW, s.trim());
     822            _newProxyPW = s.trim();
    818823    }
    819824   
    820825    public void setOutproxyAuth(String s) {
    821         _booleanOptions.add(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH);
     826        _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
    822827    }
    823828   
     
    10411046        }
    10421047
     1048        // Proxy auth including migration to MD5
     1049        if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
     1050            // Migrate even if auth is disabled
     1051            // go get the old from custom options that updateConfigGeneric() put in there
     1052            String puser = "option." + I2PTunnelHTTPClientBase.PROP_USER;
     1053            String user = config.getProperty(puser);
     1054            String ppw = "option." + I2PTunnelHTTPClientBase.PROP_PW;
     1055            String pw = config.getProperty(ppw);
     1056            if (user != null && pw != null && user.length() > 0 && pw.length() > 0) {
     1057                String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
     1058                              user + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
     1059                if (config.getProperty(pmd5) == null) {
     1060                    // not in there, migrate
     1061                    String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
     1062                                                              : I2PTunnelConnectClient.AUTH_REALM;
     1063                    String hex = PasswordManager.md5Hex(realm, user, pw);
     1064                    if (hex != null) {
     1065                        config.setProperty(pmd5, hex);
     1066                        config.remove(puser);
     1067                        config.remove(ppw);
     1068                    }
     1069                }
     1070            }
     1071            // New user/password
     1072            String auth = _otherOptions.get(I2PTunnelHTTPClientBase.PROP_AUTH);
     1073            if (auth != null && !auth.equals("false")) {
     1074                if (_newProxyUser != null && _newProxyPW != null &&
     1075                    _newProxyUser.length() > 0 && _newProxyPW.length() > 0) {
     1076                    String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
     1077                                  _newProxyUser + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
     1078                    String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
     1079                                                              : I2PTunnelConnectClient.AUTH_REALM;
     1080                    String hex = PasswordManager.md5Hex(realm, _newProxyUser, _newProxyPW);
     1081                    if (hex != null)
     1082                        config.setProperty(pmd5, hex);
     1083                }
     1084            }
     1085        }
     1086
    10431087        if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
    10441088            if (_targetDestination != null)
     
    10851129        };
    10861130    private static final String _booleanProxyOpts[] = {
    1087         I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
     1131        I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
    10881132        };
    10891133    private static final String _booleanServerOpts[] = {
     
    10921136    private static final String _otherClientOpts[] = {
    10931137        "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
    1094         "proxyUsername", "proxyPassword", "outproxyUsername", "outproxyPassword",
    1095         I2PTunnelHTTPClient.PROP_JUMP_SERVERS
     1138        "outproxyUsername", "outproxyPassword",
     1139        I2PTunnelHTTPClient.PROP_JUMP_SERVERS,
     1140        I2PTunnelHTTPClientBase.PROP_AUTH
    10961141        };
    10971142    private static final String _otherServerOpts[] = {
     
    11021147        };
    11031148
     1149    /**
     1150     *  do NOT add these to noShoOpts, we must leave them in for HTTPClient and ConnectCLient
     1151     *  so they will get migrated to MD5
     1152     *  TODO migrate socks to MD5
     1153     */
     1154    private static final String _otherProxyOpts[] = {
     1155        "proxyUsername", "proxyPassword"
     1156        };
     1157
    11041158    protected static final Set _noShowSet = new HashSet(64);
     1159    protected static final Set _nonProxyNoShowSet = new HashSet(4);
    11051160    static {
    11061161        _noShowSet.addAll(Arrays.asList(_noShowOpts));
     
    11101165        _noShowSet.addAll(Arrays.asList(_otherClientOpts));
    11111166        _noShowSet.addAll(Arrays.asList(_otherServerOpts));
     1167        _nonProxyNoShowSet.addAll(Arrays.asList(_otherProxyOpts));
    11121168    }
    11131169
     
    11401196                if (_noShowSet.contains(key))
    11411197                    continue;
     1198                // leave in for HTTP and Connect so it can get migrated to MD5
     1199                // hide for SOCKS until migrated to MD5
     1200                if ((!"httpclient".equals(_type)) &&
     1201                    (! "connectclient".equals(_type)) &&
     1202                    _nonProxyNoShowSet.contains(key))
     1203                    continue;
    11421204                String val = pair.substring(eq+1);
    11431205                config.setProperty("option." + key, val);
     
    11911253        if (tunnel < 0) return null;
    11921254        if (_group == null) return null;
    1193         List controllers = _group.getControllers();
     1255        List<TunnelController> controllers = _group.getControllers();
    11941256        if (controllers.size() > tunnel)
    1195             return (TunnelController)controllers.get(tunnel);
     1257            return controllers.get(tunnel);
    11961258        else
    11971259            return null;
  • apps/i2ptunnel/jsp/editClient.jsp

    r613dd77 ra9e1862  
    436436                    <%=intl._("Username")%>:
    437437                </label>
    438                 <input type="text" id="clientPort" name="proxyUsername" title="Set username for this service" value="<%=editBean.getProxyUsername(curTunnel)%>" class="freetext" />               
     438                <input type="text" id="clientPort" name="proxyUsername" title="Set username for this service" value="" class="freetext" />               
    439439            </div>
    440440            <div id="portField" class="rowItem">
     
    442442                    <%=intl._("Password")%>:
    443443                </label>
    444                 <input type="password" id="clientPort" name="proxyPassword" title="Set password for this service" value="<%=editBean.getProxyPassword(curTunnel)%>" class="freetext" />               
     444                <input type="password" id="clientPort" name="proxyPassword" title="Set password for this service" value="" class="freetext" />               
    445445            </div>
    446446            <div class="subdivider">
Note: See TracChangeset for help on using the changeset viewer.