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 set up as a current session,
40 * new connection 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 correspondence
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 final 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 final 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 final 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 final 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 occurred/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 completed, 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                                           // split 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     * @return  ssl packet created over the current connection state
182     */
183    protected byte[] wrap(byte content_type, DataStream dataStream) {
184        byte[] fragment = dataStream.getData(MAX_DATA_LENGTH);
185        return wrap(content_type, fragment, 0, fragment.length);
186    }
187
188    /**
189     * Depending on the Connection State (Session) encrypts and compress
190     * the provided data, and packs it into TLSCiphertext structure.
191     * @param   content_type: int
192     * @param   fragment: byte[]
193     * @return  ssl packet created over the current connection state
194     */
195    protected byte[] wrap(byte content_type,
196                       byte[] fragment, int offset, int len) {
197        if (logger != null) {
198            logger.println("SSLRecordProtocol.wrap: TLSPlaintext.fragment["
199                    +len+"]:");
200            logger.print(fragment, offset, len);
201        }
202        if (len > MAX_DATA_LENGTH) {
203            throw new AlertException(
204                AlertProtocol.INTERNAL_ERROR,
205                new SSLProtocolException(
206                    "The provided chunk of data is too big: " + len
207                    + " > MAX_DATA_LENGTH == "+MAX_DATA_LENGTH));
208        }
209        byte[] ciphered_fragment = fragment;
210        if (activeWriteState != null) {
211            ciphered_fragment =
212                activeWriteState.encrypt(content_type, fragment, offset, len);
213            if (ciphered_fragment.length > MAX_CIPHERED_DATA_LENGTH) {
214                throw new AlertException(
215                    AlertProtocol.INTERNAL_ERROR,
216                    new SSLProtocolException(
217                        "The ciphered data increased more than on 1024 bytes"));
218            }
219            if (logger != null) {
220                logger.println("SSLRecordProtocol.wrap: TLSCiphertext.fragment["
221                        +ciphered_fragment.length+"]:");
222                logger.print(ciphered_fragment);
223            }
224        }
225        return packetize(content_type, version, ciphered_fragment);
226    }
227
228    private byte[] packetize(byte type, byte[] version, byte[] fragment) {
229        byte[] buff = new byte[5+fragment.length];
230        buff[0] = type;
231        if (version != null) {
232            buff[1] = version[0];
233            buff[2] = version[1];
234        } else {
235            buff[1] = 3;
236            buff[2] = 1;
237        }
238        buff[3] = (byte) ((0x00FF00 & fragment.length) >> 8);
239        buff[4] = (byte) (0x0000FF & fragment.length);
240        System.arraycopy(fragment, 0, buff, 5, fragment.length);
241        return buff;
242    }
243
244    /**
245     * Set the ssl session to be used after sending the changeCipherSpec message
246     * @param   session:    SSLSessionImpl
247     */
248    private void setSession(SSLSessionImpl session) {
249        if (!sessionWasChanged) {
250            // session was not changed for current handshake process
251            if (logger != null) {
252                logger.println("SSLRecordProtocol.setSession: Set pending session");
253                logger.println("  cipher name: " + session.getCipherSuite());
254            }
255            this.session = session;
256            // create new connection state
257            pendingConnectionState = ((version == null) || (version[1] == 1))
258                ? (ConnectionState) new ConnectionStateTLS(getSession())
259                : (ConnectionState) new ConnectionStateSSLv3(getSession());
260            sessionWasChanged = true;
261        } else {
262            // wait for rehandshaking's session
263            sessionWasChanged = false;
264        }
265    }
266
267    /**
268     * Returns the change cipher spec message to be sent to another peer.
269     * The pending connection state will be built on the base of provided
270     * session object
271     * The calling of this method triggers pending write connection state to
272     * be active.
273     * @return ssl record containing the "change cipher spec" message.
274     */
275    protected byte[] getChangeCipherSpecMesage(SSLSessionImpl session) {
276        // make change_cipher_spec_message:
277        byte[] change_cipher_spec_message;
278        if (activeWriteState == null) {
279            change_cipher_spec_message = new byte[] {
280                    ContentType.CHANGE_CIPHER_SPEC, version[0],
281                        version[1], 0, 1, 1
282                };
283        } else {
284            change_cipher_spec_message =
285                packetize(ContentType.CHANGE_CIPHER_SPEC, version,
286                        activeWriteState.encrypt(ContentType.CHANGE_CIPHER_SPEC,
287                            change_cipher_spec_byte, 0, 1));
288        }
289        setSession(session);
290        activeWriteState = pendingConnectionState;
291        if (logger != null) {
292            logger.println("SSLRecordProtocol.getChangeCipherSpecMesage");
293            logger.println("activeWriteState = pendingConnectionState");
294            logger.print(change_cipher_spec_message);
295        }
296        return change_cipher_spec_message;
297    }
298
299    /**
300     * Retrieves the fragment field of TLSCiphertext, and than
301     * depending on the established Connection State
302     * decrypts and decompresses it. The following structure is expected
303     * on the input at the moment of the call:
304     *
305     *  struct {
306     *      ContentType type;
307     *      ProtocolVersion version;
308     *      uint16 length;
309     *      select (CipherSpec.cipher_type) {
310     *          case stream: GenericStreamCipher;
311     *          case block: GenericBlockCipher;
312     *      } fragment;
313     *  } TLSCiphertext;
314     *
315     * (as specified by RFC 2246, TLS v1 Protocol specification)
316     *
317     * In addition this method can recognize SSLv2 hello message which
318     * are often used to establish the SSL/TLS session.
319     *
320     * @throws IOException if some io errors have been occurred
321     * @throws EndOfSourceException if underlying input stream
322     *                              has ran out of data.
323     * @throws EndOfBufferException if there was not enough data
324     *                              to build complete ssl packet.
325     * @return the type of unwrapped message.
326     */
327    protected int unwrap() throws IOException {
328        if (logger != null) {
329            logger.println("SSLRecordProtocol.unwrap: BEGIN [");
330        }
331        int type = in.readUint8();
332        if ((type < ContentType.CHANGE_CIPHER_SPEC)
333                || (type > ContentType.APPLICATION_DATA)) {
334            if (logger != null) {
335                logger.println("Non v3.1 message type:" + type);
336            }
337            if (type >= 0x80) {
338                // it is probably SSL v2 client_hello message
339                // (see SSL v2 spec at:
340                // http://wp.netscape.com/eng/security/SSL_2.html)
341                int length = (type & 0x7f) << 8 | in.read();
342                byte[] fragment = in.read(length);
343                handshakeProtocol.unwrapSSLv2(fragment);
344                if (logger != null) {
345                    logger.println(
346                            "SSLRecordProtocol:unwrap ] END, SSLv2 type");
347                }
348                return ContentType.HANDSHAKE;
349            }
350            throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
351                    new SSLProtocolException(
352                        "Unexpected message type has been received: "+type));
353        }
354        if (logger != null) {
355            logger.println("Got the message of type: " + type);
356        }
357        if (version != null) {
358            if ((in.read() != version[0])
359                    || (in.read() != version[1])) {
360                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
361                        new SSLProtocolException(
362                            "Unexpected message type has been received: " +
363                            type));
364            }
365        } else {
366            in.skip(2); // just skip the version number
367        }
368        int length = in.readUint16();
369        if (logger != null) {
370            logger.println("TLSCiphertext.fragment["+length+"]: ...");
371        }
372        if (length > MAX_CIPHERED_DATA_LENGTH) {
373            throw new AlertException(AlertProtocol.RECORD_OVERFLOW,
374                    new SSLProtocolException(
375                        "Received message is too big."));
376        }
377        byte[] fragment = in.read(length);
378        if (logger != null) {
379            logger.print(fragment);
380        }
381        if (activeReadState != null) {
382            fragment = activeReadState.decrypt((byte) type, fragment);
383            if (logger != null) {
384                logger.println("TLSPlaintext.fragment:");
385                logger.print(fragment);
386            }
387        }
388        if (fragment.length > MAX_DATA_LENGTH) {
389            throw new AlertException(AlertProtocol.DECOMPRESSION_FAILURE,
390                    new SSLProtocolException(
391                        "Decompressed plain data is too big."));
392        }
393        switch (type) {
394            case ContentType.CHANGE_CIPHER_SPEC:
395                // notify handshake protocol:
396                handshakeProtocol.receiveChangeCipherSpec();
397                setSession(handshakeProtocol.getSession());
398                // change cipher spec message has been received, so:
399                if (logger != null) {
400                    logger.println("activeReadState = pendingConnectionState");
401                }
402                activeReadState = pendingConnectionState;
403                break;
404            case ContentType.ALERT:
405                alert(fragment[0], fragment[1]);
406                break;
407            case ContentType.HANDSHAKE:
408                handshakeProtocol.unwrap(fragment);
409                break;
410            case ContentType.APPLICATION_DATA:
411                if (logger != null) {
412                    logger.println(
413                            "TLSCiphertext.unwrap: APP DATA["+length+"]:");
414                    logger.println(new String(fragment));
415                }
416                appData.append(fragment);
417                break;
418            default:
419                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
420                        new SSLProtocolException(
421                            "Unexpected message type has been received: " +
422                            type));
423        }
424        if (logger != null) {
425            logger.println("SSLRecordProtocol:unwrap ] END, type: " + type);
426        }
427        return type;
428    }
429
430    /**
431     * Passes the alert information to the alert protocol.
432     * @param   level:  byte
433     * @param   description:    byte
434     */
435    protected void alert(byte level, byte description) {
436        if (logger != null) {
437            logger.println("SSLRecordProtocol.allert: "+level+" "+description);
438        }
439        alertProtocol.alert(level, description);
440    }
441
442    /**
443     * Sets up the SSL version used in this connection.
444     * This method is calling from the handshake protocol after
445     * it becomes known which protocol version will be used.
446     * @param   ver:    byte[]
447     * @return
448     */
449    protected void setVersion(byte[] ver) {
450        this.version = ver;
451    }
452
453    /**
454     * Shuts down the protocol. It will be impossible to use the instance
455     * after the calling of this method.
456     */
457    protected void shutdown() {
458        session = null;
459        version = null;
460        in = null;
461        handshakeProtocol = null;
462        alertProtocol = null;
463        appData = null;
464        if (pendingConnectionState != null) {
465            pendingConnectionState.shutdown();
466        }
467        pendingConnectionState = null;
468        if (activeReadState != null) {
469            activeReadState.shutdown();
470        }
471        activeReadState = null;
472        if (activeReadState != null) {
473            activeReadState.shutdown();
474        }
475        activeWriteState = null;
476    }
477}
478