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