SSLSocketOutputRecord.java revision 12072:6721ff11d592
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
26package sun.security.ssl;
27
28import java.io.*;
29import java.nio.*;
30import java.util.Arrays;
31
32import javax.net.ssl.SSLException;
33import javax.net.ssl.SSLHandshakeException;
34import sun.misc.HexDumpEncoder;
35
36
37/**
38 * {@code OutputRecord} implementation for {@code SSLSocket}.
39 */
40final 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}
384