--- old/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java Thu Oct 13 14:03:55 2016 +++ new/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java Thu Oct 13 14:03:55 2016 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -42,10 +42,6 @@ private DTLSReassembler reassembler = null; - // Cache the session identifier for the detection of session-resuming - // handshake. - byte[] prevSessionID = new byte[0]; - int readEpoch; int prevReadEpoch; @@ -114,13 +110,7 @@ @Override Plaintext acquirePlaintext() { if (reassembler != null) { - Plaintext plaintext = reassembler.acquirePlaintext(); - if (reassembler.finished()) { - // discard all buffered unused message. - reassembler = null; - } - - return plaintext; + return reassembler.acquirePlaintext(); } return null; @@ -149,13 +139,18 @@ packet.get(recordEnS); int recordEpoch = ((recordEnS[0] & 0xFF) << 8) | (recordEnS[1] & 0xFF); // pos: 3, 4 - long recordSeq = Authenticator.toLong(recordEnS); + long recordSeq = ((recordEnS[2] & 0xFFL) << 40) | + ((recordEnS[3] & 0xFFL) << 32) | + ((recordEnS[4] & 0xFFL) << 24) | + ((recordEnS[5] & 0xFFL) << 16) | + ((recordEnS[6] & 0xFFL) << 8) | + (recordEnS[7] & 0xFFL); // pos: 5-10 + int contentLen = ((packet.get() & 0xFF) << 8) | - (packet.get() & 0xFF); // pos: 11, 12 + (packet.get() & 0xFF); // pos: 11, 12 if (debug != null && Debug.isOn("record")) { - System.out.println(Thread.currentThread().getName() + - ", READ: " + + Debug.log("READ: " + ProtocolVersion.valueOf(majorVersion, minorVersion) + " " + Record.contentName(contentType) + ", length = " + contentLen); @@ -162,27 +157,36 @@ } int recLim = srcPos + DTLSRecord.headerSize + contentLen; - if (this.readEpoch > recordEpoch) { - // Discard old records delivered before this epoch. + if (this.prevReadEpoch > recordEpoch) { // Reset the position of the packet buffer. packet.position(recLim); + if (debug != null && Debug.isOn("record")) { + Debug.printHex("READ: discard this old records", recordEnS); + } return null; } + // Buffer next epoch message if necessary. if (this.readEpoch < recordEpoch) { - if (contentType != Record.ct_handshake) { - // just discard it if not a handshake message + // Discard the record younger than the current epcoh if: + // 1. it is not a handshake message, or + // 2. it is not of next epoch. + if (((contentType != Record.ct_handshake) && + (contentType != Record.ct_change_cipher_spec)) || + (this.readEpoch < (recordEpoch - 1))) { + packet.position(recLim); + + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Premature record (epoch), discard it."); + } + return null; } - // Not ready to decrypt this record, may be encrypted Finished + // Not ready to decrypt this record, may be an encrypted Finished // message, need to buffer it. - if (reassembler == null) { - reassembler = new DTLSReassembler(); - } - byte[] fragment = new byte[contentLen]; packet.get(fragment); // copy the fragment RecordFragment buffered = new RecordFragment(fragment, contentType, @@ -194,94 +198,130 @@ // consume the full record in the packet buffer. packet.position(recLim); - Plaintext plaintext = reassembler.acquirePlaintext(); - if (reassembler.finished()) { - // discard all buffered unused message. - reassembler = null; - } - - return plaintext; + return reassembler.acquirePlaintext(); } + // + // Now, the message is of this epoch or the previous epoch. + // + Authenticator decodeAuthenticator; + CipherBox decodeCipher; if (this.readEpoch == recordEpoch) { - // decrypt the fragment - packet.limit(recLim); - packet.position(srcPos + DTLSRecord.headerSize); + decodeAuthenticator = readAuthenticator; + decodeCipher = readCipher; + } else { // prevReadEpoch == recordEpoch + decodeAuthenticator = prevReadAuthenticator; + decodeCipher = prevReadCipher; + } - ByteBuffer plaintextFragment; - try { - plaintextFragment = decrypt(readAuthenticator, - readCipher, contentType, packet, recordEnS); - } catch (BadPaddingException bpe) { - if (debug != null && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - " discard invalid record: " + bpe); - } + // decrypt the fragment + packet.limit(recLim); + packet.position(srcPos + DTLSRecord.headerSize); - // invalid, discard this record [section 4.1.2.7, RFC 6347] - return null; - } finally { - // comsume a complete record - packet.limit(srcLim); - packet.position(recLim); + ByteBuffer plaintextFragment; + try { + plaintextFragment = decrypt(decodeAuthenticator, + decodeCipher, contentType, packet, recordEnS); + } catch (BadPaddingException bpe) { + if (debug != null && Debug.isOn("ssl")) { + Debug.log("Discard invalid record: " + bpe); } - if (contentType != Record.ct_change_cipher_spec && - contentType != Record.ct_handshake) { // app data or alert - // no retransmission - return new Plaintext(contentType, majorVersion, minorVersion, - recordEpoch, recordSeq, plaintextFragment); + // invalid, discard this record [section 4.1.2.7, RFC 6347] + return null; + } finally { + // comsume a complete record + packet.limit(srcLim); + packet.position(recLim); + } + + if (contentType != Record.ct_change_cipher_spec && + contentType != Record.ct_handshake) { // app data or alert + // no retransmission + // Cleanup the handshake reassembler if necessary. + if ((reassembler != null) && + (reassembler.handshakeEpoch < recordEpoch)) { + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Cleanup the handshake reassembler"); + } + + reassembler = null; } - if (contentType == Record.ct_change_cipher_spec) { - if (reassembler == null) { + return new Plaintext(contentType, majorVersion, minorVersion, + recordEpoch, Authenticator.toLong(recordEnS), + plaintextFragment); + } + + if (contentType == Record.ct_change_cipher_spec) { + if (reassembler == null) { + if (this.readEpoch != recordEpoch) { // handshake has not started, should be an // old handshake message, discard it. + + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "Lagging behind ChangeCipherSpec, discard it."); + } + return null; } - reassembler.queueUpFragment( - new RecordFragment(plaintextFragment, contentType, - majorVersion, minorVersion, - recordEnS, recordEpoch, recordSeq, false)); - } else { // handshake record - // One record may contain 1+ more handshake messages. - while (plaintextFragment.remaining() > 0) { + reassembler = new DTLSReassembler(recordEpoch); + } - HandshakeFragment hsFrag = parseHandshakeMessage( - contentType, majorVersion, minorVersion, - recordEnS, recordEpoch, recordSeq, plaintextFragment); + reassembler.queueUpChangeCipherSpec( + new RecordFragment(plaintextFragment, contentType, + majorVersion, minorVersion, + recordEnS, recordEpoch, recordSeq, false)); + } else { // handshake record + // One record may contain 1+ more handshake messages. + while (plaintextFragment.remaining() > 0) { - if (hsFrag == null) { - // invalid, discard this record - return null; - } + HandshakeFragment hsFrag = parseHandshakeMessage( + contentType, majorVersion, minorVersion, + recordEnS, recordEpoch, recordSeq, plaintextFragment); - if ((reassembler == null) && - isKickstart(hsFrag.handshakeType)) { - reassembler = new DTLSReassembler(); + if (hsFrag == null) { + // invalid, discard this record + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Invalid handshake message, discard it."); } - if (reassembler != null) { - reassembler.queueUpHandshake(hsFrag); - } // else, just ignore the message. + return null; } - } - // Completed the read of the full record. Acquire the reassembled - // messages. - if (reassembler != null) { - Plaintext plaintext = reassembler.acquirePlaintext(); - if (reassembler.finished()) { - // discard all buffered unused message. - reassembler = null; + if (reassembler == null) { + if (this.readEpoch != recordEpoch) { + // handshake has not started, should be an + // old handshake message, discard it. + + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "Lagging behind handshake record, discard it."); + } + + return null; + } + + reassembler = new DTLSReassembler(recordEpoch); } - return plaintext; + reassembler.queueUpHandshake(hsFrag); } } - return null; // make the complier happy + // Completed the read of the full record. Acquire the reassembled + // messages. + if (reassembler != null) { + return reassembler.acquirePlaintext(); + } + + if (debug != null && Debug.isOn("verbose")) { + Debug.log("The reassembler is not initialized yet."); + } + + return null; } @Override @@ -330,12 +370,6 @@ } } - private static boolean isKickstart(byte handshakeType) { - return (handshakeType == HandshakeMessage.ht_client_hello) || - (handshakeType == HandshakeMessage.ht_hello_request) || - (handshakeType == HandshakeMessage.ht_hello_verify_request); - } - private static HandshakeFragment parseHandshakeMessage( byte contentType, byte majorVersion, byte minorVersion, byte[] recordEnS, int recordEpoch, long recordSeq, @@ -344,9 +378,7 @@ int remaining = plaintextFragment.remaining(); if (remaining < handshakeHeaderSize) { if (debug != null && Debug.isOn("ssl")) { - System.out.println( - Thread.currentThread().getName() + - " discard invalid record: " + + Debug.log("Discard invalid record: " + "too small record to hold a handshake fragment"); } @@ -372,9 +404,7 @@ (plaintextFragment.get() & 0xFF); // pos: 9-11 if ((remaining - handshakeHeaderSize) < fragmentLength) { if (debug != null && Debug.isOn("ssl")) { - System.out.println( - Thread.currentThread().getName() + - " discard invalid record: " + + Debug.log("Discard invalid record: " + "not a complete handshake fragment in the record"); } @@ -431,8 +461,40 @@ @Override public int compareTo(RecordFragment o) { - return Long.compareUnsigned(this.recordSeq, o.recordSeq); + if (this.contentType == Record.ct_change_cipher_spec) { + if (o.contentType == Record.ct_change_cipher_spec) { + // Only one incoming ChangeCipherSpec message for an epoch. + // + // Ignore duplicated ChangeCipherSpec messages. + return Integer.compare(this.recordEpoch, o.recordEpoch); + } else if ((this.recordEpoch == o.recordEpoch) && + (o.contentType == Record.ct_handshake)) { + // ChangeCipherSpec is the latest message of an epoch. + return 1; + } + } else if (o.contentType == Record.ct_change_cipher_spec) { + if ((this.recordEpoch == o.recordEpoch) && + (this.contentType == Record.ct_handshake)) { + // ChangeCipherSpec is the latest message of an epoch. + return -1; + } else { + // different epoch or this is not a handshake message + return compareToSequence(o.recordEpoch, o.recordSeq); + } + } + + return compareToSequence(o.recordEpoch, o.recordSeq); } + + int compareToSequence(int epoch, long seq) { + if (this.recordEpoch > epoch) { + return 1; + } else if (this.recordEpoch == epoch) { + return (int)(this.recordSeq - seq); + } else { + return -1; + } + } } // buffered handshake message @@ -465,12 +527,24 @@ if (o instanceof HandshakeFragment) { HandshakeFragment other = (HandshakeFragment)o; if (this.messageSeq != other.messageSeq) { - // keep the insertion order for the same message + // keep the insertion order of handshake messages return this.messageSeq - other.messageSeq; + } else if (this.fragmentOffset != other.fragmentOffset) { + // small fragment offset was transmitted first + return this.fragmentOffset - other.fragmentOffset; + } else if (this.fragmentLength == other.fragmentLength) { + // retransmissions, ignore duplicated messages. + return 0; } + + // Should be repacked for suitable fragment length. + // + // Note that the acquiring processes will reassemble the + // the fragments later. + return compareToSequence(o.recordEpoch, o.recordSeq); } - return Long.compareUnsigned(this.recordSeq, o.recordSeq); + return super.compareTo(o); } } @@ -484,24 +558,75 @@ } } + private static final class HandshakeFlight implements Cloneable { + static final byte HF_UNKNOWN = HandshakeMessage.ht_not_applicable; + + byte handshakeType; // handshake type + int flightEpoch; // the epoch of the first message + int minMessageSeq; // minimal message sequence + + int maxMessageSeq; // maximum message sequence + int maxRecordEpoch; // maximum record sequence number + long maxRecordSeq; // maximum record sequence number + + HashMap> holesMap; + + HandshakeFlight() { + this.handshakeType = HF_UNKNOWN; + this.flightEpoch = 0; + this.minMessageSeq = 0; + + this.maxMessageSeq = 0; + this.maxRecordEpoch = 0; + this.maxRecordSeq = -1; + + this.holesMap = new HashMap<>(5); + } + + boolean isRetransmitOf(HandshakeFlight hs) { + if (hs != null) { + return (this.handshakeType == hs.handshakeType) && + (this.minMessageSeq == hs.minMessageSeq); + } + + return false; + } + + @Override + public Object clone() { + HandshakeFlight hf = new HandshakeFlight(); + + hf.handshakeType = this.handshakeType; + hf.flightEpoch = this.flightEpoch; + hf.minMessageSeq = this.minMessageSeq; + + hf.maxMessageSeq = this.maxMessageSeq; + hf.maxRecordEpoch = this.maxRecordEpoch; + hf.maxRecordSeq = this.maxRecordSeq; + + hf.holesMap = new HashMap<>(this.holesMap); + + return hf; + } + } + final class DTLSReassembler { + // The handshake epoch. + final int handshakeEpoch; + + // The buffered fragments. TreeSet bufferedFragments = new TreeSet<>(); - HashMap> holesMap = new HashMap<>(5); + // The handshake flight in progress. + HandshakeFlight handshakeFlight = new HandshakeFlight(); - // Epoch, sequence number and handshake message sequence of the - // beginning message of a flight. - byte flightType = (byte)0xFF; + // The preceding handshake flight. + HandshakeFlight precedingFlight = null; - int flightTopEpoch = 0; - long flightTopRecordSeq = -1; - int flightTopMessageSeq = 0; - // Epoch, sequence number and handshake message sequence of the // next message acquisition of a flight. - int nextRecordEpoch = 0; // next record epoch + int nextRecordEpoch; // next record epoch long nextRecordSeq = 0; // next record sequence number - int nextMessageSeq = 0; // next handshake message number // Expect ChangeCipherSpec and Finished messages for the final flight. boolean expectCCSFlight = false; @@ -510,65 +635,66 @@ boolean flightIsReady = false; boolean needToCheckFlight = false; - // Is it a session-resuming abbreviated handshake.? - boolean isAbbreviatedHandshake = false; + DTLSReassembler(int handshakeEpoch) { + this.handshakeEpoch = handshakeEpoch; + this.nextRecordEpoch = handshakeEpoch; - // The handshke fragment with the biggest record sequence number - // in a flight, not counting the Finished message. - HandshakeFragment lastHandshakeFragment = null; - - // Is handshake (intput) finished? - boolean handshakeFinished = false; - - DTLSReassembler() { - // blank + this.handshakeFlight.flightEpoch = handshakeEpoch; } - boolean finished() { - return handshakeFinished; - } - void expectingFinishFlight() { expectCCSFlight = true; } + // Queue up a handshake message. void queueUpHandshake(HandshakeFragment hsf) { - - if ((nextRecordEpoch > hsf.recordEpoch) || - (nextRecordSeq > hsf.recordSeq) || - (nextMessageSeq > hsf.messageSeq)) { - // too old, discard this record + if (!isDesirable(hsf)) { + // No a dedired record, discard it. return; } + // Clean up the retransmission messages if necessary. + cleanUpRetransmit(hsf); + // Is it the first message of next flight? - if ((flightTopMessageSeq == hsf.messageSeq) && - (hsf.fragmentOffset == 0) && (flightTopRecordSeq == -1)) { + // + // Note: the Finished message is handled in the final CCS flight. + boolean isMinimalFlightMessage = false; + if (handshakeFlight.minMessageSeq == hsf.messageSeq) { + isMinimalFlightMessage = true; + } else if ((precedingFlight != null) && + (precedingFlight.minMessageSeq == hsf.messageSeq)) { + isMinimalFlightMessage = true; + } - flightType = hsf.handshakeType; - flightTopEpoch = hsf.recordEpoch; - flightTopRecordSeq = hsf.recordSeq; + if (isMinimalFlightMessage && (hsf.fragmentOffset == 0) && + (hsf.handshakeType != HandshakeMessage.ht_finished)) { - if (hsf.handshakeType == HandshakeMessage.ht_server_hello) { - // Is it a session-resuming handshake? - try { - isAbbreviatedHandshake = - isSessionResuming(hsf.fragment, prevSessionID); - } catch (SSLException ssle) { - if (debug != null && Debug.isOn("ssl")) { - System.out.println( - Thread.currentThread().getName() + - " discard invalid record: " + ssle); - } + // reset the handshake flight + handshakeFlight.handshakeType = hsf.handshakeType; + handshakeFlight.flightEpoch = hsf.recordEpoch; + handshakeFlight.minMessageSeq = hsf.messageSeq; + } - // invalid, discard it [section 4.1.2.7, RFC 6347] - return; - } + if (hsf.handshakeType == HandshakeMessage.ht_finished) { + handshakeFlight.maxMessageSeq = hsf.messageSeq; + handshakeFlight.maxRecordEpoch = hsf.recordEpoch; + handshakeFlight.maxRecordSeq = hsf.recordSeq; + } else { + if (handshakeFlight.maxMessageSeq < hsf.messageSeq) { + handshakeFlight.maxMessageSeq = hsf.messageSeq; + } - if (!isAbbreviatedHandshake) { - prevSessionID = getSessionID(hsf.fragment); + int n = (hsf.recordEpoch - handshakeFlight.maxRecordEpoch); + if (n > 0) { + handshakeFlight.maxRecordEpoch = hsf.recordEpoch; + handshakeFlight.maxRecordSeq = hsf.recordSeq; + } else if (n == 0) { + // the same epoch + if (handshakeFlight.maxRecordSeq < hsf.recordSeq) { + handshakeFlight.maxRecordSeq = hsf.recordSeq; } - } + } // Otherwise, it is unlikely to happen. } boolean fragmented = false; @@ -578,7 +704,8 @@ fragmented = true; } - List holes = holesMap.get(hsf.handshakeType); + List holes = + handshakeFlight.holesMap.get(hsf.handshakeType); if (holes == null) { if (!fragmented) { holes = Collections.emptyList(); @@ -586,7 +713,7 @@ holes = new LinkedList(); holes.add(new HoleDescriptor(0, hsf.messageLength)); } - holesMap.put(hsf.handshakeType, holes); + handshakeFlight.holesMap.put(hsf.handshakeType, holes); } else if (holes.isEmpty()) { // Have got the full handshake message. This record may be // a handshake message retransmission. Discard this record. @@ -594,20 +721,11 @@ // It's OK to discard retransmission as the handshake hash // is computed as if each handshake message had been sent // as a single fragment. - // - // Note that ClientHello messages are delivered twice in - // DTLS handshaking. - if ((hsf.handshakeType != HandshakeMessage.ht_client_hello && - hsf.handshakeType != ht_hello_verify_request) || - (nextMessageSeq != hsf.messageSeq)) { - return; + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Have got the full message, discard it."); } - if (fragmented) { - holes = new LinkedList(); - holes.add(new HoleDescriptor(0, hsf.messageLength)); - } - holesMap.put(hsf.handshakeType, holes); + return; } if (fragmented) { @@ -628,9 +746,7 @@ (hole.limit < fragmentLimit))) { if (debug != null && Debug.isOn("ssl")) { - System.out.println( - Thread.currentThread().getName() + - " discard invalid record: " + + Debug.log("Discard invalid record: " + "handshake fragment ranges are overlapping"); } @@ -659,38 +775,59 @@ } } - // append this fragment - bufferedFragments.add(hsf); + // buffer this fragment + if (hsf.handshakeType == HandshakeMessage.ht_finished) { + // Need no status update. + bufferedFragments.add(hsf); + } else { + bufferFragment(hsf); + } + } - if ((lastHandshakeFragment == null) || - (lastHandshakeFragment.compareTo(hsf) < 0)) { + // Queue up a ChangeCipherSpec message + void queueUpChangeCipherSpec(RecordFragment rf) { + if (!isDesirable(rf)) { + // No a dedired record, discard it. + return; + } - lastHandshakeFragment = hsf; + // Clean up the retransmission messages if necessary. + cleanUpRetransmit(rf); + + // Is it the first message of this flight? + // + // Note: the first message of the final flight is ChangeCipherSpec. + if (expectCCSFlight) { + handshakeFlight.handshakeType = HandshakeFlight.HF_UNKNOWN; + handshakeFlight.flightEpoch = rf.recordEpoch; } - if (flightIsReady) { - flightIsReady = false; + // The epoch should be the same as the first message of the flight. + if (handshakeFlight.maxRecordSeq < rf.recordSeq) { + handshakeFlight.maxRecordSeq = rf.recordSeq; } - needToCheckFlight = true; + + // buffer this fragment + bufferFragment(rf); } - // queue up change_cipher_spec or encrypted message + // Queue up a ciphertext message. + // + // Note: not yet be able to decrypt the message. void queueUpFragment(RecordFragment rf) { - if ((nextRecordEpoch > rf.recordEpoch) || - (nextRecordSeq > rf.recordSeq)) { - // too old, discard this record + if (!isDesirable(rf)) { + // No a dedired record, discard it. return; } - // Is it the first message of next flight? - if (expectCCSFlight && - (rf.contentType == Record.ct_change_cipher_spec)) { + // Clean up the retransmission messages if necessary. + cleanUpRetransmit(rf); - flightType = (byte)0xFE; - flightTopEpoch = rf.recordEpoch; - flightTopRecordSeq = rf.recordSeq; - } + // buffer this fragment + bufferFragment(rf); + } + private void bufferFragment(RecordFragment rf) { // append this fragment bufferedFragments.add(rf); @@ -697,10 +834,146 @@ if (flightIsReady) { flightIsReady = false; } - needToCheckFlight = true; + + if (!needToCheckFlight) { + needToCheckFlight = true; + } } - boolean isEmpty() { + private void cleanUpRetransmit(RecordFragment rf) { + // Does the next flight start? + boolean isNewFlight = false; + if (precedingFlight != null) { + if (precedingFlight.flightEpoch < rf.recordEpoch) { + isNewFlight = true; + } else { + if (rf instanceof HandshakeFragment) { + HandshakeFragment hsf = (HandshakeFragment)rf; + if (precedingFlight.maxMessageSeq < hsf.messageSeq) { + isNewFlight = true; + } + } else if (rf.contentType != Record.ct_change_cipher_spec) { + // ciphertext + if (precedingFlight.maxRecordEpoch < rf.recordEpoch) { + isNewFlight = true; + } + } + } + } + + if (!isNewFlight) { + // Need no cleanup. + return; + } + + // clean up the buffer + for (Iterator it = bufferedFragments.iterator(); + it.hasNext();) { + + RecordFragment frag = it.next(); + boolean isOld = false; + if (frag.recordEpoch < precedingFlight.maxRecordEpoch) { + isOld = true; + } else if (frag.recordEpoch == precedingFlight.maxRecordEpoch) { + if (frag.recordSeq <= precedingFlight.maxRecordSeq) { + isOld = true; + } + } + + if (!isOld && (frag instanceof HandshakeFragment)) { + HandshakeFragment hsf = (HandshakeFragment)frag; + isOld = (hsf.messageSeq <= precedingFlight.maxMessageSeq); + } + + if (isOld) { + it.remove(); + } else { + // Safe to break as items in the buffer are ordered. + break; + } + } + + // discard retransmissions of the previous flight if any. + precedingFlight = null; + } + + // Is a desired record? + // + // Check for retransmission and lost records. + private boolean isDesirable(RecordFragment rf) { + // + // Discard records old than the previous epoch. + // + int previousEpoch = nextRecordEpoch - 1; + if (rf.recordEpoch < previousEpoch) { + // Too old to use, discard this record. + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Too old epoch to use this record, discard it."); + } + + return false; + } + + // + // Allow retransmission of last flight of the previous epoch + // + // For example, the last server delivered flight for session + // resuming abbreviated handshaking consist three messages: + // ServerHello + // [ChangeCipherSpec] + // Finished + // + // The epoch number is incremented and the sequence number is reset + // if the ChangeCipherSpec is sent. + if (rf.recordEpoch == previousEpoch) { + boolean isDesired = true; + if (precedingFlight == null) { + isDesired = false; + } else { + if (rf instanceof HandshakeFragment) { + HandshakeFragment hsf = (HandshakeFragment)rf; + if (precedingFlight.minMessageSeq > hsf.messageSeq) { + isDesired = false; + } + } else if (rf.contentType == Record.ct_change_cipher_spec) { + // ChangeCipherSpec + if (precedingFlight.flightEpoch != rf.recordEpoch) { + isDesired = false; + } + } else { // ciphertext + if ((rf.recordEpoch < precedingFlight.maxRecordEpoch) || + (rf.recordEpoch == precedingFlight.maxRecordEpoch && + rf.recordSeq <= precedingFlight.maxRecordSeq)) { + isDesired = false; + } + } + } + + if (!isDesired) { + // Too old to use, discard this retransmitted record + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Too old retransmission to use, discard it."); + } + + return false; + } + } else if ((rf.recordEpoch == nextRecordEpoch) && + (nextRecordSeq > rf.recordSeq)) { + + // Previously disordered record for the current epoch. + // + // Should has been retransmitted. Discard this record. + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Lagging behind record (sequence), discard it."); + } + + return false; + } + + return true; + } + + private boolean isEmpty() { return (bufferedFragments.isEmpty() || (!flightIsReady && !needToCheckFlight) || (needToCheckFlight && !flightIsReady())); @@ -708,12 +981,9 @@ Plaintext acquirePlaintext() { if (bufferedFragments.isEmpty()) { - // reset the flight - if (flightIsReady) { - flightIsReady = false; - needToCheckFlight = false; + if (debug != null && Debug.isOn("verbose")) { + Debug.log("No received handshake messages"); } - return null; } @@ -721,10 +991,23 @@ // check the fligth status flightIsReady = flightIsReady(); - // set for next flight + // Reset if this flight is ready. if (flightIsReady) { - flightTopMessageSeq = lastHandshakeFragment.messageSeq + 1; - flightTopRecordSeq = -1; + // Retransmitted handshake messages are not needed for + // further handshaking processing. + if (handshakeFlight.isRetransmitOf(precedingFlight)) { + // cleanup + bufferedFragments.clear(); + + // Reset the next handshake flight. + resetHandshakeFlight(precedingFlight); + + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Received a retransmission flight."); + } + + return Plaintext.PLAINTEXT_NULL; + } } needToCheckFlight = false; @@ -731,19 +1014,82 @@ } if (!flightIsReady) { + if (debug != null && Debug.isOn("verbose")) { + Debug.log("The handshake flight is not ready to use: " + + handshakeFlight.handshakeType); + } return null; } RecordFragment rFrag = bufferedFragments.first(); + Plaintext plaintext; if (!rFrag.isCiphertext) { // handshake message, or ChangeCipherSpec message - return acquireHandshakeMessage(); + plaintext = acquireHandshakeMessage(); + + // Reset the handshake flight. + if (bufferedFragments.isEmpty()) { + // Nedd not to backup the holes map. Clear up it at first. + handshakeFlight.holesMap.clear(); // cleanup holes map + + // Update the preceding flight. + precedingFlight = (HandshakeFlight)handshakeFlight.clone(); + + // Reset the next handshake flight. + resetHandshakeFlight(precedingFlight); + + if (expectCCSFlight && + (precedingFlight.flightEpoch == + HandshakeFlight.HF_UNKNOWN)) { + expectCCSFlight = false; + } + } } else { // a Finished message or other ciphertexts - return acquireCachedMessage(); + plaintext = acquireCachedMessage(); } + + return plaintext; } + // + // Reset the handshake flight from a previous one. + // + private void resetHandshakeFlight(HandshakeFlight prev) { + // Reset the next handshake flight. + handshakeFlight.handshakeType = HandshakeFlight.HF_UNKNOWN; + handshakeFlight.flightEpoch = prev.maxRecordEpoch; + if (prev.flightEpoch != prev.maxRecordEpoch) { + // a new epoch starts + handshakeFlight.minMessageSeq = 0; + } else { + // stay at the same epoch + // + // The minimal message sequence number will get updated if + // a flight retransmission happens. + handshakeFlight.minMessageSeq = prev.maxMessageSeq + 1; + } + + // cleanup the maximum sequence number and epoch number. + // + // Note: actually, we need to do nothing because the reassembler + // of handshake messages will reset them properly even for + // retransmissions. + // + handshakeFlight.maxMessageSeq = 0; + handshakeFlight.maxRecordEpoch = handshakeFlight.flightEpoch; + + // Record sequence number cannot wrap even for retransmissions. + handshakeFlight.maxRecordSeq = prev.maxRecordSeq + 1; + + // cleanup holes map + handshakeFlight.holesMap.clear(); + + // Ready to accept new input record. + flightIsReady = false; + needToCheckFlight = false; + } + private Plaintext acquireCachedMessage() { RecordFragment rFrag = bufferedFragments.first(); @@ -750,6 +1096,9 @@ if (readEpoch != rFrag.recordEpoch) { if (readEpoch > rFrag.recordEpoch) { // discard old records + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Discard old buffered ciphertext fragments."); + } bufferedFragments.remove(rFrag); // popup the fragment } @@ -757,6 +1106,10 @@ if (flightIsReady) { flightIsReady = false; } + + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Not yet ready to decrypt the cached fragments."); + } return null; } @@ -768,9 +1121,8 @@ plaintextFragment = decrypt(readAuthenticator, readCipher, rFrag.contentType, fragment, rFrag.recordEnS); } catch (BadPaddingException bpe) { - if (debug != null && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - " discard invalid record: " + bpe); + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Discard invalid record: " + bpe); } // invalid, discard this record [section 4.1.2.7, RFC 6347] @@ -782,7 +1134,6 @@ // beginning of the next flight) message. Need not to check // any ChangeCipherSpec message. if (rFrag.contentType == Record.ct_handshake) { - HandshakeFragment finFrag = null; while (plaintextFragment.remaining() > 0) { HandshakeFragment hsFrag = parseHandshakeMessage( rFrag.contentType, @@ -792,66 +1143,31 @@ if (hsFrag == null) { // invalid, discard this record + if (debug != null && Debug.isOn("verbose")) { + Debug.printHex( + "Invalid handshake fragment, discard it", + plaintextFragment); + } return null; } - if (hsFrag.handshakeType == HandshakeMessage.ht_finished) { - finFrag = hsFrag; - - // reset for the next flight - this.flightType = (byte)0xFF; - this.flightTopEpoch = rFrag.recordEpoch; - this.flightTopMessageSeq = hsFrag.messageSeq + 1; - this.flightTopRecordSeq = -1; - } else { - // reset the flight - if (flightIsReady) { - flightIsReady = false; - } - queueUpHandshake(hsFrag); + queueUpHandshake(hsFrag); + // The flight ready status (flightIsReady) should have + // been checked and updated for the Finished handshake + // message before the decryption. Please don't update + // flightIsReady for Finished messages. + if (hsFrag.handshakeType != HandshakeMessage.ht_finished) { + flightIsReady = false; + needToCheckFlight = true; } } - this.nextRecordSeq = rFrag.recordSeq + 1; - this.nextMessageSeq = 0; - - if (finFrag != null) { - this.nextRecordEpoch = finFrag.recordEpoch; - this.nextRecordSeq = finFrag.recordSeq + 1; - this.nextMessageSeq = finFrag.messageSeq + 1; - - // Finished message does not fragment. - byte[] recordFrag = new byte[finFrag.messageLength + 4]; - Plaintext plaintext = new Plaintext(finFrag.contentType, - finFrag.majorVersion, finFrag.minorVersion, - finFrag.recordEpoch, finFrag.recordSeq, - ByteBuffer.wrap(recordFrag)); - - // fill the handshake fragment of the record - recordFrag[0] = finFrag.handshakeType; - recordFrag[1] = - (byte)((finFrag.messageLength >>> 16) & 0xFF); - recordFrag[2] = - (byte)((finFrag.messageLength >>> 8) & 0xFF); - recordFrag[3] = (byte)(finFrag.messageLength & 0xFF); - - System.arraycopy(finFrag.fragment, 0, - recordFrag, 4, finFrag.fragmentLength); - - // handshake hashing - handshakeHashing(finFrag, plaintext); - - // input handshake finished - handshakeFinished = true; - - return plaintext; - } else { - return acquirePlaintext(); - } + return acquirePlaintext(); } else { return new Plaintext(rFrag.contentType, rFrag.majorVersion, rFrag.minorVersion, - rFrag.recordEpoch, rFrag.recordSeq, + rFrag.recordEpoch, + Authenticator.toLong(rFrag.recordEnS), plaintextFragment); } } @@ -861,10 +1177,15 @@ RecordFragment rFrag = bufferedFragments.first(); if (rFrag.contentType == Record.ct_change_cipher_spec) { this.nextRecordEpoch = rFrag.recordEpoch + 1; + + // For retransmissions, the next record sequence number is a + // positive value. Don't worry about it as the acquiring of + // the immediately followed Finished handshake message will + // reset the next record sequence number correctly. this.nextRecordSeq = 0; - // no change on next handshake message sequence number - bufferedFragments.remove(rFrag); // popup the fragment + // Popup the fragment. + bufferedFragments.remove(rFrag); // Reload if this message has been reserved for handshake hash. handshakeHash.reload(); @@ -871,7 +1192,8 @@ return new Plaintext(rFrag.contentType, rFrag.majorVersion, rFrag.minorVersion, - rFrag.recordEpoch, rFrag.recordSeq, + rFrag.recordEpoch, + Authenticator.toLong(rFrag.recordEnS), ByteBuffer.wrap(rFrag.fragment)); } else { // rFrag.contentType == Record.ct_handshake HandshakeFragment hsFrag = (HandshakeFragment)rFrag; @@ -882,13 +1204,13 @@ // this.nextRecordEpoch = hsFrag.recordEpoch; this.nextRecordSeq = hsFrag.recordSeq + 1; - this.nextMessageSeq = hsFrag.messageSeq + 1; // Note: may try to avoid byte array copy in the future. byte[] recordFrag = new byte[hsFrag.messageLength + 4]; Plaintext plaintext = new Plaintext(hsFrag.contentType, hsFrag.majorVersion, hsFrag.minorVersion, - hsFrag.recordEpoch, hsFrag.recordSeq, + hsFrag.recordEpoch, + Authenticator.toLong(hsFrag.recordEnS), ByteBuffer.wrap(recordFrag)); // fill the handshake fragment of the record @@ -913,7 +1235,8 @@ byte[] recordFrag = new byte[hsFrag.messageLength + 4]; Plaintext plaintext = new Plaintext(hsFrag.contentType, hsFrag.majorVersion, hsFrag.minorVersion, - hsFrag.recordEpoch, hsFrag.recordSeq, + hsFrag.recordEpoch, + Authenticator.toLong(hsFrag.recordEnS), ByteBuffer.wrap(recordFrag)); // fill the handshake fragment of the record @@ -957,7 +1280,6 @@ handshakeHashing(hsFrag, plaintext); this.nextRecordSeq = maxRecodeSN + 1; - this.nextMessageSeq = msgSeq + 1; return plaintext; } @@ -966,15 +1288,28 @@ boolean flightIsReady() { - // - // the ChangeCipherSpec/Finished flight - // - if (expectCCSFlight) { - // Have the ChangeCipherSpec/Finished messages been received? - return hasFinisedMessage(bufferedFragments); - } + byte flightType = handshakeFlight.handshakeType; + if (flightType == HandshakeFlight.HF_UNKNOWN) { + // + // the ChangeCipherSpec/Finished flight + // + if (expectCCSFlight) { + // Have the ChangeCipherSpec/Finished flight been received? + // + // return hasFinisedMessage(bufferedFragments); + boolean isReady = hasFinisedMessage(bufferedFragments); + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "Has the final flight been received? " + isReady); + } - if (flightType == (byte)0xFF) { + return isReady; + } + + if (debug != null && Debug.isOn("verbose")) { + Debug.log("No flight is received yet."); + } + return false; } @@ -983,7 +1318,12 @@ (flightType == HandshakeMessage.ht_hello_verify_request)) { // single handshake message flight - return hasCompleted(holesMap.get(flightType)); + boolean isReady = hasCompleted(flightType); + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Is the handshake message completed? " + isReady); + } + + return isReady; } // @@ -991,7 +1331,12 @@ // if (flightType == HandshakeMessage.ht_server_hello) { // Firstly, check the first flight handshake message. - if (!hasCompleted(holesMap.get(flightType))) { + if (!hasCompleted(flightType)) { + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "The ServerHello message is not completed yet."); + } + return false; } @@ -998,24 +1343,40 @@ // // an abbreviated handshake // - if (isAbbreviatedHandshake) { - // Ready to use the flight if received the - // ChangeCipherSpec and Finished messages. - return hasFinisedMessage(bufferedFragments); + if (hasFinisedMessage(bufferedFragments)) { + if (debug != null && Debug.isOn("verbose")) { + Debug.log("It's an abbreviated handshake."); + } + + return true; } // // a full handshake // - if (lastHandshakeFragment.handshakeType != - HandshakeMessage.ht_server_hello_done) { + List holes = handshakeFlight.holesMap.get( + HandshakeMessage.ht_server_hello_done); + if ((holes == null) || !holes.isEmpty()) { // Not yet got the final message of the flight. + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Not yet got the ServerHelloDone message"); + } + return false; } // Have all handshake message been received? - return hasCompleted(bufferedFragments, - flightTopMessageSeq, lastHandshakeFragment.messageSeq); + boolean isReady = hasCompleted(bufferedFragments, + handshakeFlight.minMessageSeq, + handshakeFlight.maxMessageSeq); + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Is the ServerHello flight (message " + + handshakeFlight.minMessageSeq + "-" + + handshakeFlight.maxMessageSeq + + ") completed? " + isReady); + } + + return isReady; } // @@ -1029,41 +1390,48 @@ (flightType == HandshakeMessage.ht_client_key_exchange)) { // Firstly, check the first flight handshake message. - if (!hasCompleted(holesMap.get(flightType))) { + if (!hasCompleted(flightType)) { + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "The ClientKeyExchange or client Certificate " + + "message is not completed yet."); + } + return false; } if (!hasFinisedMessage(bufferedFragments)) { - // not yet got the ChangeCipherSpec/Finished messages - return false; - } + // not yet have the ChangeCipherSpec/Finished messages + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "Not yet have the ChangeCipherSpec and " + + "Finished messages"); + } - if (flightType == HandshakeMessage.ht_client_key_exchange) { - // single handshake message flight - return true; - } - - // - // flightType == HandshakeMessage.ht_certificate - // - // We don't support certificates containing fixed - // Diffie-Hellman parameters. Therefore, CertificateVerify - // message is required if client Certificate message presents. - // - if (lastHandshakeFragment.handshakeType != - HandshakeMessage.ht_certificate_verify) { - // Not yet got the final message of the flight. return false; } // Have all handshake message been received? - return hasCompleted(bufferedFragments, - flightTopMessageSeq, lastHandshakeFragment.messageSeq); + boolean isReady = hasCompleted(bufferedFragments, + handshakeFlight.minMessageSeq, + handshakeFlight.maxMessageSeq); + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Is the ClientKeyExchange flight (message " + + handshakeFlight.minMessageSeq + "-" + + handshakeFlight.maxMessageSeq + + ") completed? " + isReady); + } + + return isReady; } // // Otherwise, need to receive more handshake messages. // + if (debug != null && Debug.isOn("verbose")) { + Debug.log("Need to receive more handshake messages"); + } + return false; } @@ -1080,7 +1448,7 @@ } int sidLen = fragment[34]; // 34: the length field - if (sidLen > 32) { // opaque SessionID<0..32> + if (sidLen > 32) { // opaque SessionID<0, 32> throw new SSLException( "Invalid ServerHello message: invalid session id"); } @@ -1147,7 +1515,9 @@ return hasFin && hasCCS; } - private boolean hasCompleted(List holes) { + private boolean hasCompleted(byte handshakeType) { + List holes = + handshakeFlight.holesMap.get(handshakeType); if (holes == null) { // not yet received this kind of handshake message return false; @@ -1173,7 +1543,7 @@ continue; } else if (hsFrag.messageSeq == (presentMsgSeq + 1)) { // check the completion of the handshake message - if (!hasCompleted(holesMap.get(hsFrag.handshakeType))) { + if (!hasCompleted(hsFrag.handshakeType)) { return false; } --- old/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java Thu Oct 13 14:03:56 2016 +++ new/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java Thu Oct 13 14:03:56 2016 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -279,6 +279,16 @@ 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; --- old/src/java.base/share/classes/sun/security/ssl/Debug.java Thu Oct 13 14:03:57 2016 +++ new/src/java.base/share/classes/sun/security/ssl/Debug.java Thu Oct 13 14:03:56 2016 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -145,6 +145,13 @@ } /** + * Print a message to stdout. + */ + static void log(String message) { + System.out.println(Thread.currentThread().getName() + ": " + message); + } + + /** * print a blank line to stderr that is prefixed with the prefix. */ @@ -156,7 +163,6 @@ /** * print a message to stderr that is prefixed with the prefix. */ - public static void println(String prefix, String message) { System.err.println(prefix + ": "+message); --- old/src/java.base/share/classes/sun/security/ssl/OutputRecord.java Thu Oct 13 14:03:57 2016 +++ new/src/java.base/share/classes/sun/security/ssl/OutputRecord.java Thu Oct 13 14:03:57 2016 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -194,6 +194,11 @@ // blank } + // apply to DTLS SSLEngine + void launchRetransmission() { + // blank + } + @Override public synchronized void close() throws IOException { if (!isClosed) { @@ -224,6 +229,9 @@ sequenceNumber = authenticator.sequenceNumber(); } + // The sequence number may be shared for different purpose. + boolean sharedSequenceNumber = false; + // "flip" but skip over header again, add MAC & encrypt if (authenticator instanceof MAC) { MAC signer = (MAC)authenticator; @@ -243,6 +251,11 @@ // reset the position and limit destination.limit(destination.position()); destination.position(dstContent); + + // The signer has used and increased the sequence number. + if (isDTLS) { + sharedSequenceNumber = true; + } } } @@ -261,6 +274,11 @@ // Encrypt may pad, so again the limit may be changed. encCipher.encrypt(destination, dstLim); + + // The cipher has used and increased the sequence number. + if (isDTLS && encCipher.isAEADMode()) { + sharedSequenceNumber = true; + } } else { destination.position(destination.limit()); } @@ -290,8 +308,10 @@ destination.put(headerOffset + 11, (byte)(fragLen >> 8)); destination.put(headerOffset + 12, (byte)fragLen); - // Increase the sequence number for next use. - authenticator.increaseSequenceNumber(); + // Increase the sequence number for next use if it is not shared. + if (!sharedSequenceNumber) { + authenticator.increaseSequenceNumber(); + } } // Update destination position to reflect the amount of data produced. --- old/src/java.base/share/classes/sun/security/ssl/Plaintext.java Thu Oct 13 14:03:58 2016 +++ new/src/java.base/share/classes/sun/security/ssl/Plaintext.java Thu Oct 13 14:03:57 2016 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -38,7 +38,7 @@ byte majorVersion; byte minorVersion; int recordEpoch; // incremented on every cipher state change - long recordSN; + long recordSN; // contains epcoh number (epoch | sequence) ByteBuffer fragment; // null if need to be reassembled HandshakeStatus handshakeStatus; // null if not used or not handshaking --- old/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java Thu Oct 13 14:03:58 2016 +++ new/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java Thu Oct 13 14:03:58 2016 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -994,7 +994,22 @@ // plainText should never be null for TLS protocols HandshakeStatus hsStatus = null; - if (!isDTLS || plainText != null) { + if (plainText == Plaintext.PLAINTEXT_NULL) { + // Only happens for DTLS protocols. + // + // Received a retransmitted flight, and need to retransmit the + // previous delivered handshake flight messages. + if (enableRetransmissions) { + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "Retransmit the previous handshake flight messages."); + } + + synchronized (this) { + outputRecord.launchRetransmission(); + } + } // Otherwise, discard the retransmitted flight. + } else if (!isDTLS || plainText != null) { hsStatus = processInputRecord(plainText, appData, offset, length); } @@ -1003,7 +1018,7 @@ } if (plainText == null) { - plainText = new Plaintext(); + plainText = Plaintext.PLAINTEXT_NULL; } plainText.handshakeStatus = hsStatus; @@ -1378,7 +1393,8 @@ // Acquire the buffered to-be-delivered records or retransmissions. // // May have buffered records, or need retransmission if handshaking. - if (!outputRecord.isEmpty() || (handshaker != null)) { + if (!outputRecord.isEmpty() || + (enableRetransmissions && handshaker != null)) { ciphertext = outputRecord.acquireCiphertext(netData); } @@ -1403,13 +1419,36 @@ HandshakeStatus hsStatus = null; Ciphertext.RecordType recordType = ciphertext.recordType; - if ((handshaker != null) && - (recordType.contentType == Record.ct_handshake) && - (recordType.handshakeType == HandshakeMessage.ht_finished) && - handshaker.isDone() && outputRecord.isEmpty()) { + if ((recordType.contentType == Record.ct_handshake) && + (recordType.handshakeType == HandshakeMessage.ht_finished) && + outputRecord.isEmpty()) { - hsStatus = finishHandshake(); - connectionState = cs_DATA; + if (handshaker == null) { + hsStatus = HandshakeStatus.FINISHED; + } else if (handshaker.isDone()) { + hsStatus = finishHandshake(); + connectionState = cs_DATA; + + // Retransmit the last flight twice. + // + // The application data transactions may begin immediately + // after the last flight. If the last flight get lost, the + // application data may be discarded accordingly. As could + // be an issue for some applications. This impact can be + // mitigated by sending the last fligth twice. + if (isDTLS && enableRetransmissions) { + if (debug != null && Debug.isOn("verbose")) { + Debug.log( + "Retransmit the last flight messages."); + } + + synchronized (this) { + outputRecord.launchRetransmission(); + } + + hsStatus = HandshakeStatus.NEED_WRAP; + } + } } // Otherwise, the followed call to getHSStatus() will help. /* --- old/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java Thu Oct 13 14:03:59 2016 +++ new/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java Thu Oct 13 14:03:59 2016 @@ -558,73 +558,6 @@ applicationProtocol = ""; } - // cookie exchange - if (isDTLS) { - HelloCookieManager hcMgr = sslContext.getHelloCookieManager(); - if ((mesg.cookie == null) || (mesg.cookie.length == 0) || - (!hcMgr.isValid(mesg))) { - - // - // Perform cookie exchange for DTLS handshaking if no cookie - // or the cookie is invalid in the ClientHello message. - // - HelloVerifyRequest m0 = new HelloVerifyRequest(hcMgr, mesg); - - if (debug != null && Debug.isOn("handshake")) { - m0.print(System.out); - } - - m0.write(output); - handshakeState.update(m0, resumingSession); - output.flush(); - - return; - } - } - - /* - * FIRST, construct the ServerHello using the options and priorities - * from the ClientHello. Update the (pending) cipher spec as we do - * so, and save the client's version to protect against rollback - * attacks. - * - * There are a bunch of minor tasks here, and one major one: deciding - * if the short or the full handshake sequence will be used. - */ - ServerHello m1 = new ServerHello(); - - clientRequestedVersion = mesg.protocolVersion; - - // select a proper protocol version. - ProtocolVersion selectedVersion = - selectProtocolVersion(clientRequestedVersion); - if (selectedVersion == null || - selectedVersion.v == ProtocolVersion.SSL20Hello.v) { - fatalSE(Alerts.alert_handshake_failure, - "Client requested protocol " + clientRequestedVersion + - " not enabled or not supported"); - } - - handshakeHash.protocolDetermined(selectedVersion); - setVersion(selectedVersion); - - m1.protocolVersion = protocolVersion; - - // - // random ... save client and server values for later use - // in computing the master secret (from pre-master secret) - // and thence the other crypto keys. - // - // NOTE: this use of three inputs to generating _each_ set - // of ciphers slows things down, but it does increase the - // security since each connection in the session can hold - // its own authenticated (and strong) keys. One could make - // creation of a session a rare thing... - // - clnt_random = mesg.clnt_random; - svr_random = new RandomCookie(sslContext.getSecureRandom()); - m1.svr_random = svr_random; - session = null; // forget about the current session // // Here we go down either of two paths: (a) the fast one, where @@ -732,7 +665,74 @@ } } // else client did not try to resume + // cookie exchange + if (isDTLS && !resumingSession) { + HelloCookieManager hcMgr = sslContext.getHelloCookieManager(); + if ((mesg.cookie == null) || (mesg.cookie.length == 0) || + (!hcMgr.isValid(mesg))) { + + // + // Perform cookie exchange for DTLS handshaking if no cookie + // or the cookie is invalid in the ClientHello message. + // + HelloVerifyRequest m0 = new HelloVerifyRequest(hcMgr, mesg); + + if (debug != null && Debug.isOn("handshake")) { + m0.print(System.out); + } + + m0.write(output); + handshakeState.update(m0, resumingSession); + output.flush(); + + return; + } + } + + /* + * FIRST, construct the ServerHello using the options and priorities + * from the ClientHello. Update the (pending) cipher spec as we do + * so, and save the client's version to protect against rollback + * attacks. + * + * There are a bunch of minor tasks here, and one major one: deciding + * if the short or the full handshake sequence will be used. + */ + ServerHello m1 = new ServerHello(); + + clientRequestedVersion = mesg.protocolVersion; + + // select a proper protocol version. + ProtocolVersion selectedVersion = + selectProtocolVersion(clientRequestedVersion); + if (selectedVersion == null || + selectedVersion.v == ProtocolVersion.SSL20Hello.v) { + fatalSE(Alerts.alert_handshake_failure, + "Client requested protocol " + clientRequestedVersion + + " not enabled or not supported"); + } + + handshakeHash.protocolDetermined(selectedVersion); + setVersion(selectedVersion); + + m1.protocolVersion = protocolVersion; + // + // random ... save client and server values for later use + // in computing the master secret (from pre-master secret) + // and thence the other crypto keys. + // + // NOTE: this use of three inputs to generating _each_ set + // of ciphers slows things down, but it does increase the + // security since each connection in the session can hold + // its own authenticated (and strong) keys. One could make + // creation of a session a rare thing... + // + clnt_random = mesg.clnt_random; + svr_random = new RandomCookie(sslContext.getSecureRandom()); + m1.svr_random = svr_random; + + // // If client hasn't specified a session we can resume, start a // new one and choose its cipher suite and compression options. // Unless new session creation is disabled for this connection! --- old/test/javax/net/ssl/DTLS/DTLSOverDatagram.java Thu Oct 13 14:04:00 2016 +++ new/test/javax/net/ssl/DTLS/DTLSOverDatagram.java Thu Oct 13 14:03:59 2016 @@ -48,10 +48,6 @@ */ public class DTLSOverDatagram { - static { - System.setProperty("javax.net.debug", "ssl"); - } - private static int MAX_HANDSHAKE_LOOPS = 200; private static int MAX_APP_READ_LOOPS = 60; private static int SOCKET_TIMEOUT = 10 * 1000; // in millis @@ -160,6 +156,7 @@ } SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus(); + log(side, "=======handshake(" + loops + ", " + hs + ")======="); if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP || hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) { @@ -239,6 +236,7 @@ boolean finished = produceHandshakePackets( engine, peerAddr, side, packets); + log(side, "Produced " + packets.size() + " packets"); for (DatagramPacket p : packets) { socket.send(p); } @@ -252,7 +250,8 @@ } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) { runDelegatedTasks(engine); } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { - log(side, "Handshake status is NOT_HANDSHAKING, finish the loop"); + log(side, + "Handshake status is NOT_HANDSHAKING, finish the loop"); endLoops = true; } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) { throw new Exception( @@ -259,7 +258,8 @@ "Unexpected status, SSLEngine.getHandshakeStatus() " + "shouldn't return FINISHED"); } else { - throw new Exception("Can't reach here, handshake status is " + hs); + throw new Exception( + "Can't reach here, handshake status is " + hs); } } @@ -279,7 +279,9 @@ log(side, "Negotiated cipher suite is " + session.getCipherSuite()); // handshake status should be NOT_HANDSHAKING - // according to the spec, SSLEngine.getHandshakeStatus() can't return FINISHED + // + // According to the spec, SSLEngine.getHandshakeStatus() can't + // return FINISHED. if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { throw new Exception("Unexpected handshake status " + hs); } @@ -348,13 +350,16 @@ SSLEngineResult.Status rs = r.getStatus(); SSLEngineResult.HandshakeStatus hs = r.getHandshakeStatus(); + log(side, "====packet(" + loops + ", " + rs + ", " + hs + ")===="); if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) { // the client maximum fragment size config does not work? throw new Exception("Buffer overflow: " + "incorrect server maximum fragment size"); } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) { - log(side, "Produce handshake packets: BUFFER_UNDERFLOW occured"); - log(side, "Produce handshake packets: Handshake status: " + hs); + log(side, + "Produce handshake packets: BUFFER_UNDERFLOW occured"); + log(side, + "Produce handshake packets: Handshake status: " + hs); // bad packet, or the client maximum fragment size // config does not work? if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { @@ -453,6 +458,53 @@ return packets; } + // Get a datagram packet for the specified handshake type. + static DatagramPacket getPacket( + List packets, byte handshakeType) { + boolean matched = false; + for (DatagramPacket packet : packets) { + byte[] data = packet.getData(); + int offset = packet.getOffset(); + int length = packet.getLength(); + + // Normally, this pakcet should be a handshake message + // record. However, even if the underlying platform + // splits the record more, we don't really worry about + // the improper packet loss because DTLS implementation + // should be able to handle packet loss properly. + // + // See RFC 6347 for the detailed format of DTLS records. + if (handshakeType == -1) { // ChangeCipherSpec + // Is it a ChangeCipherSpec message? + matched = (length == 14) && (data[offset] == 0x14); + } else if ((length >= 25) && // 25: handshake mini size + (data[offset] == 0x16)) { // a handshake message + + // check epoch number for initial handshake only + if (data[offset + 3] == 0x00) { // 3,4: epoch + if (data[offset + 4] == 0x00) { // plaintext + matched = + (data[offset + 13] == handshakeType); + } else { // cipherext + // The 1st ciphertext is a Finished message. + // + // If it is not proposed to loss the Finished + // message, it is not necessary to check the + // following packets any mroe as a Finished + // message is the last handshake message. + matched = (handshakeType == 20); + } + } + } + + if (matched) { + return packet; + } + } + + return null; + } + // run delegated tasks void runDelegatedTasks(SSLEngine engine) throws Exception { Runnable runnable; --- old/test/javax/net/ssl/TLSCommon/SSLEngineTestCase.java Thu Oct 13 14:04:00 2016 +++ new/test/javax/net/ssl/TLSCommon/SSLEngineTestCase.java Thu Oct 13 14:04:00 2016 @@ -27,7 +27,9 @@ import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManagerFactory; @@ -57,19 +59,21 @@ public enum Ciphers { /** - * Ciphers supported by the tested SSLEngine without those with kerberos - * authentication. + * Ciphers supported by the tested SSLEngine without those with + * kerberos authentication. */ SUPPORTED_NON_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_NON_KRB_CIPHERS, "Supported non kerberos"), /** - * Ciphers supported by the tested SSLEngine without those with kerberos - * authentication and without those with SHA256 ans SHA384. + * Ciphers supported by the tested SSLEngine without those with + * kerberos authentication and without those with SHA256 ans SHA384. */ - SUPPORTED_NON_KRB_NON_SHA_CIPHERS(SSLEngineTestCase.SUPPORTED_NON_KRB_NON_SHA_CIPHERS, + SUPPORTED_NON_KRB_NON_SHA_CIPHERS( + SSLEngineTestCase.SUPPORTED_NON_KRB_NON_SHA_CIPHERS, "Supported non kerberos non SHA256 and SHA384"), /** - * Ciphers supported by the tested SSLEngine with kerberos authentication. + * Ciphers supported by the tested SSLEngine with kerberos + * authentication. */ SUPPORTED_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_KRB_CIPHERS, "Supported kerberos"), @@ -147,13 +151,13 @@ = System.getProperty("test.src", ".") + FS + PATH_TO_STORES + FS + TRUST_STORE_FILE; + // Need an enhancement to use none-static mutable global variables. private static ByteBuffer net; - private static ByteBuffer netReplicatedClient; - private static ByteBuffer netReplicatedServer; - private static final int MAX_HANDSHAKE_LOOPS = 100; - private static final String EXCHANGE_MSG_SENT = "Hello, peer!"; private static boolean doUnwrapForNotHandshakingStatus; private static boolean endHandshakeLoop = false; + + private static final int MAX_HANDSHAKE_LOOPS = 100; + private static final String EXCHANGE_MSG_SENT = "Hello, peer!"; private static final String TEST_SRC = System.getProperty("test.src", "."); private static final String KTAB_FILENAME = "krb5.keytab.data"; private static final String KRB_REALM = "TEST.REALM"; @@ -179,11 +183,13 @@ List supportedCiphersList = new LinkedList<>(); for (String cipher : allSupportedCiphers) { if (!cipher.contains("KRB5") - && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { + && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { + supportedCiphersList.add(cipher); } } - SUPPORTED_NON_KRB_CIPHERS = supportedCiphersList.toArray(new String[0]); + SUPPORTED_NON_KRB_CIPHERS = + supportedCiphersList.toArray(new String[0]); } catch (Exception ex) { throw new Error("Unexpected issue", ex); } @@ -220,7 +226,7 @@ List supportedCiphersList = new LinkedList<>(); for (String cipher : allSupportedCiphers) { if (cipher.contains("KRB5") - && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { + && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { supportedCiphersList.add(cipher); } } @@ -240,11 +246,12 @@ List enabledCiphersList = new LinkedList<>(); for (String cipher : enabledCiphers) { if (!cipher.contains("anon") && !cipher.contains("KRB5") - && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { + && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { enabledCiphersList.add(cipher); } } - ENABLED_NON_KRB_NOT_ANON_CIPHERS = enabledCiphersList.toArray(new String[0]); + ENABLED_NON_KRB_NOT_ANON_CIPHERS = + enabledCiphersList.toArray(new String[0]); } catch (Exception ex) { throw new Error("Unexpected issue", ex); } @@ -300,10 +307,10 @@ * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. - * @param wrapper - Set wrapper id, e.g. "server" of "client". Used for - * logging only. - * @param maxPacketSize - Max packet size to check that MFLN extension works - * or zero for no check. + * @param wrapper - Set wrapper id, e.g. "server" of "client". + * Used for logging only. + * @param maxPacketSize - Max packet size to check that MFLN extension + * works or zero for no check. * @param app - Buffer with data to wrap. * @return - Buffer with wrapped data. * @throws SSLException - thrown on engine errors. @@ -319,13 +326,13 @@ * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. - * @param wrapper - Set wrapper id, e.g. "server" of "client". Used for - * logging only. - * @param maxPacketSize - Max packet size to check that MFLN extension works - * or zero for no check. + * @param wrapper - Set wrapper id, e.g. "server" of "client". + * Used for logging only. + * @param maxPacketSize - Max packet size to check that MFLN extension + * works or zero for no check. * @param app - Buffer with data to wrap. - * @param result - Array which first element will be used to output wrap - * result object. + * @param result - Array which first element will be used to + * output wrap result object. * @return - Buffer with wrapped data. * @throws SSLException - thrown on engine errors. */ @@ -341,10 +348,10 @@ * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. - * @param wrapper - Set wrapper id, e.g. "server" of "client". Used for - * logging only. - * @param maxPacketSize - Max packet size to check that MFLN extension works - * or zero for no check. + * @param wrapper - Set wrapper id, e.g. "server" of "client". + * Used for logging only. + * @param maxPacketSize - Max packet size to check that MFLN extension + * works or zero for no check. * @param app - Buffer with data to wrap. * @param wantedStatus - Specifies expected result status of wrapping. * @return - Buffer with wrapped data. @@ -362,14 +369,14 @@ * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. - * @param wrapper - Set wrapper id, e.g. "server" of "client". Used for - * logging only. - * @param maxPacketSize - Max packet size to check that MFLN extension works - * or zero for no check. + * @param wrapper - Set wrapper id, e.g. "server" of "client". + * Used for logging only. + * @param maxPacketSize - Max packet size to check that MFLN extension + * works or zero for no check. * @param app - Buffer with data to wrap. * @param wantedStatus - Specifies expected result status of wrapping. - * @param result - Array which first element will be used to output wrap - * result object. + * @param result - Array which first element will be used to output + * wrap result object. * @return - Buffer with wrapped data. * @throws SSLException - thrown on engine errors. */ @@ -409,9 +416,9 @@ * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, - ByteBuffer net) - throws SSLException { - return doUnWrap(engine, unwrapper, net, SSLEngineResult.Status.OK, null); + ByteBuffer net) throws SSLException { + return doUnWrap(engine, unwrapper, + net, SSLEngineResult.Status.OK, null); } /** @@ -427,9 +434,9 @@ * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, - ByteBuffer net, SSLEngineResult[] result) - throws SSLException { - return doUnWrap(engine, unwrapper, net, SSLEngineResult.Status.OK, result); + ByteBuffer net, SSLEngineResult[] result) throws SSLException { + return doUnWrap(engine, unwrapper, + net, SSLEngineResult.Status.OK, result); } /** @@ -436,8 +443,8 @@ * Unwraps data with the specified engine. * * @param engine - SSLEngine that unwraps data. - * @param unwrapper - Set unwrapper id, e.g. "server" of "client". Used for - * logging only. + * @param unwrapper - Set unwrapper id, e.g. "server" of "client". + * Used for logging only. * @param net - Buffer with data to unwrap. * @param wantedStatus - Specifies expected result status of wrapping. * @return - Buffer with unwrapped data. @@ -444,9 +451,8 @@ * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, - ByteBuffer net, - SSLEngineResult.Status wantedStatus) - throws SSLException { + ByteBuffer net, + SSLEngineResult.Status wantedStatus) throws SSLException { return doUnWrap(engine, unwrapper, net, wantedStatus, null); } @@ -454,25 +460,23 @@ * Unwraps data with the specified engine. * * @param engine - SSLEngine that unwraps data. - * @param unwrapper - Set unwrapper id, e.g. "server" of "client". Used for - * logging only. + * @param unwrapper - Set unwrapper id, e.g. "server" of "client". + * Used for logging only. * @param net - Buffer with data to unwrap. * @param wantedStatus - Specifies expected result status of wrapping. - * @param result - Array which first element will be used to output wrap - * result object. + * @param result - Array which first element will be used to output + * wrap result object. * @return - Buffer with unwrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, - ByteBuffer net, - SSLEngineResult.Status wantedStatus, - SSLEngineResult[] result) - throws SSLException { - ByteBuffer app = ByteBuffer.allocate(engine.getSession() - .getApplicationBufferSize()); + ByteBuffer net, SSLEngineResult.Status wantedStatus, + SSLEngineResult[] result) throws SSLException { + + ByteBuffer app = ByteBuffer.allocate( + engine.getSession().getApplicationBufferSize()); int length = net.remaining(); - System.out.println(unwrapper + " unwrapping " - + length + " bytes..."); + System.out.println(unwrapper + " unwrapping " + length + " bytes..."); SSLEngineResult r = engine.unwrap(net, app); app.flip(); System.out.println(unwrapper + " handshake status is " @@ -491,13 +495,14 @@ * @param clientEngine - Client SSLEngine. * @param serverEngine - Server SSLEngine. * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit. - * @param mode - Handshake mode according to {@link HandshakeMode} enum. + * @param mode - Handshake mode according to + * {@link HandshakeMode} enum. * @throws SSLException - thrown on engine errors. */ public static void doHandshake(SSLEngine clientEngine, - SSLEngine serverEngine, - int maxPacketSize, HandshakeMode mode) - throws SSLException { + SSLEngine serverEngine, + int maxPacketSize, HandshakeMode mode) throws SSLException { + doHandshake(clientEngine, serverEngine, maxPacketSize, mode, false); } @@ -507,19 +512,20 @@ * * @param clientEngine - Client SSLEngine. * @param serverEngine - Server SSLEngine. - * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit. - * @param mode - Handshake mode according to {@link HandshakeMode} enum. + * @param maxPacketSize - Maximum packet size for MFLN of zero + * for no limit. + * @param mode - Handshake mode according to + * {@link HandshakeMode} enum. * @param enableReplicatedPacks - Set {@code true} to enable replicated - * packet sending. + * packet sending. * @throws SSLException - thrown on engine errors. */ public static void doHandshake(SSLEngine clientEngine, - SSLEngine serverEngine, int maxPacketSize, - HandshakeMode mode, - boolean enableReplicatedPacks) - throws SSLException { - System.out.println("=================================================" - + "==========="); + SSLEngine serverEngine, int maxPacketSize, + HandshakeMode mode, + boolean enableReplicatedPacks) throws SSLException { + + System.out.println("============================================="); System.out.println("Starting handshake " + mode.name()); int loop = 0; if (maxPacketSize < 0) { @@ -561,18 +567,16 @@ if (++loop > MAX_HANDSHAKE_LOOPS) { throw new Error("Too much loops for handshaking"); } - System.out.println("=============================================="); - System.out.println("Handshake loop " + loop); - SSLEngineResult.HandshakeStatus clientHSStatus - = clientEngine.getHandshakeStatus(); - SSLEngineResult.HandshakeStatus serverHSStatus - = serverEngine.getHandshakeStatus(); - System.out.println("Client handshake status " - + clientHSStatus.name()); - System.out.println("Server handshake status " - + serverHSStatus.name()); + System.out.println("============================================"); + System.out.println("Handshake loop " + loop + ": round 1"); + System.out.println("=========================="); handshakeProcess(firstEngine, secondEngine, maxPacketSize, enableReplicatedPacks); + if (endHandshakeLoop) { + break; + } + System.out.println("Handshake loop " + loop + ": round 2"); + System.out.println("=========================="); handshakeProcess(secondEngine, firstEngine, maxPacketSize, enableReplicatedPacks); } @@ -596,7 +600,8 @@ sender = "Client"; reciever = "Server"; excMsgSent += " Client."; - } else if (toEngine.getUseClientMode() && !fromEngine.getUseClientMode()) { + } else if (toEngine.getUseClientMode() && + !fromEngine.getUseClientMode()) { sender = "Server"; reciever = "Client"; excMsgSent += " Server."; @@ -603,8 +608,7 @@ } else { throw new Error("Test issue: both engines are in the same mode"); } - System.out.println("=================================================" - + "==========="); + System.out.println("============================================="); System.out.println("Trying to send application data from " + sender + " to " + reciever); ByteBuffer clientAppSent @@ -643,20 +647,24 @@ if (fromEngine.getUseClientMode() && !toEngine.getUseClientMode()) { from = "Client"; to = "Server"; - } else if (toEngine.getUseClientMode() && !fromEngine.getUseClientMode()) { + } else if (toEngine.getUseClientMode() && + !fromEngine.getUseClientMode()) { from = "Server"; to = "Client"; } else { throw new Error("Both engines are in the same mode"); } - System.out.println("========================================================="); - System.out.println("Trying to close engines from " + from + " to " + to); + System.out.println("============================================="); + System.out.println( + "Trying to close engines from " + from + " to " + to); // Sending close outbound request to peer fromEngine.closeOutbound(); - app = ByteBuffer.allocate(fromEngine.getSession().getApplicationBufferSize()); + app = ByteBuffer.allocate( + fromEngine.getSession().getApplicationBufferSize()); net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED); doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED); - app = ByteBuffer.allocate(fromEngine.getSession().getApplicationBufferSize()); + app = ByteBuffer.allocate( + fromEngine.getSession().getApplicationBufferSize()); net = doWrap(toEngine, to, 0, app, SSLEngineResult.Status.CLOSED); doUnWrap(fromEngine, from, net, SSLEngineResult.Status.CLOSED); if (!toEngine.isInboundDone()) { @@ -665,7 +673,8 @@ } // Executing close inbound fromEngine.closeInbound(); - app = ByteBuffer.allocate(fromEngine.getSession().getApplicationBufferSize()); + app = ByteBuffer.allocate( + fromEngine.getSession().getApplicationBufferSize()); net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED); doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED); if (!toEngine.isOutboundDone()) { @@ -712,7 +721,8 @@ runTests(Ciphers.SUPPORTED_KRB_CIPHERS); break; default: - throw new Error("Test error: unexpected test mode: " + TEST_MODE); + throw new Error( + "Test error: unexpected test mode: " + TEST_MODE); } } @@ -743,28 +753,36 @@ } /** - * Returns SSLContext with TESTED_SECURITY_PROTOCOL protocol and sets up keys. + * Returns SSLContext with TESTED_SECURITY_PROTOCOL protocol and + * sets up keys. * - * @return - SSLContext with a protocol specified by TESTED_SECURITY_PROTOCOL. + * @return - SSLContext with a protocol specified by + * TESTED_SECURITY_PROTOCOL. */ public static SSLContext getContext() { try { - java.security.Security.setProperty("jdk.tls.disabledAlgorithms", ""); - java.security.Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + java.security.Security.setProperty( + "jdk.tls.disabledAlgorithms", ""); + java.security.Security.setProperty( + "jdk.certpath.disabledAlgorithms", ""); KeyStore ks = KeyStore.getInstance("JKS"); KeyStore ts = KeyStore.getInstance("JKS"); char[] passphrase = PASSWD.toCharArray(); - try (FileInputStream keyFileStream = new FileInputStream(KEY_FILE_NAME)) { + try (FileInputStream keyFileStream = + new FileInputStream(KEY_FILE_NAME)) { ks.load(keyFileStream, passphrase); } - try (FileInputStream trustFileStream = new FileInputStream(TRUST_FILE_NAME)) { + try (FileInputStream trustFileStream = + new FileInputStream(TRUST_FILE_NAME)) { ts.load(trustFileStream, passphrase); } KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, passphrase); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + TrustManagerFactory tmf = + TrustManagerFactory.getInstance("SunX509"); tmf.init(ts); - SSLContext sslCtx = SSLContext.getInstance(TESTED_SECURITY_PROTOCOL); + SSLContext sslCtx = + SSLContext.getInstance(TESTED_SECURITY_PROTOCOL); sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); return sslCtx; } catch (KeyStoreException | IOException | NoSuchAlgorithmException | @@ -791,7 +809,8 @@ } /** - * Sets up and starts kerberos KDC server if SSLEngineTestCase.TEST_MODE is "krb". + * Sets up and starts kerberos KDC server if + * SSLEngineTestCase.TEST_MODE is "krb". */ public static void setUpAndStartKDCIfNeeded() { if (TEST_MODE.equals("krb")) { @@ -806,7 +825,9 @@ * @param useSNI - flag used to enable or disable using SNI extension. * Needed for Kerberos. */ - public static SSLEngine getClientSSLEngine(SSLContext context, boolean useSNI) { + public static SSLEngine getClientSSLEngine( + SSLContext context, boolean useSNI) { + SSLEngine clientEngine = context.createSSLEngine(HOST, 80); clientEngine.setUseClientMode(true); if (useSNI) { @@ -827,7 +848,9 @@ * @param useSNI - flag used to enable or disable using SNI extension. * Needed for Kerberos. */ - public static SSLEngine getServerSSLEngine(SSLContext context, boolean useSNI) { + public static SSLEngine getServerSSLEngine( + SSLContext context, boolean useSNI) { + SSLEngine serverEngine = context.createSSLEngine(); serverEngine.setUseClientMode(false); if (useSNI) { @@ -860,18 +883,20 @@ protected int testSomeCiphers(Ciphers ciphers) { int failedNum = 0; String description = ciphers.description; - System.out.println("===================================================" - + "========="); + System.out.println("==============================================="); System.out.println(description + " ciphers testing"); - System.out.println("===================================================" - + "========="); + System.out.println("==========================================="); for (String cs : ciphers.ciphers) { - System.out.println("-----------------------------------------------" - + "-------------"); + System.out.println("---------------------------------------"); System.out.println("Testing cipher suite " + cs); - System.out.println("-----------------------------------------------" - + "-------------"); + System.out.println("---------------------------------------"); Throwable error = null; + + // Reset global mutable static variables + net = null; + doUnwrapForNotHandshakingStatus = false; + endHandshakeLoop = false; + try { testOneCipher(cs); } catch (Throwable t) { @@ -894,8 +919,9 @@ case UNSUPPORTED_CIPHERS: if (error == null) { System.out.println("Test Failed: " + cs); - System.err.println("Test for " + cs + " should have thrown" - + " IllegalArgumentException, but it has not!"); + System.err.println("Test for " + cs + + " should have thrown " + + "IllegalArgumentException, but it has not!"); failedNum++; } else if (!(error instanceof IllegalArgumentException)) { System.out.println("Test Failed: " + cs); @@ -911,6 +937,7 @@ + ciphers.name()); } } + return failedNum; } @@ -919,20 +946,20 @@ * * @param wrapingEngine - Engine that is expected to wrap data. * @param unwrapingEngine - Engine that is expected to unwrap data. - * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit. + * @param maxPacketSize - Maximum packet size for MFLN of zero + * for no limit. * @param enableReplicatedPacks - Set {@code true} to enable replicated - * packet sending. + * packet sending. * @throws SSLException - thrown on engine errors. */ private static void handshakeProcess(SSLEngine wrapingEngine, - SSLEngine unwrapingEngine, - int maxPacketSize, - boolean enableReplicatedPacks) - throws SSLException { - SSLEngineResult.HandshakeStatus wrapingHSStatus = wrapingEngine - .getHandshakeStatus(); - SSLEngineResult.HandshakeStatus unwrapingHSStatus = unwrapingEngine - .getHandshakeStatus(); + SSLEngine unwrapingEngine, + int maxPacketSize, + boolean enableReplicatedPacks) throws SSLException { + + HandshakeStatus wrapingHSStatus = wrapingEngine.getHandshakeStatus(); + HandshakeStatus unwrapingHSStatus = + unwrapingEngine.getHandshakeStatus(); SSLEngineResult r; String wrapper, unwrapper; if (wrapingEngine.getUseClientMode() @@ -946,6 +973,13 @@ } else { throw new Error("Both engines are in the same mode"); } + System.out.println( + wrapper + " handshake (wrap) status " + wrapingHSStatus); + System.out.println( + unwrapper + " handshake (unwrap) status " + unwrapingHSStatus); + + ByteBuffer netReplicatedClient = null; + ByteBuffer netReplicatedServer = null; switch (wrapingHSStatus) { case NEED_WRAP: if (enableReplicatedPacks) { @@ -960,9 +994,11 @@ } } } - ByteBuffer app = ByteBuffer.allocate(wrapingEngine.getSession() - .getApplicationBufferSize()); + ByteBuffer app = ByteBuffer.allocate( + wrapingEngine.getSession().getApplicationBufferSize()); net = doWrap(wrapingEngine, wrapper, maxPacketSize, app); + wrapingHSStatus = wrapingEngine.getHandshakeStatus(); + // No break, falling into unwrapping. case NOT_HANDSHAKING: switch (unwrapingHSStatus) { case NEED_TASK: @@ -970,12 +1006,12 @@ case NEED_UNWRAP: doUnWrap(unwrapingEngine, unwrapper, net); if (enableReplicatedPacks) { - System.out.println("Unwrapping replicated packet..."); + System.out.println(unwrapper + + " unwrapping replicated packet..."); if (unwrapingEngine.getHandshakeStatus() - .equals(SSLEngineResult.HandshakeStatus.NEED_TASK)) { + .equals(HandshakeStatus.NEED_TASK)) { runDelegatedTasks(unwrapingEngine); } - runDelegatedTasks(unwrapingEngine); ByteBuffer netReplicated; if (unwrapingEngine.getUseClientMode()) { netReplicated = netReplicatedClient; @@ -983,7 +1019,8 @@ netReplicated = netReplicatedServer; } if (netReplicated != null) { - doUnWrap(unwrapingEngine, unwrapper, netReplicated); + doUnWrap(unwrapingEngine, + unwrapper, netReplicated); } else { net.flip(); doUnWrap(unwrapingEngine, unwrapper, net); @@ -994,15 +1031,39 @@ break; case NOT_HANDSHAKING: if (doUnwrapForNotHandshakingStatus) { + System.out.println("Not handshake status unwrap"); doUnWrap(unwrapingEngine, unwrapper, net); doUnwrapForNotHandshakingStatus = false; break; } else { + if (wrapingHSStatus == + HandshakeStatus.NOT_HANDSHAKING) { + System.out.println("Handshake is completed"); + endHandshakeLoop = true; + } + } + break; + case NEED_WRAP: + SSLSession session = unwrapingEngine.getSession(); + int bufferSize = session.getApplicationBufferSize(); + ByteBuffer b = ByteBuffer.allocate(bufferSize); + net = doWrap(unwrapingEngine, + unwrapper, maxPacketSize, b); + unwrapingHSStatus = + unwrapingEngine.getHandshakeStatus(); + if ((wrapingHSStatus == + HandshakeStatus.NOT_HANDSHAKING) && + (unwrapingHSStatus == + HandshakeStatus.NOT_HANDSHAKING)) { + + System.out.println("Handshake is completed"); endHandshakeLoop = true; } + break; default: - throw new Error("Unexpected unwraping engine handshake status " + throw new Error( + "Unexpected unwraping engine handshake status " + unwrapingHSStatus.name()); } break; @@ -1027,8 +1088,8 @@ while ((runnable = engine.getDelegatedTask()) != null) { runnable.run(); } - SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus(); - if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) { + HandshakeStatus hs = engine.getHandshakeStatus(); + if (hs == HandshakeStatus.NEED_TASK) { throw new Error("Handshake shouldn't need additional tasks."); } } --- /dev/null Thu Oct 13 14:04:01 2016 +++ new/test/javax/net/ssl/DTLS/PacketLossRetransmission.java Thu Oct 13 14:04:01 2016 @@ -0,0 +1,111 @@ +/* + * Copyright (c) 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. + * + * 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. + */ + +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. + +/* + * @test + * @bug 8161086 + * @summary DTLS handshaking fails if some messages were lost + * @modules java.base/sun.security.util + * @build DTLSOverDatagram + * + * @run main/othervm PacketLossRetransmission client 0 hello_request + * @run main/othervm PacketLossRetransmission client 1 client_hello + * @run main/othervm PacketLossRetransmission client 2 server_hello + * @run main/othervm PacketLossRetransmission client 3 hello_verify_request + * @run main/othervm PacketLossRetransmission client 4 new_session_ticket + * @run main/othervm PacketLossRetransmission client 11 certificate + * @run main/othervm PacketLossRetransmission client 12 server_key_exchange + * @run main/othervm PacketLossRetransmission client 13 certificate_request + * @run main/othervm PacketLossRetransmission client 14 server_hello_done + * @run main/othervm PacketLossRetransmission client 15 certificate_verify + * @run main/othervm PacketLossRetransmission client 16 client_key_exchange + * @run main/othervm PacketLossRetransmission client 20 finished + * @run main/othervm PacketLossRetransmission client 21 certificate_url + * @run main/othervm PacketLossRetransmission client 22 certificate_status + * @run main/othervm PacketLossRetransmission client 23 supplemental_data + * @run main/othervm PacketLossRetransmission client -1 change_cipher_spec + * @run main/othervm PacketLossRetransmission server 0 hello_request + * @run main/othervm PacketLossRetransmission server 1 client_hello + * @run main/othervm PacketLossRetransmission server 2 server_hello + * @run main/othervm PacketLossRetransmission server 3 hello_verify_request + * @run main/othervm PacketLossRetransmission server 4 new_session_ticket + * @run main/othervm PacketLossRetransmission server 11 certificate + * @run main/othervm PacketLossRetransmission server 12 server_key_exchange + * @run main/othervm PacketLossRetransmission server 13 certificate_request + * @run main/othervm PacketLossRetransmission server 14 server_hello_done + * @run main/othervm PacketLossRetransmission server 15 certificate_verify + * @run main/othervm PacketLossRetransmission server 16 client_key_exchange + * @run main/othervm PacketLossRetransmission server 20 finished + * @run main/othervm PacketLossRetransmission server 21 certificate_url + * @run main/othervm PacketLossRetransmission server 22 certificate_status + * @run main/othervm PacketLossRetransmission server 23 supplemental_data + * @run main/othervm PacketLossRetransmission server -1 change_cipher_spec + */ + +import java.util.List; +import java.util.ArrayList; +import java.net.DatagramPacket; +import java.net.SocketAddress; +import javax.net.ssl.SSLEngine; + +/** + * Test that DTLS implementation is able to do retransmission internally + * automatically if packet get lost. + */ +public class PacketLossRetransmission extends DTLSOverDatagram { + private static boolean isClient; + private static byte handshakeType; + + private boolean needPacketLoss = true; + + public static void main(String[] args) throws Exception { + isClient = args[0].equals("client"); + handshakeType = Byte.valueOf(args[1]); + + PacketLossRetransmission testCase = new PacketLossRetransmission(); + testCase.runTest(testCase); + } + + @Override + boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr, + String side, List packets) throws Exception { + + boolean finished = super.produceHandshakePackets( + engine, socketAddr, side, packets); + + if (needPacketLoss && (!(isClient ^ engine.getUseClientMode()))) { + DatagramPacket packet = getPacket(packets, handshakeType); + if (packet != null) { + needPacketLoss = false; + + System.out.println("Loss a packet of handshake messahe"); + packets.remove(packet); + } + } + + return finished; + } +} --- /dev/null Thu Oct 13 14:04:02 2016 +++ new/test/javax/net/ssl/DTLS/RespondToRetransmit.java Thu Oct 13 14:04:01 2016 @@ -0,0 +1,114 @@ +/* + * Copyright (c) 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. + * + * 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. + */ + +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. + +/* + * @test + * @bug 8161086 + * @summary DTLS handshaking fails if some messages were lost + * @modules java.base/sun.security.util + * @build DTLSOverDatagram + * + * @run main/othervm RespondToRetransmit client 0 hello_request + * @run main/othervm RespondToRetransmit client 1 client_hello + * @run main/othervm RespondToRetransmit client 2 server_hello + * @run main/othervm RespondToRetransmit client 3 hello_verify_request + * @run main/othervm RespondToRetransmit client 4 new_session_ticket + * @run main/othervm RespondToRetransmit client 11 certificate + * @run main/othervm RespondToRetransmit client 12 server_key_exchange + * @run main/othervm RespondToRetransmit client 13 certificate_request + * @run main/othervm RespondToRetransmit client 14 server_hello_done + * @run main/othervm RespondToRetransmit client 15 certificate_verify + * @run main/othervm RespondToRetransmit client 16 client_key_exchange + * @run main/othervm RespondToRetransmit client 20 finished + * @run main/othervm RespondToRetransmit client 21 certificate_url + * @run main/othervm RespondToRetransmit client 22 certificate_status + * @run main/othervm RespondToRetransmit client 23 supplemental_data + * @run main/othervm RespondToRetransmit client -1 change_cipher_spec + * @run main/othervm RespondToRetransmit server 0 hello_request + * @run main/othervm RespondToRetransmit server 1 client_hello + * @run main/othervm RespondToRetransmit server 2 server_hello + * @run main/othervm RespondToRetransmit server 3 hello_verify_request + * @run main/othervm RespondToRetransmit server 4 new_session_ticket + * @run main/othervm RespondToRetransmit server 11 certificate + * @run main/othervm RespondToRetransmit server 12 server_key_exchange + * @run main/othervm RespondToRetransmit server 13 certificate_request + * @run main/othervm RespondToRetransmit server 14 server_hello_done + * @run main/othervm RespondToRetransmit server 15 certificate_verify + * @run main/othervm RespondToRetransmit server 16 client_key_exchange + * @run main/othervm RespondToRetransmit server 20 finished + * @run main/othervm RespondToRetransmit server 21 certificate_url + * @run main/othervm RespondToRetransmit server 22 certificate_status + * @run main/othervm RespondToRetransmit server 23 supplemental_data + * @run main/othervm RespondToRetransmit server -1 change_cipher_spec + */ + +import java.util.List; +import java.util.ArrayList; +import java.net.DatagramPacket; +import java.net.SocketAddress; +import javax.net.ssl.SSLEngine; + +/** + * Test that DTLS implementation is able to do retransmission internally + * automatically if packet get lost. + */ +public class RespondToRetransmit extends DTLSOverDatagram { + private static boolean isClient; + private static byte handshakeType; + + private boolean needPacketDuplicate = true; + + public static void main(String[] args) throws Exception { + isClient = args[0].equals("client"); + handshakeType = Byte.valueOf(args[1]); + + RespondToRetransmit testCase = new RespondToRetransmit(); + testCase.runTest(testCase); + } + + @Override + boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr, + String side, List packets) throws Exception { + + boolean finished = super.produceHandshakePackets( + engine, socketAddr, side, packets); + + if (needPacketDuplicate && (!(isClient ^ engine.getUseClientMode()))) { + DatagramPacket packet = getPacket(packets, handshakeType); + if (packet != null) { + needPacketDuplicate = false; + + System.out.println("Duplicate the flight."); + List duplicates = new ArrayList<>(); + finished = super.produceHandshakePackets( + engine, socketAddr, side, duplicates); + packets.addAll(duplicates); + } + } + + return finished; + } +}