< 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 >