| /* |
| * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.security.ssl; |
| |
| import java.io.*; |
| import java.nio.*; |
| import java.util.*; |
| |
| import javax.crypto.BadPaddingException; |
| |
| import javax.net.ssl.*; |
| |
| import sun.security.util.HexDumpEncoder; |
| |
| |
| /** |
| * {@code InputRecord} takes care of the management of SSL/TLS/DTLS input |
| * records, including buffering, decryption, handshake messages marshal, etc. |
| * |
| * @author David Brownell |
| */ |
| class InputRecord implements Record, Closeable { |
| |
| /* Class and subclass dynamic debugging support */ |
| static final Debug debug = Debug.getInstance("ssl"); |
| |
| Authenticator readAuthenticator; |
| CipherBox readCipher; |
| |
| HandshakeHash handshakeHash; |
| boolean isClosed; |
| |
| // The ClientHello version to accept. If set to ProtocolVersion.SSL20Hello |
| // and the first message we read is a ClientHello in V2 format, we convert |
| // it to V3. Otherwise we throw an exception when encountering a V2 hello. |
| ProtocolVersion helloVersion; |
| |
| // fragment size |
| int fragmentSize; |
| |
| InputRecord() { |
| this.readCipher = CipherBox.NULL; |
| this.readAuthenticator = null; // Please override this assignment. |
| this.helloVersion = ProtocolVersion.DEFAULT_HELLO; |
| this.fragmentSize = Record.maxDataSize; |
| } |
| |
| void setHelloVersion(ProtocolVersion helloVersion) { |
| this.helloVersion = helloVersion; |
| } |
| |
| ProtocolVersion getHelloVersion() { |
| return helloVersion; |
| } |
| |
| /* |
| * Set instance for the computation of handshake hashes. |
| * |
| * For handshaking, we need to be able to hash every byte above the |
| * record marking layer. This is where we're guaranteed to see those |
| * bytes, so this is where we can hash them ... especially in the |
| * case of hashing the initial V2 message! |
| */ |
| void setHandshakeHash(HandshakeHash handshakeHash) { |
| if (handshakeHash != null) { |
| byte[] reserved = null; |
| if (this.handshakeHash != null) { |
| reserved = this.handshakeHash.getAllHandshakeMessages(); |
| } |
| if ((reserved != null) && (reserved.length != 0)) { |
| handshakeHash.update(reserved, 0, reserved.length); |
| |
| if (debug != null && Debug.isOn("data")) { |
| Debug.printHex( |
| "[reserved] handshake hash: len = " + reserved.length, |
| reserved); |
| } |
| } |
| } |
| |
| this.handshakeHash = handshakeHash; |
| } |
| |
| boolean seqNumIsHuge() { |
| return (readAuthenticator != null) && |
| readAuthenticator.seqNumIsHuge(); |
| } |
| |
| boolean isEmpty() { |
| return false; |
| } |
| |
| // apply to DTLS SSLEngine |
| void expectingFinishFlight() { |
| // blank |
| } |
| |
| /** |
| * Prevent any more data from being read into this record, |
| * and flag the record as holding no data. |
| */ |
| @Override |
| public synchronized void close() throws IOException { |
| if (!isClosed) { |
| isClosed = true; |
| readCipher.dispose(); |
| } |
| } |
| |
| // apply to SSLSocket and SSLEngine |
| void changeReadCiphers( |
| Authenticator readAuthenticator, CipherBox readCipher) { |
| |
| /* |
| * Dispose of any intermediate state in the underlying cipher. |
| * For PKCS11 ciphers, this will release any attached sessions, |
| * and thus make finalization faster. |
| * |
| * Since MAC's doFinal() is called for every SSL/TLS packet, it's |
| * not necessary to do the same with MAC's. |
| */ |
| readCipher.dispose(); |
| |
| this.readAuthenticator = readAuthenticator; |
| this.readCipher = readCipher; |
| } |
| |
| // change fragment size |
| void changeFragmentSize(int fragmentSize) { |
| this.fragmentSize = fragmentSize; |
| } |
| |
| /* |
| * Check if there is enough inbound data in the ByteBuffer to make |
| * a inbound packet. |
| * |
| * @return -1 if there are not enough bytes to tell (small header), |
| */ |
| // apply to SSLEngine only |
| int bytesInCompletePacket(ByteBuffer buf) throws SSLException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // apply to SSLSocket only |
| int bytesInCompletePacket(InputStream is) throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Return true if the specified record protocol version is out of the |
| * range of the possible supported versions. |
| */ |
| void checkRecordVersion(ProtocolVersion version, |
| boolean allowSSL20Hello) throws SSLException { |
| // blank |
| } |
| |
| // apply to DTLS SSLEngine only |
| Plaintext acquirePlaintext() |
| throws IOException, BadPaddingException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // read, decrypt and decompress the network record. |
| // |
| // apply to SSLEngine only |
| Plaintext decode(ByteBuffer netData) |
| throws IOException, BadPaddingException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // apply to SSLSocket only |
| Plaintext decode(InputStream is, ByteBuffer destination) |
| throws IOException, BadPaddingException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // apply to SSLSocket only |
| void setDeliverStream(OutputStream outputStream) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // calculate plaintext fragment size |
| // |
| // apply to SSLEngine only |
| int estimateFragmentSize(int packetSize) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // |
| // shared helpers |
| // |
| |
| // Not apply to DTLS |
| static ByteBuffer convertToClientHello(ByteBuffer packet) { |
| |
| int srcPos = packet.position(); |
| int srcLim = packet.limit(); |
| |
| byte firstByte = packet.get(); |
| byte secondByte = packet.get(); |
| int recordLen = (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2; |
| |
| packet.position(srcPos + 3); // the V2ClientHello record header |
| |
| byte majorVersion = packet.get(); |
| byte minorVersion = packet.get(); |
| |
| int cipherSpecLen = ((packet.get() & 0xFF) << 8) + |
| (packet.get() & 0xFF); |
| int sessionIdLen = ((packet.get() & 0xFF) << 8) + |
| (packet.get() & 0xFF); |
| int nonceLen = ((packet.get() & 0xFF) << 8) + |
| (packet.get() & 0xFF); |
| |
| // Required space for the target SSLv3 ClientHello message. |
| // 5: record header size |
| // 4: handshake header size |
| // 2: ClientHello.client_version |
| // 32: ClientHello.random |
| // 1: length byte of ClientHello.session_id |
| // 2: length bytes of ClientHello.cipher_suites |
| // 2: empty ClientHello.compression_methods |
| int requiredSize = 48 + sessionIdLen + ((cipherSpecLen * 2 ) / 3 ); |
| byte[] converted = new byte[requiredSize]; |
| |
| /* |
| * Build the first part of the V3 record header from the V2 one |
| * that's now buffered up. (Lengths are fixed up later). |
| */ |
| // Note: need not to set the header actually. |
| converted[0] = ct_handshake; |
| converted[1] = majorVersion; |
| converted[2] = minorVersion; |
| // header [3..4] for handshake message length |
| // required size is 5; |
| |
| /* |
| * Store the generic V3 handshake header: 4 bytes |
| */ |
| converted[5] = 1; // HandshakeMessage.ht_client_hello |
| // buf [6..8] for length of ClientHello (int24) |
| // required size += 4; |
| |
| /* |
| * ClientHello header starts with SSL version |
| */ |
| converted[9] = majorVersion; |
| converted[10] = minorVersion; |
| // required size += 2; |
| int pointer = 11; |
| |
| /* |
| * Copy Random value/nonce ... if less than the 32 bytes of |
| * a V3 "Random", right justify and zero pad to the left. Else |
| * just take the last 32 bytes. |
| */ |
| int offset = srcPos + 11 + cipherSpecLen + sessionIdLen; |
| |
| if (nonceLen < 32) { |
| for (int i = 0; i < (32 - nonceLen); i++) { |
| converted[pointer++] = 0; |
| } |
| packet.position(offset); |
| packet.get(converted, pointer, nonceLen); |
| |
| pointer += nonceLen; |
| } else { |
| packet.position(offset + nonceLen - 32); |
| packet.get(converted, pointer, 32); |
| |
| pointer += 32; |
| } |
| |
| /* |
| * Copy session ID (only one byte length!) |
| */ |
| offset -= sessionIdLen; |
| converted[pointer++] = (byte)(sessionIdLen & 0xFF); |
| packet.position(offset); |
| packet.get(converted, pointer, sessionIdLen); |
| |
| /* |
| * Copy and translate cipher suites ... V2 specs with first byte zero |
| * are really V3 specs (in the last 2 bytes), just copy those and drop |
| * the other ones. Preference order remains unchanged. |
| * |
| * Example: Netscape Navigator 3.0 (exportable) says: |
| * |
| * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5 |
| * 0/6, SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5 |
| * |
| * Microsoft Internet Explorer 3.0 (exportable) supports only |
| * |
| * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5 |
| */ |
| int j; |
| |
| offset -= cipherSpecLen; |
| packet.position(offset); |
| |
| j = pointer + 2; |
| for (int i = 0; i < cipherSpecLen; i += 3) { |
| if (packet.get() != 0) { |
| // Ignore version 2.0 specifix cipher suite. Clients |
| // should also include the version 3.0 equivalent in |
| // the V2ClientHello message. |
| packet.get(); // ignore the 2nd byte |
| packet.get(); // ignore the 3rd byte |
| continue; |
| } |
| |
| converted[j++] = packet.get(); |
| converted[j++] = packet.get(); |
| } |
| |
| j -= pointer + 2; |
| converted[pointer++] = (byte)((j >>> 8) & 0xFF); |
| converted[pointer++] = (byte)(j & 0xFF); |
| pointer += j; |
| |
| /* |
| * Append compression methods (default/null only) |
| */ |
| converted[pointer++] = 1; |
| converted[pointer++] = 0; // Session.compression_null |
| |
| /* |
| * Fill in lengths of the messages we synthesized (nested: |
| * V3 handshake message within V3 record). |
| */ |
| // Note: need not to set the header actually. |
| int fragLen = pointer - 5; // TLSPlaintext.length |
| converted[3] = (byte)((fragLen >>> 8) & 0xFF); |
| converted[4] = (byte)(fragLen & 0xFF); |
| |
| /* |
| * Handshake.length, length of ClientHello message |
| */ |
| fragLen = pointer - 9; // Handshake.length |
| converted[6] = (byte)((fragLen >>> 16) & 0xFF); |
| converted[7] = (byte)((fragLen >>> 8) & 0xFF); |
| converted[8] = (byte)(fragLen & 0xFF); |
| |
| // consume the full record |
| packet.position(srcPos + recordLen); |
| |
| // Need no header bytes. |
| return ByteBuffer.wrap(converted, 5, pointer - 5); // 5: header size |
| } |
| |
| static ByteBuffer decrypt(Authenticator authenticator, CipherBox box, |
| byte contentType, ByteBuffer bb) throws BadPaddingException { |
| |
| return decrypt(authenticator, box, contentType, bb, null); |
| } |
| |
| static ByteBuffer decrypt(Authenticator authenticator, |
| CipherBox box, byte contentType, ByteBuffer bb, |
| byte[] sequence) throws BadPaddingException { |
| |
| BadPaddingException reservedBPE = null; |
| int tagLen = |
| (authenticator instanceof MAC) ? ((MAC)authenticator).MAClen() : 0; |
| int cipheredLength = bb.remaining(); |
| int srcPos = bb.position(); |
| if (!box.isNullCipher()) { |
| try { |
| // apply explicit nonce for AEAD/CBC cipher suites if needed |
| int nonceSize = box.applyExplicitNonce( |
| authenticator, contentType, bb, sequence); |
| |
| // decrypt the content |
| if (box.isAEADMode()) { |
| // DON'T decrypt the nonce_explicit for AEAD mode |
| bb.position(srcPos + nonceSize); |
| } // The explicit IV for CBC mode can be decrypted. |
| |
| // Note that the CipherBox.decrypt() does not change |
| // the capacity of the buffer. |
| box.decrypt(bb, tagLen); |
| // We don't actually remove the nonce. |
| bb.position(srcPos + nonceSize); |
| } catch (BadPaddingException bpe) { |
| // RFC 2246 states that decryption_failed should be used |
| // for this purpose. However, that allows certain attacks, |
| // so we just send bad record MAC. We also need to make |
| // sure to always check the MAC to avoid a timing attack |
| // for the same issue. See paper by Vaudenay et al and the |
| // update in RFC 4346/5246. |
| // |
| // Failover to message authentication code checking. |
| reservedBPE = bpe; |
| } |
| } |
| |
| // Requires message authentication code for null, stream and block |
| // cipher suites. |
| if ((authenticator instanceof MAC) && (tagLen != 0)) { |
| MAC signer = (MAC)authenticator; |
| int contentLen = bb.remaining() - tagLen; |
| |
| // Note that although it is not necessary, we run the same MAC |
| // computation and comparison on the payload for both stream |
| // cipher and CBC block cipher. |
| if (contentLen < 0) { |
| // negative data length, something is wrong |
| if (reservedBPE == null) { |
| reservedBPE = new BadPaddingException("bad record"); |
| } |
| |
| // set offset of the dummy MAC |
| contentLen = cipheredLength - tagLen; |
| bb.limit(srcPos + 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. |
| // |
| // It is only necessary for CBC block cipher. It is used to get a |
| // constant time of MAC computation and comparison on each record. |
| if (box.isCBCMode()) { |
| 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.MAClen(); |
| 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; |
| } |
| |
| return bb.slice(); |
| } |
| |
| /* |
| * Run MAC computation and comparison |
| * |
| */ |
| private static boolean checkMacTags(byte contentType, ByteBuffer bb, |
| MAC signer, byte[] sequence, boolean isSimulated) { |
| |
| int tagLen = signer.MAClen(); |
| 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; |
| } |
| |
| /* |
| * Run MAC computation and comparison |
| * |
| * Please DON'T change the content of the byte buffer parameter! |
| */ |
| private static boolean checkMacTags(byte contentType, byte[] buffer, |
| int offset, int contentLen, MAC signer, boolean isSimulated) { |
| |
| int tagLen = signer.MAClen(); |
| byte[] hash = signer.compute( |
| contentType, buffer, offset, contentLen, isSimulated); |
| if (hash == null || tagLen != hash.length) { |
| // Something is wrong with MAC implementation. |
| throw new RuntimeException("Internal MAC error"); |
| } |
| |
| int[] results = compareMacTags(buffer, offset + contentLen, hash); |
| return (results[0] != 0); |
| } |
| |
| /* |
| * A constant-time comparison of the MAC tags. |
| * |
| * Please DON'T change the content of the byte buffer parameter! |
| */ |
| private static int[] compareMacTags( |
| byte[] buffer, int offset, 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 length of the buffer. |
| for (int i = 0; i < tag.length; i++) { |
| if (buffer[offset + i] != 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.hashBlockLen(); |
| int minimalPaddingLen = signer.minimalPaddingLen(); |
| |
| // (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; |
| } |
| } |
| |