< prev index next >
src/java.base/share/classes/sun/security/ssl/SSLCipher.java
Print this page
*** 1,7 ****
/*
! * Copyright (c) 1996, 2015, 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
--- 1,7 ----
/*
! * 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
*** 21,1150 ****
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
-
package sun.security.ssl;
! import java.io.ByteArrayInputStream;
! import java.io.IOException;
! import java.util.Hashtable;
import java.util.Arrays;
!
! import java.security.*;
! import javax.crypto.*;
! import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.GCMParameterSpec;
! import java.nio.*;
!
! import sun.security.ssl.CipherSuite.*;
! import static sun.security.ssl.CipherSuite.*;
! import static sun.security.ssl.CipherSuite.CipherType.*;
!
! import sun.security.util.HexDumpEncoder;
!
!
! /**
! * This class handles bulk data enciphering/deciphering for each SSLv3
! * message. This provides data confidentiality. Stream ciphers (such
! * as RC4) don't need to do padding; block ciphers (e.g. DES) need it.
! *
! * Individual instances are obtained by calling the static method
! * newCipherBox(), which should only be invoked by BulkCipher.newCipher().
! *
! * In RFC 2246, with bock ciphers in CBC mode, the Initialization
! * Vector (IV) for the first record is generated with the other keys
! * and secrets when the security parameters are set. The IV for
! * subsequent records is the last ciphertext block from the previous
! * record.
! *
! * In RFC 4346, the implicit Initialization Vector (IV) is replaced
! * with an explicit IV to protect against CBC attacks. RFC 4346
! * recommends two algorithms used to generated the per-record IV.
! * The implementation uses the algorithm (2)(b), as described at
! * section 6.2.3.2 of RFC 4346.
! *
! * The usage of IV in CBC block cipher can be illustrated in
! * the following diagrams.
! *
! * (random)
! * R P1 IV C1
! * | | | |
! * SIV---+ |-----+ |-... |----- |------
! * | | | | | | | |
! * +----+ | +----+ | +----+ | +----+ |
! * | Ek | | + Ek + | | Dk | | | Dk | |
! * +----+ | +----+ | +----+ | +----+ |
! * | | | | | | | |
! * |----| |----| SIV--+ |----| |-...
! * | | | |
! * IV C1 R P1
! * (discard)
! *
! * CBC Encryption CBC Decryption
! *
! * NOTE that any ciphering involved in key exchange (e.g. with RSA) is
! * handled separately.
! *
! * @author David Brownell
! * @author Andreas Sterbenz
! */
! final class CipherBox {
!
! // A CipherBox that implements the identity operation
! static final CipherBox NULL = new CipherBox();
!
! /* Class and subclass dynamic debugging support */
! private static final Debug debug = Debug.getInstance("ssl");
!
! // the protocol version this cipher conforms to
! private final ProtocolVersion protocolVersion;
! // cipher object
! private final Cipher cipher;
! /**
! * secure random
! */
! private SecureRandom random;
! /**
! * fixed IV, the implicit nonce of AEAD cipher suite, only apply to
! * AEAD cipher suites
! */
! private final byte[] fixedIv;
! /**
! * the key, reserved only for AEAD cipher initialization
! */
! private final Key key;
! /**
! * the operation mode, reserved for AEAD cipher initialization
! */
! private final int mode;
! /**
! * the authentication tag size, only apply to AEAD cipher suites
! */
! private final int tagSize;
! /**
! * the record IV length, only apply to AEAD cipher suites
! */
! private final int recordIvSize;
! /**
! * cipher type
! */
! private final CipherType cipherType;
! /**
! * Fixed masks of various block size, as the initial decryption IVs
! * for TLS 1.1 or later.
! *
! * For performance, we do not use random IVs. As the initial decryption
! * IVs will be discarded by TLS decryption processes, so the fixed masks
! * do not hurt cryptographic strength.
! */
! private static Hashtable<Integer, IvParameterSpec> masks;
! /**
! * NULL cipherbox. Identity operation, no encryption.
! */
! private CipherBox() {
! this.protocolVersion = ProtocolVersion.DEFAULT_TLS;
! this.cipher = null;
! this.cipherType = NULL_CIPHER;
! this.fixedIv = new byte[0];
! this.key = null;
! this.mode = Cipher.ENCRYPT_MODE; // choose at random
! this.random = null;
! this.tagSize = 0;
! this.recordIvSize = 0;
}
! /**
! * Construct a new CipherBox using the cipher transformation.
! *
! * @exception NoSuchAlgorithmException if no appropriate JCE Cipher
! * implementation could be found.
! */
! private CipherBox(ProtocolVersion protocolVersion, BulkCipher bulkCipher,
! SecretKey key, IvParameterSpec iv, SecureRandom random,
! boolean encrypt) throws NoSuchAlgorithmException {
! try {
! this.protocolVersion = protocolVersion;
! this.cipher = JsseJce.getCipher(bulkCipher.transformation);
! this.mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
!
! if (random == null) {
! random = JsseJce.getSecureRandom();
}
- this.random = random;
- this.cipherType = bulkCipher.cipherType;
! /*
! * RFC 4346 recommends two algorithms used to generated the
! * per-record IV. The implementation uses the algorithm (2)(b),
! * as described at section 6.2.3.2 of RFC 4346.
! *
! * As we don't care about the initial IV value for TLS 1.1 or
! * later, so if the "iv" parameter is null, we use the default
! * value generated by Cipher.init() for encryption, and a fixed
! * mask for decryption.
! */
! if (iv == null && bulkCipher.ivSize != 0 &&
! mode == Cipher.DECRYPT_MODE &&
! protocolVersion.useTLS11PlusSpec()) {
! iv = getFixedMask(bulkCipher.ivSize);
}
-
- if (cipherType == AEAD_CIPHER) {
- // AEAD must completely initialize the cipher for each packet,
- // and so we save initialization parameters for packet
- // processing time.
-
- // Set the tag size for AEAD cipher
- tagSize = bulkCipher.tagSize;
-
- // Reserve the key for AEAD cipher initialization
- this.key = key;
-
- fixedIv = iv.getIV();
- if (fixedIv == null ||
- fixedIv.length != bulkCipher.fixedIvSize) {
- throw new RuntimeException("Improper fixed IV for AEAD");
}
! // Set the record IV length for AEAD cipher
! recordIvSize = bulkCipher.ivSize - bulkCipher.fixedIvSize;
! // DON'T initialize the cipher for AEAD!
! } else {
! // CBC only requires one initialization during its lifetime
! // (future packets/IVs set the proper CBC state), so we can
! // initialize now.
!
! // Zeroize the variables that only apply to AEAD cipher
! this.tagSize = 0;
! this.fixedIv = new byte[0];
! this.recordIvSize = 0;
! this.key = null;
! // Initialize the cipher
! cipher.init(mode, key, iv, random);
}
- } catch (NoSuchAlgorithmException e) {
- throw e;
- } catch (Exception e) {
- throw new NoSuchAlgorithmException
- ("Could not create cipher " + bulkCipher, e);
- } catch (ExceptionInInitializerError e) {
- throw new NoSuchAlgorithmException
- ("Could not create cipher " + bulkCipher, e);
}
}
! /*
! * Factory method to obtain a new CipherBox object.
! */
! static CipherBox newCipherBox(ProtocolVersion version, BulkCipher cipher,
! SecretKey key, IvParameterSpec iv, SecureRandom random,
! boolean encrypt) throws NoSuchAlgorithmException {
! if (cipher.allowed == false) {
! throw new NoSuchAlgorithmException("Unsupported cipher " + cipher);
}
! if (cipher == BulkCipher.B_NULL) {
! return NULL;
! } else {
! return new CipherBox(version, cipher, key, iv, random, encrypt);
}
}
! /*
! * Get a fixed mask, as the initial decryption IVs for TLS 1.1 or later.
*/
! private static IvParameterSpec getFixedMask(int ivSize) {
! if (masks == null) {
! masks = new Hashtable<Integer, IvParameterSpec>(5);
}
! IvParameterSpec iv = masks.get(ivSize);
! if (iv == null) {
! iv = new IvParameterSpec(new byte[ivSize]);
! masks.put(ivSize, iv);
}
! return iv;
}
! /*
! * Encrypts a block of data, returning the size of the
! * resulting block.
! */
! int encrypt(byte[] buf, int offset, int len) {
! if (cipher == null) {
! return len;
}
! try {
! int blockSize = cipher.getBlockSize();
! if (cipherType == BLOCK_CIPHER) {
! len = addPadding(buf, offset, len, blockSize);
}
! if (debug != null && Debug.isOn("plaintext")) {
! try {
! HexDumpEncoder hd = new HexDumpEncoder();
! System.out.println(
! "Padded plaintext before ENCRYPTION: len = "
! + len);
! hd.encodeBuffer(
! new ByteArrayInputStream(buf, offset, len),
! System.out);
! } catch (IOException e) { }
}
!
! if (cipherType == AEAD_CIPHER) {
try {
! return cipher.doFinal(buf, offset, len, buf, offset);
! } catch (IllegalBlockSizeException | BadPaddingException ibe) {
! // unlikely to happen
! throw new RuntimeException(
! "Cipher error in AEAD mode in JCE provider " +
! cipher.getProvider().getName(), ibe);
}
- } else {
- int newLen = cipher.update(buf, offset, len, buf, offset);
- if (newLen != len) {
- // catch BouncyCastle buffering error
- throw new RuntimeException("Cipher buffering error " +
- "in JCE provider " + cipher.getProvider().getName());
}
! return newLen;
}
- } catch (ShortBufferException e) {
- // unlikely to happen, we should have enough buffer space here
- throw new ArrayIndexOutOfBoundsException(e.toString());
}
}
! /*
! * Encrypts a ByteBuffer block of data, returning the size of the
! * resulting block.
! *
! * The byte buffers position and limit initially define the amount
! * to encrypt. On return, the position and limit are
! * set to last position padded/encrypted. The limit may have changed
! * because of the added padding bytes.
! */
! int encrypt(ByteBuffer bb, int outLimit) {
! int len = bb.remaining();
! if (cipher == null) {
! bb.position(bb.limit());
! return len;
}
! int pos = bb.position();
! int blockSize = cipher.getBlockSize();
! if (cipherType == BLOCK_CIPHER) {
! // addPadding adjusts pos/limit
! len = addPadding(bb, blockSize);
! bb.position(pos);
}
! if (debug != null && Debug.isOn("plaintext")) {
try {
! HexDumpEncoder hd = new HexDumpEncoder();
! System.out.println(
! "Padded plaintext before ENCRYPTION: len = "
! + len);
! hd.encodeBuffer(bb.duplicate(), System.out);
! } catch (IOException e) { }
}
! /*
! * Encrypt "in-place". This does not add its own padding.
! */
! ByteBuffer dup = bb.duplicate();
! if (cipherType == AEAD_CIPHER) {
! try {
! 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.
! if (outLimit < pos + outputSize) {
! // unlikely to happen
! throw new ShortBufferException(
! "need more space in output buffer");
}
! bb.limit(pos + outputSize);
}
! int newLen = cipher.doFinal(dup, bb);
! if (newLen != outputSize) {
! throw new RuntimeException(
! "Cipher buffering error in JCE provider " +
! cipher.getProvider().getName());
}
- return newLen;
- } catch (IllegalBlockSizeException |
- BadPaddingException | ShortBufferException ibse) {
- // unlikely to happen
- throw new RuntimeException(
- "Cipher error in AEAD mode in JCE provider " +
- cipher.getProvider().getName(), ibse);
}
} else {
! int newLen;
! try {
! newLen = cipher.update(dup, bb);
! } catch (ShortBufferException sbe) {
! // unlikely to happen
! throw new RuntimeException("Cipher buffering error " +
! "in JCE provider " + cipher.getProvider().getName());
}
! if (bb.position() != dup.position()) {
! throw new RuntimeException("bytebuffer padding error");
}
! if (newLen != len) {
! // catch BouncyCastle buffering error
! throw new RuntimeException("Cipher buffering error " +
! "in JCE provider " + cipher.getProvider().getName());
}
- return newLen;
}
}
!
! /*
! * Decrypts a block of data, returning the size of the
! * resulting block if padding was required.
! *
! * For SSLv3 and TLSv1.0, with block ciphers in CBC mode the
! * Initialization Vector (IV) for the first record is generated by
! * the handshake protocol, the IV for subsequent records is the
! * last ciphertext block from the previous record.
! *
! * From TLSv1.1, the implicit IV is replaced with an explicit IV to
! * protect against CBC attacks.
! *
! * Differentiating between bad_record_mac and decryption_failed alerts
! * may permit certain attacks against CBC mode. It is preferable to
! * uniformly use the bad_record_mac alert to hide the specific type of
! * the error.
! */
! int decrypt(byte[] buf, int offset, int len,
! int tagLen) throws BadPaddingException {
! if (cipher == null) {
! return len;
}
! try {
! int newLen;
! if (cipherType == AEAD_CIPHER) {
! try {
! newLen = cipher.doFinal(buf, offset, len, buf, offset);
! } catch (IllegalBlockSizeException ibse) {
! // unlikely to happen
! throw new RuntimeException(
! "Cipher error in AEAD mode in JCE provider " +
! cipher.getProvider().getName(), ibse);
}
} else {
! newLen = cipher.update(buf, offset, len, buf, offset);
! if (newLen != len) {
! // catch BouncyCastle buffering error
! throw new RuntimeException("Cipher buffering error " +
! "in JCE provider " + cipher.getProvider().getName());
! }
}
- if (debug != null && Debug.isOn("plaintext")) {
- try {
- HexDumpEncoder hd = new HexDumpEncoder();
! System.out.println(
! "Padded plaintext after DECRYPTION: len = "
! + newLen);
! hd.encodeBuffer(
! new ByteArrayInputStream(buf, offset, newLen),
! System.out);
! } catch (IOException e) { }
}
- if (cipherType == BLOCK_CIPHER) {
- int blockSize = cipher.getBlockSize();
- newLen = removePadding(
- buf, offset, newLen, tagLen, blockSize, protocolVersion);
! if (protocolVersion.useTLS11PlusSpec()) {
! if (newLen < blockSize) {
! throw new BadPaddingException("The length after " +
! "padding removal (" + newLen + ") should be larger " +
! "than <" + blockSize + "> since explicit IV used");
}
}
}
- return newLen;
- } catch (ShortBufferException e) {
- // unlikely to happen, we should have enough buffer space here
- throw new ArrayIndexOutOfBoundsException(e.toString());
}
}
! /*
! * Decrypts a block of data, returning the size of the
! * resulting block if padding was required. position and limit
! * point to the end of the decrypted/depadded data. The initial
! * limit and new limit may be different, given we may
! * have stripped off some padding bytes.
! *
! * @see decrypt(byte[], int, int)
! */
! int decrypt(ByteBuffer bb, int tagLen) throws BadPaddingException {
! int len = bb.remaining();
! if (cipher == null) {
! bb.position(bb.limit());
! return len;
}
! try {
! /*
! * Decrypt "in-place".
! */
int pos = bb.position();
ByteBuffer dup = bb.duplicate();
- int newLen;
- if (cipherType == AEAD_CIPHER) {
try {
! newLen = 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());
}
! } else {
! newLen = cipher.update(dup, bb);
! if (newLen != len) {
// catch BouncyCastle buffering error
! throw new RuntimeException("Cipher buffering error " +
! "in JCE provider " + cipher.getProvider().getName());
}
}
! // reset the limit to the end of the decryted data
! bb.limit(pos + newLen);
!
! if (debug != null && Debug.isOn("plaintext")) {
! try {
! HexDumpEncoder hd = new HexDumpEncoder();
!
! System.out.println(
! "Padded plaintext after DECRYPTION: len = "
! + newLen);
!
! hd.encodeBuffer(
! bb.duplicate().position(pos), System.out);
! } catch (IOException e) { }
}
! /*
! * Remove the block padding.
! */
! if (cipherType == BLOCK_CIPHER) {
! int blockSize = cipher.getBlockSize();
! bb.position(pos);
! newLen = removePadding(bb, tagLen, blockSize, protocolVersion);
! // check the explicit IV of TLS v1.1 or later
! if (protocolVersion.useTLS11PlusSpec()) {
! if (newLen < blockSize) {
! throw new BadPaddingException("The length after " +
! "padding removal (" + newLen + ") should be larger " +
! "than <" + blockSize + "> since explicit IV used");
}
! // reset the position to the end of the decrypted data
! bb.position(bb.limit());
}
}
- return newLen;
- } catch (ShortBufferException e) {
- // unlikely to happen, we should have enough buffer space here
- throw new ArrayIndexOutOfBoundsException(e.toString());
}
}
! private static int addPadding(byte[] buf, int offset, int len,
! int blockSize) {
! int newlen = len + 1;
! byte pad;
! int i;
! if ((newlen % blockSize) != 0) {
! newlen += blockSize - 1;
! newlen -= newlen % blockSize;
}
- pad = (byte) (newlen - len);
! if (buf.length < (newlen + offset)) {
! throw new IllegalArgumentException("no space to pad buffer");
}
! /*
! * TLS version of the padding works for both SSLv3 and TLSv1
! */
! for (i = 0, offset += len; i < pad; i++) {
! buf [offset++] = (byte) (pad - 1);
}
! return newlen;
}
! /*
! * Apply the padding to the buffer.
! *
! * Limit is advanced to the new buffer length.
! * Position is equal to limit.
! */
! 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;
}
! /*
! * 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 len parameter is a positive number.
! */
! private static int[] checkPadding(
! byte[] buf, int offset, int len, byte pad) {
! if (len <= 0) {
! throw new RuntimeException("padding len 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 #}
! for (int i = 0; i <= 256;) {
! for (int j = 0; j < len && i <= 256; j++, i++) { // j <= i
! if (buf[offset + j] != pad) {
! results[0]++; // mismatched padding data
} else {
! results[1]++; // matched padding data
}
}
}
! return results;
}
! /*
! * 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;
}
! /*
! * Typical TLS padding format for a 64 bit block cipher is as follows:
! * xx xx xx xx xx xx xx 00
! * xx xx xx xx xx xx 01 01
! * ...
! * xx 06 06 06 06 06 06 06
! * 07 07 07 07 07 07 07 07
! * TLS also allows any amount of padding from 1 and 256 bytes as long
! * as it makes the data a multiple of the block size
! */
! private static int removePadding(byte[] buf, int offset, int len,
! int tagLen, int blockSize,
! ProtocolVersion protocolVersion) throws BadPaddingException {
! // last byte is length byte (i.e. actual padding length - 1)
! int padOffset = offset + len - 1;
! int padLen = buf[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(buf, offset, len, (byte)(padLen & 0xFF));
! throw new BadPaddingException("Invalid Padding length: " + padLen);
}
! // The padding data should be filled with the padding length value.
! int[] results = checkPadding(buf, offset + newLen,
! padLen + 1, (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 + ")");
}
}
! return newLen;
}
! /*
! * Position/limit is equal the removed padding.
! */
! 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.position(offset + newLen);
! bb.limit(offset + newLen);
! return newLen;
}
! /*
! * Dispose of any intermediate state in the underlying cipher.
! * For PKCS11 ciphers, this will release any attached sessions, and
! * thus make finalization faster.
! */
void dispose() {
- try {
if (cipher != null) {
! // ignore return value.
cipher.doFinal();
- }
} catch (Exception e) {
// swallow all types of exceptions.
}
}
! /*
! * Does the cipher use CBC mode?
! *
! * @return true if the cipher use CBC mode, false otherwise.
! */
! boolean isCBCMode() {
! return cipherType == BLOCK_CIPHER;
}
! /*
! * Does the cipher use AEAD mode?
! *
! * @return true if the cipher use AEAD mode, false otherwise.
! */
! boolean isAEADMode() {
! return cipherType == AEAD_CIPHER;
}
! /*
! * Is the cipher null?
! *
! * @return true if the cipher is null, false otherwise.
! */
! boolean isNullCipher() {
! return cipher == null;
}
/*
! * Gets the explicit nonce/IV size of the cipher.
*
! * The returned value is the SecurityParameters.record_iv_length in
! * RFC 4346/5246. It is the size of explicit IV for CBC mode, and the
! * size of explicit nonce for AEAD mode.
! *
! * @return the explicit nonce size of the cipher.
*/
! int getExplicitNonceSize() {
! switch (cipherType) {
! case BLOCK_CIPHER:
! // For block ciphers, the explicit IV length is of length
! // SecurityParameters.record_iv_length, which is equal to
! // the SecurityParameters.block_size.
! if (protocolVersion.useTLS11PlusSpec()) {
! return cipher.getBlockSize();
}
- break;
- case AEAD_CIPHER:
- return recordIvSize;
- // It is also the length of sequence number, which is
- // used as the nonce_explicit for AEAD cipher suites.
}
! return 0;
! }
! /*
! * Applies the explicit nonce/IV to this cipher. This method is used to
! * decrypt an SSL/TLS input record.
! *
! * The returned value is the SecurityParameters.record_iv_length in
! * RFC 4346/5246. It is the size of explicit IV for CBC mode, and the
! * size of explicit nonce for AEAD mode.
! *
! * @param authenticator the authenticator to get the additional
! * authentication data
! * @param contentType the content type of the input record
! * @param bb the byte buffer to get the explicit nonce from
! *
! * @return the explicit nonce size of the cipher.
! */
! int applyExplicitNonce(Authenticator authenticator, byte contentType,
! ByteBuffer bb, byte[] sequence) throws BadPaddingException {
! switch (cipherType) {
! case BLOCK_CIPHER:
! // sanity check length of the ciphertext
! int tagLen = (authenticator instanceof MAC) ?
! ((MAC)authenticator).MAClen() : 0;
if (tagLen != 0) {
! if (!sanityCheck(tagLen, bb.remaining())) {
! throw new BadPaddingException(
! "ciphertext sanity check failed");
! }
}
! // For block ciphers, the explicit IV length is of length
! // SecurityParameters.record_iv_length, which is equal to
! // the SecurityParameters.block_size.
! if (protocolVersion.useTLS11PlusSpec()) {
! return cipher.getBlockSize();
}
- break;
- case AEAD_CIPHER:
- 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);
- bb.position(bb.position() - recordIvSize);
- GCMParameterSpec spec = new GCMParameterSpec(tagSize * 8, iv);
- try {
- cipher.init(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() - recordIvSize - tagSize,
! sequence);
! cipher.updateAAD(aad);
! return recordIvSize;
! // It is also the length of sequence number, which is
! // used as the nonce_explicit for AEAD cipher suites.
}
! return 0;
}
! /*
! * Creates the explicit nonce/IV to this cipher. This method is used to
! * encrypt an SSL/TLS output record.
! *
! * The size of the returned array is the SecurityParameters.record_iv_length
! * in RFC 4346/5246. It is the size of explicit IV for CBC mode, and the
! * size of explicit nonce for AEAD mode.
! *
! * @param authenticator the authenticator to get the additional
! * authentication data
! * @param contentType the content type of the input record
! * @param fragmentLength the fragment length of the output record, it is
! * the TLSCompressed.length in RFC 4346/5246.
! *
! * @return the explicit nonce of the cipher.
! */
! byte[] createExplicitNonce(Authenticator authenticator,
! byte contentType, int fragmentLength) {
! byte[] nonce = new byte[0];
! switch (cipherType) {
! case BLOCK_CIPHER:
! if (protocolVersion.useTLS11PlusSpec()) {
! // For block ciphers, the explicit IV length is of length
! // SecurityParameters.record_iv_length, which is equal to
! // the SecurityParameters.block_size.
//
! // Generate a random number as the explicit IV parameter.
! nonce = new byte[cipher.getBlockSize()];
! random.nextBytes(nonce);
! }
! break;
! case AEAD_CIPHER:
! // To be unique and aware of overflow-wrap, sequence number
! // is used as the nonce_explicit of AEAD cipher suites.
! 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(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, fragmentLength, null);
! cipher.updateAAD(aad);
! break;
}
! return nonce;
}
! // See also CipherSuite.calculatePacketSize().
! int calculatePacketSize(int fragmentSize, int macLen, int headerSize) {
! int packetSize = fragmentSize;
! if (cipher != null) {
! int blockSize = cipher.getBlockSize();
! switch (cipherType) {
! case BLOCK_CIPHER:
! packetSize += macLen;
! packetSize += 1; // 1 byte padding length field
! packetSize += // use the minimal padding
! (blockSize - (packetSize % blockSize)) % blockSize;
! if (protocolVersion.useTLS11PlusSpec()) {
! packetSize += blockSize; // explicit IV
}
! break;
! case AEAD_CIPHER:
! packetSize += recordIvSize;
! packetSize += tagSize;
! break;
! default: // NULL_CIPHER or STREAM_CIPHER
! packetSize += macLen;
}
}
! return packetSize + headerSize;
}
! // See also CipherSuite.calculateFragSize().
! int calculateFragmentSize(int packetLimit, int macLen, int headerSize) {
! int fragLen = packetLimit - headerSize;
! if (cipher != null) {
! int blockSize = cipher.getBlockSize();
! switch (cipherType) {
! case BLOCK_CIPHER:
! if (protocolVersion.useTLS11PlusSpec()) {
! fragLen -= blockSize; // explicit IV
}
- fragLen -= (fragLen % blockSize); // cannot hold a block
- // No padding for a maximum fragment.
- fragLen -= 1; // 1 byte padding length field: 0x00
- fragLen -= macLen;
! break;
! case AEAD_CIPHER:
! fragLen -= recordIvSize;
! fragLen -= tagSize;
! break;
! default: // NULL_CIPHER or STREAM_CIPHER
! fragLen -= macLen;
! }
}
! return fragLen;
}
! // Estimate the maximum fragment size of a received packet.
! int estimateFragmentSize(int packetSize, int macLen, int headerSize) {
! int fragLen = packetSize - headerSize;
! if (cipher != null) {
! int blockSize = cipher.getBlockSize();
! switch (cipherType) {
! case BLOCK_CIPHER:
! if (protocolVersion.useTLS11PlusSpec()) {
! fragLen -= blockSize; // explicit IV
}
- // No padding for a maximum fragment.
- fragLen -= 1; // 1 byte padding length field: 0x00
- fragLen -= macLen;
! break;
! case AEAD_CIPHER:
! fragLen -= recordIvSize;
! fragLen -= tagSize;
! break;
! default: // NULL_CIPHER or STREAM_CIPHER
! fragLen -= macLen;
}
}
! return fragLen;
}
! /**
! * 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) {
! if (!isCBCMode()) {
! return fragmentLen >= tagLen;
}
! int blockSize = cipher.getBlockSize();
! if ((fragmentLen % blockSize) == 0) {
! int minimal = tagLen + 1;
! minimal = (minimal >= blockSize) ? minimal : blockSize;
! if (protocolVersion.useTLS11PlusSpec()) {
! minimal += blockSize; // plus the size of the explicit IV
}
-
- return (fragmentLen >= minimal);
}
-
- return false;
}
}
--- 21,2356 ----
* 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;
+ }
}
+
< prev index next >