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 
  26 package sun.security.ssl;
  27 
  28 import java.io.*;
  29 import java.nio.*;
  30 import java.util.*;
  31 
  32 import javax.net.ssl.SSLException;
  33 import javax.net.ssl.SSLHandshakeException;
  34 import sun.security.util.HexDumpEncoder;
  35 import static sun.security.ssl.Ciphertext.RecordType;
  36 
  37 /**
  38  * {@code OutputRecord} implementation for {@code SSLEngine}.
  39  */
  40 final 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 }