--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java Fri May 11 15:53:12 2018 -0700
@@ -0,0 +1,2356 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.nio.ByteBuffer;
+import java.security.AccessController;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivilegedAction;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import sun.security.ssl.Authenticator.MAC;
+import static sun.security.ssl.CipherType.*;
+import static sun.security.ssl.JsseJce.*;
+
+enum SSLCipher {
+ // exportable ciphers
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_NULL("NULL", NULL_CIPHER, 0, 0, 0, 0, true, true,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new NullReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_NONE
+ ),
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new NullReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_13
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new NullWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_NONE
+ ),
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new NullWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_13
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_RC4_40(CIPHER_RC4, STREAM_CIPHER, 5, 16, 0, 0, true, true,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new StreamReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new StreamWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_RC2_40("RC2", BLOCK_CIPHER, 5, 16, 8, 0, false, true,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new StreamReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new StreamWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_DES_40(CIPHER_DES, BLOCK_CIPHER, 5, 8, 8, 0, true, true,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T10BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T10BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ )
+ })),
+
+ // domestic strength ciphers
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_RC4_128(CIPHER_RC4, STREAM_CIPHER, 16, 16, 0, 0, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new StreamReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_12
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new StreamWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_12
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_DES(CIPHER_DES, BLOCK_CIPHER, 8, 8, 8, 0, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T10BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T11BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_11
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T10BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T11BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_11
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_3DES(CIPHER_3DES, BLOCK_CIPHER, 24, 24, 8, 0, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T10BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T11BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_11_12
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T10BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T11BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_11_12
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_IDEA("IDEA", BLOCK_CIPHER, 16, 16, 8, 0, false, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ null,
+ ProtocolVersion.PROTOCOLS_TO_12
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ null,
+ ProtocolVersion.PROTOCOLS_TO_12
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_AES_128(CIPHER_AES, BLOCK_CIPHER, 16, 16, 16, 0, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T10BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T11BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_11_12
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T10BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T11BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_11_12
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_AES_256(CIPHER_AES, BLOCK_CIPHER, 32, 32, 16, 0, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T10BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T11BlockReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_11_12
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T10BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_TO_10
+ ),
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T11BlockWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_11_12
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_AES_128_GCM(CIPHER_AES_GCM, AEAD_CIPHER, 16, 16, 12, 4, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T12GcmReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_12
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T12GcmWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_12
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_AES_256_GCM(CIPHER_AES_GCM, AEAD_CIPHER, 32, 32, 12, 4, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T12GcmReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_12
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T12GcmWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_12
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_AES_128_GCM_IV(CIPHER_AES_GCM, AEAD_CIPHER, 16, 16, 12, 0, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T13GcmReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_13
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T13GcmWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_13
+ )
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_AES_256_GCM_IV(CIPHER_AES_GCM, AEAD_CIPHER, 32, 32, 12, 0, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T13GcmReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_13
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T13GcmWriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_13
+ )
+ }));
+
+ // descriptive name including key size, e.g. AES/128
+ final String description;
+
+ // JCE cipher transformation string, e.g. AES/CBC/NoPadding
+ final String transformation;
+
+ // algorithm name, e.g. AES
+ final String algorithm;
+
+ // supported and compile time enabled. Also see isAvailable()
+ final boolean allowed;
+
+ // number of bytes of entropy in the key
+ final int keySize;
+
+ // length of the actual cipher key in bytes.
+ // for non-exportable ciphers, this is the same as keySize
+ final int expandedKeySize;
+
+ // size of the IV
+ final int ivSize;
+
+ // size of fixed IV
+ //
+ // record_iv_length = ivSize - fixedIvSize
+ final int fixedIvSize;
+
+ // exportable under 512/40 bit rules
+ final boolean exportable;
+
+ // Is the cipher algorithm of Cipher Block Chaining (CBC) mode?
+ final CipherType cipherType;
+
+ // size of the authentication tag, only applicable to cipher suites in
+ // Galois Counter Mode (GCM)
+ //
+ // As far as we know, all supported GCM cipher suites use 128-bits
+ // authentication tags.
+ final int tagSize = 16;
+
+ // runtime availability
+ private final boolean isAvailable;
+
+ private final Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[] readCipherGenerators;
+ private final Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[] writeCipherGenerators;
+
+ // Map of Ciphers listed in jdk.tls.KeyLimit
+ private static final HashMap<String, Long> cipherLimits = new HashMap<>();
+
+ // Keywords found on the jdk.tls.KeyLimit security property.
+ final static String tag[] = {"KEYUPDATE"};
+
+ static {
+ final long max = 4611686018427387904L; // 2^62
+ String prop = AccessController.doPrivileged(
+ new PrivilegedAction<String>() {
+ @Override
+ public String run() {
+ return Security.getProperty("jdk.tls.keyLimits");
+ }
+ });
+
+ if (prop != null) {
+ String propvalue[] = prop.split(",");
+
+ for (String entry : propvalue) {
+ int index;
+ // If this is not a UsageLimit, goto to next entry.
+ String values[] = entry.trim().toUpperCase().split(" ");
+
+ if (values[1].contains(tag[0])) {
+ index = 0;
+ } else {
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+ SSLLogger.fine("jdk.net.keyLimits: Unknown action: " +
+ entry);
+ }
+ continue;
+ }
+
+ long size;
+ int i = values[2].indexOf("^");
+ try {
+ if (i >= 0) {
+ size = (long) Math.pow(2,
+ Integer.parseInt(values[2].substring(i + 1)));
+ } else {
+ size = Long.parseLong(values[2]);
+ }
+ if (size < 1 || size > max) {
+ throw new NumberFormatException("Length exceeded limits");
+ }
+ } catch (NumberFormatException e) {
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+ SSLLogger.fine("jdk.net.keyLimits: " + e.getMessage() +
+ ": " + entry);
+ }
+ continue;
+ }
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+ SSLLogger.fine("jdk.net.keyLimits: entry = " + entry +
+ ". " + values[0] + ":" + tag[index] + " = " + size);
+ }
+ cipherLimits.put(values[0] + ":" + tag[index], size);
+ }
+ }
+ }
+
+ private SSLCipher(String transformation,
+ CipherType cipherType, int keySize,
+ int expandedKeySize, int ivSize,
+ int fixedIvSize, boolean allowed, boolean exportable,
+ Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[] readCipherGenerators,
+ Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[] writeCipherGenerators) {
+ this.transformation = transformation;
+ String[] splits = transformation.split("/");
+ this.algorithm = splits[0];
+ this.cipherType = cipherType;
+ this.description = this.algorithm + "/" + (keySize << 3);
+ this.keySize = keySize;
+ this.ivSize = ivSize;
+ this.fixedIvSize = fixedIvSize;
+ this.allowed = allowed;
+
+ this.expandedKeySize = expandedKeySize;
+ this.exportable = exportable;
+
+ // availability of this bulk cipher
+ //
+ // We assume all supported ciphers are always available since they are
+ // shipped with the SunJCE provider. However, AES/256 is unavailable
+ // when the default JCE policy jurisdiction files are installed because
+ // of key length restrictions.
+ this.isAvailable =
+ allowed ? isUnlimited(keySize, transformation) : false;
+
+ this.readCipherGenerators = readCipherGenerators;
+ this.writeCipherGenerators = writeCipherGenerators;
+ }
+
+ SSLReadCipher createReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion,
+ SecretKey key, IvParameterSpec iv,
+ SecureRandom random) throws GeneralSecurityException {
+ if (readCipherGenerators.length == 0) {
+ return null;
+ }
+
+ ReadCipherGenerator rcg = null;
+ for (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]> me : readCipherGenerators) {
+ for (ProtocolVersion pv : me.getValue()) {
+ if (protocolVersion == pv) {
+ rcg = me.getKey();
+ }
+ }
+ }
+
+ if (rcg != null) {
+ return rcg.createCipher(this, authenticator,
+ protocolVersion, transformation, key, iv, random);
+ }
+ return null;
+ }
+
+ SSLWriteCipher createWriteCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion,
+ SecretKey key, IvParameterSpec iv,
+ SecureRandom random) throws GeneralSecurityException {
+ if (readCipherGenerators.length == 0) {
+ return null;
+ }
+
+ WriteCipherGenerator rcg = null;
+ for (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]> me : writeCipherGenerators) {
+ for (ProtocolVersion pv : me.getValue()) {
+ if (protocolVersion == pv) {
+ rcg = me.getKey();
+ }
+ }
+ }
+
+ if (rcg != null) {
+ return rcg.createCipher(this, authenticator,
+ protocolVersion, transformation, key, iv, random);
+ }
+ return null;
+ }
+
+ public static final String getDefaultType() {
+ String prop = AccessController.doPrivileged(
+ new PrivilegedAction<String>() {
+ @Override
+ public String run() {
+ return Security.getProperty("jdk.tls.KeyLimits");
+ }
+ });
+ return prop;
+ }
+
+ /**
+ * Test if this bulk cipher is available. For use by CipherSuite.
+ */
+ boolean isAvailable() {
+ return this.isAvailable;
+ }
+
+ private static boolean isUnlimited(int keySize, String transformation) {
+ int keySizeInBits = keySize * 8;
+ if (keySizeInBits > 128) { // need the JCE unlimited
+ // strength jurisdiction policy
+ try {
+ if (Cipher.getMaxAllowedKeyLength(
+ transformation) < keySizeInBits) {
+ return false;
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+
+ interface ReadCipherGenerator {
+ SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException;
+ }
+
+ abstract static class SSLReadCipher {
+ final Authenticator authenticator;
+ final ProtocolVersion protocolVersion;
+ SecretKey baseSecret;
+
+ SSLReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion) {
+ this.authenticator = authenticator;
+ this.protocolVersion = protocolVersion;
+ }
+
+ static final SSLReadCipher nullTlsReadCipher() {
+ try {
+ return B_NULL.createReadCipher(
+ Authenticator.nullTlsMac(),
+ ProtocolVersion.NONE, null, null, null);
+ } catch (GeneralSecurityException gse) {
+ // unlikely
+ throw new RuntimeException("Cannot create NULL SSLCipher", gse);
+ }
+ }
+
+ static final SSLReadCipher nullDTlsReadCipher() {
+ try {
+ return B_NULL.createReadCipher(
+ Authenticator.nullDtlsMac(),
+ ProtocolVersion.NONE, null, null, null);
+ } catch (GeneralSecurityException gse) {
+ // unlikely
+ throw new RuntimeException("Cannot create NULL SSLCipher", gse);
+ }
+ }
+
+ abstract Plaintext decrypt(byte contentType, ByteBuffer bb,
+ byte[] sequence) throws GeneralSecurityException;
+
+ void dispose() {
+ // blank
+ }
+
+ abstract int estimateFragmentSize(int packetSize, int headerSize);
+
+ boolean isNullCipher() {
+ return false;
+ }
+ }
+
+ interface WriteCipherGenerator {
+ SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException;
+ }
+
+ abstract static class SSLWriteCipher {
+ final Authenticator authenticator;
+ final ProtocolVersion protocolVersion;
+ boolean keyLimitEnabled = false;
+ long keyLimitCountdown = 0;
+ SecretKey baseSecret;
+
+ SSLWriteCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion) {
+ this.authenticator = authenticator;
+ this.protocolVersion = protocolVersion;
+ }
+
+ abstract int encrypt(byte contentType, ByteBuffer bb);
+
+ static final SSLWriteCipher nullTlsWriteCipher() {
+ try {
+ return B_NULL.createWriteCipher(
+ Authenticator.nullTlsMac(),
+ ProtocolVersion.NONE, null, null, null);
+ } catch (GeneralSecurityException gse) {
+ // unlikely
+ throw new RuntimeException(
+ "Cannot create NULL SSL write Cipher", gse);
+ }
+ }
+
+ static final SSLWriteCipher nullDTlsWriteCipher() {
+ try {
+ return B_NULL.createWriteCipher(
+ Authenticator.nullDtlsMac(),
+ ProtocolVersion.NONE, null, null, null);
+ } catch (GeneralSecurityException gse) {
+ // unlikely
+ throw new RuntimeException(
+ "Cannot create NULL SSL write Cipher", gse);
+ }
+ }
+
+ void dispose() {
+ // blank
+ }
+
+ abstract int getExplicitNonceSize();
+ abstract int calculateFragmentSize(int packetLimit, int headerSize);
+ abstract int calculatePacketSize(int fragmentSize, int headerSize);
+
+ boolean isCBCMode() {
+ return false;
+ }
+
+ boolean isNullCipher() {
+ return false;
+ }
+
+ /**
+ * Check if processed bytes have reached the key usage limit.
+ * If key usage limit is not be monitored, return false.
+ */
+ public boolean atKeyLimit() {
+ if (keyLimitCountdown >= 0) {
+ return false;
+ }
+
+ // Turn off limit checking as KeyUpdate will be occurring
+ keyLimitEnabled = false;
+ return true;
+ }
+ }
+
+ private static final
+ class NullReadCipherGenerator implements ReadCipherGenerator {
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new NullReadCipher(authenticator, protocolVersion);
+ }
+
+ static final class NullReadCipher extends SSLReadCipher {
+ NullReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion) {
+ super(authenticator, protocolVersion);
+ }
+
+ @Override
+ public Plaintext decrypt(byte contentType, ByteBuffer bb,
+ byte[] sequence) throws GeneralSecurityException {
+ MAC signer = (MAC)authenticator;
+ if (signer.macAlg().size != 0) {
+ checkStreamMac(signer, bb, contentType, sequence);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+
+ return new Plaintext(contentType,
+ ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
+ -1, -1L, bb.slice());
+ }
+
+ @Override
+ int estimateFragmentSize(int packetSize, int headerSize) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ return packetSize - headerSize - macLen;
+ }
+
+ @Override
+ boolean isNullCipher() {
+ return true;
+ }
+ }
+ }
+
+ private static final
+ class NullWriteCipherGenerator implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new NullWriteCipher(authenticator, protocolVersion);
+ }
+
+ static final class NullWriteCipher extends SSLWriteCipher {
+ NullWriteCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion) {
+ super(authenticator, protocolVersion);
+ }
+
+ @Override
+ public int encrypt(byte contentType, ByteBuffer bb) {
+ // add message authentication code
+ MAC signer = (MAC)authenticator;
+ if (signer.macAlg().size != 0) {
+ addMac(signer, bb, contentType);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+
+ int len = bb.remaining();
+ bb.position(bb.limit());
+ return len;
+ }
+
+
+ @Override
+ int getExplicitNonceSize() {
+ return 0;
+ }
+
+ @Override
+ int calculateFragmentSize(int packetLimit, int headerSize) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ return packetLimit - headerSize - macLen;
+ }
+
+ @Override
+ int calculatePacketSize(int fragmentSize, int headerSize) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ return fragmentSize + headerSize + macLen;
+ }
+
+ @Override
+ boolean isNullCipher() {
+ return true;
+ }
+ }
+ }
+
+ private static final
+ class StreamReadCipherGenerator implements ReadCipherGenerator {
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new StreamReadCipher(authenticator, protocolVersion,
+ algorithm, key, params, random);
+ }
+
+ static final class StreamReadCipher extends SSLReadCipher {
+ private final Cipher cipher;
+
+ StreamReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, key, params, random);
+ }
+
+ @Override
+ public Plaintext decrypt(byte contentType, ByteBuffer bb,
+ byte[] sequence) throws GeneralSecurityException {
+ int len = bb.remaining();
+ int pos = bb.position();
+ ByteBuffer dup = bb.duplicate();
+ try {
+ if (len != cipher.update(dup, bb)) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException(
+ "Unexpected number of plaintext bytes");
+ }
+ if (bb.position() != dup.position()) {
+ throw new RuntimeException(
+ "Unexpected Bytebuffer position");
+ }
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+ bb.position(pos);
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Plaintext after DECRYPTION", bb.duplicate());
+ }
+
+ MAC signer = (MAC)authenticator;
+ if (signer.macAlg().size != 0) {
+ checkStreamMac(signer, bb, contentType, sequence);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+
+ 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) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ return packetSize - headerSize - macLen;
+ }
+ }
+ }
+
+ private static final
+ class StreamWriteCipherGenerator implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new StreamWriteCipher(authenticator,
+ protocolVersion, algorithm, key, params, random);
+ }
+
+ static final class StreamWriteCipher extends SSLWriteCipher {
+ private final Cipher cipher;
+
+ StreamWriteCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, key, params, random);
+ }
+
+ @Override
+ public int encrypt(byte contentType, ByteBuffer bb) {
+ // add message authentication code
+ MAC signer = (MAC)authenticator;
+ if (signer.macAlg().size != 0) {
+ addMac(signer, bb, contentType);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.finest(
+ "Padded plaintext before ENCRYPTION", bb.duplicate());
+ }
+
+ int len = bb.remaining();
+ ByteBuffer dup = bb.duplicate();
+ try {
+ if (len != cipher.update(dup, bb)) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException(
+ "Unexpected number of plaintext bytes");
+ }
+ if (bb.position() != dup.position()) {
+ throw new RuntimeException(
+ "Unexpected Bytebuffer position");
+ }
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+
+ 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) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ return packetLimit - headerSize - macLen;
+ }
+
+ @Override
+ int calculatePacketSize(int fragmentSize, int headerSize) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ return fragmentSize + headerSize + macLen;
+ }
+ }
+ }
+
+ private static final
+ class T10BlockReadCipherGenerator implements ReadCipherGenerator {
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new BlockReadCipher(authenticator,
+ protocolVersion, algorithm, key, params, random);
+ }
+
+ static final class BlockReadCipher extends SSLReadCipher {
+ private final Cipher cipher;
+
+ BlockReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, key, params, random);
+ }
+
+ @Override
+ public Plaintext decrypt(byte contentType, ByteBuffer bb,
+ byte[] sequence) throws GeneralSecurityException {
+ BadPaddingException reservedBPE = null;
+
+ // sanity check length of the ciphertext
+ MAC signer = (MAC)authenticator;
+ int cipheredLength = bb.remaining();
+ int tagLen = signer.macAlg().size;
+ if (tagLen != 0) {
+ if (!sanityCheck(tagLen, bb.remaining())) {
+ reservedBPE = new BadPaddingException(
+ "ciphertext sanity check failed");
+ }
+ }
+ // decryption
+ int len = bb.remaining();
+ int pos = bb.position();
+ ByteBuffer dup = bb.duplicate();
+ try {
+ if (len != cipher.update(dup, bb)) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException(
+ "Unexpected number of plaintext bytes");
+ }
+
+ if (bb.position() != dup.position()) {
+ throw new RuntimeException(
+ "Unexpected Bytebuffer position");
+ }
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Padded plaintext after DECRYPTION",
+ bb.duplicate().position(pos));
+ }
+
+ // remove the block padding
+ int blockSize = cipher.getBlockSize();
+ bb.position(pos);
+ try {
+ removePadding(bb, tagLen, blockSize, protocolVersion);
+ } catch (BadPaddingException bpe) {
+ if (reservedBPE == null) {
+ reservedBPE = bpe;
+ }
+ }
+
+ // Requires message authentication code for null, stream and
+ // block cipher suites.
+ try {
+ if (tagLen != 0) {
+ checkCBCMac(signer, bb,
+ contentType, cipheredLength, sequence);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+ } catch (BadPaddingException bpe) {
+ if (reservedBPE == null) {
+ reservedBPE = bpe;
+ }
+ }
+
+ // Is it a failover?
+ if (reservedBPE != null) {
+ throw reservedBPE;
+ }
+
+ 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) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+
+ // No padding for a maximum fragment.
+ //
+ // 1 byte padding length field: 0x00
+ return packetSize - headerSize - macLen - 1;
+ }
+
+ /**
+ * Sanity check the length of a fragment before decryption.
+ *
+ * In CBC mode, check that the fragment length is one or multiple
+ * times of the block size of the cipher suite, and is at least
+ * one (one is the smallest size of padding in CBC mode) bigger
+ * than the tag size of the MAC algorithm except the explicit IV
+ * size for TLS 1.1 or later.
+ *
+ * In non-CBC mode, check that the fragment length is not less than
+ * the tag size of the MAC algorithm.
+ *
+ * @return true if the length of a fragment matches above
+ * requirements
+ */
+ private boolean sanityCheck(int tagLen, int fragmentLen) {
+ int blockSize = cipher.getBlockSize();
+ if ((fragmentLen % blockSize) == 0) {
+ int minimal = tagLen + 1;
+ minimal = (minimal >= blockSize) ? minimal : blockSize;
+
+ return (fragmentLen >= minimal);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ private static final
+ class T10BlockWriteCipherGenerator implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new BlockWriteCipher(authenticator,
+ protocolVersion, algorithm, key, params, random);
+ }
+
+ static final class BlockWriteCipher extends SSLWriteCipher {
+ private final Cipher cipher;
+
+ BlockWriteCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, key, params, random);
+ }
+
+ @Override
+ public int encrypt(byte contentType, ByteBuffer bb) {
+ int pos = bb.position();
+
+ // add message authentication code
+ MAC signer = (MAC)authenticator;
+ if (signer.macAlg().size != 0) {
+ addMac(signer, bb, contentType);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+
+ int blockSize = cipher.getBlockSize();
+ int len = addPadding(bb, blockSize);
+ bb.position(pos);
+
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Padded plaintext before ENCRYPTION",
+ bb.duplicate());
+ }
+
+ ByteBuffer dup = bb.duplicate();
+ try {
+ if (len != cipher.update(dup, bb)) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException(
+ "Unexpected number of plaintext bytes");
+ }
+
+ if (bb.position() != dup.position()) {
+ throw new RuntimeException(
+ "Unexpected Bytebuffer position");
+ }
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+
+ 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) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ int blockSize = cipher.getBlockSize();
+ int fragLen = packetLimit - headerSize;
+ fragLen -= (fragLen % blockSize); // cannot hold a block
+ // No padding for a maximum fragment.
+ fragLen -= 1; // 1 byte padding length field: 0x00
+ fragLen -= macLen;
+ return fragLen;
+ }
+
+ @Override
+ int calculatePacketSize(int fragmentSize, int headerSize) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ int blockSize = cipher.getBlockSize();
+ int paddedLen = fragmentSize + macLen + 1;
+ if ((paddedLen % blockSize) != 0) {
+ paddedLen += blockSize - 1;
+ paddedLen -= paddedLen % blockSize;
+ }
+
+ return headerSize + paddedLen;
+ }
+
+ @Override
+ boolean isCBCMode() {
+ return true;
+ }
+ }
+ }
+
+ // For TLS 1.1 and 1.2
+ private static final
+ class T11BlockReadCipherGenerator implements ReadCipherGenerator {
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new BlockReadCipher(authenticator, protocolVersion,
+ sslCipher, algorithm, key, params, random);
+ }
+
+ static final class BlockReadCipher extends SSLReadCipher {
+ private final Cipher cipher;
+
+ BlockReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion,
+ SSLCipher sslCipher, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ if (params == null) {
+ params = new IvParameterSpec(new byte[sslCipher.ivSize]);
+ }
+ cipher.init(Cipher.DECRYPT_MODE, key, params, random);
+ }
+
+ @Override
+ public Plaintext decrypt(byte contentType, ByteBuffer bb,
+ byte[] sequence) throws GeneralSecurityException {
+ BadPaddingException reservedBPE = null;
+
+ // sanity check length of the ciphertext
+ MAC signer = (MAC)authenticator;
+ int cipheredLength = bb.remaining();
+ int tagLen = signer.macAlg().size;
+ if (tagLen != 0) {
+ if (!sanityCheck(tagLen, bb.remaining())) {
+ reservedBPE = new BadPaddingException(
+ "ciphertext sanity check failed");
+ }
+ }
+
+ // decryption
+ int len = bb.remaining();
+ int pos = bb.position();
+ ByteBuffer dup = bb.duplicate();
+ try {
+ if (len != cipher.update(dup, bb)) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException(
+ "Unexpected number of plaintext bytes");
+ }
+
+ if (bb.position() != dup.position()) {
+ throw new RuntimeException(
+ "Unexpected Bytebuffer position");
+ }
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Padded plaintext after DECRYPTION",
+ bb.duplicate().position(pos));
+ }
+
+ // Ignore the explicit nonce.
+ bb.position(pos + cipher.getBlockSize());
+ pos = bb.position();
+
+ // remove the block padding
+ int blockSize = cipher.getBlockSize();
+ bb.position(pos);
+ try {
+ removePadding(bb, tagLen, blockSize, protocolVersion);
+ } catch (BadPaddingException bpe) {
+ if (reservedBPE == null) {
+ reservedBPE = bpe;
+ }
+ }
+
+ // Requires message authentication code for null, stream and
+ // block cipher suites.
+ try {
+ if (tagLen != 0) {
+ checkCBCMac(signer, bb,
+ contentType, cipheredLength, sequence);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+ } catch (BadPaddingException bpe) {
+ if (reservedBPE == null) {
+ reservedBPE = bpe;
+ }
+ }
+
+ // Is it a failover?
+ if (reservedBPE != null) {
+ throw reservedBPE;
+ }
+
+ 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) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+
+ // No padding for a maximum fragment.
+ //
+ // 1 byte padding length field: 0x00
+ int nonceSize = cipher.getBlockSize();
+ return packetSize - headerSize - nonceSize - macLen - 1;
+ }
+
+ /**
+ * Sanity check the length of a fragment before decryption.
+ *
+ * In CBC mode, check that the fragment length is one or multiple
+ * times of the block size of the cipher suite, and is at least
+ * one (one is the smallest size of padding in CBC mode) bigger
+ * than the tag size of the MAC algorithm except the explicit IV
+ * size for TLS 1.1 or later.
+ *
+ * In non-CBC mode, check that the fragment length is not less than
+ * the tag size of the MAC algorithm.
+ *
+ * @return true if the length of a fragment matches above
+ * requirements
+ */
+ private boolean sanityCheck(int tagLen, int fragmentLen) {
+ int blockSize = cipher.getBlockSize();
+ if ((fragmentLen % blockSize) == 0) {
+ int minimal = tagLen + 1;
+ minimal = (minimal >= blockSize) ? minimal : blockSize;
+ minimal += blockSize;
+
+ return (fragmentLen >= minimal);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ // For TLS 1.1 and 1.2
+ private static final
+ class T11BlockWriteCipherGenerator implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new BlockWriteCipher(authenticator, protocolVersion,
+ sslCipher, algorithm, key, params, random);
+ }
+
+ static final class BlockWriteCipher extends SSLWriteCipher {
+ private final Cipher cipher;
+ private final SecureRandom random;
+
+ BlockWriteCipher(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.random = random;
+ if (params == null) {
+ params = new IvParameterSpec(new byte[sslCipher.ivSize]);
+ }
+ cipher.init(Cipher.ENCRYPT_MODE, key, params, random);
+ }
+
+ @Override
+ public int encrypt(byte contentType, ByteBuffer bb) {
+ // To be unique and aware of overflow-wrap, sequence number
+ // is used as the nonce_explicit of block cipher suites.
+ int pos = bb.position();
+
+ // add message authentication code
+ MAC signer = (MAC)authenticator;
+ if (signer.macAlg().size != 0) {
+ addMac(signer, bb, contentType);
+ } else {
+ authenticator.increaseSequenceNumber();
+ }
+
+ // DON'T WORRY, the nonce spaces are considered already.
+ byte[] nonce = new byte[cipher.getBlockSize()];
+ random.nextBytes(nonce);
+ pos = pos - nonce.length;
+ bb.position(pos);
+ bb.put(nonce);
+ bb.position(pos);
+
+ int blockSize = cipher.getBlockSize();
+ int len = addPadding(bb, blockSize);
+ bb.position(pos);
+
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Padded plaintext before ENCRYPTION",
+ bb.duplicate());
+ }
+
+ ByteBuffer dup = bb.duplicate();
+ try {
+ if (len != cipher.update(dup, bb)) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException(
+ "Unexpected number of plaintext bytes");
+ }
+
+ if (bb.position() != dup.position()) {
+ throw new RuntimeException(
+ "Unexpected Bytebuffer position");
+ }
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+
+ return len;
+ }
+
+ @Override
+ void dispose() {
+ if (cipher != null) {
+ try {
+ cipher.doFinal();
+ } catch (Exception e) {
+ // swallow all types of exceptions.
+ }
+ }
+ }
+
+ @Override
+ int getExplicitNonceSize() {
+ return cipher.getBlockSize();
+ }
+
+ @Override
+ int calculateFragmentSize(int packetLimit, int headerSize) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ int blockSize = cipher.getBlockSize();
+ int fragLen = packetLimit - headerSize - blockSize;
+ fragLen -= (fragLen % blockSize); // cannot hold a block
+ // No padding for a maximum fragment.
+ fragLen -= 1; // 1 byte padding length field: 0x00
+ fragLen -= macLen;
+ return fragLen;
+ }
+
+ @Override
+ int calculatePacketSize(int fragmentSize, int headerSize) {
+ int macLen = ((MAC)authenticator).macAlg().size;
+ int blockSize = cipher.getBlockSize();
+ int paddedLen = fragmentSize + macLen + 1;
+ if ((paddedLen % blockSize) != 0) {
+ paddedLen += blockSize - 1;
+ paddedLen -= paddedLen % blockSize;
+ }
+
+ return headerSize + blockSize + paddedLen;
+ }
+
+ @Override
+ boolean isCBCMode() {
+ return true;
+ }
+ }
+ }
+
+ private static final
+ class T12GcmReadCipherGenerator implements ReadCipherGenerator {
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new GcmReadCipher(authenticator, protocolVersion, sslCipher,
+ algorithm, key, params, random);
+ }
+
+ static final class GcmReadCipher extends SSLReadCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] fixedIv;
+ private final int recordIvSize;
+ private final SecureRandom random;
+
+ GcmReadCipher(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.fixedIv = ((IvParameterSpec)params).getIV();
+ this.recordIvSize = sslCipher.ivSize - sslCipher.fixedIvSize;
+ 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() < (recordIvSize + tagSize)) {
+ throw new BadPaddingException(
+ "Insufficient buffer remaining for AEAD cipher " +
+ "fragment (" + bb.remaining() + "). Needs to be " +
+ "more than or equal to IV size (" + recordIvSize +
+ ") + tag size (" + tagSize + ")");
+ }
+
+ // initialize the AEAD cipher for the unique IV
+ byte[] iv = Arrays.copyOf(fixedIv,
+ fixedIv.length + recordIvSize);
+ bb.get(iv, fixedIv.length, recordIvSize);
+ GCMParameterSpec spec = new GCMParameterSpec(tagSize * 8, iv);
+ try {
+ cipher.init(Cipher.DECRYPT_MODE, key, spec, random);
+ } catch (InvalidKeyException |
+ InvalidAlgorithmParameterException ikae) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "invalid key or spec in GCM 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 decryted 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 - recordIvSize - tagSize;
+ }
+ }
+ }
+
+ private static final
+ class T12GcmWriteCipherGenerator implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator,
+ ProtocolVersion protocolVersion, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new GcmWriteCipher(authenticator, protocolVersion, sslCipher,
+ algorithm, key, params, random);
+ }
+
+ private static final class GcmWriteCipher extends SSLWriteCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] fixedIv;
+ private final int recordIvSize;
+ private final SecureRandom random;
+
+ GcmWriteCipher(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.fixedIv = ((IvParameterSpec)params).getIV();
+ this.recordIvSize = sslCipher.ivSize - sslCipher.fixedIvSize;
+ this.random = random;
+
+ // DON'T initialize the cipher for AEAD!
+ }
+
+ @Override
+ public int encrypt(byte contentType,
+ ByteBuffer bb) {
+ // To be unique and aware of overflow-wrap, sequence number
+ // is used as the nonce_explicit of AEAD cipher suites.
+ byte[] nonce = authenticator.sequenceNumber();
+
+ // initialize the AEAD cipher for the unique IV
+ byte[] iv = Arrays.copyOf(fixedIv,
+ fixedIv.length + nonce.length);
+ System.arraycopy(nonce, 0, iv, fixedIv.length, nonce.length);
+
+ GCMParameterSpec spec = new GCMParameterSpec(tagSize * 8, iv);
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key, spec, random);
+ } catch (InvalidKeyException |
+ InvalidAlgorithmParameterException ikae) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "invalid key or spec in GCM 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 WORRY, the nonce spaces are considered already.
+ bb.position(bb.position() - nonce.length);
+ bb.put(nonce);
+
+ // 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 + nonce.length;
+ }
+
+ @Override
+ void dispose() {
+ if (cipher != null) {
+ try {
+ cipher.doFinal();
+ } catch (Exception e) {
+ // swallow all types of exceptions.
+ }
+ }
+ }
+
+ @Override
+ int getExplicitNonceSize() {
+ return recordIvSize;
+ }
+
+ @Override
+ int calculateFragmentSize(int packetLimit, int headerSize) {
+ return packetLimit - headerSize - recordIvSize - tagSize;
+ }
+
+ @Override
+ int calculatePacketSize(int fragmentSize, int headerSize) {
+ return fragmentSize + headerSize + recordIvSize + tagSize;
+ }
+ }
+ }
+
+ private static final
+ class T13GcmReadCipherGenerator implements ReadCipherGenerator {
+
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new GcmReadCipher(authenticator, protocolVersion, sslCipher,
+ algorithm, key, params, random);
+ }
+
+ static final class GcmReadCipher extends SSLReadCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] iv;
+ private final SecureRandom random;
+
+ GcmReadCipher(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 = iv.clone();
+ int offset = nonce.length - sn.length;
+ for (int i = 0; i < sn.length; i++) {
+ nonce[offset + i] ^= sn[i];
+ }
+
+ // initialize the AEAD cipher for the unique IV
+ GCMParameterSpec spec =
+ new GCMParameterSpec(tagSize * 8, 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 GCM 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 decryted 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 T13GcmWriteCipherGenerator implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new GcmWriteCipher(authenticator, protocolVersion, sslCipher,
+ algorithm, key, params, random);
+ }
+
+ private static final class GcmWriteCipher extends SSLWriteCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] iv;
+ private final SecureRandom random;
+
+ GcmWriteCipher(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 = iv.clone();
+ int offset = nonce.length - sn.length;
+ for (int i = 0; i < sn.length; i++) {
+ nonce[offset + i] ^= sn[i];
+ }
+
+ // initialize the AEAD cipher for the unique IV
+ GCMParameterSpec spec =
+ new GCMParameterSpec(tagSize * 8, 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 GCM 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) {
+ int dstContent = destination.position();
+ byte[] hash = signer.compute(contentType, destination, false);
+
+ /*
+ * position was advanced to limit in MAC compute above.
+ *
+ * Mark next area as writable (above layers should have
+ * established that we have plenty of room), then write
+ * out the hash.
+ */
+ destination.limit(destination.limit() + hash.length);
+ destination.put(hash);
+
+ // reset the position and limit
+ destination.position(dstContent);
+ }
+ }
+
+ // for null and stream cipher
+ private static void checkStreamMac(MAC signer, ByteBuffer bb,
+ byte contentType, byte[] sequence) throws BadPaddingException {
+ int tagLen = signer.macAlg().size;
+
+ // Requires message authentication code for null, stream and
+ // block cipher suites.
+ if (tagLen != 0) {
+ int contentLen = bb.remaining() - tagLen;
+ if (contentLen < 0) {
+ throw new BadPaddingException("bad record");
+ }
+
+ // Run MAC computation and comparison on the payload.
+ //
+ // MAC data would be stripped off during the check.
+ if (checkMacTags(contentType, bb, signer, sequence, false)) {
+ throw new BadPaddingException("bad record MAC");
+ }
+ }
+ }
+
+ // for CBC cipher
+ private static void checkCBCMac(MAC signer, ByteBuffer bb,
+ byte contentType, int cipheredLength,
+ byte[] sequence) throws BadPaddingException {
+ BadPaddingException reservedBPE = null;
+ int tagLen = signer.macAlg().size;
+ int pos = bb.position();
+
+ if (tagLen != 0) {
+ int contentLen = bb.remaining() - tagLen;
+ if (contentLen < 0) {
+ reservedBPE = new BadPaddingException("bad record");
+
+ // set offset of the dummy MAC
+ contentLen = cipheredLength - tagLen;
+ bb.limit(pos + cipheredLength);
+ }
+
+ // Run MAC computation and comparison on the payload.
+ //
+ // MAC data would be stripped off during the check.
+ if (checkMacTags(contentType, bb, signer, sequence, false)) {
+ if (reservedBPE == null) {
+ reservedBPE =
+ new BadPaddingException("bad record MAC");
+ }
+ }
+
+ // Run MAC computation and comparison on the remainder.
+ int remainingLen = calculateRemainingLen(
+ signer, cipheredLength, contentLen);
+
+ // NOTE: remainingLen may be bigger (less than 1 block of the
+ // hash algorithm of the MAC) than the cipheredLength.
+ //
+ // Is it possible to use a static buffer, rather than allocate
+ // it dynamically?
+ remainingLen += signer.macAlg().size;
+ ByteBuffer temporary = ByteBuffer.allocate(remainingLen);
+
+ // Won't need to worry about the result on the remainder. And
+ // then we won't need to worry about what's actual data to
+ // check MAC tag on. We start the check from the header of the
+ // buffer so that we don't need to construct a new byte buffer.
+ checkMacTags(contentType, temporary, signer, sequence, true);
+ }
+
+ // Is it a failover?
+ if (reservedBPE != null) {
+ throw reservedBPE;
+ }
+ }
+
+ /*
+ * Run MAC computation and comparison
+ */
+ private static boolean checkMacTags(byte contentType, ByteBuffer bb,
+ MAC signer, byte[] sequence, boolean isSimulated) {
+ int tagLen = signer.macAlg().size;
+ int position = bb.position();
+ int lim = bb.limit();
+ int macOffset = lim - tagLen;
+
+ bb.limit(macOffset);
+ byte[] hash = signer.compute(contentType, bb, sequence, isSimulated);
+ if (hash == null || tagLen != hash.length) {
+ // Something is wrong with MAC implementation.
+ throw new RuntimeException("Internal MAC error");
+ }
+
+ bb.position(macOffset);
+ bb.limit(lim);
+ try {
+ int[] results = compareMacTags(bb, hash);
+ return (results[0] != 0);
+ } finally {
+ // reset to the data
+ bb.position(position);
+ bb.limit(macOffset);
+ }
+ }
+
+ /*
+ * A constant-time comparison of the MAC tags.
+ *
+ * Please DON'T change the content of the ByteBuffer parameter!
+ */
+ private static int[] compareMacTags(ByteBuffer bb, byte[] tag) {
+ // An array of hits is used to prevent Hotspot optimization for
+ // the purpose of a constant-time check.
+ int[] results = {0, 0}; // {missed #, matched #}
+
+ // The caller ensures there are enough bytes available in the buffer.
+ // So we won't need to check the remaining of the buffer.
+ for (int i = 0; i < tag.length; i++) {
+ if (bb.get() != tag[i]) {
+ results[0]++; // mismatched bytes
+ } else {
+ results[1]++; // matched bytes
+ }
+ }
+
+ return results;
+ }
+
+ /*
+ * Calculate the length of a dummy buffer to run MAC computation
+ * and comparison on the remainder.
+ *
+ * The caller MUST ensure that the fullLen is not less than usedLen.
+ */
+ private static int calculateRemainingLen(
+ MAC signer, int fullLen, int usedLen) {
+
+ int blockLen = signer.macAlg().hashBlockSize;
+ int minimalPaddingLen = signer.macAlg().minimalPaddingSize;
+
+ // (blockLen - minimalPaddingLen) is the maximum message size of
+ // the last block of hash function operation. See FIPS 180-4, or
+ // MD5 specification.
+ fullLen += 13 - (blockLen - minimalPaddingLen);
+ usedLen += 13 - (blockLen - minimalPaddingLen);
+
+ // Note: fullLen is always not less than usedLen, and blockLen
+ // is always bigger than minimalPaddingLen, so we don't worry
+ // about negative values. 0x01 is added to the result to ensure
+ // that the return value is positive. The extra one byte does
+ // not impact the overall MAC compression function evaluations.
+ return 0x01 + (int)(Math.ceil(fullLen/(1.0d * blockLen)) -
+ Math.ceil(usedLen/(1.0d * blockLen))) * blockLen;
+ }
+
+ private static int addPadding(ByteBuffer bb, int blockSize) {
+
+ int len = bb.remaining();
+ int offset = bb.position();
+
+ int newlen = len + 1;
+ byte pad;
+ int i;
+
+ if ((newlen % blockSize) != 0) {
+ newlen += blockSize - 1;
+ newlen -= newlen % blockSize;
+ }
+ pad = (byte) (newlen - len);
+
+ /*
+ * Update the limit to what will be padded.
+ */
+ bb.limit(newlen + offset);
+
+ /*
+ * TLS version of the padding works for both SSLv3 and TLSv1
+ */
+ for (i = 0, offset += len; i < pad; i++) {
+ bb.put(offset++, (byte) (pad - 1));
+ }
+
+ bb.position(offset);
+ bb.limit(offset);
+
+ return newlen;
+ }
+
+ private static int removePadding(ByteBuffer bb,
+ int tagLen, int blockSize,
+ ProtocolVersion protocolVersion) throws BadPaddingException {
+ int len = bb.remaining();
+ int offset = bb.position();
+
+ // last byte is length byte (i.e. actual padding length - 1)
+ int padOffset = offset + len - 1;
+ int padLen = bb.get(padOffset) & 0xFF;
+
+ int newLen = len - (padLen + 1);
+ if ((newLen - tagLen) < 0) {
+ // If the buffer is not long enough to contain the padding plus
+ // a MAC tag, do a dummy constant-time padding check.
+ //
+ // Note that it is a dummy check, so we won't care about what is
+ // the actual padding data.
+ checkPadding(bb.duplicate(), (byte)(padLen & 0xFF));
+
+ throw new BadPaddingException("Invalid Padding length: " + padLen);
+ }
+
+ // The padding data should be filled with the padding length value.
+ int[] results = checkPadding(
+ bb.duplicate().position(offset + newLen),
+ (byte)(padLen & 0xFF));
+ if (protocolVersion.useTLS10PlusSpec()) {
+ if (results[0] != 0) { // padding data has invalid bytes
+ throw new BadPaddingException("Invalid TLS padding data");
+ }
+ } else { // SSLv3
+ // SSLv3 requires 0 <= length byte < block size
+ // some implementations do 1 <= length byte <= block size,
+ // so accept that as well
+ // v3 does not require any particular value for the other bytes
+ if (padLen > blockSize) {
+ throw new BadPaddingException("Padding length (" +
+ padLen + ") of SSLv3 message should not be bigger " +
+ "than the block size (" + blockSize + ")");
+ }
+ }
+
+ // Reset buffer limit to remove padding.
+ bb.limit(offset + newLen);
+
+ return newLen;
+ }
+
+ /*
+ * A constant-time check of the padding.
+ *
+ * NOTE that we are checking both the padding and the padLen bytes here.
+ *
+ * The caller MUST ensure that the bb parameter has remaining.
+ */
+ private static int[] checkPadding(ByteBuffer bb, byte pad) {
+ if (!bb.hasRemaining()) {
+ throw new RuntimeException("hasRemaining() must be positive");
+ }
+
+ // An array of hits is used to prevent Hotspot optimization for
+ // the purpose of a constant-time check.
+ int[] results = {0, 0}; // {missed #, matched #}
+ bb.mark();
+ for (int i = 0; i <= 256; bb.reset()) {
+ for (; bb.hasRemaining() && i <= 256; i++) {
+ if (bb.get() != pad) {
+ results[0]++; // mismatched padding data
+ } else {
+ results[1]++; // matched padding data
+ }
+ }
+ }
+
+ return results;
+ }
+}
+