SSLEngineOutputRecord.java revision 12745:f068a4ffddd2
1/* 2 * Copyright (c) 1996, 2015, 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.*; 31 32import javax.net.ssl.SSLException; 33import javax.net.ssl.SSLHandshakeException; 34import sun.misc.HexDumpEncoder; 35import static sun.security.ssl.Ciphertext.RecordType; 36 37/** 38 * {@code OutputRecord} implementation for {@code SSLEngine}. 39 */ 40final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { 41 42 private HandshakeFragment fragmenter = null; 43 private LinkedList<RecordMemo> alertMemos = new LinkedList<>(); 44 private boolean isTalkingToV2 = false; // SSLv2Hello 45 private ByteBuffer v2ClientHello = null; // SSLv2Hello 46 47 private boolean isCloseWaiting = false; 48 49 SSLEngineOutputRecord() { 50 this.writeAuthenticator = MAC.TLS_NULL; 51 52 this.packetSize = SSLRecord.maxRecordSize; 53 this.protocolVersion = ProtocolVersion.DEFAULT_TLS; 54 } 55 56 @Override 57 public synchronized void close() throws IOException { 58 if (!isClosed) { 59 if (alertMemos != null && !alertMemos.isEmpty()) { 60 isCloseWaiting = true; 61 } else { 62 super.close(); 63 } 64 } 65 } 66 67 @Override 68 void encodeAlert(byte level, byte description) throws IOException { 69 RecordMemo memo = new RecordMemo(); 70 71 memo.contentType = Record.ct_alert; 72 memo.majorVersion = protocolVersion.major; 73 memo.minorVersion = protocolVersion.minor; 74 memo.encodeCipher = writeCipher; 75 memo.encodeAuthenticator = writeAuthenticator; 76 77 memo.fragment = new byte[2]; 78 memo.fragment[0] = level; 79 memo.fragment[1] = description; 80 81 alertMemos.add(memo); 82 } 83 84 @Override 85 void encodeHandshake(byte[] source, 86 int offset, int length) throws IOException { 87 88 if (fragmenter == null) { 89 fragmenter = new HandshakeFragment(); 90 } 91 92 if (firstMessage) { 93 firstMessage = false; 94 95 if ((helloVersion == ProtocolVersion.SSL20Hello) && 96 (source[offset] == HandshakeMessage.ht_client_hello) && 97 // 5: recode header size 98 (source[offset + 4 + 2 + 32] == 0)) { 99 // V3 session ID is empty 100 // 4: handshake header size 101 // 2: client_version in ClientHello 102 // 32: random in ClientHello 103 104 // Double space should be big enough for the converted message. 105 v2ClientHello = encodeV2ClientHello( 106 source, (offset + 4), (length - 4)); 107 108 v2ClientHello.position(2); // exclude the header 109 handshakeHash.update(v2ClientHello); 110 v2ClientHello.position(0); 111 112 return; 113 } 114 } 115 116 byte handshakeType = source[offset]; 117 if (handshakeType != HandshakeMessage.ht_hello_request) { 118 handshakeHash.update(source, offset, length); 119 } 120 121 fragmenter.queueUpFragment(source, offset, length); 122 } 123 124 @Override 125 void encodeChangeCipherSpec() throws IOException { 126 if (fragmenter == null) { 127 fragmenter = new HandshakeFragment(); 128 } 129 fragmenter.queueUpChangeCipherSpec(); 130 } 131 132 @Override 133 void encodeV2NoCipher() throws IOException { 134 isTalkingToV2 = true; 135 } 136 137 @Override 138 Ciphertext encode(ByteBuffer[] sources, int offset, int length, 139 ByteBuffer destination) throws IOException { 140 141 if (writeAuthenticator.seqNumOverflow()) { 142 if (debug != null && Debug.isOn("ssl")) { 143 System.out.println(Thread.currentThread().getName() + 144 ", sequence number extremely close to overflow " + 145 "(2^64-1 packets). Closing connection."); 146 } 147 148 throw new SSLHandshakeException("sequence number overflow"); 149 } 150 151 int macLen = 0; 152 if (writeAuthenticator instanceof MAC) { 153 macLen = ((MAC)writeAuthenticator).MAClen(); 154 } 155 156 int dstLim = destination.limit(); 157 boolean isFirstRecordOfThePayload = true; 158 int packetLeftSize = Math.min(maxRecordSize, packetSize); 159 boolean needMorePayload = true; 160 long recordSN = 0L; 161 while (needMorePayload) { 162 int fragLen; 163 if (isFirstRecordOfThePayload && needToSplitPayload()) { 164 needMorePayload = true; 165 166 fragLen = 1; 167 isFirstRecordOfThePayload = false; 168 } else { 169 needMorePayload = false; 170 171 if (packetLeftSize > 0) { 172 fragLen = writeCipher.calculateFragmentSize( 173 packetLeftSize, macLen, headerSize); 174 175 fragLen = Math.min(fragLen, Record.maxDataSize); 176 } else { 177 fragLen = Record.maxDataSize; 178 } 179 180 if (fragmentSize > 0) { 181 fragLen = Math.min(fragLen, fragmentSize); 182 } 183 } 184 185 int dstPos = destination.position(); 186 int dstContent = dstPos + headerSize + 187 writeCipher.getExplicitNonceSize(); 188 destination.position(dstContent); 189 190 int remains = Math.min(fragLen, destination.remaining()); 191 fragLen = 0; 192 int srcsLen = offset + length; 193 for (int i = offset; (i < srcsLen) && (remains > 0); i++) { 194 int amount = Math.min(sources[i].remaining(), remains); 195 int srcLimit = sources[i].limit(); 196 sources[i].limit(sources[i].position() + amount); 197 destination.put(sources[i]); 198 sources[i].limit(srcLimit); // restore the limit 199 remains -= amount; 200 fragLen += amount; 201 202 if (remains > 0) { 203 offset++; 204 length--; 205 } 206 } 207 208 destination.limit(destination.position()); 209 destination.position(dstContent); 210 211 if ((debug != null) && Debug.isOn("record")) { 212 System.out.println(Thread.currentThread().getName() + 213 ", WRITE: " + protocolVersion + " " + 214 Record.contentName(Record.ct_application_data) + 215 ", length = " + destination.remaining()); 216 } 217 218 // Encrypt the fragment and wrap up a record. 219 recordSN = encrypt(writeAuthenticator, writeCipher, 220 Record.ct_application_data, destination, 221 dstPos, dstLim, headerSize, 222 protocolVersion, false); 223 224 if ((debug != null) && Debug.isOn("packet")) { 225 ByteBuffer temporary = destination.duplicate(); 226 temporary.limit(temporary.position()); 227 temporary.position(dstPos); 228 Debug.printHex( 229 "[Raw write]: length = " + temporary.remaining(), 230 temporary); 231 } 232 233 packetLeftSize -= destination.position() - dstPos; 234 235 // remain the limit unchanged 236 destination.limit(dstLim); 237 238 if (isFirstAppOutputRecord) { 239 isFirstAppOutputRecord = false; 240 } 241 } 242 243 return new Ciphertext(RecordType.RECORD_APPLICATION_DATA, recordSN); 244 } 245 246 @Override 247 Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException { 248 if (isTalkingToV2) { // SSLv2Hello 249 // We don't support SSLv2. Send an SSLv2 error message 250 // so that the connection can be closed gracefully. 251 // 252 // Please don't change the limit of the destination buffer. 253 destination.put(SSLRecord.v2NoCipher); 254 if (debug != null && Debug.isOn("packet")) { 255 Debug.printHex( 256 "[Raw write]: length = " + SSLRecord.v2NoCipher.length, 257 SSLRecord.v2NoCipher); 258 } 259 260 isTalkingToV2 = false; 261 262 return new Ciphertext(RecordType.RECORD_ALERT, -1L); 263 } 264 265 if (v2ClientHello != null) { 266 // deliver the SSLv2 format ClientHello message 267 // 268 // Please don't change the limit of the destination buffer. 269 if (debug != null) { 270 if (Debug.isOn("record")) { 271 System.out.println(Thread.currentThread().getName() + 272 ", WRITE: SSLv2 ClientHello message" + 273 ", length = " + v2ClientHello.remaining()); 274 } 275 276 if (Debug.isOn("packet")) { 277 Debug.printHex( 278 "[Raw write]: length = " + v2ClientHello.remaining(), 279 v2ClientHello); 280 } 281 } 282 283 destination.put(v2ClientHello); 284 v2ClientHello = null; 285 286 return new Ciphertext(RecordType.RECORD_CLIENT_HELLO, -1L); 287 } 288 289 if (alertMemos != null && !alertMemos.isEmpty()) { 290 RecordMemo memo = alertMemos.pop(); 291 292 int macLen = 0; 293 if (memo.encodeAuthenticator instanceof MAC) { 294 macLen = ((MAC)memo.encodeAuthenticator).MAClen(); 295 } 296 297 int dstPos = destination.position(); 298 int dstLim = destination.limit(); 299 int dstContent = dstPos + headerSize + 300 writeCipher.getExplicitNonceSize(); 301 destination.position(dstContent); 302 303 destination.put(memo.fragment); 304 305 destination.limit(destination.position()); 306 destination.position(dstContent); 307 308 if ((debug != null) && Debug.isOn("record")) { 309 System.out.println(Thread.currentThread().getName() + 310 ", WRITE: " + protocolVersion + " " + 311 Record.contentName(Record.ct_alert) + 312 ", length = " + destination.remaining()); 313 } 314 315 // Encrypt the fragment and wrap up a record. 316 long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher, 317 Record.ct_alert, destination, dstPos, dstLim, headerSize, 318 ProtocolVersion.valueOf(memo.majorVersion, 319 memo.minorVersion), false); 320 321 if ((debug != null) && Debug.isOn("packet")) { 322 ByteBuffer temporary = destination.duplicate(); 323 temporary.limit(temporary.position()); 324 temporary.position(dstPos); 325 Debug.printHex( 326 "[Raw write]: length = " + temporary.remaining(), 327 temporary); 328 } 329 330 // remain the limit unchanged 331 destination.limit(dstLim); 332 333 if (isCloseWaiting && (memo.contentType == Record.ct_alert)) { 334 isCloseWaiting = true; 335 close(); 336 } 337 return new Ciphertext(RecordType.RECORD_ALERT, recordSN); 338 } 339 340 if (fragmenter != null) { 341 return fragmenter.acquireCiphertext(destination); 342 } 343 344 return null; 345 } 346 347 @Override 348 boolean isEmpty() { 349 return (!isTalkingToV2) && (v2ClientHello == null) && 350 ((fragmenter == null) || fragmenter.isEmpty()) && 351 ((alertMemos == null) || alertMemos.isEmpty()); 352 } 353 354 // buffered record fragment 355 private static class RecordMemo { 356 byte contentType; 357 byte majorVersion; 358 byte minorVersion; 359 CipherBox encodeCipher; 360 Authenticator encodeAuthenticator; 361 362 byte[] fragment; 363 } 364 365 private static class HandshakeMemo extends RecordMemo { 366 byte handshakeType; 367 int acquireOffset; 368 } 369 370 final class HandshakeFragment { 371 private LinkedList<RecordMemo> handshakeMemos = new LinkedList<>(); 372 373 void queueUpFragment(byte[] source, 374 int offset, int length) throws IOException { 375 376 HandshakeMemo memo = new HandshakeMemo(); 377 378 memo.contentType = Record.ct_handshake; 379 memo.majorVersion = protocolVersion.major; // kick start version? 380 memo.minorVersion = protocolVersion.minor; 381 memo.encodeCipher = writeCipher; 382 memo.encodeAuthenticator = writeAuthenticator; 383 384 memo.handshakeType = source[offset]; 385 memo.acquireOffset = 0; 386 memo.fragment = new byte[length - 4]; // 4: header size 387 // 1: HandshakeType 388 // 3: message length 389 System.arraycopy(source, offset + 4, memo.fragment, 0, length - 4); 390 391 handshakeMemos.add(memo); 392 } 393 394 void queueUpChangeCipherSpec() { 395 RecordMemo memo = new RecordMemo(); 396 397 memo.contentType = Record.ct_change_cipher_spec; 398 memo.majorVersion = protocolVersion.major; 399 memo.minorVersion = protocolVersion.minor; 400 memo.encodeCipher = writeCipher; 401 memo.encodeAuthenticator = writeAuthenticator; 402 403 memo.fragment = new byte[1]; 404 memo.fragment[0] = 1; 405 406 handshakeMemos.add(memo); 407 } 408 409 Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException { 410 if (isEmpty()) { 411 return null; 412 } 413 414 RecordMemo memo = handshakeMemos.getFirst(); 415 HandshakeMemo hsMemo = null; 416 if (memo.contentType == Record.ct_handshake) { 417 hsMemo = (HandshakeMemo)memo; 418 } 419 420 int macLen = 0; 421 if (memo.encodeAuthenticator instanceof MAC) { 422 macLen = ((MAC)memo.encodeAuthenticator).MAClen(); 423 } 424 425 // ChangeCipherSpec message is pretty small. Don't worry about 426 // the fragmentation of ChangeCipherSpec record. 427 int fragLen; 428 if (packetSize > 0) { 429 fragLen = Math.min(maxRecordSize, packetSize); 430 fragLen = memo.encodeCipher.calculateFragmentSize( 431 fragLen, macLen, headerSize); 432 } else { 433 fragLen = Record.maxDataSize; 434 } 435 436 if (fragmentSize > 0) { 437 fragLen = Math.min(fragLen, fragmentSize); 438 } 439 440 int dstPos = dstBuf.position(); 441 int dstLim = dstBuf.limit(); 442 int dstContent = dstPos + headerSize + 443 memo.encodeCipher.getExplicitNonceSize(); 444 dstBuf.position(dstContent); 445 446 if (hsMemo != null) { 447 int remainingFragLen = fragLen; 448 while ((remainingFragLen > 0) && !handshakeMemos.isEmpty()) { 449 int memoFragLen = hsMemo.fragment.length; 450 if (hsMemo.acquireOffset == 0) { 451 // Don't fragment handshake message header 452 if (remainingFragLen <= 4) { 453 break; 454 } 455 456 dstBuf.put(hsMemo.handshakeType); 457 dstBuf.put((byte)((memoFragLen >> 16) & 0xFF)); 458 dstBuf.put((byte)((memoFragLen >> 8) & 0xFF)); 459 dstBuf.put((byte)(memoFragLen & 0xFF)); 460 461 remainingFragLen -= 4; 462 } // Otherwise, handshake message is fragmented. 463 464 int chipLen = Math.min(remainingFragLen, 465 (memoFragLen - hsMemo.acquireOffset)); 466 dstBuf.put(hsMemo.fragment, hsMemo.acquireOffset, chipLen); 467 468 hsMemo.acquireOffset += chipLen; 469 if (hsMemo.acquireOffset == memoFragLen) { 470 handshakeMemos.removeFirst(); 471 472 // still have space for more records? 473 if ((remainingFragLen > chipLen) && 474 !handshakeMemos.isEmpty()) { 475 476 // look for the next buffered record fragment 477 RecordMemo reMemo = handshakeMemos.getFirst(); 478 if (reMemo.contentType == Record.ct_handshake) { 479 hsMemo = (HandshakeMemo)reMemo; 480 } else { 481 // not handshake message, break the loop 482 break; 483 } 484 } 485 } 486 487 remainingFragLen -= chipLen; 488 } 489 490 fragLen -= remainingFragLen; 491 } else { 492 fragLen = Math.min(fragLen, memo.fragment.length); 493 dstBuf.put(memo.fragment, 0, fragLen); 494 495 handshakeMemos.removeFirst(); 496 } 497 498 dstBuf.limit(dstBuf.position()); 499 dstBuf.position(dstContent); 500 501 if ((debug != null) && Debug.isOn("record")) { 502 System.out.println(Thread.currentThread().getName() + 503 ", WRITE: " + protocolVersion + " " + 504 Record.contentName(memo.contentType) + 505 ", length = " + dstBuf.remaining()); 506 } 507 508 // Encrypt the fragment and wrap up a record. 509 long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher, 510 memo.contentType, dstBuf, 511 dstPos, dstLim, headerSize, 512 ProtocolVersion.valueOf(memo.majorVersion, 513 memo.minorVersion), false); 514 515 if ((debug != null) && Debug.isOn("packet")) { 516 ByteBuffer temporary = dstBuf.duplicate(); 517 temporary.limit(temporary.position()); 518 temporary.position(dstPos); 519 Debug.printHex( 520 "[Raw write]: length = " + temporary.remaining(), 521 temporary); 522 } 523 524 // remain the limit unchanged 525 dstBuf.limit(dstLim); 526 527 // Reset the fragmentation offset. 528 if (hsMemo != null) { 529 return new Ciphertext(RecordType.valueOf( 530 hsMemo.contentType, hsMemo.handshakeType), recordSN); 531 } else { 532 return new Ciphertext( 533 RecordType.RECORD_CHANGE_CIPHER_SPEC, recordSN); 534 } 535 } 536 537 boolean isEmpty() { 538 return handshakeMemos.isEmpty(); 539 } 540 } 541 542 /* 543 * Need to split the payload except the following cases: 544 * 545 * 1. protocol version is TLS 1.1 or later; 546 * 2. bulk cipher does not use CBC mode, including null bulk cipher suites. 547 * 3. the payload is the first application record of a freshly 548 * negotiated TLS session. 549 * 4. the CBC protection is disabled; 550 * 551 * By default, we counter chosen plaintext issues on CBC mode 552 * ciphersuites in SSLv3/TLS1.0 by sending one byte of application 553 * data in the first record of every payload, and the rest in 554 * subsequent record(s). Note that the issues have been solved in 555 * TLS 1.1 or later. 556 * 557 * It is not necessary to split the very first application record of 558 * a freshly negotiated TLS session, as there is no previous 559 * application data to guess. To improve compatibility, we will not 560 * split such records. 561 * 562 * This avoids issues in the outbound direction. For a full fix, 563 * the peer must have similar protections. 564 */ 565 boolean needToSplitPayload() { 566 return (!protocolVersion.useTLS11PlusSpec()) && 567 writeCipher.isCBCMode() && !isFirstAppOutputRecord && 568 Record.enableCBCProtection; 569 } 570} 571