001: /*
002: * @(#)SSLStreamConnection.java 1.29 02/07/30 @(#)
003: *
004: * Copyright (c) 2000-2002 Sun Microsystems, Inc. All rights reserved.
005: * PROPRIETARY/CONFIDENTIAL
006: * Use is subject to license terms.
007: */
008:
009: package com.sun.portal.kssl;
010:
011: import java.io.IOException;
012: import java.io.InputStream;
013: import java.io.OutputStream;
014: import java.io.DataInputStream;
015: import java.io.DataOutputStream;
016: import java.util.Vector;
017: import java.util.Hashtable;
018:
019: import com.sun.portal.microedition.io.Connector;
020: import com.sun.portal.microedition.io.SecurityInfo;
021: import com.sun.portal.microedition.io.StreamConnection;
022:
023: import com.sun.portal.microedition.pki.Certificate;
024:
025: import com.sun.portal.ksecurity.CertStore;
026:
027: /**
028: * The SSLStreamConnection class implements the StreamConnection
029: * interface. Data exchanged through a SSLStreamConnection is
030: * automatically protected by SSL. Currently, only SSL version 3.0
031: * is supported and the list of cipher suites proposed
032: * by the client is hardcoded to {SSL_RSA_WITH_RC4_128_MD5,
033: * SSL_RSA_EXPORT_WITH_RC4_40_MD5}. This version of the implementation
034: * does not support client authentication at the SSL layer -- a feature
035: * that is rarely used.
036: *
037: * Typical usage of this class by an application would be along the
038: * following lines: <BR />
039: *
040: * <PRE>
041: * // create a TCP connection
042: * StreamConnection t = Connector.open("socket://www.server.com:443");
043: *
044: * // Create an SSL connection
045: * SSLStreamConnection s = new SSLStreamConnection("www.server.com", 443,
046: * t.openInputStream(), t.openOutputStream());
047: * t.close();
048: *
049: * // obtain the associated input/output streams
050: * OutputStream sout = s.openOutputStream();
051: * InputStream sin = s.openInputStream();
052: * ...
053: * // send SSL-protected data by writing to sout and
054: * // receive SSL-protected by reading from sin
055: * ...
056: * sin.close();
057: * sout.close();
058: * s.close(); // close the SSL connection when done
059: *
060: * </PRE>
061: *
062: * @author Vipul Gupta <vipul.gupta@sun.com>
063: */
064:
065: public class SSLStreamConnection implements StreamConnection {
066: /** Indicates that a is ready to be opened. */
067: static final int READY = 0;
068: /** Indicates that a stream is opened. */
069: static final int OPEN = 1;
070: /** Indicates that a stream is closed. */
071: static final int CLOSED = 2;
072:
073: // Stores a collection of trusted certificates
074: /** Handle to trusted certificate store. */
075: private static CertStore trustedCertStore = null;
076: /** Flag to indicate the certificate store has been locked. */
077: private static boolean trustedCertStoreLocked = false;
078: /** Flag to indicate that the certificate store has been initialized. */
079: private static boolean trustedCertStoreInitialized = false;
080: /** Current record being processed. */
081: private Record rec = null;
082: /** Input stream for buffered records. */
083: private In uin = null;
084: /** Output stream for buffered records. */
085: private Out uout = null;
086: /** Raw encrypted input stream. */
087: private InputStream sin = null;
088: /** Raw encrypted output stream. */
089: private OutputStream sout = null;
090: /** Current host name. */
091: private String host = null;
092: /** Current port number. */
093: private int port = 0;
094: /** Flag indicating the underlying TCP connection is open. */
095: private boolean copen = false;
096: /** Server certificate from a successful handshake. */
097: private X509Certificate serverCert;
098: /** Cipher suite from a successful handshake. */
099: private String cipherSuite;
100: //Support for cipher selection
101: private static Hashtable SupportedCipherSuites = new Hashtable();
102:
103: /*
104: * The following are visible within the package so In and Out can
105: * manipulate them directly
106: */
107: /** State of the input stream given out by getInputStream. */
108: int inputStreamState;
109: /** State of the output stream given out by getOutputStream. */
110: int outputStreamState;
111:
112: /**
113: * Sets the trusted certificate store which contains root certificates used
114: * to verify certificate chains sent by an SSL server. This method
115: * does nothing if the certificate store is already locked.
116: *
117: * @param cs trusted certificate store to be used
118: * @see #lockTrustedCertStore
119: * @see #getTrustedCertStore
120: */
121: //Support for cipher selection
122: static {
123: SupportedCipherSuites.put(
124: "KSSL_SSL3_RSA_EXPORT_WITH_RC4_40_MD5", new Byte(
125: CipherSuites.ARCFOUR_40_MD5));
126: SupportedCipherSuites.put("KSSL_SSL3_RSA_WITH_RC4_128_SHA",
127: new Byte(CipherSuites.ARCFOUR_128_SHA));
128: SupportedCipherSuites.put("KSSL_SSL3_RSA_WITH_RC4_128_MD5",
129: new Byte(CipherSuites.ARCFOUR_128_MD5));
130: SupportedCipherSuites.put("KSSL_SSL3_RSA_WITH_DES_CBC_SHA",
131: new Byte(CipherSuites.DES_CBC_SHA));
132: SupportedCipherSuites.put(
133: "KSSL_SSL3_RSA_WITH_3DES_EDE_CBC_SHA", new Byte(
134: CipherSuites.TRIPLEDES_CBC_SHA));
135: }
136:
137: synchronized public static void setTrustedCertStore(CertStore cs) {
138: if (trustedCertStoreLocked) {
139: return;
140: }
141:
142: trustedCertStore = cs;
143: trustedCertStoreInitialized = true;
144: }
145:
146: /**
147: * Gets the certificate store containing trusted root certificates used
148: * to verify SSL server certificate chains.
149: *
150: * @return certificate store containing trusted certificates
151: * @see #setTrustedCertStore
152: */
153: synchronized public static CertStore getTrustedCertStore() {
154: // Initialize at the time of first use
155: if (!trustedCertStoreInitialized) {
156: try {
157: /*trustedCertStore = (CertStore)
158: Class.forName("com.sun.portal.kssl.KSImpl").newInstance();*/
159: /*trustedCertStore = (CertStore)
160: Class.forName("com.sun.portal.kssl.TrustStoreImpl").newInstance();*/
161: trustedCertStore = new TrustStoreImpl();
162:
163: } catch (Exception e) {
164: // ingore
165: }
166:
167: trustedCertStoreInitialized = true;
168: }
169:
170: return trustedCertStore;
171: }
172:
173: /**
174: * Locks the current trusted certificate store so it cannot be changed.
175: * This method does nothing if the trusted certificate store is null.
176: */
177: synchronized public static void lockTrustedCertStore() {
178: if (trustedCertStore == null) {
179: return;
180: }
181:
182: trustedCertStoreLocked = true;
183: }
184:
185: /**
186: * Establish and SSL session over a reliable stream.
187: * This connection will forward the input and output stream close methods
188: * to the given connection. If the caller wants to have the given
189: * connection closed with this connection, the caller can close given
190: * connection after constructing this connection, but leaving the closing
191: * of the streams to this connection.
192: *
193: * @param host hostname of the SSL server
194: * @param port port number of the SSL server
195: * @param in InputStream associated with the StreamConnection.
196: * @param out OutputStream associated with the StreamConnection.
197: * @exception IOException if there is a problem initializing the SSL
198: * data structures or the SSL handshake fails
199: */
200: //Support for cipher selection
201: public SSLStreamConnection(String host, int port, InputStream in,
202: OutputStream out) throws IOException {
203: this (host, port, in, out, null);
204: }
205:
206: public SSLStreamConnection(String host, int port, InputStream in,
207: OutputStream out, String[] cipherSuites) throws IOException {
208:
209: if ((in == null) || (out == null)) {
210: throw new IllegalArgumentException(
211: "SSLStreamConnection: stream missing");
212: }
213:
214: this .host = host;
215: this .port = port;
216: this .sin = in;
217: this .sout = out;
218: this .rec = new Record(Record.CLIENT, sin, sout);
219:
220: uin = new In(rec, this );
221: uout = new Out(rec, this );
222:
223: Handshake hndshk = null;
224: try {
225: hndshk = new Handshake(host, port, rec);
226: //Support for cipher selection
227: if (cipherSuites != null)
228: hndshk
229: .setCipherSuites(getSelectedCipherSuites(cipherSuites));
230: hndshk.doHandShake(Record.CLIENT);
231: serverCert = hndshk.sCert;
232: cipherSuite = hndshk.negSuiteName;
233: hndshk.destroy();
234: } catch (IOException e) {
235: if (hndshk != null) {
236: hndshk.destroy();
237: }
238:
239: cleanupIfNeeded();
240: throw e;
241: }
242:
243: copen = true;
244: }
245:
246: /**
247: * Returns the InputStream associated with this SSLStreamConnection.
248: *
249: * @return InputStream object from which SSL protected bytes can
250: * be read
251: * @exception IOException if the connection is not open or the stream was
252: * already open
253: */
254: synchronized public InputStream openInputStream()
255: throws IOException {
256: if (!copen) {
257: throw new IOException("Connection closed");
258: }
259:
260: if (inputStreamState != READY) {
261: throw new IOException("Input stream already opened");
262: }
263:
264: inputStreamState = OPEN;
265: return (uin);
266: }
267:
268: /**
269: * Returns the OutputStream associated with this SSLStreamConnection.
270: *
271: * @return OutputStream object such that bytes written to this stream
272: * are sent over an SSL secured channel
273: * @exception IOException if the connection is not open or the stream was
274: * already open
275: */
276: synchronized public OutputStream openOutputStream()
277: throws IOException {
278: if (!copen) {
279: throw new IOException("Connection closed");
280: }
281:
282: if (outputStreamState != READY) {
283: throw new IOException("Output stream already opened");
284: }
285:
286: outputStreamState = OPEN;
287: return (uout);
288: }
289:
290: /**
291: * Returns the DataInputStream associated with this SSLStreamConnection.
292: * @return a DataInputStream object
293: */
294: public DataInputStream openDataInputStream() throws IOException {
295: return (new DataInputStream(openInputStream()));
296: }
297:
298: /**
299: * Returns the DataOutputStream associated with this SSLStreamConnection.
300: *
301: * @return a DataOutputStream object
302: */
303: public DataOutputStream openDataOutputStream() throws IOException {
304: return (new DataOutputStream(openOutputStream()));
305: }
306:
307: /**
308: * Closes the SSL connection. The underlying TCP socket, over which
309: * SSL is layered, is also closed unless the latter was opened by
310: * an external application and its input/output streams were passed
311: * as argument to the SSLStreamConnection constructor.
312: *
313: * @exception IOException if the SSL connection could not be
314: * terminated cleanly
315: */
316: synchronized public void close() throws IOException {
317: if (copen) {
318: copen = false;
319: cleanupIfNeeded();
320: }
321: }
322:
323: /**
324: * Return the security information associated with this connection.
325: *
326: * @return the security information associated with this open connection.
327: *
328: * @exception IOException if the connection is closed
329: */
330: public SecurityInfo getSecurityInfo() throws IOException {
331: if (!copen) {
332: throw new IOException("Connection closed");
333: }
334:
335: return new SSLSecurityInfo(this );
336: }
337:
338: /**
339: * Return the server certificate associated with this connection.
340: *
341: * @return the server certificate associated with this connection.
342: */
343: public X509Certificate getServerCertificate() {
344: return serverCert;
345: }
346:
347: /**
348: * Returns the cipher suite in use for the connection.
349: * The value returned is one of the CipherSuite definitions
350: * in Appendix C of RFC 2246.
351: * The cipher suite string should be used to represent the
352: * actual parameters used to establish the connection regardless
353: * of whether the secure connection uses SSL V3 or TLS 1.0 or WTLS.
354: *
355: * @return a String containing the cipher suite in use.
356: */
357: public String getCipherSuite() {
358: return cipherSuite;
359: }
360:
361: //Support for cipher selection
362: byte[] getSelectedCipherSuites(String[] cipherSuites) {
363: Vector ciphers = new Vector();
364: String cipher;
365: for (int i = 0; i < cipherSuites.length; i++) {
366: cipher = cipherSuites[i];
367: if (SupportedCipherSuites.containsKey(cipher)) {
368: ciphers.insertElementAt(SupportedCipherSuites
369: .get(cipher), i);
370: }
371: }
372: int size = ciphers.size();
373: byte[] selectedCipherSuites = new byte[2 + size * 2 + 2];
374: selectedCipherSuites[0] = 0x00;
375: selectedCipherSuites[1] = (byte) (size * 2);
376: int index = -1;
377: for (int i = 0; i < size; i++) {
378: index = 2 * i + 2;
379: selectedCipherSuites[index] = 0x00;
380: selectedCipherSuites[index + 1] = ((Byte) ciphers
381: .elementAt(i)).byteValue();
382: }
383: index = selectedCipherSuites.length;
384: selectedCipherSuites[index - 2] = 0x01;
385: selectedCipherSuites[index - 1] = 0x00;
386: return selectedCipherSuites;
387: }
388:
389: /**
390: * Closes the SSL connection. The underlying TCP socket, over which
391: * SSL is layered, is also closed unless the latter was opened by
392: * an external application and its input/output streams were passed
393: * as argument to the SSLStreamConnection constructor.
394: *
395: * @exception IOException if the SSL connection could not be
396: * terminated cleanly
397: */
398: void cleanupIfNeeded() {
399: if (copen || inputStreamState == OPEN
400: || outputStreamState == OPEN || rec == null) {
401: // we do not need to cleanup
402: return;
403: }
404:
405: try {
406: rec.alert(Record.WARNING, Record.CLOSE_NTFY);
407:
408: rec.destroy();
409: rec = null;
410:
411: uin = null;
412: uout = null;
413:
414: copen = false;
415: host = null;
416:
417: try {
418: sin.close();
419: } finally {
420: sin = null;
421: try {
422: sout.close();
423: } finally {
424: sout = null;
425: }
426: }
427: } catch (IOException e) {
428: // ignore
429: }
430: }
431: }
432:
433: /**
434: * This class implements methods
435: * to access information about a SSL secure network connection.
436: */
437: class SSLSecurityInfo implements SecurityInfo {
438:
439: /** Parent connection. */
440: private SSLStreamConnection parent;
441:
442: /**
443: * Create a <code>SecurityInfo</code> object to pass back to
444: * an application.
445: *
446: * @param parentObj parent object
447: */
448: SSLSecurityInfo(SSLStreamConnection parentObj) {
449: parent = parentObj;
450: }
451:
452: /**
453: * Get the <CODE>Certificate</CODE> used to establish the
454: * secure connection with the server.
455: *
456: * @return the <CODE>Certificate</CODE> used to establish the
457: * secure connection with the server.
458: */
459: public Certificate getServerCertificate() {
460: return parent.getServerCertificate();
461: }
462:
463: /**
464: * Return the security status of the connection.
465: *
466: * @return <CODE>true</CODE> if the connection has been made directly to
467: * the server specified in <code>Connector.open</code> and a handshake
468: * with that server has established a secure connection.
469: * <CODE>false</CODE> is returned otherwise.
470: */
471: public boolean isSecure() {
472: return true;
473: }
474:
475: /**
476: * Return the protocol version.
477: * If appropriate, it should contain the major and minor versions
478: * for the protocol separated with a "." (Unicode x2E).
479: * For example,
480: * for SSL V3 it MUST return "3.0";
481: * for TLS 1.0 it MUST return "3.1".
482: *
483: * @return a String containing the version of the protocol.
484: */
485: public String getProtocolVersion() {
486: return "3.0";
487: }
488:
489: /**
490: * Return the secure protocol name.
491: *
492: * @return a <code>String</code> containing the secure protocol identifier;
493: * if TLS (RFC 2246) is used for the connection the return value is "TLS".
494: * if SSL V3 (The SSL Protocol Version 3.0) is used for the connection
495: * the return value is "SSL").
496: * if WTLS (WAP 199) is used for the connection the return value is "WTLS".
497: */
498: public String getProtocolName() {
499: return "SSL";
500: }
501:
502: /**
503: * Returns the cipher suite in use for the connection.
504: * The value returned is one of the CipherSuite definitions
505: * in Appendix C of RFC 2246.
506: * The cipher suite string should be used to represent the
507: * actual parameters used to establish the connection regardless
508: * of whether the secure connection uses SSL V3 or TLS 1.0 or WTLS.
509: *
510: * @return a String containing the cipher suite in use.
511: */
512: public String getCipherSuite() {
513: return parent.getCipherSuite();
514: }
515: }
|