1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package org.conscrypt;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.net.InetAddress;
24import java.net.SocketAddress;
25import java.net.SocketException;
26import java.net.UnknownHostException;
27import java.util.ArrayList;
28import javax.net.ssl.HandshakeCompletedEvent;
29import javax.net.ssl.HandshakeCompletedListener;
30import javax.net.ssl.SSLEngineResult;
31import javax.net.ssl.SSLException;
32import javax.net.ssl.SSLSession;
33import javax.net.ssl.SSLSocket;
34
35/**
36 * SSLSocket implementation.
37 * @see javax.net.ssl.SSLSocket class documentation for more information.
38 */
39public class SSLSocketImpl extends SSLSocket {
40
41    // indicates if handshake has been started
42    private boolean handshake_started = false;
43
44    // used when we're wrapping a socket
45    private final String wrappedHost;
46    private final int wrappedPort;
47
48    // record protocol to be used
49    protected SSLRecordProtocol recordProtocol;
50    // handshake protocol to be used
51    private HandshakeProtocol handshakeProtocol;
52    // alert protocol to be used
53    private AlertProtocol alertProtocol;
54    // application data input stream, this stream is presented by
55    // ssl socket as an input stream. Additionally this object is a
56    // place where application data will be stored by record protocol
57    private SSLSocketInputStream appDataIS;
58    // outgoing application data stream
59    private SSLSocketOutputStream appDataOS;
60    // active session object
61    private SSLSessionImpl session;
62
63    private boolean socket_was_closed = false;
64
65    // the sslParameters object encapsulates all the info
66    // about supported and enabled cipher suites and protocols,
67    // as well as the information about client/server mode of
68    // ssl socket, whether it require/want client authentication or not,
69    // and controls whether new SSL sessions may be established by this
70    // socket or not.
71    protected SSLParametersImpl sslParameters;
72    // super's streams to be wrapped:
73    protected InputStream input;
74    protected OutputStream output;
75    // handshake complete listeners
76    private ArrayList<HandshakeCompletedListener> listeners;
77    // logger
78    private Logger.Stream logger = Logger.getStream("socket");
79
80    // ----------------- Constructors and initializers --------------------
81
82    /**
83     * Constructor
84     * @param   sslParameters:  SSLParametersImpl
85     * @see javax.net.ssl.SSLSocket#SSLSocket() method documentation
86     * for more information.
87     */
88    protected SSLSocketImpl(SSLParametersImpl sslParameters) {
89        this.sslParameters = sslParameters;
90        this.wrappedHost = null;
91        this.wrappedPort = -1;
92        // init should be called after creation!
93    }
94
95    /**
96     * Constructor
97     * @param   host:   String
98     * @param   port:   int
99     * @param   sslParameters:  SSLParametersImpl
100     * @throws  IOException
101     * @throws  UnknownHostException
102     * @see javax.net.ssl.SSLSocket#SSLSocket(String,int)
103     * method documentation for more information.
104     */
105    protected SSLSocketImpl(String host, int port, SSLParametersImpl sslParameters)
106            throws IOException, UnknownHostException {
107        super(host, port);
108        this.wrappedHost = host;
109        this.wrappedPort = port;
110        this.sslParameters = sslParameters;
111        init();
112    }
113
114    /**
115     * Constructor
116     * @param   host:   String
117     * @param   port:   int
118     * @param   localHost:  InetAddress
119     * @param   localPort:  int
120     * @param   sslParameters:  SSLParametersImpl
121     * @throws  IOException
122     * @throws  UnknownHostException
123     * @see javax.net.ssl.SSLSocket#SSLSocket(String,int,InetAddress,int)
124     * method documentation for more information.
125     */
126    protected SSLSocketImpl(String host, int port,
127            InetAddress localHost, int localPort,
128            SSLParametersImpl sslParameters) throws IOException,
129            UnknownHostException {
130        super(host, port, localHost, localPort);
131        this.wrappedHost = host;
132        this.wrappedPort = port;
133        this.sslParameters = sslParameters;
134        init();
135    }
136
137    /**
138     * Constructor
139     * @param   host:   InetAddress
140     * @param   port:   int
141     * @param   sslParameters:  SSLParametersImpl
142     * @return
143     * @throws  IOException
144     * @see javax.net.ssl.SSLSocket#SSLSocket(InetAddress,int)
145     * method documentation for more information.
146     */
147    protected SSLSocketImpl(InetAddress host, int port,
148            SSLParametersImpl sslParameters) throws IOException {
149        super(host, port);
150        this.sslParameters = sslParameters;
151        this.wrappedHost = null;
152        this.wrappedPort = -1;
153        init();
154    }
155
156    /**
157     * Constructor
158     * @param   address:    InetAddress
159     * @param   port:   int
160     * @param   localAddress:   InetAddress
161     * @param   localPort:  int
162     * @param   sslParameters:  SSLParametersImpl
163     * @return
164     * @throws  IOException
165     * @see javax.net.ssl.SSLSocket#SSLSocket(InetAddress,int,InetAddress,int)
166     * method documentation for more information.
167     */
168    protected SSLSocketImpl(InetAddress address, int port,
169            InetAddress localAddress, int localPort,
170            SSLParametersImpl sslParameters) throws IOException {
171        super(address, port, localAddress, localPort);
172        this.sslParameters = sslParameters;
173        this.wrappedHost = null;
174        this.wrappedPort = -1;
175        init();
176    }
177
178    /**
179     * Initialize the SSL socket.
180     */
181    protected void init() throws IOException {
182        if (appDataIS != null) {
183            // already initialized
184            return;
185        }
186        initTransportLayer();
187        appDataIS = new SSLSocketInputStream(this);
188        appDataOS = new SSLSocketOutputStream(this);
189    }
190
191    /**
192     * Initialize the transport data streams.
193     */
194    protected void initTransportLayer() throws IOException {
195        input = super.getInputStream();
196        output = super.getOutputStream();
197    }
198
199    /**
200     * Closes the transport data streams.
201     */
202    protected void closeTransportLayer() throws IOException {
203        super.close();
204        if (input != null) {
205            input.close();
206            output.close();
207        }
208    }
209
210    String getWrappedHostName() {
211        return wrappedHost;
212    }
213
214    int getWrappedPort() {
215        return wrappedPort;
216    }
217
218    String getPeerHostName() {
219        if (wrappedHost != null) {
220            return wrappedHost;
221        }
222        InetAddress inetAddress = super.getInetAddress();
223        if (inetAddress != null) {
224            return inetAddress.getHostName();
225        }
226        return null;
227    }
228
229    int getPeerPort() {
230        return (wrappedPort == -1) ? super.getPort() : wrappedPort;
231    }
232
233    // --------------- SSLParameters based methods ---------------------
234
235    /**
236     * This method works according to the specification of implemented class.
237     * @see javax.net.ssl.SSLSocket#getSupportedCipherSuites()
238     * method documentation for more information
239     */
240    @Override
241    public String[] getSupportedCipherSuites() {
242        return CipherSuite.getSupportedCipherSuiteNames();
243    }
244
245    /**
246     * This method works according to the specification of implemented class.
247     * @see javax.net.ssl.SSLSocket#getEnabledCipherSuites()
248     * method documentation for more information
249     */
250    @Override
251    public String[] getEnabledCipherSuites() {
252        return sslParameters.getEnabledCipherSuites();
253    }
254
255    /**
256     * This method works according to the specification of implemented class.
257     * @see javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[])
258     * method documentation for more information
259     */
260    @Override
261    public void setEnabledCipherSuites(String[] suites) {
262        sslParameters.setEnabledCipherSuites(suites);
263    }
264
265    /**
266     * This method works according to the specification of implemented class.
267     * @see javax.net.ssl.SSLSocket#getSupportedProtocols()
268     * method documentation for more information
269     */
270    @Override
271    public String[] getSupportedProtocols() {
272        return ProtocolVersion.supportedProtocols.clone();
273    }
274
275    /**
276     * This method works according to the specification of implemented class.
277     * @see javax.net.ssl.SSLSocket#getEnabledProtocols()
278     * method documentation for more information
279     */
280    @Override
281    public String[] getEnabledProtocols() {
282        return sslParameters.getEnabledProtocols();
283    }
284
285    /**
286     * This method works according to the specification of implemented class.
287     * @see javax.net.ssl.SSLSocket#setEnabledProtocols(String[])
288     * method documentation for more information
289     */
290    @Override
291    public void setEnabledProtocols(String[] protocols) {
292        sslParameters.setEnabledProtocols(protocols);
293    }
294
295    /**
296     * This method works according to the specification of implemented class.
297     * @see javax.net.ssl.SSLSocket#setUseClientMode(boolean)
298     * method documentation for more information
299     */
300    @Override
301    public void setUseClientMode(boolean mode) {
302        if (handshake_started) {
303            throw new IllegalArgumentException(
304            "Could not change the mode after the initial handshake has begun.");
305        }
306        sslParameters.setUseClientMode(mode);
307    }
308
309    /**
310     * This method works according to the specification of implemented class.
311     * @see javax.net.ssl.SSLSocket#getUseClientMode()
312     * method documentation for more information
313     */
314    @Override
315    public boolean getUseClientMode() {
316        return sslParameters.getUseClientMode();
317    }
318
319    /**
320     * This method works according to the specification of implemented class.
321     * @see javax.net.ssl.SSLSocket#setNeedClientAuth(boolean)
322     * method documentation for more information
323     */
324    @Override
325    public void setNeedClientAuth(boolean need) {
326        sslParameters.setNeedClientAuth(need);
327    }
328
329    /**
330     * This method works according to the specification of implemented class.
331     * @see javax.net.ssl.SSLSocket#getNeedClientAuth()
332     * method documentation for more information
333     */
334    @Override
335    public boolean getNeedClientAuth() {
336        return sslParameters.getNeedClientAuth();
337    }
338
339    /**
340     * This method works according to the specification of implemented class.
341     * @see javax.net.ssl.SSLSocket#setWantClientAuth(boolean)
342     * method documentation for more information
343     */
344    @Override
345    public void setWantClientAuth(boolean want) {
346        sslParameters.setWantClientAuth(want);
347    }
348
349    /**
350     * This method works according to the specification of implemented class.
351     * @see javax.net.ssl.SSLSocket#getWantClientAuth()
352     * method documentation for more information
353     */
354    @Override
355    public boolean getWantClientAuth() {
356        return sslParameters.getWantClientAuth();
357    }
358
359    /**
360     * This method works according to the specification of implemented class.
361     * @see javax.net.ssl.SSLSocket#setEnableSessionCreation(boolean)
362     * method documentation for more information
363     */
364    @Override
365    public void setEnableSessionCreation(boolean flag) {
366        sslParameters.setEnableSessionCreation(flag);
367    }
368
369    /**
370     * This method works according to the specification of implemented class.
371     * @see javax.net.ssl.SSLSocket#getEnableSessionCreation()
372     * method documentation for more information
373     */
374    @Override
375    public boolean getEnableSessionCreation() {
376        return sslParameters.getEnableSessionCreation();
377    }
378
379    // -----------------------------------------------------------------
380
381    /**
382     * This method works according to the specification of implemented class.
383     * @see javax.net.ssl.SSLSocket#getSession()
384     * method documentation for more information
385     */
386    @Override
387    public SSLSession getSession() {
388        if (!handshake_started) {
389            try {
390                startHandshake();
391            } catch (IOException e) {
392                // return an invalid session with
393                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
394                return SSLSessionImpl.getNullSession();
395            }
396        }
397        return session;
398    }
399
400    /**
401     * This method works according to the specification of implemented class.
402     * @see javax.net.ssl.SSLSocket#addHandshakeCompletedListener(HandshakeCompletedListener)
403     * method documentation for more information
404     */
405    @Override
406    public void addHandshakeCompletedListener(
407            HandshakeCompletedListener listener) {
408        if (listener == null) {
409            throw new IllegalArgumentException("Provided listener is null");
410        }
411        if (listeners == null) {
412            listeners = new ArrayList<HandshakeCompletedListener>();
413        }
414        listeners.add(listener);
415    }
416
417    /**
418     * This method works according to the specification of implemented class.
419     * @see javax.net.ssl.SSLSocket#removeHandshakeCompletedListener(HandshakeCompletedListener)
420     * method documentation for more information
421     */
422    @Override
423    public void removeHandshakeCompletedListener(
424            HandshakeCompletedListener listener) {
425        if (listener == null) {
426            throw new IllegalArgumentException("Provided listener is null");
427        }
428        if (listeners == null) {
429            throw new IllegalArgumentException(
430                    "Provided listener is not registered");
431        }
432        if (!listeners.remove(listener)) {
433            throw new IllegalArgumentException(
434                    "Provided listener is not registered");
435        }
436    }
437
438    /**
439     * Performs the handshake process over the SSL/TLS connection
440     * as described in rfc 2246, TLS v1 specification
441     * http://www.ietf.org/rfc/rfc2246.txt. If the initial handshake
442     * has been already done, this method initiates rehandshake.
443     * This method works according to the specification of implemented class.
444     * @see javax.net.ssl.SSLSocket#startHandshake()
445     * method documentation for more information
446     */
447    @Override
448    public void startHandshake() throws IOException {
449        if (appDataIS == null) {
450            throw new IOException("Socket is not connected.");
451        }
452        if (socket_was_closed) {
453            throw new IOException("Socket has already been closed.");
454        }
455
456        if (!handshake_started) {
457            handshake_started = true;
458            if (sslParameters.getUseClientMode()) {
459                if (logger != null) {
460                    logger.println("SSLSocketImpl: CLIENT");
461                }
462                handshakeProtocol = new ClientHandshakeImpl(this);
463            } else {
464                if (logger != null) {
465                    logger.println("SSLSocketImpl: SERVER");
466                }
467                handshakeProtocol = new ServerHandshakeImpl(this);
468            }
469
470            alertProtocol = new AlertProtocol();
471            recordProtocol = new SSLRecordProtocol(handshakeProtocol,
472                    alertProtocol, new SSLStreamedInput(input),
473                    appDataIS.dataPoint);
474        }
475
476        if (logger != null) {
477            logger.println("SSLSocketImpl.startHandshake");
478        }
479
480        handshakeProtocol.start();
481
482        doHandshake();
483
484        if (logger != null) {
485            logger.println("SSLSocketImpl.startHandshake: END");
486        }
487    }
488
489    /**
490     * This method works according to the specification of implemented class.
491     * @see javax.net.ssl.SSLSocket#getInputStream()
492     * method documentation for more information
493     */
494    @Override
495    public InputStream getInputStream() throws IOException {
496        if (socket_was_closed) {
497            throw new IOException("Socket has already been closed.");
498        }
499        return appDataIS;
500    }
501
502    /**
503     * This method works according to the specification of implemented class.
504     * @see javax.net.ssl.SSLSocket#getOutputStream()
505     * method documentation for more information
506     */
507    @Override
508    public OutputStream getOutputStream() throws IOException {
509        if (socket_was_closed) {
510            throw new IOException("Socket has already been closed.");
511        }
512        return appDataOS;
513    }
514
515    /**
516     * This method works according to the specification of implemented class.
517     * @see java.net.Socket#connect(SocketAddress)
518     * method documentation for more information
519     */
520    @Override
521    public void connect(SocketAddress endpoint) throws IOException {
522        super.connect(endpoint);
523        init();
524    }
525
526    /**
527     * This method works according to the specification of implemented class.
528     * @see java.net.Socket#connect(SocketAddress,int)
529     * method documentation for more information
530     */
531    @Override
532    public void connect(SocketAddress endpoint, int timeout)
533            throws IOException {
534        super.connect(endpoint, timeout);
535        init();
536    }
537
538    /**
539     * This method works according to the specification of implemented class.
540     * @see javax.net.ssl.SSLSocket#close()
541     * method documentation for more information
542     */
543    @Override
544    public void close() throws IOException {
545        if (logger != null) {
546            logger.println("SSLSocket.close "+socket_was_closed);
547        }
548        if (!socket_was_closed) {
549            if (handshake_started) {
550                alertProtocol.alert(AlertProtocol.WARNING,
551                        AlertProtocol.CLOSE_NOTIFY);
552                try {
553                    output.write(alertProtocol.wrap());
554                } catch (IOException ex) { }
555                alertProtocol.setProcessed();
556            }
557            shutdown();
558            closeTransportLayer();
559            socket_was_closed = true;
560        }
561    }
562
563    /**
564     * This method is not supported for SSLSocket implementation.
565     */
566    @Override
567    public void sendUrgentData(int data) throws IOException {
568        throw new SocketException(
569                "Method sendUrgentData() is not supported.");
570    }
571
572    /**
573     * This method is not supported for SSLSocket implementation.
574     */
575    @Override
576    public void setOOBInline(boolean on) throws SocketException {
577        throw new SocketException(
578                "Methods sendUrgentData, setOOBInline are not supported.");
579    }
580
581    // -----------------------------------------------------------------
582
583    private void shutdown() {
584        if (handshake_started) {
585            alertProtocol.shutdown();
586            alertProtocol = null;
587            handshakeProtocol.shutdown();
588            handshakeProtocol = null;
589            recordProtocol.shutdown();
590            recordProtocol = null;
591        }
592        socket_was_closed = true;
593    }
594
595    /**
596     * This method is called by SSLSocketInputStream class
597     * when client application tries to read application data from
598     * the stream, but there is no data in its underlying buffer.
599     * @throws  IOException
600     */
601    protected void needAppData() throws IOException {
602        if (!handshake_started) {
603            startHandshake();
604        }
605        int type;
606        if (logger != null) {
607            logger.println("SSLSocket.needAppData..");
608        }
609        try {
610            while(appDataIS.available() == 0) {
611                // read and unwrap the record contained in the transport
612                // input stream (SSLStreamedInput), pass it
613                // to appropriate client protocol (alert, handshake, or app)
614                // and retrieve the type of unwrapped data
615                switch (type = recordProtocol.unwrap()) {
616                    case ContentType.HANDSHAKE:
617                        if (!handshakeProtocol.getStatus().equals(
618                                SSLEngineResult.HandshakeStatus
619                                .NOT_HANDSHAKING)) {
620                            // handshake protocol got addressed to it message
621                            // and did not ignore it, so it's a rehandshake
622                            doHandshake();
623                        }
624                        break;
625                    case ContentType.ALERT:
626                        processAlert();
627                        if (socket_was_closed) {
628                            return;
629                        }
630                        break;
631                    case ContentType.APPLICATION_DATA:
632                        if (logger != null) {
633                            logger.println(
634                                    "SSLSocket.needAppData: got the data");
635                        }
636                        break;
637                    default:
638                        // will throw exception
639                        reportFatalAlert(AlertProtocol.UNEXPECTED_MESSAGE,
640                                new SSLException("Unexpected message of type "
641                                    + type + " has been got"));
642                }
643                if (alertProtocol.hasAlert()) {
644                    // warning alert occurred during wrap or unwrap
645                    // (note: fatal alert causes AlertException
646                    // to be thrown)
647                    output.write(alertProtocol.wrap());
648                    alertProtocol.setProcessed();
649                }
650                if (socket_was_closed) {
651                    appDataIS.setEnd();
652                    return;
653                }
654            }
655        } catch (AlertException e) {
656            // will throw exception
657            reportFatalAlert(e.getDescriptionCode(), e.getReason());
658        } catch (EndOfSourceException e) {
659            // end of socket's input stream has been reached
660            appDataIS.setEnd();
661        }
662        if (logger != null) {
663            logger.println("SSLSocket.needAppData: app data len: "
664                    + appDataIS.available());
665        }
666    }
667
668    /**
669     * This method is called by SSLSocketOutputStream when a client application
670     * tries to send the data over ssl protocol.
671     */
672    protected void writeAppData(byte[] data, int offset, int len) throws IOException {
673        if (!handshake_started) {
674            startHandshake();
675        }
676        if (logger != null) {
677            logger.println("SSLSocket.writeAppData: " +
678                    len + " " + SSLRecordProtocol.MAX_DATA_LENGTH);
679            //logger.println(new String(data, offset, len));
680        }
681        try {
682            if (len < SSLRecordProtocol.MAX_DATA_LENGTH) {
683                output.write(recordProtocol.wrap(ContentType.APPLICATION_DATA,
684                            data, offset, len));
685            } else {
686                while (len >= SSLRecordProtocol.MAX_DATA_LENGTH) {
687                    output.write(recordProtocol.wrap(
688                                ContentType.APPLICATION_DATA, data, offset,
689                                SSLRecordProtocol.MAX_DATA_LENGTH));
690                    offset += SSLRecordProtocol.MAX_DATA_LENGTH;
691                    len -= SSLRecordProtocol.MAX_DATA_LENGTH;
692                }
693                if (len > 0) {
694                    output.write(
695                        recordProtocol.wrap(ContentType.APPLICATION_DATA,
696                                            data, offset, len));
697                }
698            }
699        } catch (AlertException e) {
700            // will throw exception
701            reportFatalAlert(e.getDescriptionCode(), e.getReason());
702        }
703    }
704
705    /*
706     * Performs handshake process over this connection. The handshake
707     * process is directed by the handshake status code provided by
708     * handshake protocol. If this status is NEED_WRAP, method retrieves
709     * handshake message from handshake protocol and sends it to another peer.
710     * If this status is NEED_UNWRAP, method receives and processes handshake
711     * message from another peer. Each of this stages (wrap/unwrap) change
712     * the state of handshake protocol and this process is performed
713     * until handshake status is FINISHED. After handshake process is finished
714     * handshake completed event are sent to the registered listeners.
715     * For more information about the handshake process see
716     * TLS v1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 7.3.
717     */
718    private void doHandshake() throws IOException {
719        SSLEngineResult.HandshakeStatus status;
720        int type;
721        try {
722            while (!(status = handshakeProtocol.getStatus()).equals(
723                        SSLEngineResult.HandshakeStatus.FINISHED)) {
724                if (logger != null) {
725                    String s = (status.equals(
726                                SSLEngineResult.HandshakeStatus.NEED_WRAP))
727                        ? "NEED_WRAP"
728                        : (status.equals(
729                                SSLEngineResult.HandshakeStatus.NEED_UNWRAP))
730                            ? "NEED_UNWRAP"
731                            : "STATUS: OTHER!";
732                    logger.println("SSLSocketImpl: HS status: "+s+" "+status);
733                }
734                if (status.equals(SSLEngineResult.HandshakeStatus.NEED_WRAP)) {
735                    output.write(handshakeProtocol.wrap());
736                } else if (status.equals(
737                            SSLEngineResult.HandshakeStatus.NEED_UNWRAP)) {
738                    // read and unwrap the record contained in the transport
739                    // input stream (SSLStreamedInput), pass it
740                    // to appropriate client protocol (alert, handshake, or app)
741                    // and retrieve the type of unwrapped data
742                    switch (type = recordProtocol.unwrap()) {
743                        case ContentType.HANDSHAKE:
744                        case ContentType.CHANGE_CIPHER_SPEC:
745                            break;
746                        case ContentType.APPLICATION_DATA:
747                            // So it's rehandshake and
748                            // if app data buffer will be overloaded
749                            // it will throw alert exception.
750                            // Probably we should count the number of
751                            // not handshaking data and make additional
752                            // constraints (do not expect buffer overflow).
753                            break;
754                        case ContentType.ALERT:
755                            processAlert();
756                            if (socket_was_closed) {
757                                return;
758                            }
759                            break;
760                        default:
761                            // will throw exception
762                            reportFatalAlert(AlertProtocol.UNEXPECTED_MESSAGE,
763                                    new SSLException(
764                                        "Unexpected message of type "
765                                        + type + " has been got"));
766                    }
767                } else {
768                    // will throw exception
769                    reportFatalAlert(AlertProtocol.INTERNAL_ERROR,
770                        new SSLException(
771                            "Handshake passed unexpected status: "+status));
772                }
773                if (alertProtocol.hasAlert()) {
774                    // warning alert occurred during wrap or unwrap
775                    // (note: fatal alert causes AlertException
776                    // to be thrown)
777                    output.write(alertProtocol.wrap());
778                    alertProtocol.setProcessed();
779                }
780            }
781        } catch (EndOfSourceException e) {
782            appDataIS.setEnd();
783            throw new IOException("Connection was closed");
784        } catch (AlertException e) {
785            // will throw exception
786            reportFatalAlert(e.getDescriptionCode(), e.getReason());
787        }
788
789        session = recordProtocol.getSession();
790        if (listeners != null) {
791            // notify the listeners
792            HandshakeCompletedEvent event =
793                new HandshakeCompletedEvent(this, session);
794            int size = listeners.size();
795            for (int i=0; i<size; i++) {
796                listeners.get(i)
797                    .handshakeCompleted(event);
798            }
799        }
800    }
801
802    /*
803     * Process received alert message
804     */
805    private void processAlert() throws IOException {
806        if (!alertProtocol.hasAlert()) {
807            return;
808        }
809        if (alertProtocol.isFatalAlert()) {
810            alertProtocol.setProcessed();
811            String description = "Fatal alert received "
812                + alertProtocol.getAlertDescription();
813            shutdown();
814            throw new SSLException(description);
815        }
816
817        if (logger != null) {
818            logger.println("Warning alert received: "
819                + alertProtocol.getAlertDescription());
820        }
821        switch(alertProtocol.getDescriptionCode()) {
822            case AlertProtocol.CLOSE_NOTIFY:
823                alertProtocol.setProcessed();
824                appDataIS.setEnd();
825                close();
826                return;
827            default:
828                alertProtocol.setProcessed();
829            // TODO: process other warning messages
830        }
831    }
832
833    /*
834     * Sends fatal alert message and throws exception
835     */
836    private void reportFatalAlert(byte description_code,
837            SSLException reason) throws IOException {
838        alertProtocol.alert(AlertProtocol.FATAL, description_code);
839        try {
840            // the output stream can be closed
841            output.write(alertProtocol.wrap());
842        } catch (IOException ex) { }
843        alertProtocol.setProcessed();
844        shutdown();
845        throw reason;
846    }
847}
848