/* * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl; import java.io.*; import java.nio.*; import java.util.*; import javax.crypto.BadPaddingException; import javax.net.ssl.*; import sun.security.util.HexDumpEncoder; import static sun.security.ssl.Ciphertext.RecordType; /** * DTLS {@code OutputRecord} implementation for {@code SSLEngine}. */ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { private DTLSFragmenter fragmenter = null; int writeEpoch; int prevWriteEpoch; Authenticator prevWriteAuthenticator; CipherBox prevWriteCipher; private LinkedList<RecordMemo> alertMemos = new LinkedList<>(); DTLSOutputRecord() { this.writeAuthenticator = new MAC(true); this.writeEpoch = 0; this.prevWriteEpoch = 0; this.prevWriteCipher = CipherBox.NULL; this.prevWriteAuthenticator = new MAC(true); this.packetSize = DTLSRecord.maxRecordSize; this.protocolVersion = ProtocolVersion.DEFAULT_DTLS; } @Override void changeWriteCiphers(Authenticator writeAuthenticator, CipherBox writeCipher) throws IOException { encodeChangeCipherSpec(); prevWriteCipher.dispose(); this.prevWriteAuthenticator = this.writeAuthenticator; this.prevWriteCipher = this.writeCipher; this.prevWriteEpoch = this.writeEpoch; this.writeAuthenticator = writeAuthenticator; this.writeCipher = writeCipher; this.writeEpoch++; this.isFirstAppOutputRecord = true; // set the epoch number this.writeAuthenticator.setEpochNumber(this.writeEpoch); } @Override void encodeAlert(byte level, byte description) throws IOException { RecordMemo memo = new RecordMemo(); memo.contentType = Record.ct_alert; memo.majorVersion = protocolVersion.major; memo.minorVersion = protocolVersion.minor; memo.encodeEpoch = writeEpoch; memo.encodeCipher = writeCipher; memo.encodeAuthenticator = writeAuthenticator; memo.fragment = new byte[2]; memo.fragment[0] = level; memo.fragment[1] = description; alertMemos.add(memo); } @Override void encodeChangeCipherSpec() throws IOException { if (fragmenter == null) { fragmenter = new DTLSFragmenter(); } fragmenter.queueUpChangeCipherSpec(); } @Override void encodeHandshake(byte[] source, int offset, int length) throws IOException { if (firstMessage) { firstMessage = false; } if (fragmenter == null) { fragmenter = new DTLSFragmenter(); } fragmenter.queueUpHandshake(source, offset, length); } @Override Ciphertext encode(ByteBuffer[] sources, int offset, int length, ByteBuffer destination) throws IOException { if (writeAuthenticator.seqNumOverflow()) { if (debug != null && Debug.isOn("ssl")) { System.out.println(Thread.currentThread().getName() + ", sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); } throw new SSLHandshakeException("sequence number overflow"); } // not apply to handshake message int macLen = 0; if (writeAuthenticator instanceof MAC) { macLen = ((MAC)writeAuthenticator).MAClen(); } int fragLen; if (packetSize > 0) { fragLen = Math.min(maxRecordSize, packetSize); fragLen = writeCipher.calculateFragmentSize( fragLen, macLen, headerSize); fragLen = Math.min(fragLen, Record.maxDataSize); } else { fragLen = Record.maxDataSize; } if (fragmentSize > 0) { fragLen = Math.min(fragLen, fragmentSize); } int dstPos = destination.position(); int dstLim = destination.limit(); int dstContent = dstPos + headerSize + writeCipher.getExplicitNonceSize(); destination.position(dstContent); int remains = Math.min(fragLen, destination.remaining()); fragLen = 0; int srcsLen = offset + length; for (int i = offset; (i < srcsLen) && (remains > 0); i++) { int amount = Math.min(sources[i].remaining(), remains); int srcLimit = sources[i].limit(); sources[i].limit(sources[i].position() + amount); destination.put(sources[i]); sources[i].limit(srcLimit); // restore the limit remains -= amount; fragLen += amount; } destination.limit(destination.position()); destination.position(dstContent); if ((debug != null) && Debug.isOn("record")) { System.out.println(Thread.currentThread().getName() + ", WRITE: " + protocolVersion + " " + Record.contentName(Record.ct_application_data) + ", length = " + destination.remaining()); } // Encrypt the fragment and wrap up a record. long recordSN = encrypt(writeAuthenticator, writeCipher, Record.ct_application_data, destination, dstPos, dstLim, headerSize, protocolVersion, true); if ((debug != null) && Debug.isOn("packet")) { ByteBuffer temporary = destination.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); Debug.printHex( "[Raw write]: length = " + temporary.remaining(), temporary); } // remain the limit unchanged destination.limit(dstLim); return new Ciphertext(RecordType.RECORD_APPLICATION_DATA, recordSN); } @Override Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException { if (alertMemos != null && !alertMemos.isEmpty()) { RecordMemo memo = alertMemos.pop(); int macLen = 0; if (memo.encodeAuthenticator instanceof MAC) { macLen = ((MAC)memo.encodeAuthenticator).MAClen(); } int dstPos = destination.position(); int dstLim = destination.limit(); int dstContent = dstPos + headerSize + writeCipher.getExplicitNonceSize(); destination.position(dstContent); destination.put(memo.fragment); destination.limit(destination.position()); destination.position(dstContent); if ((debug != null) && Debug.isOn("record")) { System.out.println(Thread.currentThread().getName() + ", WRITE: " + protocolVersion + " " + Record.contentName(Record.ct_alert) + ", length = " + destination.remaining()); } // Encrypt the fragment and wrap up a record. long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher, Record.ct_alert, destination, dstPos, dstLim, headerSize, ProtocolVersion.valueOf(memo.majorVersion, memo.minorVersion), true); if ((debug != null) && Debug.isOn("packet")) { ByteBuffer temporary = destination.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); Debug.printHex( "[Raw write]: length = " + temporary.remaining(), temporary); } // remain the limit unchanged destination.limit(dstLim); return new Ciphertext(RecordType.RECORD_ALERT, recordSN); } if (fragmenter != null) { return fragmenter.acquireCiphertext(destination); } return null; } @Override boolean isEmpty() { return ((fragmenter == null) || fragmenter.isEmpty()) && ((alertMemos == null) || alertMemos.isEmpty()); } @Override void initHandshaker() { // clean up fragmenter = null; } @Override void launchRetransmission() { // Note: Please don't retransmit if there are handshake messages // or alerts waiting in the queue. if (((alertMemos == null) || alertMemos.isEmpty()) && (fragmenter != null) && fragmenter.isRetransmittable()) { fragmenter.setRetransmission(); } } // buffered record fragment private static class RecordMemo { byte contentType; byte majorVersion; byte minorVersion; int encodeEpoch; CipherBox encodeCipher; Authenticator encodeAuthenticator; byte[] fragment; } private static class HandshakeMemo extends RecordMemo { byte handshakeType; int messageSequence; int acquireOffset; } private final class DTLSFragmenter { private LinkedList<RecordMemo> handshakeMemos = new LinkedList<>(); private int acquireIndex = 0; private int messageSequence = 0; private boolean flightIsReady = false; // Per section 4.1.1, RFC 6347: // // If repeated retransmissions do not result in a response, and the // PMTU is unknown, subsequent retransmissions SHOULD back off to a // smaller record size, fragmenting the handshake message as // appropriate. // // In this implementation, two times of retransmits would be attempted // before backing off. The back off is supported only if the packet // size is bigger than 256 bytes. private int retransmits = 2; // attemps of retransmits void queueUpChangeCipherSpec() { // Cleanup if a new flight starts. if (flightIsReady) { handshakeMemos.clear(); acquireIndex = 0; flightIsReady = false; } RecordMemo memo = new RecordMemo(); memo.contentType = Record.ct_change_cipher_spec; memo.majorVersion = protocolVersion.major; memo.minorVersion = protocolVersion.minor; memo.encodeEpoch = writeEpoch; memo.encodeCipher = writeCipher; memo.encodeAuthenticator = writeAuthenticator; memo.fragment = new byte[1]; memo.fragment[0] = 1; handshakeMemos.add(memo); } void queueUpHandshake(byte[] buf, int offset, int length) throws IOException { // Cleanup if a new flight starts. if (flightIsReady) { handshakeMemos.clear(); acquireIndex = 0; flightIsReady = false; } HandshakeMemo memo = new HandshakeMemo(); memo.contentType = Record.ct_handshake; memo.majorVersion = protocolVersion.major; memo.minorVersion = protocolVersion.minor; memo.encodeEpoch = writeEpoch; memo.encodeCipher = writeCipher; memo.encodeAuthenticator = writeAuthenticator; memo.handshakeType = buf[offset]; memo.messageSequence = messageSequence++; memo.acquireOffset = 0; memo.fragment = new byte[length - 4]; // 4: header size // 1: HandshakeType // 3: message length System.arraycopy(buf, offset + 4, memo.fragment, 0, length - 4); handshakeHashing(memo, memo.fragment); handshakeMemos.add(memo); if ((memo.handshakeType == HandshakeMessage.ht_client_hello) || (memo.handshakeType == HandshakeMessage.ht_hello_request) || (memo.handshakeType == HandshakeMessage.ht_hello_verify_request) || (memo.handshakeType == HandshakeMessage.ht_server_hello_done) || (memo.handshakeType == HandshakeMessage.ht_finished)) { flightIsReady = true; } } Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException { if (isEmpty()) { if (isRetransmittable()) { setRetransmission(); // configure for retransmission } else { return null; } } RecordMemo memo = handshakeMemos.get(acquireIndex); HandshakeMemo hsMemo = null; if (memo.contentType == Record.ct_handshake) { hsMemo = (HandshakeMemo)memo; } int macLen = 0; if (memo.encodeAuthenticator instanceof MAC) { macLen = ((MAC)memo.encodeAuthenticator).MAClen(); } // ChangeCipherSpec message is pretty small. Don't worry about // the fragmentation of ChangeCipherSpec record. int fragLen; if (packetSize > 0) { fragLen = Math.min(maxRecordSize, packetSize); fragLen = memo.encodeCipher.calculateFragmentSize( fragLen, macLen, 25); // 25: header size // 13: DTLS record // 12: DTLS handshake message fragLen = Math.min(fragLen, Record.maxDataSize); } else { fragLen = Record.maxDataSize; } if (fragmentSize > 0) { fragLen = Math.min(fragLen, fragmentSize); } int dstPos = dstBuf.position(); int dstLim = dstBuf.limit(); int dstContent = dstPos + headerSize + memo.encodeCipher.getExplicitNonceSize(); dstBuf.position(dstContent); if (hsMemo != null) { fragLen = Math.min(fragLen, (hsMemo.fragment.length - hsMemo.acquireOffset)); dstBuf.put(hsMemo.handshakeType); dstBuf.put((byte)((hsMemo.fragment.length >> 16) & 0xFF)); dstBuf.put((byte)((hsMemo.fragment.length >> 8) & 0xFF)); dstBuf.put((byte)(hsMemo.fragment.length & 0xFF)); dstBuf.put((byte)((hsMemo.messageSequence >> 8) & 0xFF)); dstBuf.put((byte)(hsMemo.messageSequence & 0xFF)); dstBuf.put((byte)((hsMemo.acquireOffset >> 16) & 0xFF)); dstBuf.put((byte)((hsMemo.acquireOffset >> 8) & 0xFF)); dstBuf.put((byte)(hsMemo.acquireOffset & 0xFF)); dstBuf.put((byte)((fragLen >> 16) & 0xFF)); dstBuf.put((byte)((fragLen >> 8) & 0xFF)); dstBuf.put((byte)(fragLen & 0xFF)); dstBuf.put(hsMemo.fragment, hsMemo.acquireOffset, fragLen); } else { fragLen = Math.min(fragLen, memo.fragment.length); dstBuf.put(memo.fragment, 0, fragLen); } dstBuf.limit(dstBuf.position()); dstBuf.position(dstContent); if ((debug != null) && Debug.isOn("record")) { System.out.println(Thread.currentThread().getName() + ", WRITE: " + protocolVersion + " " + Record.contentName(memo.contentType) + ", length = " + dstBuf.remaining()); } // Encrypt the fragment and wrap up a record. long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher, memo.contentType, dstBuf, dstPos, dstLim, headerSize, ProtocolVersion.valueOf(memo.majorVersion, memo.minorVersion), true); if ((debug != null) && Debug.isOn("packet")) { ByteBuffer temporary = dstBuf.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); Debug.printHex( "[Raw write]: length = " + temporary.remaining(), temporary); } // remain the limit unchanged dstBuf.limit(dstLim); // Reset the fragmentation offset. if (hsMemo != null) { hsMemo.acquireOffset += fragLen; if (hsMemo.acquireOffset == hsMemo.fragment.length) { acquireIndex++; } return new Ciphertext(RecordType.valueOf( hsMemo.contentType, hsMemo.handshakeType), recordSN); } else { acquireIndex++; return new Ciphertext( RecordType.RECORD_CHANGE_CIPHER_SPEC, recordSN); } } private void handshakeHashing(HandshakeMemo hsFrag, byte[] hsBody) { byte hsType = hsFrag.handshakeType; if ((hsType == HandshakeMessage.ht_hello_request) || (hsType == HandshakeMessage.ht_hello_verify_request)) { // omitted from handshake hash computation return; } if ((hsFrag.messageSequence == 0) && (hsType == HandshakeMessage.ht_client_hello)) { // omit initial ClientHello message // // 2: ClientHello.client_version // 32: ClientHello.random int sidLen = hsBody[34]; if (sidLen == 0) { // empty session_id, initial handshake return; } } // calculate the DTLS header byte[] temporary = new byte[12]; // 12: handshake header size // Handshake.msg_type temporary[0] = hsFrag.handshakeType; // Handshake.length temporary[1] = (byte)((hsBody.length >> 16) & 0xFF); temporary[2] = (byte)((hsBody.length >> 8) & 0xFF); temporary[3] = (byte)(hsBody.length & 0xFF); // Handshake.message_seq temporary[4] = (byte)((hsFrag.messageSequence >> 8) & 0xFF); temporary[5] = (byte)(hsFrag.messageSequence & 0xFF); // Handshake.fragment_offset temporary[6] = 0; temporary[7] = 0; temporary[8] = 0; // Handshake.fragment_length temporary[9] = temporary[1]; temporary[10] = temporary[2]; temporary[11] = temporary[3]; if ((hsType != HandshakeMessage.ht_finished) && (hsType != HandshakeMessage.ht_certificate_verify)) { handshakeHash.update(temporary, 0, 12); handshakeHash.update(hsBody, 0, hsBody.length); } else { // Reserve until this handshake message has been processed. handshakeHash.reserve(temporary, 0, 12); handshakeHash.reserve(hsBody, 0, hsBody.length); } } boolean isEmpty() { if (!flightIsReady || handshakeMemos.isEmpty() || acquireIndex >= handshakeMemos.size()) { return true; } return false; } boolean isRetransmittable() { return (flightIsReady && !handshakeMemos.isEmpty() && (acquireIndex >= handshakeMemos.size())); } private void setRetransmission() { acquireIndex = 0; for (RecordMemo memo : handshakeMemos) { if (memo instanceof HandshakeMemo) { HandshakeMemo hmemo = (HandshakeMemo)memo; hmemo.acquireOffset = 0; } } // Shrink packet size if: // 1. maximum fragment size is allowed, in which case the packet // size is configured bigger than maxRecordSize; // 2. maximum packet is bigger than 256 bytes; // 3. two times of retransmits have been attempted. if ((packetSize <= maxRecordSize) && (packetSize > 256) && ((retransmits--) <= 0)) { // shrink packet size shrinkPacketSize(); retransmits = 2; // attemps of retransmits } } private void shrinkPacketSize() { packetSize = Math.max(256, packetSize / 2); } } }