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
26package sun.security.ssl;
27
28import java.io.*;
29import java.nio.*;
30import java.util.*;
31
32import javax.net.ssl.SSLException;
33import javax.net.ssl.SSLHandshakeException;
34import sun.security.util.HexDumpEncoder;
35import static sun.security.ssl.Ciphertext.RecordType;
36
37/**
38 * {@code OutputRecord} implementation for {@code SSLEngine}.
39 */
40final 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    public synchronized 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}
571