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.conscrypt; 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 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