src/java.base/share/classes/sun/security/ssl/InputRecord.java

Print this page

        

*** 21,179 **** * 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 javax.crypto.BadPaddingException; import javax.net.ssl.*; import sun.misc.HexDumpEncoder; /** ! * SSL 3.0 records, as pulled off a TCP stream. Input records are ! * basically buffers tied to a particular input stream ... a layer ! * above this must map these records into the model of a continuous ! * stream of data. * - * Since this returns SSL 3.0 records, it's the layer that needs to - * map SSL 2.0 style handshake records into SSL 3.0 ones for those - * "old" clients that interop with both V2 and V3 servers. Not as - * pretty as might be desired. - * - * NOTE: During handshaking, each message must be hashed to support - * verification that the handshake process wasn't compromised. - * * @author David Brownell */ ! class InputRecord extends ByteArrayInputStream implements Record { ! private HandshakeHash handshakeHash; ! private int lastHashed; ! boolean formatVerified = true; // SSLv2 ruled out? ! private boolean isClosed; ! private boolean appDataValid; // 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. ! private ProtocolVersion helloVersion; ! /* Class and subclass dynamic debugging support */ ! static final Debug debug = Debug.getInstance("ssl"); ! /* The existing record length */ ! private int exlen; ! /* V2 handshake message */ ! private byte v2Buf[]; /* ! * Construct the record to hold the maximum sized input record. ! * Data will be filled in separately. * ! * The structure of the byte buffer looks like: * ! * |--------+---------+---------------------------------| ! * | header | IV | content, MAC/TAG, padding, etc. | ! * | headerPlusIVSize | * ! * header: the header of an SSL records ! * IV: the optional IV/nonce field, it is only required for block ! * (TLS 1.1 or later) and AEAD cipher suites. ! * */ ! InputRecord() { ! super(new byte[maxRecordSize]); ! setHelloVersion(ProtocolVersion.DEFAULT_HELLO); ! pos = headerSize; ! count = headerSize; ! lastHashed = count; ! exlen = 0; ! v2Buf = null; } ! void setHelloVersion(ProtocolVersion helloVersion) { ! this.helloVersion = helloVersion; } ! ProtocolVersion getHelloVersion() { ! return helloVersion; } /* ! * Enable format checks if initial handshaking hasn't completed */ ! void enableFormatChecks() { ! formatVerified = false; ! } ! // return whether the data in this record is valid, decrypted data ! boolean isAppDataValid() { ! return appDataValid; } ! void setAppDataValid(boolean value) { ! appDataValid = value; } /* ! * Return the content type of the record. */ ! byte contentType() { ! return buf[0]; } /* ! * 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) { ! this.handshakeHash = handshakeHash; } ! HandshakeHash getHandshakeHash() { ! return handshakeHash; } ! void decrypt(Authenticator authenticator, ! CipherBox box) throws BadPaddingException { BadPaddingException reservedBPE = null; int tagLen = (authenticator instanceof MAC) ? ((MAC)authenticator).MAClen() : 0; ! int cipheredLength = count - headerSize; ! if (!box.isNullCipher()) { try { // apply explicit nonce for AEAD/CBC cipher suites if needed ! int nonceSize = box.applyExplicitNonce(authenticator, ! contentType(), buf, headerSize, cipheredLength); ! pos = headerSize + nonceSize; ! lastHashed = pos; // don't digest the explicit nonce // decrypt the content - int offset = headerSize; if (box.isAEADMode()) { ! // DON'T encrypt the nonce_explicit for AEAD mode ! offset += nonceSize; } // The explicit IV for CBC mode can be decrypted. // Note that the CipherBox.decrypt() does not change // the capacity of the buffer. ! count = offset + ! box.decrypt(buf, offset, count - offset, tagLen); ! ! // Note that we don't remove the nonce from the buffer. } 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 --- 21,410 ---- * 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.misc.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 ! synchronized public 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: empty ClientHello.compression_methods + int requiredSize = 46 + 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
*** 185,198 **** } } // Requires message authentication code for null, stream and block // cipher suites. ! if (authenticator instanceof MAC && tagLen != 0) { MAC signer = (MAC)authenticator; ! int macOffset = count - tagLen; ! int contentLen = macOffset - pos; // 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) { --- 416,428 ---- } } // 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) {
*** 200,222 **** if (reservedBPE == null) { reservedBPE = new BadPaddingException("bad record"); } // set offset of the dummy MAC ! macOffset = headerSize + cipheredLength - tagLen; ! contentLen = macOffset - headerSize; } - count -= tagLen; // Set the count before any MAC checking - // exception occurs, so that the following - // process can read the actual decrypted - // content (minus the MAC) in the fragment - // if necessary. - // Run MAC computation and comparison on the payload. ! if (checkMacTags(contentType(), ! buf, pos, contentLen, signer, false)) { if (reservedBPE == null) { reservedBPE = new BadPaddingException("bad record MAC"); } } --- 430,447 ---- 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"); } }
*** 227,266 **** 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. However, ! // We won't need to worry about it because we always use a ! // maximum buffer for every record. We need a change here if ! // we use small buffer size in the future. ! if (remainingLen > buf.length) { ! // unlikely to happen, just a placehold ! throw new RuntimeException( ! "Internal buffer capacity error"); ! } // 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(), buf, 0, remainingLen, signer, true); } } // Is it a failover? if (reservedBPE != null) { throw reservedBPE; } } /* * Run MAC computation and comparison * * Please DON'T change the content of the byte buffer parameter! */ ! 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); --- 452,545 ---- 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);
*** 302,312 **** * 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. */ ! static int calculateRemainingLen( MAC signer, int fullLen, int usedLen) { int blockLen = signer.hashBlockLen(); int minimalPaddingLen = signer.minimalPaddingLen(); --- 581,591 ---- * 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();
*** 320,872 **** // 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))) * signer.hashBlockLen(); } - - /* - * Well ... hello_request messages are _never_ hashed since we can't - * know when they'd appear in the sequence. - */ - void ignore(int bytes) { - if (bytes > 0) { - pos += bytes; - lastHashed = pos; - } - } - - /* - * We hash the (plaintext) we've processed, but only on demand. - * - * There is one place where we want to access the hash in the middle - * of a record: client cert message gets hashed, and part of the - * same record is the client cert verify message which uses that hash. - * So we track how much we've read and hashed. - */ - void doHashes() { - int len = pos - lastHashed; - - if (len > 0) { - hashInternal(buf, lastHashed, len); - lastHashed = pos; - } - } - - /* - * Need a helper function so we can hash the V2 hello correctly - */ - private void hashInternal(byte databuf [], int offset, int len) { - if (debug != null && Debug.isOn("data")) { - try { - HexDumpEncoder hd = new HexDumpEncoder(); - - System.out.println("[read] MD5 and SHA1 hashes: len = " - + len); - hd.encodeBuffer(new ByteArrayInputStream(databuf, offset, len), - System.out); - } catch (IOException e) { } - } - handshakeHash.update(databuf, offset, len); - } - - - /* - * Handshake messages may cross record boundaries. We "queue" - * these in big buffers if we need to cope with this problem. - * This is not anticipated to be a common case; if this turns - * out to be wrong, this can readily be sped up. - */ - void queueHandshake(InputRecord r) throws IOException { - int len; - - /* - * Hash any data that's read but unhashed. - */ - doHashes(); - - /* - * Move any unread data to the front of the buffer, - * flagging it all as unhashed. - */ - if (pos > headerSize) { - len = count - pos; - if (len != 0) { - System.arraycopy(buf, pos, buf, headerSize, len); - } - pos = headerSize; - lastHashed = pos; - count = headerSize + len; - } - - /* - * Grow "buf" if needed - */ - len = r.available() + count; - if (buf.length < len) { - byte newbuf []; - - newbuf = new byte [len]; - System.arraycopy(buf, 0, newbuf, 0, count); - buf = newbuf; - } - - /* - * Append the new buffer to this one. - */ - System.arraycopy(r.buf, r.pos, buf, count, len - count); - count = len; - - /* - * Adjust lastHashed; important for now with clients which - * send SSL V2 client hellos. This will go away eventually, - * by buffer code cleanup. - */ - len = r.lastHashed - r.pos; - if (pos == headerSize) { - lastHashed += len; - } else { - throw new SSLProtocolException("?? confused buffer hashing ??"); - } - // we've read the record, advance the pointers - r.pos = r.count; - } - - - /** - * Prevent any more data from being read into this record, - * and flag the record as holding no data. - */ - @Override - public void close() { - appDataValid = false; - isClosed = true; - mark = 0; - pos = 0; - count = 0; - } - - - /* - * We may need to send this SSL v2 "No Cipher" message back, if we - * are faced with an SSLv2 "hello" that's not saying "I talk v3". - * It's the only one documented in the V2 spec as a fatal error. - */ - private static final byte[] v2NoCipher = { - (byte)0x80, (byte)0x03, // unpadded 3 byte record - (byte)0x00, // ... error message - (byte)0x00, (byte)0x01 // ... NO_CIPHER error - }; - - private int readFully(InputStream s, byte b[], int off, int len) - throws IOException { - int n = 0; - while (n < len) { - int readLen = s.read(b, off + n, len - n); - if (readLen < 0) { - return readLen; - } - - if (debug != null && Debug.isOn("packet")) { - try { - HexDumpEncoder hd = new HexDumpEncoder(); - ByteBuffer bb = ByteBuffer.wrap(b, off + n, readLen); - - System.out.println("[Raw read]: length = " + - bb.remaining()); - hd.encodeBuffer(bb, System.out); - } catch (IOException e) { } - } - - n += readLen; - exlen += readLen; - } - - return n; - } - - /* - * Read the SSL V3 record ... first time around, check to see if it - * really IS a V3 record. Handle SSL V2 clients which can talk V3.0, - * as well as real V3 record format; otherwise report an error. - */ - void read(InputStream s, OutputStream o) throws IOException { - if (isClosed) { - return; - } - - /* - * For SSL it really _is_ an error if the other end went away - * so ungracefully as to not shut down cleanly. - */ - if(exlen < headerSize) { - int really = readFully(s, buf, exlen, headerSize - exlen); - if (really < 0) { - throw new EOFException("SSL peer shut down incorrectly"); - } - - pos = headerSize; - count = headerSize; - lastHashed = pos; - } - - /* - * The first record might use some other record marking convention, - * typically SSL v2 header. (PCT could also be detected here.) - * This case is currently common -- Navigator 3.0 usually works - * this way, as do IE 3.0 and other products. - */ - if (!formatVerified) { - formatVerified = true; - /* - * The first record must either be a handshake record or an - * alert message. If it's not, it is either invalid or an - * SSLv2 message. - */ - if (buf[0] != ct_handshake && buf[0] != ct_alert) { - handleUnknownRecord(s, o); - } else { - readV3Record(s, o); - } - } else { // formatVerified == true - readV3Record(s, o); - } - } - - /** - * Return true if the specified record protocol version is out of the - * range of the possible supported versions. - */ - static void checkRecordVersion(ProtocolVersion version, - boolean allowSSL20Hello) throws SSLException { - // Check if the record version is too old (currently not possible) - // or if the major version does not match. - // - // The actual version negotiation is in the handshaker classes - if ((version.v < ProtocolVersion.MIN.v) || - ((version.major & 0xFF) > (ProtocolVersion.MAX.major & 0xFF))) { - - // if it's not SSLv2, we're out of here. - if (!allowSSL20Hello || - (version.v != ProtocolVersion.SSL20Hello.v)) { - throw new SSLException("Unsupported record version " + version); - } - } - } - - /** - * Read a SSL/TLS record. Throw an IOException if the format is invalid. - */ - private void readV3Record(InputStream s, OutputStream o) - throws IOException { - ProtocolVersion recordVersion = ProtocolVersion.valueOf(buf[1], buf[2]); - - // check the record version - checkRecordVersion(recordVersion, false); - - /* - * Get and check length, then the data. - */ - int contentLen = ((buf[3] & 0x0ff) << 8) + (buf[4] & 0xff); - - /* - * Check for upper bound. - */ - if (contentLen < 0 || contentLen > maxLargeRecordSize - headerSize) { - throw new SSLProtocolException("Bad InputRecord size" - + ", count = " + contentLen - + ", buf.length = " + buf.length); - } - - /* - * Grow "buf" if needed. Since buf is maxRecordSize by default, - * this only occurs when we receive records which violate the - * SSL specification. This is a workaround for a Microsoft SSL bug. - */ - if (contentLen > buf.length - headerSize) { - byte[] newbuf = new byte[contentLen + headerSize]; - System.arraycopy(buf, 0, newbuf, 0, headerSize); - buf = newbuf; - } - - if (exlen < contentLen + headerSize) { - int really = readFully( - s, buf, exlen, contentLen + headerSize - exlen); - if (really < 0) { - throw new SSLException("SSL peer shut down incorrectly"); - } - } - - // now we've got a complete record. - count = contentLen + headerSize; - exlen = 0; - - if (debug != null && Debug.isOn("record")) { - if (count < 0 || count > (maxRecordSize - headerSize)) { - System.out.println(Thread.currentThread().getName() - + ", Bad InputRecord size" + ", count = " + count); - } - System.out.println(Thread.currentThread().getName() - + ", READ: " + recordVersion + " " - + contentName(contentType()) + ", length = " + available()); - } - /* - * then caller decrypts, verifies, and uncompresses - */ - } - - /** - * Deal with unknown records. Called if the first data we read on this - * connection does not look like an SSL/TLS record. It could a SSLv2 - * message, or just garbage. - */ - private void handleUnknownRecord(InputStream s, OutputStream o) - throws IOException { - /* - * No? Oh well; does it look like a V2 "ClientHello"? - * That'd be an unpadded handshake message; we don't - * bother checking length just now. - */ - if (((buf[0] & 0x080) != 0) && buf[2] == 1) { - /* - * if the user has disabled SSLv2Hello (using - * setEnabledProtocol) then throw an - * exception - */ - if (helloVersion != ProtocolVersion.SSL20Hello) { - throw new SSLHandshakeException("SSLv2Hello is disabled"); - } - - ProtocolVersion recordVersion = - ProtocolVersion.valueOf(buf[3], buf[4]); - - if (recordVersion == ProtocolVersion.SSL20Hello) { - /* - * Looks like a V2 client hello, but not one saying - * "let's talk SSLv3". So we send an SSLv2 error - * message, one that's treated as fatal by clients. - * (Otherwise we'll hang.) - */ - try { - writeBuffer(o, v2NoCipher, 0, v2NoCipher.length); - } catch (Exception e) { - /* NOTHING */ - } - throw new SSLException("Unsupported SSL v2.0 ClientHello"); - } - - /* - * If we can map this into a V3 ClientHello, read and - * hash the rest of the V2 handshake, turn it into a - * V3 ClientHello message, and pass it up. - */ - int len = ((buf[0] & 0x7f) << 8) + - (buf[1] & 0xff) - 3; - if (v2Buf == null) { - v2Buf = new byte[len]; - } - if (exlen < len + headerSize) { - int really = readFully( - s, v2Buf, exlen - headerSize, len + headerSize - exlen); - if (really < 0) { - throw new EOFException("SSL peer shut down incorrectly"); - } - } - - // now we've got a complete record. - exlen = 0; - - hashInternal(buf, 2, 3); - hashInternal(v2Buf, 0, len); - V2toV3ClientHello(v2Buf); - v2Buf = null; - lastHashed = count; - - if (debug != null && Debug.isOn("record")) { - System.out.println( - Thread.currentThread().getName() - + ", READ: SSL v2, contentType = " - + contentName(contentType()) - + ", translated length = " + available()); - } - return; - - } else { - /* - * Does it look like a V2 "ServerHello"? - */ - if (((buf [0] & 0x080) != 0) && buf [2] == 4) { - throw new SSLException( - "SSL V2.0 servers are not supported."); - } - - /* - * If this is a V2 NoCipher message then this means - * the other server doesn't support V3. Otherwise, we just - * don't understand what it's saying. - */ - for (int i = 0; i < v2NoCipher.length; i++) { - if (buf[i] != v2NoCipher[i]) { - throw new SSLException( - "Unrecognized SSL message, plaintext connection?"); - } - } - - throw new SSLException("SSL V2.0 servers are not supported."); - } - } - - /* - * Actually do the write here. For SSLEngine's HS data, - * we'll override this method and let it take the appropriate - * action. - */ - void writeBuffer(OutputStream s, byte [] buf, int off, int len) - throws IOException { - s.write(buf, 0, len); - s.flush(); - } - - /* - * Support "old" clients which are capable of SSL V3.0 protocol ... for - * example, Navigator 3.0 clients. The V2 message is in the header and - * the bytes passed as parameter. This routine translates the V2 message - * into an equivalent V3 one. - */ - private void V2toV3ClientHello(byte v2Msg []) throws SSLException - { - int i; - - /* - * Build the first part of the V3 record header from the V2 one - * that's now buffered up. (Lengths are fixed up later). - */ - buf [0] = ct_handshake; - buf [1] = buf [3]; // V3.x - buf[2] = buf[4]; - // header [3..4] for handshake message length - // count = 5; - - /* - * Store the generic V3 handshake header: 4 bytes - */ - buf [5] = 1; // HandshakeMessage.ht_client_hello - // buf [6..8] for length of ClientHello (int24) - // count += 4; - - /* - * ClientHello header starts with SSL version - */ - buf [9] = buf [1]; - buf [10] = buf [2]; - // count += 2; - count = 11; - - /* - * Start parsing the V2 message ... - */ - int cipherSpecLen, sessionIdLen, nonceLen; - - cipherSpecLen = ((v2Msg [0] & 0xff) << 8) + (v2Msg [1] & 0xff); - sessionIdLen = ((v2Msg [2] & 0xff) << 8) + (v2Msg [3] & 0xff); - nonceLen = ((v2Msg [4] & 0xff) << 8) + (v2Msg [5] & 0xff); - - /* - * 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 = 6 + cipherSpecLen + sessionIdLen; - - if (nonceLen < 32) { - for (i = 0; i < (32 - nonceLen); i++) - buf [count++] = 0; - System.arraycopy(v2Msg, offset, buf, count, nonceLen); - count += nonceLen; - } else { - System.arraycopy(v2Msg, offset + (nonceLen - 32), - buf, count, 32); - count += 32; - } - - /* - * Copy Session ID (only one byte length!) - */ - offset -= sessionIdLen; - buf [count++] = (byte) sessionIdLen; - - System.arraycopy(v2Msg, offset, buf, count, sessionIdLen); - count += 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; - j = count + 2; - - for (i = 0; i < cipherSpecLen; i += 3) { - if (v2Msg [offset + i] != 0) - continue; - buf [j++] = v2Msg [offset + i + 1]; - buf [j++] = v2Msg [offset + i + 2]; - } - - j -= count + 2; - buf [count++] = (byte) (j >>> 8); - buf [count++] = (byte) j; - count += j; - - /* - * Append compression methods (default/null only) - */ - buf [count++] = 1; - buf [count++] = 0; // Session.compression_null - - /* - * Fill in lengths of the messages we synthesized (nested: - * V3 handshake message within V3 record) and then return - */ - buf [3] = (byte) (count - headerSize); - buf [4] = (byte) ((count - headerSize) >>> 8); - - buf [headerSize + 1] = 0; - buf [headerSize + 2] = (byte) (((count - headerSize) - 4) >>> 8); - buf [headerSize + 3] = (byte) ((count - headerSize) - 4); - - pos = headerSize; - } - - /** - * Return a description for the given content type. This method should be - * in Record, but since that is an interface this is not possible. - * Called from InputRecord and OutputRecord. - */ - static String contentName(int contentType) { - switch (contentType) { - case ct_change_cipher_spec: - return "Change Cipher Spec"; - case ct_alert: - return "Alert"; - case ct_handshake: - return "Handshake"; - case ct_application_data: - return "Application Data"; - default: - return "contentType = " + contentType; - } - } - } --- 599,607 ---- // 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; } } +