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