8140466: ChaCha20 and Poly1305 TLS Cipher Suites
authorjnimeh
Mon, 17 Sep 2018 15:25:42 -0700
changeset 51773 720fd6544b03
parent 51772 5432cebf6627
child 51774 79dc492c00ab
child 56889 6658178cefe6
8140466: ChaCha20 and Poly1305 TLS Cipher Suites Reviewed-by: xuelei, mullan
src/java.base/share/classes/sun/security/ssl/CipherSuite.java
src/java.base/share/classes/sun/security/ssl/JsseJce.java
src/java.base/share/classes/sun/security/ssl/SSLCipher.java
test/jdk/javax/net/ssl/TLSCommon/CipherSuite.java
test/jdk/javax/net/ssl/TLSCommon/SSLEngineTestCase.java
--- a/src/java.base/share/classes/sun/security/ssl/CipherSuite.java	Mon Sep 17 15:22:46 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/CipherSuite.java	Mon Sep 17 15:25:42 2018 -0700
@@ -70,6 +70,9 @@
     TLS_AES_256_GCM_SHA384(
             0x1302, true, "TLS_AES_256_GCM_SHA384",
             ProtocolVersion.PROTOCOLS_OF_13, B_AES_256_GCM_IV, H_SHA384),
+    TLS_CHACHA20_POLY1305_SHA256(
+            0x1303, true, "TLS_CHACHA20_POLY1305_SHA256",
+            ProtocolVersion.PROTOCOLS_OF_13, B_CC20_P1305, H_SHA256),
 
     // Suite B compliant cipher suites, see RFC 6460.
     //
@@ -87,11 +90,22 @@
             ProtocolVersion.PROTOCOLS_OF_12,
             K_ECDHE_ECDSA, B_AES_128_GCM, M_NULL, H_SHA256),
 
+    // Not suite B, but we want it to position the suite early in the list
+    // of 1.2 suites.
+    TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(
+            0xCCA9, true, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "",
+            ProtocolVersion.PROTOCOLS_OF_12,
+            K_ECDHE_ECDSA, B_CC20_P1305, M_NULL, H_SHA256),
+
     // AES_256(GCM)
     TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(
             0xC030, true, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "",
             ProtocolVersion.PROTOCOLS_OF_12,
             K_ECDHE_RSA, B_AES_256_GCM, M_NULL, H_SHA384),
+    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
+            0xCCA8, true, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "",
+            ProtocolVersion.PROTOCOLS_OF_12,
+            K_ECDHE_RSA, B_CC20_P1305, M_NULL, H_SHA256),
     TLS_RSA_WITH_AES_256_GCM_SHA384(
             0x009D, true, "TLS_RSA_WITH_AES_256_GCM_SHA384", "",
             ProtocolVersion.PROTOCOLS_OF_12,
@@ -108,6 +122,10 @@
             0x009F, true, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "",
             ProtocolVersion.PROTOCOLS_OF_12,
             K_DHE_RSA, B_AES_256_GCM, M_NULL, H_SHA384),
+    TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
+            0xCCAA, true, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "",
+            ProtocolVersion.PROTOCOLS_OF_12,
+            K_DHE_RSA, B_CC20_P1305, M_NULL, H_SHA256),
     TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(
             0x00A3, true, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "",
             ProtocolVersion.PROTOCOLS_OF_12,
@@ -480,8 +498,6 @@
 
     // Definition of the CipherSuites that are not supported but the names
     // are known.
-    TLS_CHACHA20_POLY1305_SHA256(                    // TLS 1.3
-            "TLS_CHACHA20_POLY1305_SHA256", 0x1303),
     TLS_AES_128_CCM_SHA256(                          // TLS 1.3
             "TLS_AES_128_CCM_SHA256", 0x1304),
     TLS_AES_128_CCM_8_SHA256(                        // TLS 1.3
--- a/src/java.base/share/classes/sun/security/ssl/JsseJce.java	Mon Sep 17 15:22:46 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/JsseJce.java	Mon Sep 17 15:25:42 2018 -0700
@@ -130,6 +130,11 @@
     static final String CIPHER_AES_GCM = "AES/GCM/NoPadding";
 
     /**
+     * JCE transformation string for ChaCha20-Poly1305
+     */
+    static final String CIPHER_CHACHA20_POLY1305 = "ChaCha20-Poly1305";
+
+    /**
      * JCA identifier string for DSA, i.e. a DSA with SHA-1.
      */
     static final String SIGNATURE_DSA = "DSA";
--- a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java	Mon Sep 17 15:22:46 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java	Mon Sep 17 15:25:42 2018 -0700
@@ -329,6 +329,32 @@
                 new T13GcmWriteCipherGenerator(),
                 ProtocolVersion.PROTOCOLS_OF_13
             )
+        })),
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    B_CC20_P1305(CIPHER_CHACHA20_POLY1305, AEAD_CIPHER, 32, 32, 12,
+            12, true, false,
+        (Map.Entry<ReadCipherGenerator,
+                ProtocolVersion[]>[])(new Map.Entry[] {
+            new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+                new T12CC20P1305ReadCipherGenerator(),
+                ProtocolVersion.PROTOCOLS_OF_12
+            ),
+            new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+                new T13CC20P1305ReadCipherGenerator(),
+                ProtocolVersion.PROTOCOLS_OF_13
+            )
+        }),
+        (Map.Entry<WriteCipherGenerator,
+                ProtocolVersion[]>[])(new Map.Entry[] {
+            new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+                new T12CC20P1305WriteCipherGenerator(),
+                ProtocolVersion.PROTOCOLS_OF_12
+            ),
+            new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+                new T13CC20P1305WriteCipherGenerator(),
+                ProtocolVersion.PROTOCOLS_OF_13
+            )
         }));
 
     // descriptive name including key size, e.g. AES/128
@@ -2082,6 +2108,549 @@
         }
     }
 
+    private static final class T12CC20P1305ReadCipherGenerator
+            implements ReadCipherGenerator {
+
+        @Override
+        public SSLReadCipher createCipher(SSLCipher sslCipher,
+                Authenticator authenticator, ProtocolVersion protocolVersion,
+                String algorithm, Key key, AlgorithmParameterSpec params,
+                SecureRandom random) throws GeneralSecurityException {
+            return new CC20P1305ReadCipher(authenticator, protocolVersion,
+                    sslCipher, algorithm, key, params, random);
+        }
+
+        static final class CC20P1305ReadCipher extends SSLReadCipher {
+            private final Cipher cipher;
+            private final int tagSize;
+            private final Key key;
+            private final byte[] iv;
+            private final SecureRandom random;
+
+            CC20P1305ReadCipher(Authenticator authenticator,
+                    ProtocolVersion protocolVersion,
+                    SSLCipher sslCipher, String algorithm,
+                    Key key, AlgorithmParameterSpec params,
+                    SecureRandom random) throws GeneralSecurityException {
+                super(authenticator, protocolVersion);
+                this.cipher = JsseJce.getCipher(algorithm);
+                this.tagSize = sslCipher.tagSize;
+                this.key = key;
+                this.iv = ((IvParameterSpec)params).getIV();
+                this.random = random;
+
+                // DON'T initialize the cipher for AEAD!
+            }
+
+            @Override
+            public Plaintext decrypt(byte contentType, ByteBuffer bb,
+                    byte[] sequence) throws GeneralSecurityException {
+                if (bb.remaining() <= tagSize) {
+                    throw new BadPaddingException(
+                        "Insufficient buffer remaining for AEAD cipher " +
+                        "fragment (" + bb.remaining() + "). Needs to be " +
+                        "more than tag size (" + tagSize + ")");
+                }
+
+                byte[] sn = sequence;
+                if (sn == null) {
+                    sn = authenticator.sequenceNumber();
+                }
+                byte[] nonce = new byte[iv.length];
+                System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+                        sn.length);
+                for (int i = 0; i < nonce.length; i++) {
+                    nonce[i] ^= iv[i];
+                }
+
+                // initialize the AEAD cipher with the unique IV
+                AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+                try {
+                    cipher.init(Cipher.DECRYPT_MODE, key, spec, random);
+                } catch (InvalidKeyException |
+                            InvalidAlgorithmParameterException ikae) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                                "invalid key or spec in AEAD mode", ikae);
+                }
+
+                // update the additional authentication data
+                byte[] aad = authenticator.acquireAuthenticationBytes(
+                        contentType, bb.remaining() - tagSize, sequence);
+                cipher.updateAAD(aad);
+
+                // DON'T decrypt the nonce_explicit for AEAD mode. The buffer
+                // position has moved out of the nonce_explicit range.
+                int len = bb.remaining();
+                int pos = bb.position();
+                ByteBuffer dup = bb.duplicate();
+                try {
+                    len = cipher.doFinal(dup, bb);
+                } catch (IllegalBlockSizeException ibse) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                        "Cipher error in AEAD mode \"" + ibse.getMessage() +
+                        " \"in JCE provider " + cipher.getProvider().getName());
+                } catch (ShortBufferException sbe) {
+                    // catch BouncyCastle buffering error
+                    throw new RuntimeException("Cipher buffering error in " +
+                        "JCE provider " + cipher.getProvider().getName(), sbe);
+                }
+                // reset the limit to the end of the decrypted data
+                bb.position(pos);
+                bb.limit(pos + len);
+
+                if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+                    SSLLogger.fine(
+                            "Plaintext after DECRYPTION", bb.duplicate());
+                }
+
+                return new Plaintext(contentType,
+                        ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
+                        -1, -1L, bb.slice());
+            }
+
+            @Override
+            void dispose() {
+                if (cipher != null) {
+                    try {
+                        cipher.doFinal();
+                    } catch (Exception e) {
+                        // swallow all types of exceptions.
+                    }
+                }
+            }
+
+            @Override
+            int estimateFragmentSize(int packetSize, int headerSize) {
+                return packetSize - headerSize - tagSize;
+            }
+        }
+    }
+
+    private static final class T12CC20P1305WriteCipherGenerator
+            implements WriteCipherGenerator {
+        @Override
+        public SSLWriteCipher createCipher(SSLCipher sslCipher,
+                Authenticator authenticator, ProtocolVersion protocolVersion,
+                String algorithm, Key key, AlgorithmParameterSpec params,
+                SecureRandom random) throws GeneralSecurityException {
+            return new CC20P1305WriteCipher(authenticator, protocolVersion,
+                    sslCipher, algorithm, key, params, random);
+        }
+
+        private static final class CC20P1305WriteCipher extends SSLWriteCipher {
+            private final Cipher cipher;
+            private final int tagSize;
+            private final Key key;
+            private final byte[] iv;
+            private final SecureRandom random;
+
+            CC20P1305WriteCipher(Authenticator authenticator,
+                    ProtocolVersion protocolVersion,
+                    SSLCipher sslCipher, String algorithm,
+                    Key key, AlgorithmParameterSpec params,
+                    SecureRandom random) throws GeneralSecurityException {
+                super(authenticator, protocolVersion);
+                this.cipher = JsseJce.getCipher(algorithm);
+                this.tagSize = sslCipher.tagSize;
+                this.key = key;
+                this.iv = ((IvParameterSpec)params).getIV();
+                this.random = random;
+
+                keyLimitCountdown = cipherLimits.getOrDefault(
+                        algorithm.toUpperCase() + ":" + tag[0], 0L);
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.fine("algorithm = " + algorithm.toUpperCase() +
+                            ":" + tag[0] + "\ncountdown value = " +
+                            keyLimitCountdown);
+                }
+                if (keyLimitCountdown > 0) {
+                    keyLimitEnabled = true;
+                }
+
+                // DON'T initialize the cipher for AEAD!
+            }
+
+            @Override
+            public int encrypt(byte contentType,
+                    ByteBuffer bb) {
+                byte[] sn = authenticator.sequenceNumber();
+                byte[] nonce = new byte[iv.length];
+                System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+                        sn.length);
+                for (int i = 0; i < nonce.length; i++) {
+                    nonce[i] ^= iv[i];
+                }
+
+                // initialize the AEAD cipher for the unique IV
+                AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+                try {
+                    cipher.init(Cipher.ENCRYPT_MODE, key, spec, random);
+                } catch (InvalidKeyException |
+                            InvalidAlgorithmParameterException ikae) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                                "invalid key or spec in AEAD mode", ikae);
+                }
+
+                // Update the additional authentication data, using the
+                // implicit sequence number of the authenticator.
+                byte[] aad = authenticator.acquireAuthenticationBytes(
+                                        contentType, bb.remaining(), null);
+                cipher.updateAAD(aad);
+
+                // DON'T encrypt the nonce for AEAD mode.
+                int len = bb.remaining();
+                int pos = bb.position();
+                if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+                    SSLLogger.fine(
+                            "Plaintext before ENCRYPTION",
+                            bb.duplicate());
+                }
+
+                ByteBuffer dup = bb.duplicate();
+                int outputSize = cipher.getOutputSize(dup.remaining());
+                if (outputSize > bb.remaining()) {
+                    // Need to expand the limit of the output buffer for
+                    // the authentication tag.
+                    //
+                    // DON'T worry about the buffer's capacity, we have
+                    // reserved space for the authentication tag.
+                    bb.limit(pos + outputSize);
+                }
+
+                try {
+                    len = cipher.doFinal(dup, bb);
+                } catch (IllegalBlockSizeException |
+                            BadPaddingException | ShortBufferException ibse) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                            "Cipher error in AEAD mode in JCE provider " +
+                            cipher.getProvider().getName(), ibse);
+                }
+
+                if (len != outputSize) {
+                    throw new RuntimeException(
+                            "Cipher buffering error in JCE provider " +
+                            cipher.getProvider().getName());
+                }
+
+                return len;
+            }
+
+            @Override
+            void dispose() {
+                if (cipher != null) {
+                    try {
+                        cipher.doFinal();
+                    } catch (Exception e) {
+                        // swallow all types of exceptions.
+                    }
+                }
+            }
+
+            @Override
+            int getExplicitNonceSize() {
+                return 0;
+            }
+
+            @Override
+            int calculateFragmentSize(int packetLimit, int headerSize) {
+                return packetLimit - headerSize - tagSize;
+            }
+
+            @Override
+            int calculatePacketSize(int fragmentSize, int headerSize) {
+                return fragmentSize + headerSize + tagSize;
+            }
+        }
+    }
+
+    private static final class T13CC20P1305ReadCipherGenerator
+            implements ReadCipherGenerator {
+
+        @Override
+        public SSLReadCipher createCipher(SSLCipher sslCipher,
+                Authenticator authenticator, ProtocolVersion protocolVersion,
+                String algorithm, Key key, AlgorithmParameterSpec params,
+                SecureRandom random) throws GeneralSecurityException {
+            return new CC20P1305ReadCipher(authenticator, protocolVersion,
+                    sslCipher, algorithm, key, params, random);
+        }
+
+        static final class CC20P1305ReadCipher extends SSLReadCipher {
+            private final Cipher cipher;
+            private final int tagSize;
+            private final Key key;
+            private final byte[] iv;
+            private final SecureRandom random;
+
+            CC20P1305ReadCipher(Authenticator authenticator,
+                    ProtocolVersion protocolVersion,
+                    SSLCipher sslCipher, String algorithm,
+                    Key key, AlgorithmParameterSpec params,
+                    SecureRandom random) throws GeneralSecurityException {
+                super(authenticator, protocolVersion);
+                this.cipher = JsseJce.getCipher(algorithm);
+                this.tagSize = sslCipher.tagSize;
+                this.key = key;
+                this.iv = ((IvParameterSpec)params).getIV();
+                this.random = random;
+
+                // DON'T initialize the cipher for AEAD!
+            }
+
+            @Override
+            public Plaintext decrypt(byte contentType, ByteBuffer bb,
+                    byte[] sequence) throws GeneralSecurityException {
+                // An implementation may receive an unencrypted record of type
+                // change_cipher_spec consisting of the single byte value 0x01
+                // at any time after the first ClientHello message has been
+                // sent or received and before the peer's Finished message has
+                // been received and MUST simply drop it without further
+                // processing.
+                if (contentType == ContentType.CHANGE_CIPHER_SPEC.id) {
+                    return new Plaintext(contentType,
+                        ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
+                        -1, -1L, bb.slice());
+                }
+
+                if (bb.remaining() <= tagSize) {
+                    throw new BadPaddingException(
+                        "Insufficient buffer remaining for AEAD cipher " +
+                        "fragment (" + bb.remaining() + "). Needs to be " +
+                        "more than tag size (" + tagSize + ")");
+                }
+
+                byte[] sn = sequence;
+                if (sn == null) {
+                    sn = authenticator.sequenceNumber();
+                }
+                byte[] nonce = new byte[iv.length];
+                System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+                        sn.length);
+                for (int i = 0; i < nonce.length; i++) {
+                    nonce[i] ^= iv[i];
+                }
+
+                // initialize the AEAD cipher with the unique IV
+                AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+                try {
+                    cipher.init(Cipher.DECRYPT_MODE, key, spec, random);
+                } catch (InvalidKeyException |
+                            InvalidAlgorithmParameterException ikae) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                                "invalid key or spec in AEAD mode", ikae);
+                }
+
+                // Update the additional authentication data, using the
+                // implicit sequence number of the authenticator.
+                byte[] aad = authenticator.acquireAuthenticationBytes(
+                                        contentType, bb.remaining(), sn);
+                cipher.updateAAD(aad);
+
+                int len = bb.remaining();
+                int pos = bb.position();
+                ByteBuffer dup = bb.duplicate();
+                try {
+                    len = cipher.doFinal(dup, bb);
+                } catch (IllegalBlockSizeException ibse) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                        "Cipher error in AEAD mode \"" + ibse.getMessage() +
+                        " \"in JCE provider " + cipher.getProvider().getName());
+                } catch (ShortBufferException sbe) {
+                    // catch BouncyCastle buffering error
+                    throw new RuntimeException("Cipher buffering error in " +
+                        "JCE provider " + cipher.getProvider().getName(), sbe);
+                }
+                // reset the limit to the end of the decrypted data
+                bb.position(pos);
+                bb.limit(pos + len);
+
+                // remove inner plaintext padding
+                int i = bb.limit() - 1;
+                for (; i > 0 && bb.get(i) == 0; i--) {
+                    // blank
+                }
+                if (i < (pos + 1)) {
+                    throw new BadPaddingException(
+                            "Incorrect inner plaintext: no content type");
+                }
+                contentType = bb.get(i);
+                bb.limit(i);
+
+                if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+                    SSLLogger.fine(
+                            "Plaintext after DECRYPTION", bb.duplicate());
+                }
+
+                return new Plaintext(contentType,
+                        ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
+                        -1, -1L, bb.slice());
+            }
+
+            @Override
+            void dispose() {
+                if (cipher != null) {
+                    try {
+                        cipher.doFinal();
+                    } catch (Exception e) {
+                        // swallow all types of exceptions.
+                    }
+                }
+            }
+
+            @Override
+            int estimateFragmentSize(int packetSize, int headerSize) {
+                return packetSize - headerSize - tagSize;
+            }
+        }
+    }
+
+    private static final class T13CC20P1305WriteCipherGenerator
+            implements WriteCipherGenerator {
+        @Override
+        public SSLWriteCipher createCipher(SSLCipher sslCipher,
+                Authenticator authenticator, ProtocolVersion protocolVersion,
+                String algorithm, Key key, AlgorithmParameterSpec params,
+                SecureRandom random) throws GeneralSecurityException {
+            return new CC20P1305WriteCipher(authenticator, protocolVersion,
+                    sslCipher, algorithm, key, params, random);
+        }
+
+        private static final class CC20P1305WriteCipher extends SSLWriteCipher {
+            private final Cipher cipher;
+            private final int tagSize;
+            private final Key key;
+            private final byte[] iv;
+            private final SecureRandom random;
+
+            CC20P1305WriteCipher(Authenticator authenticator,
+                    ProtocolVersion protocolVersion,
+                    SSLCipher sslCipher, String algorithm,
+                    Key key, AlgorithmParameterSpec params,
+                    SecureRandom random) throws GeneralSecurityException {
+                super(authenticator, protocolVersion);
+                this.cipher = JsseJce.getCipher(algorithm);
+                this.tagSize = sslCipher.tagSize;
+                this.key = key;
+                this.iv = ((IvParameterSpec)params).getIV();
+                this.random = random;
+
+                keyLimitCountdown = cipherLimits.getOrDefault(
+                        algorithm.toUpperCase() + ":" + tag[0], 0L);
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.fine("algorithm = " + algorithm.toUpperCase() +
+                            ":" + tag[0] + "\ncountdown value = " +
+                            keyLimitCountdown);
+                }
+                if (keyLimitCountdown > 0) {
+                    keyLimitEnabled = true;
+                }
+
+                // DON'T initialize the cipher for AEAD!
+            }
+
+            @Override
+            public int encrypt(byte contentType,
+                    ByteBuffer bb) {
+                byte[] sn = authenticator.sequenceNumber();
+                byte[] nonce = new byte[iv.length];
+                System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+                        sn.length);
+                for (int i = 0; i < nonce.length; i++) {
+                    nonce[i] ^= iv[i];
+                }
+
+                // initialize the AEAD cipher for the unique IV
+                AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+                try {
+                    cipher.init(Cipher.ENCRYPT_MODE, key, spec, random);
+                } catch (InvalidKeyException |
+                            InvalidAlgorithmParameterException ikae) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                                "invalid key or spec in AEAD mode", ikae);
+                }
+
+                // Update the additional authentication data, using the
+                // implicit sequence number of the authenticator.
+                int outputSize = cipher.getOutputSize(bb.remaining());
+                byte[] aad = authenticator.acquireAuthenticationBytes(
+                                        contentType, outputSize, sn);
+                cipher.updateAAD(aad);
+
+                int len = bb.remaining();
+                int pos = bb.position();
+                if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+                    SSLLogger.fine(
+                            "Plaintext before ENCRYPTION",
+                            bb.duplicate());
+                }
+
+                ByteBuffer dup = bb.duplicate();
+                if (outputSize > bb.remaining()) {
+                    // Need to expand the limit of the output buffer for
+                    // the authentication tag.
+                    //
+                    // DON'T worry about the buffer's capacity, we have
+                    // reserved space for the authentication tag.
+                    bb.limit(pos + outputSize);
+                }
+
+                try {
+                    len = cipher.doFinal(dup, bb);
+                } catch (IllegalBlockSizeException |
+                            BadPaddingException | ShortBufferException ibse) {
+                    // unlikely to happen
+                    throw new RuntimeException(
+                            "Cipher error in AEAD mode in JCE provider " +
+                            cipher.getProvider().getName(), ibse);
+                }
+
+                if (len != outputSize) {
+                    throw new RuntimeException(
+                            "Cipher buffering error in JCE provider " +
+                            cipher.getProvider().getName());
+                }
+
+                if (keyLimitEnabled) {
+                    keyLimitCountdown -= len;
+                }
+                return len;
+            }
+
+            @Override
+            void dispose() {
+                if (cipher != null) {
+                    try {
+                        cipher.doFinal();
+                    } catch (Exception e) {
+                        // swallow all types of exceptions.
+                    }
+                }
+            }
+
+            @Override
+            int getExplicitNonceSize() {
+                return 0;
+            }
+
+            @Override
+            int calculateFragmentSize(int packetLimit, int headerSize) {
+                return packetLimit - headerSize - tagSize;
+            }
+
+            @Override
+            int calculatePacketSize(int fragmentSize, int headerSize) {
+                return fragmentSize + headerSize + tagSize;
+            }
+        }
+    }
+
     private static void addMac(MAC signer,
             ByteBuffer destination, byte contentType) {
         if (signer.macAlg().size != 0) {
@@ -2367,4 +2936,3 @@
         return results;
     }
 }
-
--- a/test/jdk/javax/net/ssl/TLSCommon/CipherSuite.java	Mon Sep 17 15:22:46 2018 -0700
+++ b/test/jdk/javax/net/ssl/TLSCommon/CipherSuite.java	Mon Sep 17 15:25:42 2018 -0700
@@ -27,6 +27,8 @@
             0x1302, Protocol.TLSV1_3, Protocol.TLSV1_3),
     TLS_AES_128_GCM_SHA256(
             0x1301, Protocol.TLSV1_3, Protocol.TLSV1_3),
+    TLS_CHACHA20_POLY1305_SHA256(
+            0x1303, Protocol.TLSV1_3, Protocol.TLSV1_3),
     TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
             0xCCAA, Protocol.TLSV1_2, Protocol.TLSV1_2),
     TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(
--- a/test/jdk/javax/net/ssl/TLSCommon/SSLEngineTestCase.java	Mon Sep 17 15:22:46 2018 -0700
+++ b/test/jdk/javax/net/ssl/TLSCommon/SSLEngineTestCase.java	Mon Sep 17 15:25:42 2018 -0700
@@ -182,7 +182,8 @@
 
     private static final String[] TLS13_CIPHERS = {
             "TLS_AES_256_GCM_SHA384",
-            "TLS_AES_128_GCM_SHA256"
+            "TLS_AES_128_GCM_SHA256",
+            "TLS_CHACHA20_POLY1305_SHA256"
     };
 
     private static final String[] SUPPORTED_NON_KRB_CIPHERS;