package net.jradius.tls;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.crypto.prng.ThreadedSeedGenerator;
/**
* An implementation of all high level protocols in TLS 1.0.
*/
public class TlsProtocolHandler
{
// private static final int EXT_RenegotiationInfo = 0xFF01;
private static final int TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF;
private static final short RL_CHANGE_CIPHER_SPEC = 20;
private static final short RL_ALERT = 21;
private static final short RL_HANDSHAKE = 22;
private static final short RL_APPLICATION_DATA = 23;
/*
* hello_request(0), client_hello(1), server_hello(2), certificate(11),
* server_key_exchange (12), certificate_request(13), server_hello_done(14),
* certificate_verify(15), client_key_exchange(16), finished(20), (255)
*/
private static final short HP_HELLO_REQUEST = 0;
private static final short HP_CLIENT_HELLO = 1;
private static final short HP_SERVER_HELLO = 2;
private static final short HP_CERTIFICATE = 11;
private static final short HP_SERVER_KEY_EXCHANGE = 12;
private static final short HP_CERTIFICATE_REQUEST = 13;
private static final short HP_SERVER_HELLO_DONE = 14;
private static final short HP_CERTIFICATE_VERIFY = 15;
private static final short HP_CLIENT_KEY_EXCHANGE = 16;
private static final short HP_FINISHED = 20;
/*
* Our Connection states
*/
private static final short CS_CLIENT_HELLO_SEND = 1;
private static final short CS_SERVER_HELLO_RECEIVED = 2;
private static final short CS_SERVER_CERTIFICATE_RECEIVED = 3;
private static final short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4;
private static final short CS_CERTIFICATE_REQUEST_RECEIVED = 5;
private static final short CS_SERVER_HELLO_DONE_RECEIVED = 6;
private static final short CS_CLIENT_KEY_EXCHANGE_SEND = 7;
private static final short CS_CERTIFICATE_VERIFY_SEND = 8;
private static final short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 9;
private static final short CS_CLIENT_FINISHED_SEND = 10;
private static final short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 11;
public static final short CS_DONE = 12;
/*
* AlertLevel enum (255)
*/
// RFC 2246
protected static final short AL_warning = 1;
protected static final short AL_fatal = 2;
/*
* AlertDescription enum (255)
*/
// RFC 2246
protected static final short AP_close_notify = 0;
protected static final short AP_unexpected_message = 10;
protected static final short AP_bad_record_mac = 20;
protected static final short AP_decryption_failed = 21;
protected static final short AP_record_overflow = 22;
protected static final short AP_decompression_failure = 30;
protected static final short AP_handshake_failure = 40;
protected static final short AP_bad_certificate = 42;
protected static final short AP_unsupported_certificate = 43;
protected static final short AP_certificate_revoked = 44;
protected static final short AP_certificate_expired = 45;
protected static final short AP_certificate_unknown = 46;
protected static final short AP_illegal_parameter = 47;
protected static final short AP_unknown_ca = 48;
protected static final short AP_access_denied = 49;
protected static final short AP_decode_error = 50;
protected static final short AP_decrypt_error = 51;
protected static final short AP_export_restriction = 60;
protected static final short AP_protocol_version = 70;
protected static final short AP_insufficient_security = 71;
protected static final short AP_internal_error = 80;
protected static final short AP_user_canceled = 90;
protected static final short AP_no_renegotiation = 100;
// RFC 4279
protected static final short AP_unknown_psk_identity = 115;
private static final byte[] emptybuf = new byte[0];
private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
/*
* Queues for data from some protocols.
*/
private ByteQueue applicationDataQueue = new ByteQueue();
private ByteQueue changeCipherSpecQueue = new ByteQueue();
private ByteQueue alertQueue = new ByteQueue();
private ByteQueue handshakeQueue = new ByteQueue();
/*
* The Record Stream we use
*/
private RecordStream rs;
private SecureRandom random;
private TlsInputStream tlsInputStream = null;
private TlsOutputStream tlsOutputStream = null;
private boolean closed = false;
private boolean failedWithError = false;
private boolean appDataReady = false;
private boolean extendedClientHello;
private SecurityParameters securityParameters = null;
private TlsClient tlsClient = null;
private int[] offeredCipherSuites = null;
private TlsKeyExchange keyExchange = null;
private short connection_state = 0;
private KeyManager[] keyManagers = null;
private TrustManager[] trustManagers = null;
private boolean isSendCertificate = false;
private static SecureRandom createSecureRandom()
{
/*
* We use our threaded seed generator to generate a good random seed. If the user
* has a better random seed, he should use the constructor with a SecureRandom.
*/
ThreadedSeedGenerator tsg = new ThreadedSeedGenerator();
SecureRandom random = new SecureRandom();
/*
* Hopefully, 20 bytes in fast mode are good enough.
*/
random.setSeed(tsg.generateSeed(20, true));
return random;
}
public TlsProtocolHandler(InputStream is, OutputStream os)
{
this(is, os, createSecureRandom());
}
public TlsProtocolHandler(InputStream is, OutputStream os, SecureRandom sr)
{
this.rs = new RecordStream(this, is, os);
this.random = sr;
}
public TlsProtocolHandler()
{
this.rs = new RecordStream(this);
this.random = createSecureRandom();
}
SecureRandom getRandom()
{
return random;
}
public void setSendCertificate(boolean b)
{
this.isSendCertificate = b;
}
protected void processData(short protocol, byte[] buf, int offset, int len) throws IOException
{
/*
* Have a look at the protocol type, and add it to the correct queue.
*/
switch (protocol)
{
case RL_CHANGE_CIPHER_SPEC:
changeCipherSpecQueue.addData(buf, offset, len);
processChangeCipherSpec();
break;
case RL_ALERT:
alertQueue.addData(buf, offset, len);
processAlert();
break;
case RL_HANDSHAKE:
handshakeQueue.addData(buf, offset, len);
processHandshake();
break;
case RL_APPLICATION_DATA:
if (!appDataReady)
{
this.failWithError(AL_fatal, AP_unexpected_message);
}
applicationDataQueue.addData(buf, offset, len);
processApplicationData();
break;
default:
/*
* Uh, we don't know this protocol.
*
* RFC2246 defines on page 13, that we should ignore this.
*/
}
}
private void processHandshake() throws IOException
{
boolean read;
do
{
read = false;
/*
* We need the first 4 bytes, they contain type and length of the message.
*/
if (handshakeQueue.size() >= 4)
{
byte[] beginning = new byte[4];
handshakeQueue.read(beginning, 0, 4, 0);
ByteArrayInputStream bis = new ByteArrayInputStream(beginning);
short type = TlsUtils.readUint8(bis);
int len = TlsUtils.readUint24(bis);
/*
* Check if we have enough bytes in the buffer to read the full message.
*/
if (handshakeQueue.size() >= (len + 4))
{
/*
* Read the message.
*/
byte[] buf = new byte[len];
handshakeQueue.read(buf, 0, len, 4);
handshakeQueue.removeData(len + 4);
/*
* RFC 2246 7.4.9. "The value handshake_messages includes all
* handshake messages starting at client hello up to, but not
* including, this finished message. [..] Note: [Also,] Hello Request
* messages are omitted from handshake hashes."
*/
switch (type)
{
case HP_HELLO_REQUEST:
case HP_FINISHED:
break;
default:
rs.updateHandshakeData(beginning, 0, 4);
rs.updateHandshakeData(buf, 0, len);
break;
}
/*
* Now, parse the message.
*/
processHandshakeMessage(type, buf);
read = true;
}
}
}
while (read);
}
private void processHandshakeMessage(short type, byte[] buf) throws IOException
{
ByteArrayInputStream is = new ByteArrayInputStream(buf);
switch (type)
{
case HP_CERTIFICATE:
{
switch (connection_state)
{
case CS_SERVER_HELLO_RECEIVED:
{
// Parse the Certificate message and send to cipher suite
Certificate serverCertificate = Certificate.parse(is);
assertEmpty(is);
this.keyExchange.processServerCertificate(serverCertificate);
break;
}
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
connection_state = CS_SERVER_CERTIFICATE_RECEIVED;
break;
}
case HP_FINISHED:
switch (connection_state)
{
case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED:
/*
* Read the checksum from the finished message, it has always 12
* bytes.
*/
byte[] serverVerifyData = new byte[12];
TlsUtils.readFully(serverVerifyData, is);
assertEmpty(is);
/*
* Calculate our own checksum.
*/
byte[] expectedServerVerifyData = TlsUtils.PRF(
securityParameters.masterSecret, "server finished",
rs.getCurrentHash(), 12);
/*
* Compare both checksums.
*/
if (!Arrays.constantTimeAreEqual(expectedServerVerifyData, serverVerifyData))
{
/*
* Wrong checksum in the finished message.
*/
this.failWithError(AL_fatal, AP_handshake_failure);
}
connection_state = CS_DONE;
/*
* We are now ready to receive application data.
*/
this.appDataReady = true;
break;
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
break;
case HP_SERVER_HELLO:
switch (connection_state)
{
case CS_CLIENT_HELLO_SEND:
/*
* Read the server hello message
*/
TlsUtils.checkVersion(is, this);
/*
* Read the server random
*/
securityParameters.serverRandom = new byte[32];
TlsUtils.readFully(securityParameters.serverRandom, is);
byte[] sessionID = TlsUtils.readOpaque8(is);
if (sessionID.length > 32)
{
this.failWithError(TlsProtocolHandler.AL_fatal,
TlsProtocolHandler.AP_illegal_parameter);
}
this.tlsClient.notifySessionID(sessionID);
/*
* Find out which ciphersuite the server has chosen and check that
* it was one of the offered ones.
*/
int selectedCipherSuite = TlsUtils.readUint16(is);
if (!wasCipherSuiteOffered(selectedCipherSuite))
{
this.failWithError(TlsProtocolHandler.AL_fatal,
TlsProtocolHandler.AP_illegal_parameter);
}
this.tlsClient.notifySelectedCipherSuite(selectedCipherSuite);
/*
* We support only the null compression which means no
* compression.
*/
short compressionMethod = TlsUtils.readUint8(is);
if (compressionMethod != 0)
{
this.failWithError(TlsProtocolHandler.AL_fatal,
TlsProtocolHandler.AP_illegal_parameter);
}
/*
* RFC4366 2.2 The extended server hello message format MAY be
* sent in place of the server hello message when the client has
* requested extended functionality via the extended client hello
* message specified in Section 2.1.
*/
if (extendedClientHello)
{
// Integer -> byte[]
Hashtable serverExtensions = new Hashtable();
if (is.available() > 0)
{
// Process extensions from extended server hello
byte[] extBytes = TlsUtils.readOpaque16(is);
ByteArrayInputStream ext = new ByteArrayInputStream(extBytes);
while (ext.available() > 0)
{
int extType = TlsUtils.readUint16(ext);
byte[] extValue = TlsUtils.readOpaque16(ext);
serverExtensions.put(new Integer(extType), extValue);
}
}
// TODO[RFC 5746] If renegotiation_info was sent in client hello, check here
tlsClient.processServerExtensions(serverExtensions);
}
assertEmpty(is);
this.keyExchange = tlsClient.createKeyExchange();
connection_state = CS_SERVER_HELLO_RECEIVED;
break;
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
break;
case HP_SERVER_HELLO_DONE:
switch (connection_state)
{
case CS_SERVER_CERTIFICATE_RECEIVED:
// There was no server key exchange message; check it's OK
this.keyExchange.skipServerKeyExchange();
// NB: Fall through to next case label
case CS_SERVER_KEY_EXCHANGE_RECEIVED:
case CS_CERTIFICATE_REQUEST_RECEIVED:
assertEmpty(is);
boolean isClientCertificateRequested = (connection_state == CS_CERTIFICATE_REQUEST_RECEIVED || isSendCertificate);
connection_state = CS_SERVER_HELLO_DONE_RECEIVED;
if (isClientCertificateRequested)
{
sendClientCertificate(tlsClient.getCertificate());
}
/*
* Send the client key exchange message, depending on the key
* exchange we are using in our ciphersuite.
*/
sendClientKeyExchange(this.keyExchange.generateClientKeyExchange());
connection_state = CS_CLIENT_KEY_EXCHANGE_SEND;
if (isClientCertificateRequested)
{
byte[] clientCertificateSignature = tlsClient.generateCertificateSignature(rs.getCurrentHash());
if (clientCertificateSignature != null)
{
sendCertificateVerify(clientCertificateSignature);
connection_state = CS_CERTIFICATE_VERIFY_SEND;
}
}
/*
* Now, we send change cipher state
*/
byte[] cmessage = new byte[1];
cmessage[0] = 1;
rs.writeMessage(RL_CHANGE_CIPHER_SPEC, cmessage, 0, cmessage.length);
connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;
/*
* Calculate the master_secret
*/
byte[] pms = this.keyExchange.generatePremasterSecret();
securityParameters.masterSecret = TlsUtils.PRF(pms, "master secret",
TlsUtils.concat(securityParameters.clientRandom,
securityParameters.serverRandom), 48);
// TODO Is there a way to ensure the data is really overwritten?
/*
* RFC 2246 8.1. "The pre_master_secret should be deleted from
* memory once the master_secret has been computed."
*/
Arrays.fill(pms, (byte)0);
/*
* Initialize our cipher suite
*/
rs.clientCipherSpecDecided(tlsClient.createCipher(securityParameters));
/*
* Send our finished message.
*/
byte[] clientVerifyData = TlsUtils.PRF(securityParameters.masterSecret,
"client finished", rs.getCurrentHash(), 12);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_FINISHED, bos);
TlsUtils.writeOpaque24(clientVerifyData, bos);
byte[] message = bos.toByteArray();
rs.writeMessage(RL_HANDSHAKE, message, 0, message.length);
this.connection_state = CS_CLIENT_FINISHED_SEND;
break;
default:
this.failWithError(AL_fatal, AP_handshake_failure);
}
break;
case HP_SERVER_KEY_EXCHANGE:
{
switch (connection_state)
{
case CS_SERVER_HELLO_RECEIVED:
// There was no server certificate message; check it's OK
this.keyExchange.skipServerCertificate();
// NB: Fall through to next case label
case CS_SERVER_CERTIFICATE_RECEIVED:
this.keyExchange.processServerKeyExchange(is, securityParameters);
assertEmpty(is);
break;
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED;
break;
}
case HP_CERTIFICATE_REQUEST:
{
switch (connection_state)
{
case CS_SERVER_CERTIFICATE_RECEIVED:
// There was no server key exchange message; check it's OK
this.keyExchange.skipServerKeyExchange();
// NB: Fall through to next case label
case CS_SERVER_KEY_EXCHANGE_RECEIVED:
{
byte[] types = TlsUtils.readOpaque8(is);
byte[] authorities = TlsUtils.readOpaque16(is);
assertEmpty(is);
ArrayList authorityDNs = new ArrayList();
ByteArrayInputStream bis = new ByteArrayInputStream(authorities);
while (bis.available() > 0)
{
byte[] dnBytes = TlsUtils.readOpaque16(bis);
authorityDNs.add(X509Name.getInstance(ASN1Object.fromByteArray(dnBytes)));
}
this.tlsClient.processServerCertificateRequest(types, authorityDNs);
break;
}
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED;
break;
}
case HP_HELLO_REQUEST:
/*
* RFC 2246 7.4.1.1 Hello request
* "This message will be ignored by the client if the client is currently
* negotiating a session. This message may be ignored by the client if it
* does not wish to renegotiate a session, or the client may, if it wishes,
* respond with a no_renegotiation alert."
*/
if (connection_state == CS_DONE)
{
// Renegotiation not supported yet
sendAlert(AL_warning, AP_no_renegotiation);
}
break;
case HP_CLIENT_KEY_EXCHANGE:
case HP_CERTIFICATE_VERIFY:
case HP_CLIENT_HELLO:
default:
// We do not support this!
this.failWithError(AL_fatal, AP_unexpected_message);
break;
}
}
private void processApplicationData()
{
/*
* There is nothing we need to do here.
*
* This function could be used for callbacks when application data arrives in the
* future.
*/
}
private void processAlert() throws IOException
{
while (alertQueue.size() >= 2)
{
/*
* An alert is always 2 bytes. Read the alert.
*/
byte[] tmp = new byte[2];
alertQueue.read(tmp, 0, 2, 0);
alertQueue.removeData(2);
short level = tmp[0];
short description = tmp[1];
if (level == AL_fatal)
{
/*
* This is a fatal error.
*/
this.failedWithError = true;
this.closed = true;
/*
* Now try to close the stream, ignore errors.
*/
try
{
rs.close();
}
catch (Exception e)
{
}
throw new IOException(TLS_ERROR_MESSAGE);
}
else
{
/*
* This is just a warning.
*/
if (description == AP_close_notify)
{
/*
* Close notify
*/
this.failWithError(AL_warning, AP_close_notify);
}
/*
* If it is just a warning, we continue.
*/
}
}
}
/**
* This method is called, when a change cipher spec message is received.
*
* @throws IOException If the message has an invalid content or the handshake is not
* in the correct state.
*/
private void processChangeCipherSpec() throws IOException
{
while (changeCipherSpecQueue.size() > 0)
{
/*
* A change cipher spec message is only one byte with the value 1.
*/
byte[] b = new byte[1];
changeCipherSpecQueue.read(b, 0, 1, 0);
changeCipherSpecQueue.removeData(1);
if (b[0] != 1)
{
/*
* This should never happen.
*/
this.failWithError(AL_fatal, AP_unexpected_message);
}
/*
* Check if we are in the correct connection state.
*/
if (this.connection_state != CS_CLIENT_FINISHED_SEND)
{
this.failWithError(AL_fatal, AP_handshake_failure);
}
rs.serverClientSpecReceived();
this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED;
}
}
private void sendClientCertificate(Certificate clientCert) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_CERTIFICATE, bos);
clientCert.encode(bos);
byte[] message = bos.toByteArray();
rs.writeMessage(RL_HANDSHAKE, message, 0, message.length);
}
private void sendClientKeyExchange(byte[] keData) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_CLIENT_KEY_EXCHANGE, bos);
if (keData == null)
{
TlsUtils.writeUint24(0, bos);
}
else
{
TlsUtils.writeUint24(keData.length + 2, bos);
TlsUtils.writeOpaque16(keData, bos);
}
byte[] message = bos.toByteArray();
rs.writeMessage(RL_HANDSHAKE, message, 0, message.length);
}
private void sendCertificateVerify(byte[] data) throws IOException
{
/*
* Send signature of handshake messages so far to prove we are the owner of the
* cert See RFC 2246 sections 4.7, 7.4.3 and 7.4.8
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_CERTIFICATE_VERIFY, bos);
TlsUtils.writeUint24(data.length + 2, bos);
TlsUtils.writeOpaque16(data, bos);
byte[] message = bos.toByteArray();
rs.writeMessage(RL_HANDSHAKE, message, 0, message.length);
}
/**
* Connects to the remote system.
* @param is
* @param out
*
* @param verifyer Will be used when a certificate is received to verify that this
* certificate is accepted by the client.
* @throws IOException If handshake was not successful.
*/
// TODO Deprecate
public void connect(ByteArrayInputStream is, ByteArrayOutputStream out, CertificateVerifyer verifyer) throws IOException
{
this.connect(is, out, new DefaultTlsClient(verifyer));
}
// public void connect(CertificateVerifyer verifyer, Certificate clientCertificate,
// AsymmetricKeyParameter clientPrivateKey) throws IOException
// {
// DefaultTlsClient client = new DefaultTlsClient(verifyer);
// client.enableClientAuthentication(clientCertificate, clientPrivateKey);
//
// this.connect(client);
// }
/**
* Connects to the remote system using client authentication
*
* @param verifyer Will be used when a certificate is received to verify that this
* certificate is accepted by the client.
* @param clientCertificate The client's certificate to be provided to the remote
* system
* @param clientPrivateKey The client's private key for the certificate to
* authenticate to the remote system (RSA or DSA)
* @throws IOException If handshake was not successful.
*/
public // TODO Make public
void connect(ByteArrayInputStream is, ByteArrayOutputStream out, TlsClient tlsClient) throws IOException
{
if (tlsClient == null)
{
throw new IllegalArgumentException("'tlsClient' cannot be null");
}
if (this.tlsClient != null)
{
throw new IllegalStateException("connect can only be called once");
}
rs.setInputStream(is);
rs.setOutputStream(out);
this.tlsClient = tlsClient;
this.tlsClient.init(this);
/*
* Send Client hello
*
* First, generate some random data.
*/
securityParameters = new SecurityParameters();
securityParameters.clientRandom = new byte[32];
random.nextBytes(securityParameters.clientRandom);
TlsUtils.writeGMTUnixTime(securityParameters.clientRandom, 0);
ByteArrayOutputStream os = new ByteArrayOutputStream();
TlsUtils.writeVersion(os);
os.write(securityParameters.clientRandom);
/*
* Length of Session id
*/
TlsUtils.writeUint8((short)0, os);
//TlsUtils.writeUint8((short)1, os);
/*
* Cipher suites
*/
this.offeredCipherSuites = this.tlsClient.getCipherSuites();
// Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV
TlsUtils.writeUint16(2 * (offeredCipherSuites.length/* + 1*/), os);
for (int i = 0; i < offeredCipherSuites.length; ++i)
{
TlsUtils.writeUint16(offeredCipherSuites[i], os);
}
// RFC 5746 3.3
// Note: If renegotiation added, remove this (and extra slot above)
//TlsUtils.writeUint16(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, os);
/*
* Compression methods, just the null method.
*/
byte[] compressionMethods = new byte[] { 0x00 };
TlsUtils.writeOpaque8(compressionMethods, os);
/*
* Extensions
*/
// Integer -> byte[]
Hashtable clientExtensions = this.tlsClient.generateClientExtensions();
// RFC 5746 3.4
// Note: If renegotiation is implemented, need to use this instead of TLS_EMPTY_RENEGOTIATION_INFO_SCSV
// {
// if (clientExtensions == null)
// {
// clientExtensions = new Hashtable();
// }
//
// clientExtensions.put(EXT_RenegotiationInfo, createRenegotiationInfo(emptybuf));
// }
this.extendedClientHello = clientExtensions != null && !clientExtensions.isEmpty();
if (extendedClientHello)
{
ByteArrayOutputStream ext = new ByteArrayOutputStream();
Enumeration keys = clientExtensions.keys();
while (keys.hasMoreElements())
{
Integer extType = (Integer)keys.nextElement();
byte[] extValue = (byte[])clientExtensions.get(extType);
TlsUtils.writeUint16(extType.intValue(), ext);
TlsUtils.writeOpaque16(extValue, ext);
}
TlsUtils.writeOpaque16(ext.toByteArray(), os);
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_CLIENT_HELLO, bos);
TlsUtils.writeUint24(os.size(), bos);
bos.write(os.toByteArray());
byte[] message = bos.toByteArray();
rs.writeMessage(RL_HANDSHAKE, message, 0, message.length);
connection_state = CS_CLIENT_HELLO_SEND;
/*
* We will now read data, until we have completed the handshake.
while (connection_state != CS_DONE)
{
// TODO Should we send fatal alerts in the event of an exception
// (see readApplicationData)
rs.readData();
}
this.tlsInputStream = new TlsInputStream(this);
this.tlsOutputStream = new TlsOutputStream(this);
*/
}
public void writeApplicationData(ByteArrayInputStream is, ByteArrayOutputStream os, byte[] b) throws IOException
{
/*
* We will now read data, until we have completed the handshake.
*/
rs.setInputStream(is);
rs.setOutputStream(os);
writeData(b, 0, b.length);
this.tlsInputStream = new TlsInputStream(this);
this.tlsOutputStream = new TlsOutputStream(this);
}
public byte[] readApplicationData(ByteArrayInputStream is, ByteArrayOutputStream os) throws IOException
{
/*
* We will now read data, until we have completed the handshake.
*/
rs.setInputStream(is);
rs.setOutputStream(os);
return readApplicationData();
}
protected byte[] readApplicationData() throws IOException
{
while (rs.hasMore())
{
/*
* We need to read some data.
*/
if (this.failedWithError)
{
/*
* Something went terribly wrong, we should throw an IOException
*/
throw new IOException(TLS_ERROR_MESSAGE);
}
if (this.closed)
{
/*
* Connection has been closed, there is no more data to read.
*/
return null;
}
try
{
rs.readData();
}
catch (IOException e)
{
if (!this.closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
catch (RuntimeException e)
{
e.printStackTrace();
if (!this.closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
}
int len = applicationDataQueue.size();
byte[] b = new byte[len];
applicationDataQueue.read(b, 0, len, 0);
applicationDataQueue.removeData(len);
return b;
}
public short updateConnectState(ByteArrayInputStream is, ByteArrayOutputStream os) throws IOException
{
rs.setInputStream(is);
rs.setOutputStream(os);
while (connection_state != CS_DONE && rs.hasMore())
{
rs.readData();
}
return connection_state;
}
/**
* Read data from the network. The method will return immediately, if there is still
* some data left in the buffer, or block until some application data has been read
* from the network.
*
* @param buf The buffer where the data will be copied to.
* @param offset The position where the data will be placed in the buffer.
* @param len The maximum number of bytes to read.
* @return The number of bytes read.
* @throws IOException If something goes wrong during reading data.
*/
protected int readApplicationData(byte[] buf, int offset, int len) throws IOException
{
while (applicationDataQueue.size() == 0)
{
/*
* We need to read some data.
*/
if (this.closed)
{
if (this.failedWithError)
{
/*
* Something went terribly wrong, we should throw an IOException
*/
throw new IOException(TLS_ERROR_MESSAGE);
}
/*
* Connection has been closed, there is no more data to read.
*/
return -1;
}
try
{
rs.readData();
}
catch (IOException e)
{
if (!this.closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
catch (RuntimeException e)
{
if (!this.closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
}
len = Math.min(len, applicationDataQueue.size());
applicationDataQueue.read(buf, offset, len, 0);
applicationDataQueue.removeData(len);
return len;
}
/**
* Send some application data to the remote system.
* <p/>
* The method will handle fragmentation internally.
*
* @param buf The buffer with the data.
* @param offset The position in the buffer where the data is placed.
* @param len The length of the data.
* @throws IOException If something goes wrong during sending.
*/
protected void writeData(byte[] buf, int offset, int len) throws IOException
{
if (this.closed)
{
if (this.failedWithError)
{
throw new IOException(TLS_ERROR_MESSAGE);
}
throw new IOException("Sorry, connection has been closed, you cannot write more data");
}
/*
* Protect against known IV attack!
*
* DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
*/
rs.writeMessage(RL_APPLICATION_DATA, emptybuf, 0, 0);
do
{
/*
* We are only allowed to write fragments up to 2^14 bytes.
*/
int toWrite = Math.min(len, 1 << 14);
try
{
rs.writeMessage(RL_APPLICATION_DATA, buf, offset, toWrite);
}
catch (IOException e)
{
if (!closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
catch (RuntimeException e)
{
if (!closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
offset += toWrite;
len -= toWrite;
}
while (len > 0);
}
/**
* @return An OutputStream which can be used to send data.
*/
public OutputStream getOutputStream()
{
return this.tlsOutputStream;
}
/**
* @return An InputStream which can be used to read data.
*/
public InputStream getInputStream()
{
return this.tlsInputStream;
}
/**
* Terminate this connection with an alert.
* <p/>
* Can be used for normal closure too.
*
* @param alertLevel The level of the alert, an be AL_fatal or AL_warning.
* @param alertDescription The exact alert message.
* @throws IOException If alert was fatal.
*/
protected void failWithError(short alertLevel, short alertDescription) throws IOException
{
/*
* Check if the connection is still open.
*/
if (!closed)
{
/*
* Prepare the message
*/
this.closed = true;
if (alertLevel == AL_fatal)
{
/*
* This is a fatal message.
*/
this.failedWithError = true;
}
sendAlert(alertLevel, alertDescription);
rs.close();
if (alertLevel == AL_fatal)
{
throw new IOException(TLS_ERROR_MESSAGE);
}
}
else
{
throw new IOException(TLS_ERROR_MESSAGE);
}
}
private void sendAlert(short alertLevel, short alertDescription) throws IOException
{
byte[] error = new byte[2];
error[0] = (byte)alertLevel;
error[1] = (byte)alertDescription;
rs.writeMessage(RL_ALERT, error, 0, 2);
}
/**
* Closes this connection.
*
* @throws IOException If something goes wrong during closing.
*/
public void close() throws IOException
{
if (!closed)
{
this.failWithError((short)1, (short)0);
}
}
/**
* Make sure the InputStream is now empty. Fail otherwise.
*
* @param is The InputStream to check.
* @throws IOException If is is not empty.
*/
protected void assertEmpty(ByteArrayInputStream is) throws IOException
{
if (is.available() > 0)
{
this.failWithError(AL_fatal, AP_decode_error);
}
}
protected void flush() throws IOException
{
rs.flush();
}
private boolean wasCipherSuiteOffered(int cipherSuite)
{
for (int i = 0; i < offeredCipherSuites.length; ++i)
{
if (offeredCipherSuites[i] == cipherSuite)
{
return true;
}
}
return false;
}
public void setKeyManagers(KeyManager[] keyManagers) {
this.keyManagers = keyManagers;
}
public void setTrustManagers(TrustManager[] trustManagers) {
this.trustManagers = trustManagers;
}
// private byte[] CreateRenegotiationInfo(byte[] renegotiated_connection) throws IOException
// {
// ByteArrayOutputStream buf = new ByteArrayOutputStream();
// TlsUtils.writeOpaque8(renegotiated_connection, buf);
// return buf.toByteArray();
// }
}