001: /*
002: * Copyright (c) 2001 Silvere Martin-Michiellot All Rights Reserved.
003: *
004: * Silvere Martin-Michiellot grants you ("Licensee") a non-exclusive,
005: * royalty free, license to use, modify and redistribute this
006: * software in source and binary code form,
007: * provided that i) this copyright notice and license appear on all copies of
008: * the software; and ii) Licensee does not utilize the software in a manner
009: * which is disparaging to Silvere Martin-Michiellot.
010: *
011: * This software is provided "AS IS," without a warranty of any kind. ALL
012: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
013: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
014: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. Silvere Martin-Michiellot
015: * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
016: * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
017: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
018: * Silvere Martin-Michiellot OR ITS LICENSORS BE LIABLE
019: * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
020: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
021: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
022: * OR INABILITY TO USE SOFTWARE, EVEN IF Silvere Martin-Michiellot HAS BEEN
023: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
024: *
025: * This software is not designed or intended for use in on-line control of
026: * aircraft, air traffic, aircraft navigation or aircraft communications; or in
027: * the design, construction, operation or maintenance of any nuclear
028: * facility. Licensee represents and warrants that it will not use or
029: * redistribute the Software for such purposes.
030: *
031: * @Author: Silvere Martin-Michiellot
032: *
033: */
034:
035: package com.db.net;
036:
037: import java.net.*;
038: import java.io.*;
039: import javax.net.ssl.*;
040: import javax.security.cert.X509Certificate;
041: import com.sun.net.ssl.*;
042: import java.security.KeyStore;
043:
044: /*
045: * This example illustrates how to do proxy Tunneling to access a
046: * secure web server from behind a firewall.
047: *
048: * Please set the following Java system properties
049: * to the appropriate values:
050: *
051: * https.proxyHost = <secure proxy server hostname>
052: * https.proxyPort = <secure proxy server port>
053: *
054: * usage example:
055:
056: * SSLSocketClientTunnelAuth sSLSocketClientTunnelAuth;
057: * sSLSocketClientTunnelAuth = new SSLSocketClientTunnelAuth();
058: * sSLSocketClientTunnelAuth.connect("www.verisign.com", 443);
059:
060: * PrintWriter out = new PrintWriter( new BufferedWriter(new OutputStreamWriter(
061: * sSLSocketClientTunnelAuth.getOutputStream())));
062: * out.println("GET http://www.verisign.com/index.html HTTP/1.1");
063: * out.println();
064: * out.flush();
065: * if (out.checkError())
066: * System.out.println(
067: * "SSLSocketClient: java.io.PrintWriter error");
068: * BufferedReader in = new BufferedReader(
069: * new InputStreamReader(
070: * this.getInputStream()));
071: * String inputLine;
072: * while ((inputLine = in.readLine()) != null)
073: * System.out.println(inputLine);
074: */
075:
076: public class SSLSocketClientTunnelAuth {
077:
078: private String tunnelHost;
079: private int tunnelPort;
080: private boolean connected;
081: SSLSocket socket;
082: Socket tunnel;
083: SSLSocketFactory factory;
084:
085: public SSLSocketClientTunnelAuth() {
086:
087: connected = false;
088:
089: }
090:
091: //connects securely only if not already connected
092: public void connect(String host, int port,
093: boolean clientAuthentification) {
094:
095: if (!connected) {
096: try {
097:
098: /*
099: * Let's setup the SSLContext first, as there's a lot of
100: * computations to be done. If the socket were created
101: * before the SSLContext, the server/proxy might timeout
102: * waiting for the client to actually send something.
103: */
104:
105: if (clientAuthentification) {
106: factory = null;
107: try {
108: SSLContext ctx;
109: KeyManagerFactory kmf;
110: KeyStore ks;
111: char[] passphrase = "passphrase".toCharArray();
112:
113: ctx = SSLContext.getInstance("TLS");
114: kmf = KeyManagerFactory.getInstance("SunX509");
115: ks = KeyStore.getInstance("JKS");
116:
117: ks.load(new FileInputStream("testkeys"),
118: passphrase);
119:
120: kmf.init(ks, passphrase);
121: ctx.init(kmf.getKeyManagers(), null, null);
122:
123: factory = ctx.getSocketFactory();
124: } catch (Exception e) {
125: throw new IOException(e.getMessage());
126: }
127: } else {
128: factory = (SSLSocketFactory) SSLSocketFactory
129: .getDefault();
130: }
131:
132: /*
133: * Set up a socket to do tunneling through the proxy.
134: * Start it off as a regular socket, then layer SSL
135: * over the top of it.
136: */
137: tunnelHost = System.getProperty("https.proxyHost");
138: tunnelPort = Integer.getInteger("https.proxyPort")
139: .intValue();
140:
141: tunnel = new Socket(tunnelHost, tunnelPort);
142: doTunnelHandshake(tunnel, host, port);
143:
144: /*
145: * Ok, let's overlay the tunnel socket with SSL.
146: */
147: socket = (SSLSocket) factory.createSocket(tunnel, host,
148: port, true);
149:
150: // would be without tunnel
151: // SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
152:
153: /*
154: * register a callback for handshaking completion event
155: */
156: socket
157: .addHandshakeCompletedListener(new HandshakeCompletedListener() {
158: public void handshakeCompleted(
159: HandshakeCompletedEvent event) {
160: System.out
161: .println("Handshake finished!");
162: System.out.println("\t CipherSuite:"
163: + event.getCipherSuite());
164: System.out.println("\t SessionId "
165: + event.getSession());
166: System.out.println("\t PeerHost "
167: + event.getSession()
168: .getPeerHost());
169: }
170: });
171:
172: /*
173: * send http request
174: *
175: * See SSLSocketClient.java for more information about why
176: * there is a forced handshake here when using PrintWriters.
177: */
178: socket.startHandshake();
179:
180: connected = true;
181:
182: } catch (Exception e) {
183: e.printStackTrace();
184: }
185: }
186: }
187:
188: public InputStream getInputStream() {
189:
190: if (connected) {
191: try {
192: return socket.getInputStream();
193: } catch (Exception IOException) {
194: return null;
195: }
196: } else {
197: return null;
198: }
199:
200: }
201:
202: public OutputStream getOutputStream() {
203:
204: if (connected) {
205: try {
206: return socket.getOutputStream();
207: } catch (Exception IOException) {
208: return null;
209: }
210: } else {
211: return null;
212: }
213:
214: }
215:
216: public boolean isConnected() {
217:
218: return connected;
219:
220: }
221:
222: public void disconnect() {
223:
224: if (isConnected()) {
225: try {
226: this .getOutputStream().close();
227: this .getInputStream().close();
228: socket.close();
229: tunnel.close();
230: connected = false;
231: } catch (Exception IOException) {
232: }
233:
234: }
235: }
236:
237: /*
238: * Tell our tunnel where we want to CONNECT, and look for the
239: * right reply. Throw IOException if anything goes wrong.
240: */
241: private void doTunnelHandshake(Socket tunnel, String host, int port)
242: throws IOException {
243: OutputStream out = tunnel.getOutputStream();
244: String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n"
245: + "User-Agent: "
246: + sun.net.www.protocol.http.HttpURLConnection.userAgent
247: + "\r\n\r\n";
248: byte b[];
249: try {
250: /*
251: * We really do want ASCII7 -- the http protocol doesn't change
252: * with locale.
253: */
254: b = msg.getBytes("ASCII7");
255: } catch (UnsupportedEncodingException ignored) {
256: /*
257: * If ASCII7 isn't there, something serious is wrong, but
258: * Paranoia Is Good (tm)
259: */
260: b = msg.getBytes();
261: }
262: out.write(b);
263: out.flush();
264:
265: /*
266: * We need to store the reply so we can create a detailed
267: * error message to the user.
268: */
269: byte reply[] = new byte[200];
270: int replyLen = 0;
271: int newlinesSeen = 0;
272: boolean headerDone = false; /* Done on first newline */
273:
274: InputStream in = tunnel.getInputStream();
275: boolean error = false;
276:
277: while (newlinesSeen < 2) {
278: int i = in.read();
279: if (i < 0) {
280: throw new IOException("Unexpected EOF from proxy");
281: }
282: if (i == '\n') {
283: headerDone = true;
284: ++newlinesSeen;
285: } else if (i != '\r') {
286: newlinesSeen = 0;
287: if (!headerDone && replyLen < reply.length) {
288: reply[replyLen++] = (byte) i;
289: }
290: }
291: }
292:
293: /*
294: * Converting the byte array to a string is slightly wasteful
295: * in the case where the connection was successful, but it's
296: * insignificant compared to the network overhead.
297: */
298: String replyStr;
299: try {
300: replyStr = new String(reply, 0, replyLen, "ASCII7");
301: } catch (UnsupportedEncodingException ignored) {
302: replyStr = new String(reply, 0, replyLen);
303: }
304:
305: /* We asked for HTTP/1.0, so we should get that back */
306: if (!replyStr.startsWith("HTTP/1.0 200")) {
307: throw new IOException("Unable to tunnel through "
308: + tunnelHost + ":" + tunnelPort
309: + ". Proxy returns \"" + replyStr + "\"");
310: }
311:
312: /* tunneling Handshake was successful! */
313: }
314: }
|