jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java
changeset 30904 ec0224270f90
parent 27292 7ff4b24b33ce
child 32649 2ee9017c7597
equal deleted inserted replaced
30903:0c7d705209c6 30904:ec0224270f90
       
     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.misc.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     synchronized public 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 }