SSLSocketOutputRecord.java revision 12072:6721ff11d592
1/* 2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.security.ssl; 27 28import java.io.*; 29import java.nio.*; 30import java.util.Arrays; 31 32import javax.net.ssl.SSLException; 33import javax.net.ssl.SSLHandshakeException; 34import sun.misc.HexDumpEncoder; 35 36 37/** 38 * {@code OutputRecord} implementation for {@code SSLSocket}. 39 */ 40final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { 41 private OutputStream deliverStream = null; 42 43 SSLSocketOutputRecord() { 44 this.writeAuthenticator = MAC.TLS_NULL; 45 46 this.packetSize = SSLRecord.maxRecordSize; 47 this.protocolVersion = ProtocolVersion.DEFAULT_TLS; 48 } 49 50 @Override 51 void encodeAlert(byte level, byte description) throws IOException { 52 // use the buf of ByteArrayOutputStream 53 int position = headerSize + writeCipher.getExplicitNonceSize(); 54 count = position; 55 56 write(level); 57 write(description); 58 59 if (debug != null && Debug.isOn("record")) { 60 System.out.println(Thread.currentThread().getName() + 61 ", WRITE: " + protocolVersion + 62 " " + Record.contentName(Record.ct_alert) + 63 ", length = " + (count - headerSize)); 64 } 65 66 // Encrypt the fragment and wrap up a record. 67 encrypt(writeAuthenticator, writeCipher, 68 Record.ct_alert, headerSize); 69 70 // deliver this message 71 deliverStream.write(buf, 0, count); // may throw IOException 72 deliverStream.flush(); // may throw IOException 73 74 if (debug != null && Debug.isOn("packet")) { 75 Debug.printHex( 76 "[Raw write]: length = " + count, buf, 0, count); 77 } 78 79 // reset the internal buffer 80 count = 0; 81 } 82 83 @Override 84 void encodeHandshake(byte[] source, 85 int offset, int length) throws IOException { 86 87 if (firstMessage) { 88 firstMessage = false; 89 90 if ((helloVersion == ProtocolVersion.SSL20Hello) && 91 (source[offset] == HandshakeMessage.ht_client_hello) && 92 // 5: recode header size 93 (source[offset + 4 + 2 + 32] == 0)) { 94 // V3 session ID is empty 95 // 4: handshake header size 96 // 2: client_version in ClientHello 97 // 32: random in ClientHello 98 99 ByteBuffer v2ClientHello = encodeV2ClientHello( 100 source, (offset + 4), (length - 4)); 101 102 byte[] record = v2ClientHello.array(); // array offset is zero 103 int limit = v2ClientHello.limit(); 104 handshakeHash.update(record, 2, (limit - 2)); 105 106 if (debug != null && Debug.isOn("record")) { 107 System.out.println(Thread.currentThread().getName() + 108 ", WRITE: SSLv2 ClientHello message" + 109 ", length = " + limit); 110 } 111 112 // deliver this message 113 // 114 // Version 2 ClientHello message should be plaintext. 115 // 116 // No max fragment length negotiation. 117 deliverStream.write(record, 0, limit); 118 deliverStream.flush(); 119 120 if (debug != null && Debug.isOn("packet")) { 121 Debug.printHex( 122 "[Raw write]: length = " + count, record, 0, limit); 123 } 124 125 return; 126 } 127 } 128 129 byte handshakeType = source[0]; 130 if (handshakeType != HandshakeMessage.ht_hello_request) { 131 handshakeHash.update(source, offset, length); 132 } 133 134 int fragLimit = getFragLimit(); 135 int position = headerSize + writeCipher.getExplicitNonceSize(); 136 if (count == 0) { 137 count = position; 138 } 139 140 if ((count - position) < (fragLimit - length)) { 141 write(source, offset, length); 142 return; 143 } 144 145 for (int limit = (offset + length); offset < limit;) { 146 147 int remains = (limit - offset) + (count - position); 148 int fragLen = Math.min(fragLimit, remains); 149 150 // use the buf of ByteArrayOutputStream 151 write(source, offset, fragLen); 152 if (remains < fragLimit) { 153 return; 154 } 155 156 if (debug != null && Debug.isOn("record")) { 157 System.out.println(Thread.currentThread().getName() + 158 ", WRITE: " + protocolVersion + 159 " " + Record.contentName(Record.ct_handshake) + 160 ", length = " + (count - headerSize)); 161 } 162 163 // Encrypt the fragment and wrap up a record. 164 encrypt(writeAuthenticator, writeCipher, 165 Record.ct_handshake, headerSize); 166 167 // deliver this message 168 deliverStream.write(buf, 0, count); // may throw IOException 169 deliverStream.flush(); // may throw IOException 170 171 if (debug != null && Debug.isOn("packet")) { 172 Debug.printHex( 173 "[Raw write]: length = " + count, buf, 0, count); 174 } 175 176 // reset the offset 177 offset += fragLen; 178 179 // reset the internal buffer 180 count = position; 181 } 182 } 183 184 @Override 185 void encodeChangeCipherSpec() throws IOException { 186 187 // use the buf of ByteArrayOutputStream 188 int position = headerSize + writeCipher.getExplicitNonceSize(); 189 count = position; 190 191 write((byte)1); // byte 1: change_cipher_spec( 192 193 if (debug != null && Debug.isOn("record")) { 194 System.out.println(Thread.currentThread().getName() + 195 ", WRITE: " + protocolVersion + 196 " " + Record.contentName(Record.ct_change_cipher_spec) + 197 ", length = " + (count - headerSize)); 198 } 199 200 // Encrypt the fragment and wrap up a record. 201 encrypt(writeAuthenticator, writeCipher, 202 Record.ct_change_cipher_spec, headerSize); 203 204 // deliver this message 205 deliverStream.write(buf, 0, count); // may throw IOException 206 // deliverStream.flush(); // flush in Finished 207 208 if (debug != null && Debug.isOn("packet")) { 209 Debug.printHex( 210 "[Raw write]: length = " + count, buf, 0, count); 211 } 212 213 // reset the internal buffer 214 count = 0; 215 } 216 217 @Override 218 public void flush() throws IOException { 219 int position = headerSize + writeCipher.getExplicitNonceSize(); 220 if (count <= position) { 221 return; 222 } 223 224 if (debug != null && Debug.isOn("record")) { 225 System.out.println(Thread.currentThread().getName() + 226 ", WRITE: " + protocolVersion + 227 " " + Record.contentName(Record.ct_handshake) + 228 ", length = " + (count - headerSize)); 229 } 230 231 // Encrypt the fragment and wrap up a record. 232 encrypt(writeAuthenticator, writeCipher, 233 Record.ct_handshake, headerSize); 234 235 // deliver this message 236 deliverStream.write(buf, 0, count); // may throw IOException 237 deliverStream.flush(); // may throw IOException 238 239 if (debug != null && Debug.isOn("packet")) { 240 Debug.printHex( 241 "[Raw write]: length = " + count, buf, 0, count); 242 } 243 244 // reset the internal buffer 245 count = 0; // DON'T use position 246 } 247 248 @Override 249 void deliver(byte[] source, int offset, int length) throws IOException { 250 251 if (writeAuthenticator.seqNumOverflow()) { 252 if (debug != null && Debug.isOn("ssl")) { 253 System.out.println(Thread.currentThread().getName() + 254 ", sequence number extremely close to overflow " + 255 "(2^64-1 packets). Closing connection."); 256 } 257 258 throw new SSLHandshakeException("sequence number overflow"); 259 } 260 261 boolean isFirstRecordOfThePayload = true; 262 for (int limit = (offset + length); offset < limit;) { 263 int macLen = 0; 264 if (writeAuthenticator instanceof MAC) { 265 macLen = ((MAC)writeAuthenticator).MAClen(); 266 } 267 268 int fragLen; 269 if (packetSize > 0) { 270 fragLen = Math.min(maxRecordSize, packetSize); 271 fragLen = writeCipher.calculateFragmentSize( 272 fragLen, macLen, headerSize); 273 274 fragLen = Math.min(fragLen, Record.maxDataSize); 275 } else { 276 fragLen = Record.maxDataSize; 277 } 278 279 if (fragmentSize > 0) { 280 fragLen = Math.min(fragLen, fragmentSize); 281 } 282 283 if (isFirstRecordOfThePayload && needToSplitPayload()) { 284 fragLen = 1; 285 isFirstRecordOfThePayload = false; 286 } else { 287 fragLen = Math.min(fragLen, (limit - offset)); 288 } 289 290 // use the buf of ByteArrayOutputStream 291 int position = headerSize + writeCipher.getExplicitNonceSize(); 292 count = position; 293 write(source, offset, fragLen); 294 295 if (debug != null && Debug.isOn("record")) { 296 System.out.println(Thread.currentThread().getName() + 297 ", WRITE: " + protocolVersion + 298 " " + Record.contentName(Record.ct_application_data) + 299 ", length = " + (count - headerSize)); 300 } 301 302 // Encrypt the fragment and wrap up a record. 303 encrypt(writeAuthenticator, writeCipher, 304 Record.ct_application_data, headerSize); 305 306 // deliver this message 307 deliverStream.write(buf, 0, count); // may throw IOException 308 deliverStream.flush(); // may throw IOException 309 310 if (debug != null && Debug.isOn("packet")) { 311 Debug.printHex( 312 "[Raw write]: length = " + count, buf, 0, count); 313 } 314 315 // reset the internal buffer 316 count = 0; 317 318 if (isFirstAppOutputRecord) { 319 isFirstAppOutputRecord = false; 320 } 321 322 offset += fragLen; 323 } 324 } 325 326 @Override 327 void setDeliverStream(OutputStream outputStream) { 328 this.deliverStream = outputStream; 329 } 330 331 /* 332 * Need to split the payload except the following cases: 333 * 334 * 1. protocol version is TLS 1.1 or later; 335 * 2. bulk cipher does not use CBC mode, including null bulk cipher suites. 336 * 3. the payload is the first application record of a freshly 337 * negotiated TLS session. 338 * 4. the CBC protection is disabled; 339 * 340 * By default, we counter chosen plaintext issues on CBC mode 341 * ciphersuites in SSLv3/TLS1.0 by sending one byte of application 342 * data in the first record of every payload, and the rest in 343 * subsequent record(s). Note that the issues have been solved in 344 * TLS 1.1 or later. 345 * 346 * It is not necessary to split the very first application record of 347 * a freshly negotiated TLS session, as there is no previous 348 * application data to guess. To improve compatibility, we will not 349 * split such records. 350 * 351 * This avoids issues in the outbound direction. For a full fix, 352 * the peer must have similar protections. 353 */ 354 boolean needToSplitPayload() { 355 return (!protocolVersion.useTLS11PlusSpec()) && 356 writeCipher.isCBCMode() && !isFirstAppOutputRecord && 357 Record.enableCBCProtection; 358 } 359 360 private int getFragLimit() { 361 int macLen = 0; 362 if (writeAuthenticator instanceof MAC) { 363 macLen = ((MAC)writeAuthenticator).MAClen(); 364 } 365 366 int fragLimit; 367 if (packetSize > 0) { 368 fragLimit = Math.min(maxRecordSize, packetSize); 369 fragLimit = writeCipher.calculateFragmentSize( 370 fragLimit, macLen, headerSize); 371 372 fragLimit = Math.min(fragLimit, Record.maxDataSize); 373 } else { 374 fragLimit = Record.maxDataSize; 375 } 376 377 if (fragmentSize > 0) { 378 fragLimit = Math.min(fragLimit, fragmentSize); 379 } 380 381 return fragLimit; 382 } 383} 384