Home | History | Annotate | Download | only in jsse
      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 
     18 package org.apache.harmony.xnet.provider.jsse;
     19 
     20 import java.io.IOException;
     21 import 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  */
     49 public 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