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.ByteArrayInputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.net.SocketException;
32 import java.nio.ByteBuffer;
33 import javax.net.ssl.SSLHandshakeException;
34 
35 /**
36  * {@code OutputRecord} implementation for {@code SSLSocket}.
37  */
38 final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
39     private OutputStream deliverStream = null;
40 
SSLSocketOutputRecord(HandshakeHash handshakeHash)41     SSLSocketOutputRecord(HandshakeHash handshakeHash) {
42         this(handshakeHash, null);
43     }
44 
SSLSocketOutputRecord(HandshakeHash handshakeHash, TransportContext tc)45     SSLSocketOutputRecord(HandshakeHash handshakeHash,
46             TransportContext tc) {
47         super(handshakeHash, SSLCipher.SSLWriteCipher.nullTlsWriteCipher());
48         this.tc = tc;
49         this.packetSize = SSLRecord.maxRecordSize;
50         this.protocolVersion = ProtocolVersion.NONE;
51     }
52 
53     @Override
encodeAlert( byte level, byte description)54     synchronized void encodeAlert(
55             byte level, byte description) throws IOException {
56         if (isClosed()) {
57             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
58                 SSLLogger.warning("outbound has closed, ignore outbound " +
59                     "alert message: " + Alert.nameOf(description));
60             }
61             return;
62         }
63 
64         // use the buf of ByteArrayOutputStream
65         int position = headerSize + writeCipher.getExplicitNonceSize();
66         count = position;
67 
68         write(level);
69         write(description);
70         if (SSLLogger.isOn && SSLLogger.isOn("record")) {
71             SSLLogger.fine("WRITE: " + protocolVersion +
72                     " " + ContentType.ALERT.name +
73                     "(" + Alert.nameOf(description) + ")" +
74                     ", length = " + (count - headerSize));
75         }
76 
77         // Encrypt the fragment and wrap up a record.
78         encrypt(writeCipher, ContentType.ALERT.id, headerSize);
79 
80         // deliver this message
81         deliverStream.write(buf, 0, count);    // may throw IOException
82         deliverStream.flush();                 // may throw IOException
83 
84         if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
85             SSLLogger.fine("Raw write",
86                     (new ByteArrayInputStream(buf, 0, count)));
87         }
88 
89         // reset the internal buffer
90         count = 0;
91     }
92 
93     @Override
encodeHandshake(byte[] source, int offset, int length)94     synchronized void encodeHandshake(byte[] source,
95             int offset, int length) throws IOException {
96         if (isClosed()) {
97             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
98                 SSLLogger.warning("outbound has closed, ignore outbound " +
99                         "handshake message",
100                         ByteBuffer.wrap(source, offset, length));
101             }
102             return;
103         }
104 
105         if (firstMessage) {
106             firstMessage = false;
107 
108             if ((helloVersion == ProtocolVersion.SSL20Hello) &&
109                 (source[offset] == SSLHandshake.CLIENT_HELLO.id) &&
110                                             //  5: recode header size
111                 (source[offset + 4 + 2 + 32] == 0)) {
112                                             // V3 session ID is empty
113                                             //  4: handshake header size
114                                             //  2: client_version in ClientHello
115                                             // 32: random in ClientHello
116 
117                 ByteBuffer v2ClientHello = encodeV2ClientHello(
118                         source, (offset + 4), (length - 4));
119 
120                 byte[] record = v2ClientHello.array();  // array offset is zero
121                 int limit = v2ClientHello.limit();
122                 handshakeHash.deliver(record, 2, (limit - 2));
123 
124                 if (SSLLogger.isOn && SSLLogger.isOn("record")) {
125                     SSLLogger.fine(
126                             "WRITE: SSLv2 ClientHello message" +
127                             ", length = " + limit);
128                 }
129 
130                 // deliver this message
131                 //
132                 // Version 2 ClientHello message should be plaintext.
133                 //
134                 // No max fragment length negotiation.
135                 deliverStream.write(record, 0, limit);
136                 deliverStream.flush();
137 
138                 if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
139                     SSLLogger.fine("Raw write",
140                             (new ByteArrayInputStream(record, 0, limit)));
141                 }
142 
143                 return;
144             }
145         }
146 
147         byte handshakeType = source[0];
148         if (handshakeHash.isHashable(handshakeType)) {
149             handshakeHash.deliver(source, offset, length);
150         }
151 
152         int fragLimit = getFragLimit();
153         int position = headerSize + writeCipher.getExplicitNonceSize();
154         if (count == 0) {
155             count = position;
156         }
157 
158         if ((count - position) < (fragLimit - length)) {
159             write(source, offset, length);
160             return;
161         }
162 
163         for (int limit = (offset + length); offset < limit;) {
164 
165             int remains = (limit - offset) + (count - position);
166             int fragLen = Math.min(fragLimit, remains);
167 
168             // use the buf of ByteArrayOutputStream
169             write(source, offset, fragLen);
170             if (remains < fragLimit) {
171                 return;
172             }
173 
174             if (SSLLogger.isOn && SSLLogger.isOn("record")) {
175                 SSLLogger.fine(
176                         "WRITE: " + protocolVersion +
177                         " " + ContentType.HANDSHAKE.name +
178                         ", length = " + (count - headerSize));
179             }
180 
181             // Encrypt the fragment and wrap up a record.
182             encrypt(writeCipher, ContentType.HANDSHAKE.id, headerSize);
183 
184             // deliver this message
185             deliverStream.write(buf, 0, count);    // may throw IOException
186             deliverStream.flush();                 // may throw IOException
187 
188             if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
189                 SSLLogger.fine("Raw write",
190                         (new ByteArrayInputStream(buf, 0, count)));
191             }
192 
193             // reset the offset
194             offset += fragLen;
195 
196             // reset the internal buffer
197             count = position;
198         }
199     }
200 
201     @Override
encodeChangeCipherSpec()202     synchronized void encodeChangeCipherSpec() throws IOException {
203         if (isClosed()) {
204             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
205                 SSLLogger.warning("outbound has closed, ignore outbound " +
206                     "change_cipher_spec message");
207             }
208             return;
209         }
210 
211         // use the buf of ByteArrayOutputStream
212         int position = headerSize + writeCipher.getExplicitNonceSize();
213         count = position;
214 
215         write((byte)1);         // byte 1: change_cipher_spec(
216 
217         // Encrypt the fragment and wrap up a record.
218         encrypt(writeCipher, ContentType.CHANGE_CIPHER_SPEC.id, headerSize);
219 
220         // deliver this message
221         deliverStream.write(buf, 0, count);        // may throw IOException
222         // deliverStream.flush();                  // flush in Finished
223 
224         if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
225             SSLLogger.fine("Raw write",
226                     (new ByteArrayInputStream(buf, 0, count)));
227         }
228 
229         // reset the internal buffer
230         count = 0;
231     }
232 
233     @Override
flush()234     public synchronized void flush() throws IOException {
235         int position = headerSize + writeCipher.getExplicitNonceSize();
236         if (count <= position) {
237             return;
238         }
239 
240         if (SSLLogger.isOn && SSLLogger.isOn("record")) {
241             SSLLogger.fine(
242                     "WRITE: " + protocolVersion +
243                     " " + ContentType.HANDSHAKE.name +
244                     ", length = " + (count - headerSize));
245         }
246 
247         // Encrypt the fragment and wrap up a record.
248         encrypt(writeCipher, ContentType.HANDSHAKE.id, headerSize);
249 
250         // deliver this message
251         deliverStream.write(buf, 0, count);    // may throw IOException
252         deliverStream.flush();                 // may throw IOException
253 
254         if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
255             SSLLogger.fine("Raw write",
256                     (new ByteArrayInputStream(buf, 0, count)));
257         }
258 
259         // reset the internal buffer
260         count = 0;      // DON'T use position
261     }
262 
263     @Override
deliver( byte[] source, int offset, int length)264     synchronized void deliver(
265             byte[] source, int offset, int length) throws IOException {
266         if (isClosed()) {
267             throw new SocketException("Connection or outbound has been closed");
268         }
269 
270         if (writeCipher.authenticator.seqNumOverflow()) {
271             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
272                 SSLLogger.fine(
273                     "sequence number extremely close to overflow " +
274                     "(2^64-1 packets). Closing connection.");
275             }
276 
277             throw new SSLHandshakeException("sequence number overflow");
278         }
279 
280         boolean isFirstRecordOfThePayload = true;
281         for (int limit = (offset + length); offset < limit;) {
282             int fragLen;
283             if (packetSize > 0) {
284                 fragLen = Math.min(maxRecordSize, packetSize);
285                 fragLen =
286                         writeCipher.calculateFragmentSize(fragLen, headerSize);
287 
288                 fragLen = Math.min(fragLen, Record.maxDataSize);
289             } else {
290                 fragLen = Record.maxDataSize;
291             }
292 
293             // Calculate more impact, for example TLS 1.3 padding.
294             fragLen = calculateFragmentSize(fragLen);
295 
296             if (isFirstRecordOfThePayload && needToSplitPayload()) {
297                 fragLen = 1;
298                 isFirstRecordOfThePayload = false;
299             } else {
300                 fragLen = Math.min(fragLen, (limit - offset));
301             }
302 
303             // use the buf of ByteArrayOutputStream
304             int position = headerSize + writeCipher.getExplicitNonceSize();
305             count = position;
306             write(source, offset, fragLen);
307 
308             if (SSLLogger.isOn && SSLLogger.isOn("record")) {
309                 SSLLogger.fine(
310                         "WRITE: " + protocolVersion +
311                         " " + ContentType.APPLICATION_DATA.name +
312                         ", length = " + (count - position));
313             }
314 
315             // Encrypt the fragment and wrap up a record.
316             encrypt(writeCipher, ContentType.APPLICATION_DATA.id, headerSize);
317 
318             // deliver this message
319             deliverStream.write(buf, 0, count);    // may throw IOException
320             deliverStream.flush();                 // may throw IOException
321 
322             if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
323                 SSLLogger.fine("Raw write",
324                         (new ByteArrayInputStream(buf, 0, count)));
325             }
326 
327             // reset the internal buffer
328             count = 0;
329 
330             if (isFirstAppOutputRecord) {
331                 isFirstAppOutputRecord = false;
332             }
333 
334             offset += fragLen;
335         }
336     }
337 
338     @Override
setDeliverStream(OutputStream outputStream)339     synchronized void setDeliverStream(OutputStream outputStream) {
340         this.deliverStream = outputStream;
341     }
342 
343     /*
344      * Need to split the payload except the following cases:
345      *
346      * 1. protocol version is TLS 1.1 or later;
347      * 2. bulk cipher does not use CBC mode, including null bulk cipher suites.
348      * 3. the payload is the first application record of a freshly
349      *    negotiated TLS session.
350      * 4. the CBC protection is disabled;
351      *
352      * By default, we counter chosen plaintext issues on CBC mode
353      * ciphersuites in SSLv3/TLS1.0 by sending one byte of application
354      * data in the first record of every payload, and the rest in
355      * subsequent record(s). Note that the issues have been solved in
356      * TLS 1.1 or later.
357      *
358      * It is not necessary to split the very first application record of
359      * a freshly negotiated TLS session, as there is no previous
360      * application data to guess.  To improve compatibility, we will not
361      * split such records.
362      *
363      * This avoids issues in the outbound direction.  For a full fix,
364      * the peer must have similar protections.
365      */
needToSplitPayload()366     private boolean needToSplitPayload() {
367         return (!protocolVersion.useTLS11PlusSpec()) &&
368                 writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
369                 Record.enableCBCProtection;
370     }
371 
getFragLimit()372     private int getFragLimit() {
373         int fragLimit;
374         if (packetSize > 0) {
375             fragLimit = Math.min(maxRecordSize, packetSize);
376             fragLimit =
377                     writeCipher.calculateFragmentSize(fragLimit, headerSize);
378 
379             fragLimit = Math.min(fragLimit, Record.maxDataSize);
380         } else {
381             fragLimit = Record.maxDataSize;
382         }
383 
384         // Calculate more impact, for example TLS 1.3 padding.
385         fragLimit = calculateFragmentSize(fragLimit);
386 
387         return fragLimit;
388     }
389 }
390