/*
* Copyright (c) 2003, 2013, 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 javax.net.ssl.SSLException;
import sun.misc.HexDumpEncoder;
A OutputRecord class extension which uses external ByteBuffers
or the internal ByteArrayOutputStream for data manipulations.
Instead of rewriting this entire class
to use ByteBuffers, we leave things intact, so handshake, CCS,
and alerts will continue to use the internal buffers, but application
data will use external buffers.
Author: Brad Wetmore
/**
* A OutputRecord class extension which uses external ByteBuffers
* or the internal ByteArrayOutputStream for data manipulations.
* <P>
* Instead of rewriting this entire class
* to use ByteBuffers, we leave things intact, so handshake, CCS,
* and alerts will continue to use the internal buffers, but application
* data will use external buffers.
*
* @author Brad Wetmore
*/
final class EngineOutputRecord extends OutputRecord {
private SSLEngineImpl engine;
private EngineWriter writer;
private boolean finishedMsg = false;
/*
* All handshake hashing is done by the superclass
*/
/*
* Default constructor makes a record supporting the maximum
* SSL record size. It allocates the header bytes directly.
*
* @param type the content type for the record
*/
EngineOutputRecord(byte type, SSLEngineImpl engine) {
super(type, recordSize(type));
this.engine = engine;
writer = engine.writer;
}
Get the size of the buffer we need for records of the specified
type.
Application data buffers will provide their own byte buffers,
and will not use the internal byte caching.
/**
* Get the size of the buffer we need for records of the specified
* type.
* <P>
* Application data buffers will provide their own byte buffers,
* and will not use the internal byte caching.
*/
private static int recordSize(byte type) {
switch (type) {
case ct_change_cipher_spec:
case ct_alert:
return maxAlertRecordSize;
case ct_handshake:
return maxRecordSize;
case ct_application_data:
return 0;
}
throw new RuntimeException("Unknown record type: " + type);
}
void setFinishedMsg() {
finishedMsg = true;
}
public void flush() throws IOException {
finishedMsg = false;
}
boolean isFinishedMsg() {
return finishedMsg;
}
Calculate the MAC value, storing the result either in
the internal buffer, or at the end of the destination
ByteBuffer.
We assume that the higher levels have assured us enough
room, otherwise we'll indirectly throw a
BufferOverFlowException runtime exception.
position should equal limit, and points to the next
free spot.
/**
* Calculate the MAC value, storing the result either in
* the internal buffer, or at the end of the destination
* ByteBuffer.
* <P>
* We assume that the higher levels have assured us enough
* room, otherwise we'll indirectly throw a
* BufferOverFlowException runtime exception.
*
* position should equal limit, and points to the next
* free spot.
*/
private void addMAC(MAC signer, ByteBuffer bb)
throws IOException {
if (signer.MAClen() != 0) {
byte[] hash = signer.compute(contentType(), bb, false);
/*
* position was advanced to limit in compute above.
*
* Mark next area as writable (above layers should have
* established that we have plenty of room), then write
* out the hash.
*/
bb.limit(bb.limit() + hash.length);
bb.put(hash);
}
}
/*
* Encrypt a ByteBuffer.
*
* We assume that the higher levels have assured us enough
* room for the encryption (plus padding), otherwise we'll
* indirectly throw a BufferOverFlowException runtime exception.
*
* position and limit will be the same, and points to the
* next free spot.
*/
void encrypt(CipherBox box, ByteBuffer bb) {
box.encrypt(bb);
}
/*
* Override the actual write below. We do things this way to be
* consistent with InputRecord. InputRecord may try to write out
* data to the peer, and *then* throw an Exception. This forces
* data to be generated/output before the exception is ever
* generated.
*/
@Override
void writeBuffer(OutputStream s, byte [] buf, int off, int len,
int debugOffset) throws IOException {
/*
* Copy data out of buffer, it's ready to go.
*/
ByteBuffer netBB = (ByteBuffer)
ByteBuffer.allocate(len).put(buf, 0, len).flip();
writer.putOutboundData(netBB);
}
/*
* Main method for writing non-application data.
* We MAC/encrypt, then send down for processing.
*/
void write(MAC writeMAC, CipherBox writeCipher) throws IOException {
/*
* Sanity check.
*/
switch (contentType()) {
case ct_change_cipher_spec:
case ct_alert:
case ct_handshake:
break;
default:
throw new RuntimeException("unexpected byte buffers");
}
/*
* Don't bother to really write empty records. We went this
* far to drive the handshake machinery, for correctness; not
* writing empty records improves performance by cutting CPU
* time and network resource usage. Also, some protocol
* implementations are fragile and don't like to see empty
* records, so this increases robustness.
*
* (Even change cipher spec messages have a byte of data!)
*/
if (!isEmpty()) {
// compress(); // eventually
addMAC(writeMAC);
encrypt(writeCipher);
write((OutputStream)null, false, // send down for processing
(ByteArrayOutputStream)null);
}
return;
}
Main wrap/write driver.
/**
* Main wrap/write driver.
*/
void write(EngineArgs ea, MAC writeMAC, CipherBox writeCipher)
throws IOException {
/*
* sanity check to make sure someone didn't inadvertantly
* send us an impossible combination we don't know how
* to process.
*/
assert(contentType() == ct_application_data);
/*
* Have we set the MAC's yet? If not, we're not ready
* to process application data yet.
*/
if (writeMAC == MAC.NULL) {
return;
}
/*
* Don't bother to really write empty records. We went this
* far to drive the handshake machinery, for correctness; not
* writing empty records improves performance by cutting CPU
* time and network resource usage. Also, some protocol
* implementations are fragile and don't like to see empty
* records, so this increases robustness.
*/
if (ea.getAppRemaining() == 0) {
return;
}
/*
* By default, we counter chosen plaintext issues on CBC mode
* ciphersuites in SSLv3/TLS1.0 by sending one byte of application
* data in the first record of every payload, and the rest in
* subsequent record(s). Note that the issues have been solved in
* TLS 1.1 or later.
*
* It is not necessary to split the very first application record of
* a freshly negotiated TLS session, as there is no previous
* application data to guess. To improve compatibility, we will not
* split such records.
*
* Because of the compatibility, we'd better produce no more than
* SSLSession.getPacketBufferSize() net data for each wrap. As we
* need a one-byte record at first, the 2nd record size should be
* equal to or less than Record.maxDataSizeMinusOneByteRecord.
*
* This avoids issues in the outbound direction. For a full fix,
* the peer must have similar protections.
*/
int length;
if (engine.needToSplitPayload(writeCipher, protocolVersion)) {
write(ea, writeMAC, writeCipher, 0x01);
ea.resetLim(); // reset application data buffer limit
length = Math.min(ea.getAppRemaining(),
maxDataSizeMinusOneByteRecord);
} else {
length = Math.min(ea.getAppRemaining(), maxDataSize);
}
// Don't bother to really write empty records.
if (length > 0) {
write(ea, writeMAC, writeCipher, length);
}
return;
}
void write(EngineArgs ea, MAC writeMAC, CipherBox writeCipher,
int length) throws IOException {
/*
* Copy out existing buffer values.
*/
ByteBuffer dstBB = ea.netData;
int dstPos = dstBB.position();
int dstLim = dstBB.limit();
/*
* Where to put the data. Jump over the header.
*
* Don't need to worry about SSLv2 rewrites, if we're here,
* that's long since done.
*/
int dstData = dstPos + headerSize;
dstBB.position(dstData);
ea.gather(length);
/*
* "flip" but skip over header again, add MAC & encrypt
* addMAC will expand the limit to reflect the new
* data.
*/
dstBB.limit(dstBB.position());
dstBB.position(dstData);
addMAC(writeMAC, dstBB);
/*
* Encrypt may pad, so again the limit may have changed.
*/
dstBB.limit(dstBB.position());
dstBB.position(dstData);
encrypt(writeCipher, dstBB);
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);
}
int packetLength = dstBB.limit() - dstData;
/*
* Finish out the record header.
*/
dstBB.put(dstPos, contentType());
dstBB.put(dstPos + 1, protocolVersion.major);
dstBB.put(dstPos + 2, protocolVersion.minor);
dstBB.put(dstPos + 3, (byte)(packetLength >> 8));
dstBB.put(dstPos + 4, (byte)packetLength);
/*
* Position was already set by encrypt() above.
*/
dstBB.limit(dstLim);
return;
}
}