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