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