Java tutorial
/* * This file is part of the OWASP Proxy, a free intercepting proxy library. * Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to: * The Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package org.owasp.proxy; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.Authenticator; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.sql.SQLException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.Logger; import javax.net.ssl.X509KeyManager; import javax.security.auth.x500.X500Principal; import javax.sql.DataSource; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.owasp.proxy.ajp.AJPProperties; import org.owasp.proxy.ajp.DefaultAJPRequestHandler; import org.owasp.proxy.daemon.LoopAvoidingTargetedConnectionHandler; import org.owasp.proxy.daemon.Proxy; import org.owasp.proxy.daemon.ServerGroup; import org.owasp.proxy.daemon.TargetedConnectionHandler; import org.owasp.proxy.http.MessageFormatException; import org.owasp.proxy.http.MutableRequestHeader; import org.owasp.proxy.http.MutableResponseHeader; import org.owasp.proxy.http.RequestHeader; import org.owasp.proxy.http.StreamingRequest; import org.owasp.proxy.http.StreamingResponse; import org.owasp.proxy.http.client.HttpClient; import org.owasp.proxy.http.dao.JdbcMessageDAO; import org.owasp.proxy.http.server.AuthenticatingHttpRequestHandler; import org.owasp.proxy.http.server.BufferedMessageInterceptor; import org.owasp.proxy.http.server.BufferingHttpRequestHandler; import org.owasp.proxy.http.server.ConversationServiceHttpRequestHandler; import org.owasp.proxy.http.server.DefaultHttpRequestHandler; import org.owasp.proxy.http.server.HttpProxyConnectionHandler; import org.owasp.proxy.http.server.HttpRequestHandler; import org.owasp.proxy.http.server.LoggingHttpRequestHandler; import org.owasp.proxy.http.server.RecordingHttpRequestHandler; import org.owasp.proxy.socks.SocksConnectionHandler; import org.owasp.proxy.ssl.AutoGeneratingContextSelector; import org.owasp.proxy.ssl.DefaultClientContextSelector; import org.owasp.proxy.ssl.KeystoreUtils; import org.owasp.proxy.ssl.SSLConnectionHandler; import org.owasp.proxy.ssl.SSLContextSelector; import org.owasp.proxy.tcp.ConnectConnectionHandler; import org.owasp.proxy.util.TextFormatter; import org.springframework.jdbc.datasource.DriverManagerDataSource; public class Main { private static Logger logger = Logger.getLogger("org.owasp.proxy"); private static class Configuration { private static final String OPT_AUTHUSER = "authUser"; private static final String OPT_AUTHPASSWORD = "authPassword"; private static final String OPT_AJPSERVER = "ajpServer"; private static final String OPT_AJPHOST = "ajpHost"; private static final String OPT_AJPUSER = "ajpUser"; private static final String OPT_AJPCLIENTADDRESS = "ajpClientAddress"; private static final String OPT_PKCS11SLOTLOCATION = "pkcs11SlotLocation"; private static final String OPT_KEYSTOREPASSWORD = "keyStorePassword"; private static final String OPT_KEYSTOREALIAS = "keyStoreAlias"; private static final String OPT_KEYSTORELOCATION = "keyStoreLocation"; private static final String OPT_KEYSTORETYPE = "keyStoreType"; private static final String OPT_JDBCPASSWORD = "jdbcPassword"; private static final String OPT_JDBCUSER = "jdbcUser"; private static final String OPT_JDBCURL = "jdbcUrl"; private static final String OPT_JDBCDRIVER = "jdbcDriver"; private static final String OPT_PROXY = "proxy"; private static final String OPT_INTERFACE = "interface"; private static final String OPT_PORT = "port"; private static final String OPT_CONNECT = "httpConnect"; private int port = 1080; private String iface = "localhost"; private String proxy = "DIRECT"; private String jdbcDriver, jdbcUrl, jdbcUser, jdbcPassword; private String keystoreType, keyStoreLocation, keyStoreAlias, keyStorePassword; private int pkcs11SlotLocation = 0; private String[] ajpHosts = null; private InetSocketAddress ajpServer = null; private String ajpUser = null; private String ajpClientAddress = "127.0.0.1"; private String ajpClientCert = null; private String authUser, authPassword; private InetSocketAddress httpConnect = null; @SuppressWarnings("static-access") private static Configuration init(String[] args) { Options options = new Options(); options.addOption(OptionBuilder.withLongOpt(OPT_PORT).hasArg().isRequired() .withDescription("the port to accept connections on").create()); options.addOption(OptionBuilder.withLongOpt(OPT_INTERFACE).hasArg() .withDescription("the network interface to listen on [default localhost]").create()); options.addOption(OptionBuilder.withLongOpt(OPT_PROXY).hasArg() .withDescription("proxy instruction for upstream proxy access").create()); options.addOption(OptionBuilder.withLongOpt(OPT_JDBCDRIVER).hasArg() .withDescription("the JDBC driver to use").create()); options.addOption( OptionBuilder.withLongOpt(OPT_JDBCURL).hasArg().withDescription("the JDBC URL").create()); options.addOption( OptionBuilder.withLongOpt(OPT_JDBCUSER).hasArg().withDescription("the JDBC username").create()); options.addOption(OptionBuilder.withLongOpt(OPT_JDBCPASSWORD).hasArg() .withDescription("the JDBC password").create()); options.addOption(OptionBuilder.withLongOpt(OPT_KEYSTORETYPE).hasArg() .withDescription("the KeyStore type for client keys").create()); options.addOption(OptionBuilder.withLongOpt(OPT_KEYSTORELOCATION).hasArg() .withDescription("the KeyStore location").create()); options.addOption(OptionBuilder.withLongOpt(OPT_KEYSTOREALIAS).hasArg() .withDescription("the alias for the desired key [defaults to the first key]").create()); options.addOption(OptionBuilder.withLongOpt(OPT_KEYSTOREPASSWORD).hasArg() .withDescription("the password for the KeyStore").create()); options.addOption(OptionBuilder.withLongOpt(OPT_PKCS11SLOTLOCATION).hasArg() .withDescription("the index of the hardware token to use").create()); options.addOption(OptionBuilder.withLongOpt(OPT_AJPUSER).hasArg() .withDescription("the username to forward to the AJP server").create()); options.addOption(OptionBuilder.withLongOpt(OPT_AJPCLIENTADDRESS).hasArg() .withDescription("the client address to forward to the AJP server [default 127.0.0.1]") .create()); options.addOption(OptionBuilder.withLongOpt(OPT_AJPSERVER).hasArg() .withDescription("the AJP server to connect to [host:port]").create()); options.addOption(OptionBuilder.withLongOpt(OPT_AJPHOST).hasArgs().withDescription( "the target domain reachable via the AJP server [can be repeated, missing implies all hosts]") .create()); options.addOption(OptionBuilder.withLongOpt(OPT_AUTHUSER).hasArg() .withDescription("the user to authenticate as").create()); options.addOption(OptionBuilder.withLongOpt(OPT_AUTHPASSWORD).hasArg() .withDescription("the password to use when authenticating").create()); options.addOption(OptionBuilder.withLongOpt(OPT_CONNECT).hasArg().withDescription( "Uses the specified upstream HTTP proxy to CONNECT to the desired end point [host:port]. Disables all other proxy functionality.") .create()); try { CommandLine cmd = new GnuParser().parse(options, args); Configuration config = new Configuration(); if (cmd.hasOption(OPT_INTERFACE)) config.iface = cmd.getOptionValue(OPT_INTERFACE); if (cmd.hasOption(OPT_PORT)) config.port = Integer.parseInt(cmd.getOptionValue(OPT_PORT)); if (cmd.hasOption(OPT_PROXY)) config.proxy = cmd.getOptionValue(OPT_PROXY); if (cmd.hasOption(OPT_JDBCDRIVER)) config.jdbcDriver = cmd.getOptionValue(OPT_JDBCDRIVER); if (cmd.hasOption(OPT_JDBCURL)) config.jdbcUrl = cmd.getOptionValue(OPT_JDBCURL); if (cmd.hasOption(OPT_JDBCUSER)) config.jdbcUser = cmd.getOptionValue(OPT_JDBCUSER); if (cmd.hasOption(OPT_JDBCPASSWORD)) config.jdbcPassword = cmd.getOptionValue(OPT_JDBCPASSWORD); if (cmd.hasOption(OPT_KEYSTORETYPE)) config.keystoreType = cmd.getOptionValue(OPT_KEYSTORETYPE); if (cmd.hasOption(OPT_KEYSTORELOCATION)) config.keyStoreLocation = cmd.getOptionValue(OPT_KEYSTORELOCATION); if (cmd.hasOption(OPT_KEYSTOREPASSWORD)) config.keyStorePassword = cmd.getOptionValue(OPT_KEYSTOREPASSWORD); if (cmd.hasOption(OPT_KEYSTOREALIAS)) config.keyStoreAlias = cmd.getOptionValue(OPT_KEYSTOREALIAS); if (cmd.hasOption(OPT_PKCS11SLOTLOCATION)) config.pkcs11SlotLocation = Integer.parseInt(cmd.getOptionValue(OPT_PKCS11SLOTLOCATION)); if (cmd.hasOption(OPT_AJPUSER)) config.ajpUser = cmd.getOptionValue(OPT_AJPUSER); if (cmd.hasOption(OPT_AJPCLIENTADDRESS)) config.ajpClientAddress = cmd.getOptionValue(OPT_AJPCLIENTADDRESS); if (cmd.hasOption(OPT_AJPSERVER)) { String server = cmd.getOptionValue(OPT_AJPSERVER); int colon = server.indexOf(':'); int port = 8009; if (colon > -1) { port = Integer.parseInt(server.substring(colon + 1)); server = server.substring(0, colon - 1); } config.ajpServer = new InetSocketAddress(server, port); } if (cmd.hasOption(OPT_AJPHOST)) config.ajpHosts = cmd.getOptionValues(OPT_AJPHOST); if (cmd.hasOption(OPT_AUTHUSER)) config.authUser = cmd.getOptionValue(OPT_AUTHUSER); if (cmd.hasOption(OPT_AUTHPASSWORD)) config.authPassword = cmd.getOptionValue(OPT_AUTHPASSWORD); if (cmd.hasOption(OPT_CONNECT)) { String proxy = cmd.getOptionValue(OPT_CONNECT); int colon = proxy.indexOf(':'); int port = 3128; if (colon > -1) { port = Integer.parseInt(proxy.substring(colon + 1)); proxy = proxy.substring(0, colon); System.out.println("Proxy is " + proxy + ":" + port); } config.httpConnect = new InetSocketAddress(proxy, port); } return config; } catch (ParseException e) { System.out.println(e.getLocalizedMessage()); // automatically generate the help statement HelpFormatter formatter = new HelpFormatter(); formatter.printHelp(Main.class.getCanonicalName(), options); System.exit(2); return null; } } } private static ProxySelector getProxySelector(Configuration config) { String proxy = config.proxy; final java.net.Proxy upstream; if ("DIRECT".equals(proxy)) { upstream = java.net.Proxy.NO_PROXY; } else { java.net.Proxy.Type type = null; if (proxy.startsWith("PROXY ")) { type = java.net.Proxy.Type.HTTP; } else if (proxy.startsWith("SOCKS ")) { type = java.net.Proxy.Type.SOCKS; } else throw new IllegalArgumentException("Unknown Proxy type: " + proxy); proxy = proxy.substring(6); // "SOCKS " or "PROXY " int c = proxy.indexOf(':'); if (c == -1) throw new IllegalArgumentException("Illegal proxy address: " + proxy); InetSocketAddress addr = new InetSocketAddress(proxy.substring(0, c), Integer.parseInt(proxy.substring(c + 1))); upstream = new java.net.Proxy(type, addr); } ProxySelector ps = new ProxySelector() { @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { logger.info("Proxy connection to " + uri + " via " + sa + " failed! " + ioe.getLocalizedMessage()); } @Override public List<java.net.Proxy> select(URI uri) { return Arrays.asList(upstream); } }; return ps; } private static DataSource createDataSource(Configuration config) throws SQLException { if (config.jdbcDriver == null) return null; DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(config.jdbcDriver); dataSource.setUrl(config.jdbcUrl); dataSource.setUsername(config.jdbcUser); dataSource.setPassword(config.jdbcPassword); return dataSource; } private static SSLContextSelector getServerSSLContextSelector() throws GeneralSecurityException, IOException { File ks = new File("ca.p12"); String type = "PKCS12"; char[] password = "password".toCharArray(); String alias = "CA"; if (ks.exists()) { try { return new AutoGeneratingContextSelector(ks, type, password, password, alias); } catch (GeneralSecurityException e) { System.err.println("Error loading CA keys from keystore: " + e.getLocalizedMessage()); } catch (IOException e) { System.err.println("Error loading CA keys from keystore: " + e.getLocalizedMessage()); } } System.err.println("Generating a new CA"); X500Principal ca = new X500Principal( "cn=OWASP Custom CA for " + java.net.InetAddress.getLocalHost().getHostName() + ",ou=OWASP Custom CA,o=OWASP,l=OWASP,st=OWASP,c=OWASP"); AutoGeneratingContextSelector ssl = new AutoGeneratingContextSelector(ca); try { ssl.save(ks, type, password, password, alias); } catch (GeneralSecurityException e) { System.err.println("Error saving CA keys to keystore: " + e.getLocalizedMessage()); } catch (IOException e) { System.err.println("Error saving CA keys to keystore: " + e.getLocalizedMessage()); } FileWriter pem = null; try { pem = new FileWriter("ca.pem"); pem.write(ssl.getCACert()); } catch (IOException e) { System.err.println("Error writing CA cert : " + e.getLocalizedMessage()); } finally { if (pem != null) pem.close(); } return ssl; } private static SSLContextSelector getClientSSLContextSelector(Configuration config) { String type = config.keystoreType; char[] password = config.keyStorePassword == null ? null : config.keyStorePassword.toCharArray(); File location = config.keyStoreLocation == null ? null : new File(config.keyStoreLocation); if (type != null) { KeyStore ks = null; if (type.equals("PKCS11")) { try { int slot = config.pkcs11SlotLocation; ks = KeystoreUtils.getPKCS11Keystore("PKCS11", location, slot, password); } catch (Exception e) { System.err.println(e.getLocalizedMessage()); System.exit(2); } } else { try { FileInputStream in = new FileInputStream(location); ks = KeyStore.getInstance(type); ks.load(in, password); } catch (Exception e) { System.err.println(e.getLocalizedMessage()); System.exit(2); } } String alias = config.keyStoreAlias; if (alias == null) { try { Map<String, String> aliases = KeystoreUtils.getAliases(ks); if (aliases.size() > 0) { System.err.println("Keystore contains the following aliases: \n"); for (String a : aliases.keySet()) { System.err.println("Alias \"" + a + "\"" + " : " + aliases.get(a)); } alias = aliases.keySet().iterator().next(); System.err.println("Using " + alias + " : " + aliases.get(alias)); } else { System.err.println("Keystore contains no aliases!"); System.exit(3); } } catch (KeyStoreException kse) { System.err.println(kse.getLocalizedMessage()); System.exit(4); } } try { final X509KeyManager km = KeystoreUtils.getKeyManagerForAlias(ks, alias, password); return new DefaultClientContextSelector(km); } catch (GeneralSecurityException gse) { System.err.println(gse.getLocalizedMessage()); System.exit(5); } } return new DefaultClientContextSelector(); } private static DefaultHttpRequestHandler configureRequestHandler(Configuration config) { final ProxySelector ps = getProxySelector(config); final SSLContextSelector sslc = getClientSSLContextSelector(config); return new DefaultHttpRequestHandler() { @Override protected HttpClient createClient() { HttpClient client = super.createClient(); client.setSslContextSelector(sslc); client.setProxySelector(ps); client.setSoTimeout(90000); return client; } }; } private static HttpRequestHandler configureAuthentication(HttpRequestHandler rh, final Configuration config) { if (config.authUser != null) { if (config.authPassword == null) { System.err.println("authPassword must be provided!"); System.exit(1); } Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(config.authUser, config.authPassword.toCharArray()); } }); return new AuthenticatingHttpRequestHandler(rh); } else { return rh; } } private static HttpRequestHandler configureAJP(HttpRequestHandler rh, Configuration config) { if (config.ajpServer != null) { final DefaultAJPRequestHandler arh = new DefaultAJPRequestHandler(); arh.setTarget(config.ajpServer); AJPProperties ajpProperties = new AJPProperties(); ajpProperties.setRemoteAddress(config.ajpClientAddress); if (config.ajpClientCert != null && config.ajpClientCert.endsWith(".pem")) { try { BufferedReader in = new BufferedReader(new FileReader(config.ajpClientCert)); StringBuffer buff = new StringBuffer(); String line; while ((line = in.readLine()) != null) { buff.append(line); } in.close(); ajpProperties.setSslCert(buff.toString()); ajpProperties.setSslCipher("ECDHE-RSA-AES256-SHA"); ajpProperties.setSslSession("RANDOMID"); ajpProperties.setSslKeySize("256"); } catch (IOException ioe) { ioe.printStackTrace(); System.exit(1); } } ajpProperties.setRemoteUser(config.ajpUser); ajpProperties.setAuthType("BASIC"); ajpProperties.setContext("/manager"); arh.setProperties(ajpProperties); final Set<String> ajpHosts = new HashSet<String>(); if (config.ajpHosts != null) ajpHosts.addAll(Arrays.asList(config.ajpHosts)); final HttpRequestHandler hrh = rh; return new HttpRequestHandler() { @Override public StreamingResponse handleRequest(InetAddress source, StreamingRequest request, boolean isContinue) throws IOException, MessageFormatException { InetSocketAddress target = request.getTarget(); if (ajpHosts.isEmpty() || ajpHosts.contains(target.getHostName()) || ajpHosts.contains(target.getAddress().getHostAddress())) { return arh.handleRequest(source, request, isContinue); } else { return hrh.handleRequest(source, request, isContinue); } } @Override public void dispose() throws IOException { arh.dispose(); hrh.dispose(); } }; } else { return rh; } } private static HttpRequestHandler configureJDBCLogging(HttpRequestHandler rh, Configuration config) throws SQLException { final DataSource dataSource = createDataSource(config); if (dataSource != null) { JdbcMessageDAO dao = new JdbcMessageDAO(); dao.setDataSource(dataSource); dao.createTables(); rh = new RecordingHttpRequestHandler(dao, rh, 1024 * 1024); return new ConversationServiceHttpRequestHandler("127.0.0.2", dao, rh); } else { return rh; } } private static HttpRequestHandler configureInterception(HttpRequestHandler rh, Configuration config) { BufferedMessageInterceptor bmi = new BufferedMessageInterceptor() { @Override public Action directResponse(RequestHeader request, MutableResponseHeader response) { // System.err.println(new String(request.getHeader()) // + "+++++++++++\n" + new String(response.getHeader()) // + "==========="); return Action.STREAM; } @Override public Action directRequest(MutableRequestHeader request) { // System.err.println(new String(request.getHeader()) // + "==========="); return Action.STREAM; } }; return new BufferingHttpRequestHandler(rh, bmi, 10240); } public static void main(String[] args) throws Exception { java.lang.System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true"); logger.setUseParentHandlers(false); Handler ch = new ConsoleHandler(); ch.setFormatter(new TextFormatter()); logger.addHandler(ch); final Configuration config = Configuration.init(args); final InetSocketAddress listen = new InetSocketAddress(config.iface, config.port); TargetedConnectionHandler tch; if (config.httpConnect == null) { DefaultHttpRequestHandler drh = configureRequestHandler(config); ServerGroup sg = new ServerGroup(); sg.addServer(listen); drh.setServerGroup(sg); HttpRequestHandler rh = drh; rh = configureAuthentication(rh, config); rh = configureAJP(rh, config); rh = new LoggingHttpRequestHandler(rh); rh = configureJDBCLogging(rh, config); rh = configureInterception(rh, config); HttpProxyConnectionHandler hpch = new HttpProxyConnectionHandler(rh); SSLContextSelector cp = getServerSSLContextSelector(); tch = new SSLConnectionHandler(cp, true, hpch); tch = new LoopAvoidingTargetedConnectionHandler(sg, tch); hpch.setConnectHandler(tch); } else { tch = new ConnectConnectionHandler(config.httpConnect); } TargetedConnectionHandler socks = new SocksConnectionHandler(tch, true); Proxy p = new Proxy(listen, socks, null); p.setSocketTimeout(90000); p.start(); System.out.println("Listener started on " + listen); System.out.println("Press Enter to terminate"); new BufferedReader(new InputStreamReader(System.in)).readLine(); p.stop(); System.out.println("Terminated"); System.exit(0); } }