SSLRecordProtocol.java revision 7365de1056414750d0a7d1fdd26025fd247f0d04
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.apache.harmony.xnet.provider.jsse;
19
20import java.io.IOException;
21import javax.net.ssl.SSLProtocolException;
22
23/**
24 * This class performs functionality dedicated to SSL record layer.
25 * It unpacks and routes income data to the appropriate
26 * client protocol (handshake, alert, application data protocols)
27 * and packages outcome data into SSL/TLS records.
28 * Initially created object has null connection state and does not
29 * perform any cryptography computations over the income/outcome data.
30 * After handshake protocol agreed upon security parameters they are placed
31 * into SSLSessionImpl object and available for record protocol as
32 * pending session. The order of setting up of the pending session
33 * as an active session differs for client and server modes.
34 * So for client mode the parameters are provided by handshake protocol
35 * during retrieving of change_cipher_spec message to be sent (by calling of
36 * getChangeCipherSpecMesage method).
37 * For server side mode record protocol retrieves the parameters from
38 * handshake protocol after receiving of client's change_cipher_spec message.
39 * After the pending session has been setted up as a curent session,
40 * new connectin state object is created and used for encryption/decryption
41 * of the messages.
42 * Among with base functionality this class provides the information about
43 * constrains on the data length, and information about correspondance
44 * of plain and encrypted data lengths.
45 * For more information on TLS v1 see http://www.ietf.org/rfc/rfc2246.txt,
46 * on SSL v3 see http://wp.netscape.com/eng/ssl3,
47 * on SSL v2 see http://wp.netscape.com/eng/security/SSL_2.html.
48 */
49public class SSLRecordProtocol {
50
51    /**
52     * Maximum length of allowed plain data fragment
53     * as specified by TLS specification.
54     */
55    protected static int MAX_DATA_LENGTH = 16384; // 2^14
56    /**
57     * Maximum length of allowed compressed data fragment
58     * as specified by TLS specification.
59     */
60    protected static int MAX_COMPRESSED_DATA_LENGTH
61                                    = MAX_DATA_LENGTH + 1024;
62    /**
63     * Maximum length of allowed ciphered data fragment
64     * as specified by TLS specification.
65     */
66    protected static int MAX_CIPHERED_DATA_LENGTH
67                                    = MAX_COMPRESSED_DATA_LENGTH + 1024;
68    /**
69     * Maximum length of ssl record. It is counted as:
70     * type(1) + version(2) + length(2) + MAX_CIPHERED_DATA_LENGTH
71     */
72    protected static int MAX_SSL_PACKET_SIZE
73                                    = MAX_CIPHERED_DATA_LENGTH + 5;
74    // the SSL session used for connection
75    private SSLSessionImpl session;
76    // protocol version of the connection
77    private byte[] version;
78    // input stream of record protocol
79    private SSLInputStream in;
80    // handshake protocol object to which handshaking data will be transmitted
81    private HandshakeProtocol handshakeProtocol;
82    // alert protocol to indicate alerts occured/received
83    private AlertProtocol alertProtocol;
84    // application data object to which application data will be transmitted
85    private org.apache.harmony.xnet.provider.jsse.Appendable appData;
86    // connection state holding object
87    private ConnectionState
88        activeReadState, activeWriteState, pendingConnectionState;
89
90    // logger
91    private Logger.Stream logger = Logger.getStream("record");
92
93    // flag indicating if session object has been changed after
94    // handshake phase (to distinguish session pending state)
95    private boolean sessionWasChanged = false;
96
97    // change cipher spec message content
98    private static final byte[] change_cipher_spec_byte = new byte[] {1};
99
100    /**
101     * Creates an instance of record protocol and tunes
102     * up the client protocols to use ut.
103     * @param   handshakeProtocol:  HandshakeProtocol
104     * @param   alertProtocol:  AlertProtocol
105     * @param   in: SSLInputStream
106     * @param   appData:    Appendable
107     */
108    protected SSLRecordProtocol(HandshakeProtocol handshakeProtocol,
109            AlertProtocol alertProtocol,
110            SSLInputStream in,
111            Appendable appData) {
112        this.handshakeProtocol = handshakeProtocol;
113        this.handshakeProtocol.setRecordProtocol(this);
114        this.alertProtocol = alertProtocol;
115        this.alertProtocol.setRecordProtocol(this);
116        this.in = in;
117        this.appData = appData;
118    }
119
120    /**
121     * Returns the session obtained during the handshake negotiation.
122     * If the handshake process was not compleated, method returns null.
123     * @return the session in effect.
124     */
125    protected SSLSessionImpl getSession() {
126        return session;
127    }
128
129    /**
130     * Returns the minimum possible length of the SSL record.
131     * @return
132     */
133    protected int getMinRecordSize() {
134        return (activeReadState == null)
135            ? 6 // type + version + length + 1 byte of data
136            : 5 + activeReadState.getMinFragmentSize();
137    }
138
139    /**
140     * Returns the record length for the specified incoming data length.
141     * If actual resulting record length is greater than
142     * MAX_CIPHERED_DATA_LENGTH, MAX_CIPHERED_DATA_LENGTH is returned.
143     */
144    protected int getRecordSize(int data_size) {
145        if (activeWriteState == null) {
146            return 5+data_size; // type + version + length + data_size
147        } else {
148            int res = 5 + activeWriteState.getFragmentSize(data_size);
149            return (res > MAX_CIPHERED_DATA_LENGTH)
150                ? MAX_CIPHERED_DATA_LENGTH // so the source data should be
151                                           // splitted into several packets
152                : res;
153        }
154    }
155
156    /**
157     * Returns the upper bound of length of data containing in the record with
158     * specified length.
159     * If the provided record_size is greater or equal to
160     * MAX_CIPHERED_DATA_LENGTH the returned value will be
161     * MAX_DATA_LENGTH
162     * counted as for data with
163     * MAX_CIPHERED_DATA_LENGTH length.
164     */
165    protected int getDataSize(int record_size) {
166        record_size -= 5; // - (type + version + length + data_size)
167        if (record_size > MAX_CIPHERED_DATA_LENGTH) {
168            // the data of such size consists of the several packets
169            return MAX_DATA_LENGTH;
170        }
171        if (activeReadState == null) {
172            return record_size;
173        }
174        return activeReadState.getContentSize(record_size);
175    }
176
177    /**
178     * Depending on the Connection State (Session) encrypts and compress
179     * the provided data, and packs it into TLSCiphertext structure.
180     * @param   content_type: int
181     * @param   fragment: byte[]
182     * @return  ssl packet created over the current connection state
183     */
184    protected byte[] wrap(byte content_type, DataStream dataStream) {
185        byte[] fragment = dataStream.getData(MAX_DATA_LENGTH);
186        return wrap(content_type, fragment, 0, fragment.length);
187    }
188
189    /**
190     * Depending on the Connection State (Session) encrypts and compress
191     * the provided data, and packs it into TLSCiphertext structure.
192     * @param   content_type: int
193     * @param   fragment: byte[]
194     * @return  ssl packet created over the current connection state
195     */
196    protected byte[] wrap(byte content_type,
197                       byte[] fragment, int offset, int len) {
198        if (logger != null) {
199            logger.println("SSLRecordProtocol.wrap: TLSPlaintext.fragment["
200                    +len+"]:");
201            logger.print(fragment, offset, len);
202        }
203        if (len > MAX_DATA_LENGTH) {
204            throw new AlertException(
205                AlertProtocol.INTERNAL_ERROR,
206                new SSLProtocolException(
207                    "The provided chunk of data is too big: " + len
208                    + " > MAX_DATA_LENGTH == "+MAX_DATA_LENGTH));
209        }
210        byte[] ciphered_fragment = fragment;
211        if (activeWriteState != null) {
212            ciphered_fragment =
213                activeWriteState.encrypt(content_type, fragment, offset, len);
214            if (ciphered_fragment.length > MAX_CIPHERED_DATA_LENGTH) {
215                throw new AlertException(
216                    AlertProtocol.INTERNAL_ERROR,
217                    new SSLProtocolException(
218                        "The ciphered data increased more than on 1024 bytes"));
219            }
220            if (logger != null) {
221                logger.println("SSLRecordProtocol.wrap: TLSCiphertext.fragment["
222                        +ciphered_fragment.length+"]:");
223                logger.print(ciphered_fragment);
224            }
225        }
226        return packetize(content_type, version, ciphered_fragment);
227    }
228
229    private byte[] packetize(byte type, byte[] version, byte[] fragment) {
230        byte[] buff = new byte[5+fragment.length];
231        buff[0] = type;
232        if (version != null) {
233            buff[1] = version[0];
234            buff[2] = version[1];
235        } else {
236            buff[1] = 3;
237            buff[2] = 1;
238        }
239        buff[3] = (byte) ((0x00FF00 & fragment.length) >> 8);
240        buff[4] = (byte) (0x0000FF & fragment.length);
241        System.arraycopy(fragment, 0, buff, 5, fragment.length);
242        return buff;
243    }
244
245    /**
246     * Set the ssl session to be used after sending the changeCipherSpec message
247     * @param   session:    SSLSessionImpl
248     */
249    private void setSession(SSLSessionImpl session) {
250        if (!sessionWasChanged) {
251            // session was not changed for current handshake process
252            if (logger != null) {
253                logger.println("SSLRecordProtocol.setSession: Set pending session");
254                logger.println("  cipher name: " + session.getCipherSuite());
255            }
256            this.session = session;
257            // create new connection state
258            pendingConnectionState = ((version == null) || (version[1] == 1))
259                ? (ConnectionState) new ConnectionStateTLS(getSession())
260                : (ConnectionState) new ConnectionStateSSLv3(getSession());
261            sessionWasChanged = true;
262        } else {
263            // wait for rehandshaking's session
264            sessionWasChanged = false;
265        }
266    }
267
268    /**
269     * Returns the change cipher spec message to be sent to another peer.
270     * The pending connection state will be built on the base of provided
271     * session object
272     * The calling of this method triggers pending write connection state to
273     * be active.
274     * @return ssl record containing the "change cipher spec" message.
275     */
276    protected byte[] getChangeCipherSpecMesage(SSLSessionImpl session) {
277        // make change_cipher_spec_message:
278        byte[] change_cipher_spec_message;
279        if (activeWriteState == null) {
280            change_cipher_spec_message = new byte[] {
281                    ContentType.CHANGE_CIPHER_SPEC, version[0],
282                        version[1], 0, 1, 1
283                };
284        } else {
285            change_cipher_spec_message =
286                packetize(ContentType.CHANGE_CIPHER_SPEC, version,
287                        activeWriteState.encrypt(ContentType.CHANGE_CIPHER_SPEC,
288                            change_cipher_spec_byte, 0, 1));
289        }
290        setSession(session);
291        activeWriteState = pendingConnectionState;
292        if (logger != null) {
293            logger.println("SSLRecordProtocol.getChangeCipherSpecMesage");
294            logger.println("activeWriteState = pendingConnectionState");
295            logger.print(change_cipher_spec_message);
296        }
297        return change_cipher_spec_message;
298    }
299
300    /**
301     * Retrieves the fragment field of TLSCiphertext, and than
302     * depending on the established Connection State
303     * decrypts and decompresses it. The following structure is expected
304     * on the input at the moment of the call:
305     *
306     *  struct {
307     *      ContentType type;
308     *      ProtocolVersion version;
309     *      uint16 length;
310     *      select (CipherSpec.cipher_type) {
311     *          case stream: GenericStreamCipher;
312     *          case block: GenericBlockCipher;
313     *      } fragment;
314     *  } TLSCiphertext;
315     *
316     * (as specified by RFC 2246, TLS v1 Protocol specification)
317     *
318     * In addition this method can recognize SSLv2 hello message which
319     * are often used to establish the SSL/TLS session.
320     *
321     * @throws IOException if some io errors have been occured
322     * @throws EndOfSourceException if underlying input stream
323     *                              has ran out of data.
324     * @throws EndOfBufferException if there was not enought data
325     *                              to build complete ssl packet.
326     * @return the type of unwrapped message.
327     */
328    protected int unwrap() throws IOException {
329        if (logger != null) {
330            logger.println("SSLRecordProtocol.unwrap: BEGIN [");
331        }
332        int type = in.readUint8();
333        if ((type < ContentType.CHANGE_CIPHER_SPEC)
334                || (type > ContentType.APPLICATION_DATA)) {
335            if (logger != null) {
336                logger.println("Non v3.1 message type:" + type);
337            }
338            if (type >= 0x80) {
339                // it is probably SSL v2 client_hello message
340                // (see SSL v2 spec at:
341                // http://wp.netscape.com/eng/security/SSL_2.html)
342                int length = (type & 0x7f) << 8 | in.read();
343                byte[] fragment = in.read(length);
344                handshakeProtocol.unwrapSSLv2(fragment);
345                if (logger != null) {
346                    logger.println(
347                            "SSLRecordProtocol:unwrap ] END, SSLv2 type");
348                }
349                return ContentType.HANDSHAKE;
350            }
351            throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
352                    new SSLProtocolException(
353                        "Unexpected message type has been received: "+type));
354        }
355        if (logger != null) {
356            logger.println("Got the message of type: " + type);
357        }
358        if (version != null) {
359            if ((in.read() != version[0])
360                    || (in.read() != version[1])) {
361                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
362                        new SSLProtocolException(
363                            "Unexpected message type has been received: " +
364                            type));
365            }
366        } else {
367            in.skip(2); // just skip the version number
368        }
369        int length = in.readUint16();
370        if (logger != null) {
371            logger.println("TLSCiphertext.fragment["+length+"]: ...");
372        }
373        if (length > MAX_CIPHERED_DATA_LENGTH) {
374            throw new AlertException(AlertProtocol.RECORD_OVERFLOW,
375                    new SSLProtocolException(
376                        "Received message is too big."));
377        }
378        byte[] fragment = in.read(length);
379        if (logger != null) {
380            logger.print(fragment);
381        }
382        if (activeReadState != null) {
383            fragment = activeReadState.decrypt((byte) type, fragment);
384            if (logger != null) {
385                logger.println("TLSPlaintext.fragment:");
386                logger.print(fragment);
387            }
388        }
389        if (fragment.length > MAX_DATA_LENGTH) {
390            throw new AlertException(AlertProtocol.DECOMPRESSION_FAILURE,
391                    new SSLProtocolException(
392                        "Decompressed plain data is too big."));
393        }
394        switch (type) {
395            case ContentType.CHANGE_CIPHER_SPEC:
396                // notify handshake protocol:
397                handshakeProtocol.receiveChangeCipherSpec();
398                setSession(handshakeProtocol.getSession());
399                // change cipher spec message has been received, so:
400                if (logger != null) {
401                    logger.println("activeReadState = pendingConnectionState");
402                }
403                activeReadState = pendingConnectionState;
404                break;
405            case ContentType.ALERT:
406                alert(fragment[0], fragment[1]);
407                break;
408            case ContentType.HANDSHAKE:
409                handshakeProtocol.unwrap(fragment);
410                break;
411            case ContentType.APPLICATION_DATA:
412                if (logger != null) {
413                    logger.println(
414                            "TLSCiphertext.unwrap: APP DATA["+length+"]:");
415                    logger.println(new String(fragment));
416                }
417                appData.append(fragment);
418                break;
419            default:
420                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
421                        new SSLProtocolException(
422                            "Unexpected message type has been received: " +
423                            type));
424        }
425        if (logger != null) {
426            logger.println("SSLRecordProtocol:unwrap ] END, type: " + type);
427        }
428        return type;
429    }
430
431    /**
432     * Passes the alert information to the alert protocol.
433     * @param   level:  byte
434     * @param   description:    byte
435     */
436    protected void alert(byte level, byte description) {
437        if (logger != null) {
438            logger.println("SSLRecordProtocol.allert: "+level+" "+description);
439        }
440        alertProtocol.alert(level, description);
441    }
442
443    /**
444     * Sets up the SSL version used in this connection.
445     * This method is calling from the hanshake protocol after
446     * it becomes known witch protocol version will be used.
447     * @param   ver:    byte[]
448     * @return
449     */
450    protected void setVersion(byte[] ver) {
451        this.version = ver;
452    }
453
454    /**
455     * Shutdownes the protocol. It will be impossiblke to use the instance
456     * after the calling of this method.
457     */
458    protected void shutdown() {
459        session = null;
460        version = null;
461        in = null;
462        handshakeProtocol = null;
463        alertProtocol = null;
464        appData = null;
465        if (pendingConnectionState != null) {
466            pendingConnectionState.shutdown();
467        }
468        pendingConnectionState = null;
469        if (activeReadState != null) {
470            activeReadState.shutdown();
471        }
472        activeReadState = null;
473        if (activeReadState != null) {
474            activeReadState.shutdown();
475        }
476        activeWriteState = null;
477    }
478}
479