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

Print this page

        

*** 21,31 **** * 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.Arrays; --- 21,30 ----
*** 33,128 **** import javax.net.ssl.SSLException; import sun.misc.HexDumpEncoder; /** ! * SSL 3.0 records, as written to a TCP stream. * - * Each record has a message area that starts out with data supplied by the - * application. It may grow/shrink due to compression and will be modified - * in place for mac-ing and encryption. - * - * Handshake records have additional needs, notably accumulation of a set - * of hashes which are used to establish that handshaking was done right. - * Handshake records usually have several handshake messages each, and we - * need message-level control over what's hashed. - * * @author David Brownell */ ! class OutputRecord extends ByteArrayOutputStream implements Record { ! private HandshakeHash handshakeHash; ! private int lastHashed; ! private boolean firstMessage; ! final private byte contentType; ! private int headerOffset; // current protocol version, sent as record version ProtocolVersion protocolVersion; // version for the ClientHello message. Only relevant if this is a // client handshake record. If set to ProtocolVersion.SSL20Hello, // the V3 client hello is converted to V2 format. ! private ProtocolVersion helloVersion; ! /* Class and subclass dynamic debugging support */ ! static final Debug debug = Debug.getInstance("ssl"); /* ! * Default constructor makes a record supporting the maximum ! * SSL record size. It allocates the header bytes directly. ! * ! * The structure of the byte buffer looks like: ! * ! * |---------+--------+-------+---------------------------------| ! * | unused | header | IV | content, MAC/TAG, padding, etc. | ! * | headerPlusMaxIVSize | ! * ! * unused: unused part of the buffer of size ! * ! * headerPlusMaxIVSize - header size - IV size ! * ! * When this object is created, we don't know the protocol ! * version number, IV length, etc., so reserve space in front ! * to avoid extra data movement (copies). ! * header: the header of an SSL record ! * IV: the optional IV/nonce field, it is only required for block ! * (TLS 1.1 or later) and AEAD cipher suites. ! * ! * @param type the content type for the record */ ! OutputRecord(byte type, int size) { ! super(size); ! this.protocolVersion = ProtocolVersion.DEFAULT; ! this.helloVersion = ProtocolVersion.DEFAULT_HELLO; ! firstMessage = true; ! count = headerPlusMaxIVSize; ! contentType = type; ! lastHashed = count; ! headerOffset = headerPlusMaxIVSize - headerSize; ! } ! OutputRecord(byte type) { ! this(type, recordSize(type)); ! } ! /** ! * Get the size of the buffer we need for records of the specified ! * type. ! */ ! private static int recordSize(byte type) { ! if ((type == ct_change_cipher_spec) || (type == ct_alert)) { ! return maxAlertRecordSize; ! } else { ! return maxRecordSize; } - } ! /* ! * Updates the SSL version of this record. ! */ ! synchronized void setVersion(ProtocolVersion protocolVersion) { this.protocolVersion = protocolVersion; } /* * Updates helloVersion of this record. --- 32,96 ---- import javax.net.ssl.SSLException; import sun.misc.HexDumpEncoder; /** ! * {@code OutputRecord} takes care of the management of SSL/TLS/DTLS output ! * records, including buffering, encryption, handshake messages marshal, etc. * * @author David Brownell */ ! abstract class OutputRecord extends ByteArrayOutputStream ! implements Record, Closeable { ! /* Class and subclass dynamic debugging support */ ! static final Debug debug = Debug.getInstance("ssl"); + Authenticator writeAuthenticator; + CipherBox writeCipher; + + HandshakeHash handshakeHash; + boolean firstMessage; + // current protocol version, sent as record version ProtocolVersion protocolVersion; // version for the ClientHello message. Only relevant if this is a // client handshake record. If set to ProtocolVersion.SSL20Hello, // the V3 client hello is converted to V2 format. ! ProtocolVersion helloVersion; ! // Is it the first application record to write? ! boolean isFirstAppOutputRecord = true; + // packet size + int packetSize; + + // fragment size + int fragmentSize; + + // closed or not? + boolean isClosed; + /* ! * Mappings from V3 cipher suite encodings to their pure V2 equivalents. ! * This is taken from the SSL V3 specification, Appendix E. */ ! private static int[] V3toV2CipherMap1 = ! {-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07}; ! private static int[] V3toV2CipherMap3 = ! {-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0}; ! OutputRecord() { ! this.writeCipher = CipherBox.NULL; ! this.firstMessage = true; ! this.fragmentSize = Record.maxDataSize; ! // Please set packetSize and protocolVersion in the implementation. } ! void setVersion(ProtocolVersion protocolVersion) { this.protocolVersion = protocolVersion; } /* * Updates helloVersion of this record.
*** 130,573 **** synchronized void setHelloVersion(ProtocolVersion helloVersion) { this.helloVersion = helloVersion; } /* - * Reset the record so that it can be refilled, starting - * immediately after the header. - */ - @Override - public synchronized void reset() { - super.reset(); - count = headerPlusMaxIVSize; - lastHashed = count; - headerOffset = headerPlusMaxIVSize - headerSize; - } - - /* * 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. */ void setHandshakeHash(HandshakeHash handshakeHash) { - assert(contentType == ct_handshake); this.handshakeHash = handshakeHash; } /* ! * We hash (the plaintext) 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 of each record we've hashed so far. */ ! void doHashes() { ! int len = count - lastHashed; ! if (len > 0) { ! hashInternal(buf, lastHashed, len); ! lastHashed = count; } - } ! /* ! * Need a helper function so we can hash the V2 hello correctly ! */ ! private void hashInternal(byte buf [], int offset, int len) { ! if (debug != null && Debug.isOn("data")) { ! try { ! HexDumpEncoder hd = new HexDumpEncoder(); ! System.out.println("[write] MD5 and SHA1 hashes: len = " ! + len); ! hd.encodeBuffer(new ByteArrayInputStream(buf, ! lastHashed, len), System.out); ! } catch (IOException e) { } } ! handshakeHash.update(buf, lastHashed, len); ! lastHashed = count; } ! /* ! * Return true iff the record is empty -- to avoid doing the work ! * of sending empty records over the network. ! */ ! boolean isEmpty() { ! return count == headerPlusMaxIVSize; } ! /* ! * Return true if the record is of an alert of the given description. ! * ! * Per SSL/TLS specifications, alert messages convey the severity of the ! * message (warning or fatal) and a description of the alert. An alert ! * is defined with a two bytes struct, {byte level, byte description}, ! * following after the header bytes. ! */ ! boolean isAlert(byte description) { ! if ((count > (headerPlusMaxIVSize + 1)) && (contentType == ct_alert)) { ! return buf[headerPlusMaxIVSize + 1] == description; } ! return false; } /* ! * Encrypt ... length may grow due to block cipher padding, or ! * message authentication code or tag. */ ! void encrypt(Authenticator authenticator, CipherBox box) ! throws IOException { ! // In case we are automatically flushing a handshake stream, make ! // sure we have hashed the message first. ! // ! // when we support compression, hashing can't go here ! // since it'll need to be done on the uncompressed data, ! // and the MAC applies to the compressed data. ! if (contentType == ct_handshake) { ! doHashes(); } ! // Requires message authentication code for stream and block ! // cipher suites. ! if (authenticator instanceof MAC) { ! MAC signer = (MAC)authenticator; ! if (signer.MAClen() != 0) { ! byte[] hash = signer.compute(contentType, buf, ! headerPlusMaxIVSize, count - headerPlusMaxIVSize, false); ! write(hash); } - } ! if (!box.isNullCipher()) { ! // Requires explicit IV/nonce for CBC/AEAD cipher suites for ! // TLS 1.1 or later. ! if ((protocolVersion.v >= ProtocolVersion.TLS11.v) && ! (box.isCBCMode() || box.isAEADMode())) { ! byte[] nonce = box.createExplicitNonce(authenticator, ! contentType, count - headerPlusMaxIVSize); ! int offset = headerPlusMaxIVSize - nonce.length; ! System.arraycopy(nonce, 0, buf, offset, nonce.length); ! headerOffset = offset - headerSize; ! } else { ! headerOffset = headerPlusMaxIVSize - headerSize; } ! // encrypt the content ! int offset = headerPlusMaxIVSize; ! if (!box.isAEADMode()) { ! // The explicit IV can be encrypted. ! offset = headerOffset + headerSize; ! } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode ! ! count = offset + box.encrypt(buf, offset, count - offset); } - } ! /* ! * Tell how full the buffer is ... for filling it with application or ! * handshake data. ! */ ! final int availableDataBytes() { ! int dataSize = count - headerPlusMaxIVSize; ! return maxDataSize - dataSize; } ! /* ! * Increases the capacity if necessary to ensure that it can hold ! * at least the number of elements specified by the minimum ! * capacity argument. ! * ! * Note that the increased capacity is only can be used for held ! * record buffer. Please DO NOT update the availableDataBytes() ! * according to the expended buffer capacity. ! * ! * @see availableDataBytes() ! */ ! private void ensureCapacity(int minCapacity) { ! // overflow-conscious code ! if (minCapacity > buf.length) { ! buf = Arrays.copyOf(buf, minCapacity); } } ! /* ! * Return the type of SSL record that's buffered here. ! */ ! final byte contentType() { ! return contentType; } /* ! * Write the record out on the stream. Note that you must have (in ! * order) compressed the data, appended the MAC, and encrypted it in ! * order for the record to be understood by the other end. (Some of ! * those steps will be null early in handshaking.) * ! * Note that this does no locking for the connection, it's required ! * that synchronization be done elsewhere. Also, this does its work ! * in a single low level write, for efficiency. */ ! void write(OutputStream s, boolean holdRecord, ! ByteArrayOutputStream heldRecordBuffer) throws IOException { ! /* ! * Don't emit content-free records. (Even change cipher spec ! * messages have a byte of data!) ! */ ! if (count == headerPlusMaxIVSize) { ! return; } - - int length = count - headerOffset - headerSize; - // "should" really never write more than about 14 Kb... - if (length < 0) { - throw new SSLException("output record size too small: " - + length); } ! if (debug != null ! && (Debug.isOn("record") || Debug.isOn("handshake"))) { ! if ((debug != null && Debug.isOn("record")) ! || contentType() == ct_change_cipher_spec) ! System.out.println(Thread.currentThread().getName() ! // v3.0/v3.1 ... ! + ", WRITE: " + protocolVersion ! + " " + InputRecord.contentName(contentType()) ! + ", length = " + length); } ! /* ! * If this is the initial ClientHello on this connection and ! * we're not trying to resume a (V3) session then send a V2 ! * ClientHello instead so we can detect V2 servers cleanly. ! */ ! if (firstMessage && useV2Hello()) { ! byte[] v3Msg = new byte[length - 4]; ! System.arraycopy(buf, headerPlusMaxIVSize + 4, ! v3Msg, 0, v3Msg.length); ! headerOffset = 0; // reset the header offset ! V3toV2ClientHello(v3Msg); ! handshakeHash.reset(); ! lastHashed = 2; ! doHashes(); ! if (debug != null && Debug.isOn("record")) { ! System.out.println( ! Thread.currentThread().getName() ! + ", WRITE: SSLv2 client hello message" ! + ", length = " + (count - 2)); // 2 byte SSLv2 header ! } } else { ! /* ! * Fill out the header, write it and the message. ! */ ! buf[headerOffset + 0] = contentType; ! buf[headerOffset + 1] = protocolVersion.major; ! buf[headerOffset + 2] = protocolVersion.minor; ! buf[headerOffset + 3] = (byte)(length >> 8); ! buf[headerOffset + 4] = (byte)(length); } - firstMessage = false; ! /* ! * The upper levels may want us to delay sending this packet so ! * multiple TLS Records can be sent in one (or more) TCP packets. ! * If so, add this packet to the heldRecordBuffer. ! * ! * NOTE: all writes have been synchronized by upper levels. ! */ ! int debugOffset = 0; ! if (holdRecord) { ! /* ! * If holdRecord is true, we must have a heldRecordBuffer. ! * ! * Don't worry about the override of writeBuffer(), because ! * when holdRecord is true, the implementation in this class ! * will be used. ! */ ! writeBuffer(heldRecordBuffer, ! buf, headerOffset, count - headerOffset, debugOffset); } else { ! // It's time to send, do we have buffered data? ! // May or may not have a heldRecordBuffer. ! if (heldRecordBuffer != null && heldRecordBuffer.size() > 0) { ! int heldLen = heldRecordBuffer.size(); ! // Ensure the capacity of this buffer. ! int newCount = count + heldLen - headerOffset; ! ensureCapacity(newCount); ! // Slide everything in the buffer to the right. ! System.arraycopy(buf, headerOffset, ! buf, heldLen, count - headerOffset); ! // Prepend the held record to the buffer. ! System.arraycopy( ! heldRecordBuffer.toByteArray(), 0, buf, 0, heldLen); ! count = newCount; ! headerOffset = 0; ! // Clear the held buffer. ! heldRecordBuffer.reset(); ! // The held buffer has been dumped, set the debug dump offset. ! debugOffset = heldLen; } - writeBuffer(s, buf, headerOffset, - count - headerOffset, debugOffset); } ! reset(); } ! /* ! * 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, ! int debugOffset) throws IOException { ! s.write(buf, off, len); ! s.flush(); ! // Output only the record from the specified debug offset. ! if (debug != null && Debug.isOn("packet")) { ! try { ! HexDumpEncoder hd = new HexDumpEncoder(); ! System.out.println("[Raw write]: length = " + ! (len - debugOffset)); ! hd.encodeBuffer(new ByteArrayInputStream(buf, ! off + debugOffset, len - debugOffset), System.out); ! } catch (IOException e) { } } - } ! /* ! * Return whether the buffer contains a ClientHello message that should ! * be converted to V2 format. ! */ ! private boolean useV2Hello() { ! return firstMessage ! && (helloVersion == ProtocolVersion.SSL20Hello) ! && (contentType == ct_handshake) ! && (buf[headerOffset + 5] == HandshakeMessage.ht_client_hello) ! // 5: recode header size ! && (buf[headerPlusMaxIVSize + 4 + 2 + 32] == 0); ! // V3 session ID is empty ! // 4: handshake header size ! // 2: client_version in ClientHello ! // 32: random in ClientHello } ! /* ! * Detect "old" servers which are capable of SSL V2.0 protocol ... for ! * example, Netscape Commerce 1.0 servers. The V3 message is in the ! * header and the bytes passed as parameter. This routine translates ! * the V3 message into an equivalent V2 one. ! * ! * Note that the translation will strip off all hello extensions as ! * SSL V2.0 does not support hello extension. ! */ ! private void V3toV2ClientHello(byte v3Msg []) throws SSLException { ! int v3SessionIdLenOffset = 2 + 32; // version + nonce ! int v3SessionIdLen = v3Msg[v3SessionIdLenOffset]; ! int v3CipherSpecLenOffset = v3SessionIdLenOffset + 1 + v3SessionIdLen; ! int v3CipherSpecLen = ((v3Msg[v3CipherSpecLenOffset] & 0xff) << 8) + ! (v3Msg[v3CipherSpecLenOffset + 1] & 0xff); ! int cipherSpecs = v3CipherSpecLen / 2; // 2 bytes each in V3 /* ! * Copy over the cipher specs. We don't care about actually translating ! * them for use with an actual V2 server since we only talk V3. ! * Therefore, just copy over the V3 cipher spec values with a leading ! * 0. */ ! int v3CipherSpecOffset = v3CipherSpecLenOffset + 2; // skip length ! int v2CipherSpecLen = 0; ! count = 11; boolean containsRenegoInfoSCSV = false; for (int i = 0; i < cipherSpecs; i++) { byte byte1, byte2; ! byte1 = v3Msg[v3CipherSpecOffset++]; ! byte2 = v3Msg[v3CipherSpecOffset++]; ! v2CipherSpecLen += V3toV2CipherSuite(byte1, byte2); if (!containsRenegoInfoSCSV && byte1 == (byte)0x00 && byte2 == (byte)0xFF) { containsRenegoInfoSCSV = true; } } if (!containsRenegoInfoSCSV) { ! v2CipherSpecLen += V3toV2CipherSuite((byte)0x00, (byte)0xFF); } /* - * Build the first part of the V3 record header from the V2 one - * that's now buffered up. (Lengths are fixed up later). - */ - buf[2] = HandshakeMessage.ht_client_hello; - buf[3] = v3Msg[0]; // major version - buf[4] = v3Msg[1]; // minor version - buf[5] = (byte)(v2CipherSpecLen >>> 8); - buf[6] = (byte)v2CipherSpecLen; - buf[7] = 0; - buf[8] = 0; // always no session - buf[9] = 0; - buf[10] = 32; // nonce length (always 32 in V3) - - /* * Copy in the nonce. */ ! System.arraycopy(v3Msg, 2, buf, count, 32); ! count += 32; /* ! * Set the length of the message. */ ! count -= 2; // don't include length field itself ! buf[0] = (byte)(count >>> 8); ! buf[0] |= 0x80; ! buf[1] = (byte)(count); ! count += 2; ! } ! /* ! * Mappings from V3 cipher suite encodings to their pure V2 equivalents. ! * This is taken from the SSL V3 specification, Appendix E. ! */ ! private static int[] V3toV2CipherMap1 = ! {-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07}; ! private static int[] V3toV2CipherMap3 = ! {-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0}; ! /* ! * See which matching pure-V2 cipher specs we need to include. ! * We are including these not because we are actually prepared ! * to talk V2 but because the Oracle Web Server insists on receiving ! * at least 1 "pure V2" cipher suite that it supports and returns an ! * illegal_parameter alert unless one is present. Rather than mindlessly ! * claiming to implement all documented pure V2 cipher suites the code below ! * just claims to implement the V2 cipher suite that is "equivalent" ! * in terms of cipher algorithm & exportability with the actual V3 cipher ! * suite that we do support. ! */ ! private int V3toV2CipherSuite(byte byte1, byte byte2) { ! buf[count++] = 0; ! buf[count++] = byte1; ! buf[count++] = byte2; ! if (((byte2 & 0xff) > 0xA) || ! (V3toV2CipherMap1[byte2] == -1)) { return 3; } ! buf[count++] = (byte)V3toV2CipherMap1[byte2]; ! buf[count++] = 0; ! buf[count++] = (byte)V3toV2CipherMap3[byte2]; return 6; } } --- 98,464 ---- synchronized void setHelloVersion(ProtocolVersion helloVersion) { this.helloVersion = helloVersion; } /* * 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. */ void setHandshakeHash(HandshakeHash handshakeHash) { this.handshakeHash = handshakeHash; } /* ! * Return true iff the record is empty -- to avoid doing the work ! * of sending empty records over the network. */ ! boolean isEmpty() { ! return false; ! } ! boolean seqNumIsHuge() { ! return (writeAuthenticator != null) && ! writeAuthenticator.seqNumIsHuge(); } ! // SSLEngine and SSLSocket ! abstract void encodeAlert(byte level, byte description) throws IOException; ! // SSLEngine and SSLSocket ! abstract void encodeHandshake(byte[] buffer, ! int offset, int length) throws IOException; ! ! // SSLEngine and SSLSocket ! abstract void encodeChangeCipherSpec() throws IOException; ! ! // apply to SSLEngine only ! Ciphertext encode(ByteBuffer[] sources, int offset, int length, ! ByteBuffer destination) throws IOException { ! throw new UnsupportedOperationException(); } ! // apply to SSLEngine only ! void encodeV2NoCipher() throws IOException { ! throw new UnsupportedOperationException(); } ! // apply to SSLSocket only ! void deliver(byte[] source, int offset, int length) throws IOException { ! throw new UnsupportedOperationException(); } ! // apply to SSLSocket only ! void setDeliverStream(OutputStream outputStream) { ! throw new UnsupportedOperationException(); } ! // apply to SSLEngine only ! Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException { ! throw new UnsupportedOperationException(); } + void changeWriteCiphers(Authenticator writeAuthenticator, + CipherBox writeCipher) throws IOException { + + encodeChangeCipherSpec(); + /* ! * 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. */ ! writeCipher.dispose(); ! this.writeAuthenticator = writeAuthenticator; ! this.writeCipher = writeCipher; ! this.isFirstAppOutputRecord = true; } ! void changePacketSize(int packetSize) { ! this.packetSize = packetSize; } ! void changeFragmentSize(int fragmentSize) { ! this.fragmentSize = fragmentSize; } ! int getMaxPacketSize() { ! return packetSize; } ! // apply to DTLS SSLEngine ! void initHandshaker() { ! // blank } ! @Override ! synchronized public void close() throws IOException { ! if (!isClosed) { ! isClosed = true; ! writeCipher.dispose(); } } ! // ! // shared helpers ! // ! ! // Encrypt a fragment and wrap up a record. ! // ! // To be consistent with the spec of SSLEngine.wrap() methods, the ! // destination ByteBuffer's position is updated to reflect the amount ! // of data produced. The limit remains the same. ! static long encrypt(Authenticator authenticator, ! CipherBox encCipher, byte contentType, ByteBuffer destination, ! int headerOffset, int dstLim, int headerSize, ! ProtocolVersion protocolVersion, boolean isDTLS) { ! ! byte[] sequenceNumber = null; ! int dstContent = destination.position(); ! ! // Acquire the current sequence number before using. ! if (isDTLS) { ! sequenceNumber = authenticator.sequenceNumber(); } + // "flip" but skip over header again, add MAC & encrypt + if (authenticator instanceof MAC) { + MAC signer = (MAC)authenticator; + if (signer.MAClen() != 0) { + 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.limit(destination.position()); ! destination.position(dstContent); } } ! if (!encCipher.isNullCipher()) { ! if (protocolVersion.useTLS11PlusSpec() && ! (encCipher.isCBCMode() || encCipher.isAEADMode())) { ! byte[] nonce = encCipher.createExplicitNonce( ! authenticator, contentType, destination.remaining()); ! destination.position(headerOffset + headerSize); ! destination.put(nonce); } + if (!encCipher.isAEADMode()) { + // The explicit IV in TLS 1.1 and later can be encrypted. + destination.position(headerOffset + headerSize); + } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode ! // Encrypt may pad, so again the limit may be changed. ! encCipher.encrypt(destination, dstLim); } else { ! destination.position(destination.limit()); } ! // Finish out the record header. ! int fragLen = destination.limit() - headerOffset - headerSize; ! ! destination.put(headerOffset, contentType); // content type ! destination.put(headerOffset + 1, protocolVersion.major); ! destination.put(headerOffset + 2, protocolVersion.minor); ! if (!isDTLS) { ! // fragment length ! destination.put(headerOffset + 3, (byte)(fragLen >> 8)); ! destination.put(headerOffset + 4, (byte)fragLen); } else { ! // epoch and sequence_number ! destination.put(headerOffset + 3, sequenceNumber[0]); ! destination.put(headerOffset + 4, sequenceNumber[1]); ! destination.put(headerOffset + 5, sequenceNumber[2]); ! destination.put(headerOffset + 6, sequenceNumber[3]); ! destination.put(headerOffset + 7, sequenceNumber[4]); ! destination.put(headerOffset + 8, sequenceNumber[5]); ! destination.put(headerOffset + 9, sequenceNumber[6]); ! destination.put(headerOffset + 10, sequenceNumber[7]); ! // fragment length ! destination.put(headerOffset + 11, (byte)(fragLen >> 8)); ! destination.put(headerOffset + 12, (byte)fragLen); ! // Increase the sequence number for next use. ! authenticator.increaseSequenceNumber(); ! } ! // Update destination position to reflect the amount of data produced. ! destination.position(destination.limit()); ! return Authenticator.toLong(sequenceNumber); ! } ! // Encrypt a fragment and wrap up a record. ! // ! // Uses the internal expandable buf variable and the current ! // protocolVersion variable. ! void encrypt(Authenticator authenticator, ! CipherBox encCipher, byte contentType, int headerSize) { ! ! int position = headerSize + writeCipher.getExplicitNonceSize(); ! ! // "flip" but skip over header again, add MAC & encrypt ! int macLen = 0; ! if (authenticator instanceof MAC) { ! MAC signer = (MAC)authenticator; ! macLen = signer.MAClen(); ! if (macLen != 0) { ! byte[] hash = signer.compute(contentType, ! buf, position, (count - position), false); ! ! write(hash, 0, hash.length); } } ! if (!encCipher.isNullCipher()) { ! // Requires explicit IV/nonce for CBC/AEAD cipher suites for ! // TLS 1.1 or later. ! if (protocolVersion.useTLS11PlusSpec() && ! (encCipher.isCBCMode() || encCipher.isAEADMode())) { ! ! byte[] nonce = encCipher.createExplicitNonce( ! authenticator, contentType, (count - position)); ! int noncePos = position - nonce.length; ! System.arraycopy(nonce, 0, buf, noncePos, nonce.length); } ! if (!encCipher.isAEADMode()) { ! // The explicit IV in TLS 1.1 and later can be encrypted. ! position = headerSize; ! } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode ! // increase buf capacity if necessary ! int fragSize = count - position; ! int packetSize = ! encCipher.calculatePacketSize(fragSize, macLen, headerSize); ! if (packetSize > (buf.length - position)) { ! byte[] newBuf = new byte[position + packetSize]; ! System.arraycopy(buf, 0, newBuf, 0, count); ! buf = newBuf; ! } ! // Encrypt may pad, so again the count may be changed. ! count = position + ! encCipher.encrypt(buf, position, (count - position)); } ! // Fill out the header, write it and the message. ! int fragLen = count - headerSize; ! buf[0] = contentType; ! buf[1] = protocolVersion.major; ! buf[2] = protocolVersion.minor; ! buf[3] = (byte)((fragLen >> 8) & 0xFF); ! buf[4] = (byte)(fragLen & 0xFF); } ! static ByteBuffer encodeV2ClientHello( ! byte[] fragment, int offset, int length) throws IOException { + int v3SessIdLenOffset = offset + 34; // 2: client_version + // 32: random + + int v3SessIdLen = fragment[v3SessIdLenOffset]; + int v3CSLenOffset = v3SessIdLenOffset + 1 + v3SessIdLen; + int v3CSLen = ((fragment[v3CSLenOffset] & 0xff) << 8) + + (fragment[v3CSLenOffset + 1] & 0xff); + int cipherSpecs = v3CSLen / 2; // 2: cipher spec size + + // Estimate the max V2ClientHello message length + // + // 11: header size + // (cipherSpecs * 6): cipher_specs + // 6: one cipher suite may need 6 bytes, see V3toV2CipherSuite. + // 3: placeholder for the TLS_EMPTY_RENEGOTIATION_INFO_SCSV + // signaling cipher suite + // 32: challenge size + int v2MaxMsgLen = 11 + (cipherSpecs * 6) + 3 + 32; + + // Create a ByteBuffer backed by an accessible byte array. + byte[] dstBytes = new byte[v2MaxMsgLen]; + ByteBuffer dstBuf = ByteBuffer.wrap(dstBytes); + /* ! * Copy over the cipher specs. We don't care about actually ! * translating them for use with an actual V2 server since ! * we only talk V3. Therefore, just copy over the V3 cipher ! * spec values with a leading 0. */ ! int v3CSOffset = v3CSLenOffset + 2; // skip length field ! int v2CSLen = 0; ! ! dstBuf.position(11); boolean containsRenegoInfoSCSV = false; for (int i = 0; i < cipherSpecs; i++) { byte byte1, byte2; ! byte1 = fragment[v3CSOffset++]; ! byte2 = fragment[v3CSOffset++]; ! v2CSLen += V3toV2CipherSuite(dstBuf, byte1, byte2); if (!containsRenegoInfoSCSV && byte1 == (byte)0x00 && byte2 == (byte)0xFF) { containsRenegoInfoSCSV = true; } } if (!containsRenegoInfoSCSV) { ! v2CSLen += V3toV2CipherSuite(dstBuf, (byte)0x00, (byte)0xFF); } /* * Copy in the nonce. */ ! dstBuf.put(fragment, (offset + 2), 32); /* ! * Build the first part of the V3 record header from the V2 one ! * that's now buffered up. (Lengths are fixed up later). */ ! int msgLen = dstBuf.position() - 2; // Exclude the legth field itself ! dstBuf.position(0); ! dstBuf.put((byte)(0x80 | ((msgLen >>> 8) & 0xFF))); // pos: 0 ! dstBuf.put((byte)(msgLen & 0xFF)); // pos: 1 ! dstBuf.put(HandshakeMessage.ht_client_hello); // pos: 2 ! dstBuf.put(fragment[offset]); // major version, pos: 3 ! dstBuf.put(fragment[offset + 1]); // minor version, pos: 4 ! dstBuf.put((byte)(v2CSLen >>> 8)); // pos: 5 ! dstBuf.put((byte)(v2CSLen & 0xFF)); // pos: 6 ! dstBuf.put((byte)0x00); // session_id_length, pos: 7 ! dstBuf.put((byte)0x00); // pos: 8 ! dstBuf.put((byte)0x00); // challenge_length, pos: 9 ! dstBuf.put((byte)32); // pos: 10 ! dstBuf.position(0); ! dstBuf.limit(msgLen + 2); ! return dstBuf; ! } ! private static int V3toV2CipherSuite(ByteBuffer dstBuf, ! byte byte1, byte byte2) { ! dstBuf.put((byte)0); ! dstBuf.put(byte1); ! dstBuf.put(byte2); ! ! if (((byte2 & 0xff) > 0xA) || (V3toV2CipherMap1[byte2] == -1)) { return 3; } ! dstBuf.put((byte)V3toV2CipherMap1[byte2]); ! dstBuf.put((byte)0); ! dstBuf.put((byte)V3toV2CipherMap3[byte2]); return 6; } }