package org.bouncycastle.crypto.tls;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.prng.RandomGenerator;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Integers;
public abstract class TlsProtocol
{
protected static final Integer EXT_RenegotiationInfo = Integers.valueOf(ExtensionType.renegotiation_info);
protected static final Integer EXT_SessionTicket = Integers.valueOf(ExtensionType.session_ticket);
protected static final short CS_START = 0;
protected static final short CS_CLIENT_HELLO = 1;
protected static final short CS_SERVER_HELLO = 2;
protected static final short CS_SERVER_SUPPLEMENTAL_DATA = 3;
protected static final short CS_SERVER_CERTIFICATE = 4;
protected static final short CS_CERTIFICATE_STATUS = 5;
protected static final short CS_SERVER_KEY_EXCHANGE = 6;
protected static final short CS_CERTIFICATE_REQUEST = 7;
protected static final short CS_SERVER_HELLO_DONE = 8;
protected static final short CS_CLIENT_SUPPLEMENTAL_DATA = 9;
protected static final short CS_CLIENT_CERTIFICATE = 10;
protected static final short CS_CLIENT_KEY_EXCHANGE = 11;
protected static final short CS_CERTIFICATE_VERIFY = 12;
protected static final short CS_CLIENT_FINISHED = 13;
protected static final short CS_SERVER_SESSION_TICKET = 14;
protected static final short CS_SERVER_FINISHED = 15;
protected static final short CS_END = 16;
protected static final short ADS_MODE_1_Nsub1 = 0;
protected static final short ADS_MODE_0_N = 1;
protected static final short ADS_MODE_0_N_FIRSTONLY = 2;
private ByteQueue applicationDataQueue = new ByteQueue(0);
private ByteQueue alertQueue = new ByteQueue(2);
private ByteQueue handshakeQueue = new ByteQueue(0);
RecordStream recordStream;
protected SecureRandom secureRandom;
private TlsInputStream tlsInputStream = null;
private TlsOutputStream tlsOutputStream = null;
private volatile boolean closed = false;
private volatile boolean failedWithError = false;
private volatile boolean appDataReady = false;
private volatile boolean appDataSplitEnabled = true;
private volatile int appDataSplitMode = ADS_MODE_1_Nsub1;
private byte[] expected_verify_data = null;
protected TlsSession tlsSession = null;
protected SessionParameters sessionParameters = null;
protected SecurityParameters securityParameters = null;
protected Certificate peerCertificate = null;
protected int[] offeredCipherSuites = null;
protected short[] offeredCompressionMethods = null;
protected Hashtable clientExtensions = null;
protected Hashtable serverExtensions = null;
protected short connection_state = CS_START;
protected boolean resumedSession = false;
protected boolean receivedChangeCipherSpec = false;
protected boolean secure_renegotiation = false;
protected boolean allowCertificateStatus = false;
protected boolean expectSessionTicket = false;
protected boolean blocking;
protected ByteQueueInputStream inputBuffers;
protected ByteQueueOutputStream outputBuffer;
public TlsProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
{
this.blocking = true;
this.recordStream = new RecordStream(this, input, output);
this.secureRandom = secureRandom;
}
public TlsProtocol(SecureRandom secureRandom)
{
this.blocking = false;
this.inputBuffers = new ByteQueueInputStream();
this.outputBuffer = new ByteQueueOutputStream();
this.recordStream = new RecordStream(this, inputBuffers, outputBuffer);
this.secureRandom = secureRandom;
}
protected abstract TlsContext getContext();
abstract AbstractTlsContext getContextAdmin();
protected abstract TlsPeer getPeer();
protected void handleAlertMessage(short alertLevel, short alertDescription)
throws IOException
{
getPeer().notifyAlertReceived(alertLevel, alertDescription);
if (alertLevel == AlertLevel.warning)
{
handleAlertWarningMessage(alertDescription);
}
else
{
handleFailure();
throw new TlsFatalAlertReceived(alertDescription);
}
}
protected void handleAlertWarningMessage(short alertDescription)
throws IOException
{
if (alertDescription == AlertDescription.close_notify)
{
if (!appDataReady)
{
throw new TlsFatalAlert(AlertDescription.handshake_failure);
}
handleClose(false);
}
}
protected void handleChangeCipherSpecMessage() throws IOException
{
}
protected void handleClose(boolean user_canceled)
throws IOException
{
if (!closed)
{
this.closed = true;
if (user_canceled && !appDataReady)
{
raiseAlertWarning(AlertDescription.user_canceled, "User canceled handshake");
}
raiseAlertWarning(AlertDescription.close_notify, "Connection closed");
recordStream.safeClose();
if (!appDataReady)
{
cleanupHandshake();
}
}
}
protected void handleException(short alertDescription, String message, Throwable cause)
throws IOException
{
if (!closed)
{
raiseAlertFatal(alertDescription, message, cause);
handleFailure();
}
}
protected void handleFailure()
{
this.closed = true;
this.failedWithError = true;
invalidateSession();
recordStream.safeClose();
if (!appDataReady)
{
cleanupHandshake();
}
}
protected abstract void handleHandshakeMessage(short type, ByteArrayInputStream buf)
throws IOException;
protected void applyMaxFragmentLengthExtension()
throws IOException
{
if (securityParameters.maxFragmentLength >= 0)
{
if (!MaxFragmentLength.isValid(securityParameters.maxFragmentLength))
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
int plainTextLimit = 1 << (8 + securityParameters.maxFragmentLength);
recordStream.setPlaintextLimit(plainTextLimit);
}
}
protected void checkReceivedChangeCipherSpec(boolean expected)
throws IOException
{
if (expected != receivedChangeCipherSpec)
{
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
}
protected void cleanupHandshake()
{
if (this.expected_verify_data != null)
{
Arrays.fill(this.expected_verify_data, (byte)0);
this.expected_verify_data = null;
}
this.securityParameters.clear();
this.peerCertificate = null;
this.offeredCipherSuites = null;
this.offeredCompressionMethods = null;
this.clientExtensions = null;
this.serverExtensions = null;
this.resumedSession = false;
this.receivedChangeCipherSpec = false;
this.secure_renegotiation = false;
this.allowCertificateStatus = false;
this.expectSessionTicket = false;
}
protected void blockForHandshake() throws IOException
{
if (blocking)
{
while (this.connection_state != CS_END)
{
if (this.closed)
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
safeReadRecord();
}
}
}
protected void completeHandshake()
throws IOException
{
try
{
this.connection_state = CS_END;
this.alertQueue.shrink();
this.handshakeQueue.shrink();
this.recordStream.finaliseHandshake();
this.appDataSplitEnabled = !TlsUtils.isTLSv11(getContext());
if (!appDataReady)
{
this.appDataReady = true;
if (blocking)
{
this.tlsInputStream = new TlsInputStream(this);
this.tlsOutputStream = new TlsOutputStream(this);
}
}
if (this.tlsSession != null)
{
if (this.sessionParameters == null)
{
this.sessionParameters = new SessionParameters.Builder()
.setCipherSuite(this.securityParameters.getCipherSuite())
.setCompressionAlgorithm(this.securityParameters.getCompressionAlgorithm())
.setExtendedMasterSecret(securityParameters.isExtendedMasterSecret())
.setMasterSecret(this.securityParameters.getMasterSecret())
.setPeerCertificate(this.peerCertificate)
.setPSKIdentity(this.securityParameters.getPSKIdentity())
.setSRPIdentity(this.securityParameters.getSRPIdentity())
.setServerExtensions(this.serverExtensions)
.build();
this.tlsSession = new TlsSessionImpl(this.tlsSession.getSessionID(), this.sessionParameters);
}
getContextAdmin().setResumableSession(this.tlsSession);
}
getPeer().notifyHandshakeComplete();
}
finally
{
cleanupHandshake();
}
}
protected void processRecord(short protocol, byte[] buf, int off, int len)
throws IOException
{
switch (protocol)
{
case ContentType.alert:
{
alertQueue.addData(buf, off, len);
processAlertQueue();
break;
}
case ContentType.application_data:
{
if (!appDataReady)
{
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
applicationDataQueue.addData(buf, off, len);
processApplicationDataQueue();
break;
}
case ContentType.change_cipher_spec:
{
processChangeCipherSpec(buf, off, len);
break;
}
case ContentType.handshake:
{
if (handshakeQueue.available() > 0)
{
handshakeQueue.addData(buf, off, len);
processHandshakeQueue(handshakeQueue);
}
else
{
ByteQueue tmpQueue = new ByteQueue(buf, off, len);
processHandshakeQueue(tmpQueue);
int remaining = tmpQueue.available();
if (remaining > 0)
{
handshakeQueue.addData(buf, off + len - remaining, remaining);
}
}
break;
}
default:
throw new TlsFatalAlert(AlertDescription.internal_error);
}
}
private void processHandshakeQueue(ByteQueue queue)
throws IOException
{
while (queue.available() >= 4)
{
byte[] beginning = new byte[4];
queue.read(beginning, 0, 4, 0);
short type = TlsUtils.readUint8(beginning, 0);
int length = TlsUtils.readUint24(beginning, 1);
int totalLength = 4 + length;
if (queue.available() < totalLength)
{
break;
}
if (HandshakeType.hello_request != type)
{
if (HandshakeType.finished == type)
{
checkReceivedChangeCipherSpec(true);
TlsContext ctx = getContext();
if (this.expected_verify_data == null
&& ctx.getSecurityParameters().getMasterSecret() != null)
{
this.expected_verify_data = createVerifyData(!ctx.isServer());
}
}
else
{
checkReceivedChangeCipherSpec(connection_state == CS_END);
}
queue.copyTo(recordStream.getHandshakeHashUpdater(), totalLength);
}
queue.removeData(4);
ByteArrayInputStream buf = queue.readFrom(length);
handleHandshakeMessage(type, buf);
}
}
private void processApplicationDataQueue()
{
}
private void processAlertQueue()
throws IOException
{
while (alertQueue.available() >= 2)
{
byte[] alert = alertQueue.removeData(2, 0);
short alertLevel = alert[0];
short alertDescription = alert[1];
handleAlertMessage(alertLevel, alertDescription);
}
}
private void processChangeCipherSpec(byte[] buf, int off, int len)
throws IOException
{
for (int i = 0; i < len; ++i)
{
short message = TlsUtils.readUint8(buf, off + i);
if (message != ChangeCipherSpec.change_cipher_spec)
{
throw new TlsFatalAlert(AlertDescription.decode_error);
}
if (this.receivedChangeCipherSpec
|| alertQueue.available() > 0
|| handshakeQueue.available() > 0)
{
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
recordStream.receivedReadCipherSpec();
this.receivedChangeCipherSpec = true;
handleChangeCipherSpecMessage();
}
}
protected int applicationDataAvailable()
{
return applicationDataQueue.available();
}
protected int readApplicationData(byte[] buf, int offset, int len)
throws IOException
{
if (len < 1)
{
return 0;
}
while (applicationDataQueue.available() == 0)
{
if (this.closed)
{
if (this.failedWithError)
{
throw new IOException("Cannot read application data on failed TLS connection");
}
if (!appDataReady)
{
throw new IllegalStateException("Cannot read application data until initial handshake completed.");
}
return -1;
}
safeReadRecord();
}
len = Math.min(len, applicationDataQueue.available());
applicationDataQueue.removeData(buf, offset, len, 0);
return len;
}
protected void (byte[] recordHeader)
throws IOException
{
try
{
recordStream.checkRecordHeader(recordHeader);
}
catch (TlsFatalAlert e)
{
handleException(e.getAlertDescription(), "Failed to read record", e);
throw e;
}
catch (IOException e)
{
handleException(AlertDescription.internal_error, "Failed to read record", e);
throw e;
}
catch (RuntimeException e)
{
handleException(AlertDescription.internal_error, "Failed to read record", e);
throw new TlsFatalAlert(AlertDescription.internal_error, e);
}
}
protected void safeReadRecord()
throws IOException
{
try
{
if (recordStream.readRecord())
{
return;
}
if (!appDataReady)
{
throw new TlsFatalAlert(AlertDescription.handshake_failure);
}
}
catch (TlsFatalAlertReceived e)
{
throw e;
}
catch (TlsFatalAlert e)
{
handleException(e.getAlertDescription(), "Failed to read record", e);
throw e;
}
catch (IOException e)
{
handleException(AlertDescription.internal_error, "Failed to read record", e);
throw e;
}
catch (RuntimeException e)
{
handleException(AlertDescription.internal_error, "Failed to read record", e);
throw new TlsFatalAlert(AlertDescription.internal_error, e);
}
handleFailure();
throw new TlsNoCloseNotifyException();
}
protected void safeWriteRecord(short type, byte[] buf, int offset, int len)
throws IOException
{
try
{
recordStream.writeRecord(type, buf, offset, len);
}
catch (TlsFatalAlert e)
{
handleException(e.getAlertDescription(), "Failed to write record", e);
throw e;
}
catch (IOException e)
{
handleException(AlertDescription.internal_error, "Failed to write record", e);
throw e;
}
catch (RuntimeException e)
{
handleException(AlertDescription.internal_error, "Failed to write record", e);
throw new TlsFatalAlert(AlertDescription.internal_error, e);
}
}
protected void writeData(byte[] buf, int offset, int len)
throws IOException
{
if (this.closed)
{
throw new IOException("Cannot write application data on closed/failed TLS connection");
}
while (len > 0)
{
if (this.appDataSplitEnabled)
{
switch (appDataSplitMode) {
case ADS_MODE_0_N_FIRSTONLY:
this.appDataSplitEnabled = false;
case ADS_MODE_0_N:
safeWriteRecord(ContentType.application_data, TlsUtils.EMPTY_BYTES, 0, 0);
break;
case ADS_MODE_1_Nsub1:
default:
safeWriteRecord(ContentType.application_data, buf, offset, 1);
++offset;
--len;
break;
}
}
if (len > 0)
{
int toWrite = Math.min(len, recordStream.getPlaintextLimit());
safeWriteRecord(ContentType.application_data, buf, offset, toWrite);
offset += toWrite;
len -= toWrite;
}
}
}
protected void setAppDataSplitMode(int appDataSplitMode) {
if (appDataSplitMode < ADS_MODE_1_Nsub1 ||
appDataSplitMode > ADS_MODE_0_N_FIRSTONLY)
{
throw new IllegalArgumentException("Illegal appDataSplitMode mode: " + appDataSplitMode);
}
this.appDataSplitMode = appDataSplitMode;
}
protected void writeHandshakeMessage(byte[] buf, int off, int len) throws IOException
{
if (len < 4)
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
short type = TlsUtils.readUint8(buf, off);
if (type != HandshakeType.hello_request)
{
recordStream.getHandshakeHashUpdater().write(buf, off, len);
}
int total = 0;
do
{
int toWrite = Math.min(len - total, recordStream.getPlaintextLimit());
safeWriteRecord(ContentType.handshake, buf, off + total, toWrite);
total += toWrite;
}
while (total < len);
}
public OutputStream getOutputStream()
{
if (!blocking)
{
throw new IllegalStateException("Cannot use OutputStream in non-blocking mode! Use offerOutput() instead.");
}
return this.tlsOutputStream;
}
public InputStream getInputStream()
{
if (!blocking)
{
throw new IllegalStateException("Cannot use InputStream in non-blocking mode! Use offerInput() instead.");
}
return this.tlsInputStream;
}
public void closeInput() throws IOException
{
if (blocking)
{
throw new IllegalStateException("Cannot use closeInput() in blocking mode!");
}
if (closed)
{
return;
}
if (inputBuffers.available() > 0)
{
throw new EOFException();
}
if (!appDataReady)
{
throw new TlsFatalAlert(AlertDescription.handshake_failure);
}
throw new TlsNoCloseNotifyException();
}
public void offerInput(byte[] input) throws IOException
{
if (blocking)
{
throw new IllegalStateException("Cannot use offerInput() in blocking mode! Use getInputStream() instead.");
}
if (closed)
{
throw new IOException("Connection is closed, cannot accept any more input");
}
inputBuffers.addBytes(input);
while (inputBuffers.available() >= RecordStream.TLS_HEADER_SIZE)
{
byte[] recordHeader = new byte[RecordStream.TLS_HEADER_SIZE];
inputBuffers.peek(recordHeader);
int totalLength = TlsUtils.readUint16(recordHeader, RecordStream.TLS_HEADER_LENGTH_OFFSET) + RecordStream.TLS_HEADER_SIZE;
if (inputBuffers.available() < totalLength)
{
safeCheckRecordHeader(recordHeader);
break;
}
safeReadRecord();
if (closed)
{
if (connection_state != CS_END)
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
break;
}
}
}
public int getAvailableInputBytes()
{
if (blocking)
{
throw new IllegalStateException("Cannot use getAvailableInputBytes() in blocking mode! Use getInputStream().available() instead.");
}
return applicationDataAvailable();
}
public int readInput(byte[] buffer, int offset, int length)
{
if (blocking)
{
throw new IllegalStateException("Cannot use readInput() in blocking mode! Use getInputStream() instead.");
}
try
{
return readApplicationData(buffer, offset, Math.min(length, applicationDataAvailable()));
}
catch (IOException e)
{
throw new RuntimeException(e.toString());
}
}
public void offerOutput(byte[] buffer, int offset, int length)
throws IOException
{
if (blocking)
{
throw new IllegalStateException("Cannot use offerOutput() in blocking mode! Use getOutputStream() instead.");
}
if (!appDataReady)
{
throw new IOException("Application data cannot be sent until the handshake is complete!");
}
writeData(buffer, offset, length);
}
public int getAvailableOutputBytes()
{
if (blocking)
{
throw new IllegalStateException("Cannot use getAvailableOutputBytes() in blocking mode! Use getOutputStream() instead.");
}
return outputBuffer.getBuffer().available();
}
public int readOutput(byte[] buffer, int offset, int length)
{
if (blocking)
{
throw new IllegalStateException("Cannot use readOutput() in blocking mode! Use getOutputStream() instead.");
}
int bytesToRead = Math.min(getAvailableOutputBytes(), length);
outputBuffer.getBuffer().removeData(buffer, offset, bytesToRead, 0);
return bytesToRead;
}
protected void invalidateSession()
{
if (this.sessionParameters != null)
{
this.sessionParameters.clear();
this.sessionParameters = null;
}
if (this.tlsSession != null)
{
this.tlsSession.invalidate();
this.tlsSession = null;
}
}
protected void processFinishedMessage(ByteArrayInputStream buf)
throws IOException
{
if (expected_verify_data == null)
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf);
assertEmpty(buf);
if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data))
{
throw new TlsFatalAlert(AlertDescription.decrypt_error);
}
}
protected void raiseAlertFatal(short alertDescription, String message, Throwable cause)
throws IOException
{
getPeer().notifyAlertRaised(AlertLevel.fatal, alertDescription, message, cause);
byte[] alert = new byte[]{ (byte)AlertLevel.fatal, (byte)alertDescription };
try
{
recordStream.writeRecord(ContentType.alert, alert, 0, 2);
}
catch (Exception e)
{
}
}
protected void raiseAlertWarning(short alertDescription, String message)
throws IOException
{
getPeer().notifyAlertRaised(AlertLevel.warning, alertDescription, message, null);
byte[] alert = new byte[]{ (byte)AlertLevel.warning, (byte)alertDescription };
safeWriteRecord(ContentType.alert, alert, 0, 2);
}
protected void sendCertificateMessage(Certificate certificate)
throws IOException
{
if (certificate == null)
{
certificate = Certificate.EMPTY_CHAIN;
}
if (certificate.isEmpty())
{
TlsContext context = getContext();
if (!context.isServer())
{
ProtocolVersion serverVersion = getContext().getServerVersion();
if (serverVersion.isSSL())
{
String errorMessage = serverVersion.toString() + " client didn't provide credentials";
raiseAlertWarning(AlertDescription.no_certificate, errorMessage);
return;
}
}
}
HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate);
certificate.encode(message);
message.writeToRecordStream();
}
protected void sendChangeCipherSpecMessage()
throws IOException
{
byte[] message = new byte[]{ 1 };
safeWriteRecord(ContentType.change_cipher_spec, message, 0, message.length);
recordStream.sentWriteCipherSpec();
}
protected void sendFinishedMessage()
throws IOException
{
byte[] verify_data = createVerifyData(getContext().isServer());
HandshakeMessage message = new HandshakeMessage(HandshakeType.finished, verify_data.length);
message.write(verify_data);
message.writeToRecordStream();
}
protected void sendSupplementalDataMessage(Vector supplementalData)
throws IOException
{
HandshakeMessage message = new HandshakeMessage(HandshakeType.supplemental_data);
writeSupplementalData(message, supplementalData);
message.writeToRecordStream();
}
protected byte[] createVerifyData(boolean isServer)
{
TlsContext context = getContext();
String asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished;
byte[] sslSender = isServer ? TlsUtils.SSL_SERVER : TlsUtils.SSL_CLIENT;
byte[] hash = getCurrentPRFHash(context, recordStream.getHandshakeHash(), sslSender);
return TlsUtils.calculateVerifyData(context, asciiLabel, hash);
}
public void close()
throws IOException
{
handleClose(true);
}
protected void flush()
throws IOException
{
recordStream.flush();
}
public boolean isClosed()
{
return closed;
}
protected short processMaxFragmentLengthExtension(Hashtable clientExtensions, Hashtable serverExtensions,
short alertDescription)
throws IOException
{
short maxFragmentLength = TlsExtensionsUtils.getMaxFragmentLengthExtension(serverExtensions);
if (maxFragmentLength >= 0)
{
if (!MaxFragmentLength.isValid(maxFragmentLength)
|| (!this.resumedSession && maxFragmentLength != TlsExtensionsUtils
.getMaxFragmentLengthExtension(clientExtensions)))
{
throw new TlsFatalAlert(alertDescription);
}
}
return maxFragmentLength;
}
protected void refuseRenegotiation() throws IOException
{
if (TlsUtils.isSSL(getContext()))
{
throw new TlsFatalAlert(AlertDescription.handshake_failure);
}
raiseAlertWarning(AlertDescription.no_renegotiation, "Renegotiation not supported");
}
protected static void assertEmpty(ByteArrayInputStream buf)
throws IOException
{
if (buf.available() > 0)
{
throw new TlsFatalAlert(AlertDescription.decode_error);
}
}
protected static byte[] createRandomBlock(boolean useGMTUnixTime, RandomGenerator randomGenerator)
{
byte[] result = new byte[32];
randomGenerator.nextBytes(result);
if (useGMTUnixTime)
{
TlsUtils.writeGMTUnixTime(result, 0);
}
return result;
}
protected static byte[] createRenegotiationInfo(byte[] renegotiated_connection)
throws IOException
{
return TlsUtils.encodeOpaque8(renegotiated_connection);
}
protected static void establishMasterSecret(TlsContext context, TlsKeyExchange keyExchange)
throws IOException
{
byte[] pre_master_secret = keyExchange.generatePremasterSecret();
try
{
context.getSecurityParameters().masterSecret = TlsUtils.calculateMasterSecret(context, pre_master_secret);
}
finally
{
if (pre_master_secret != null)
{
Arrays.fill(pre_master_secret, (byte)0);
}
}
}
protected static byte[] getCurrentPRFHash(TlsContext context, TlsHandshakeHash handshakeHash, byte[] sslSender)
{
Digest d = handshakeHash.forkPRFHash();
if (sslSender != null && TlsUtils.isSSL(context))
{
d.update(sslSender, 0, sslSender.length);
}
byte[] bs = new byte[d.getDigestSize()];
d.doFinal(bs, 0);
return bs;
}
protected static Hashtable readExtensions(ByteArrayInputStream input)
throws IOException
{
if (input.available() < 1)
{
return null;
}
byte[] extBytes = TlsUtils.readOpaque16(input);
assertEmpty(input);
ByteArrayInputStream buf = new ByteArrayInputStream(extBytes);
Hashtable extensions = new Hashtable();
while (buf.available() > 0)
{
Integer extension_type = Integers.valueOf(TlsUtils.readUint16(buf));
byte[] extension_data = TlsUtils.readOpaque16(buf);
if (null != extensions.put(extension_type, extension_data))
{
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
}
return extensions;
}
protected static Vector readSupplementalDataMessage(ByteArrayInputStream input)
throws IOException
{
byte[] supp_data = TlsUtils.readOpaque24(input);
assertEmpty(input);
ByteArrayInputStream buf = new ByteArrayInputStream(supp_data);
Vector supplementalData = new Vector();
while (buf.available() > 0)
{
int supp_data_type = TlsUtils.readUint16(buf);
byte[] data = TlsUtils.readOpaque16(buf);
supplementalData.addElement(new SupplementalDataEntry(supp_data_type, data));
}
return supplementalData;
}
protected static void writeExtensions(OutputStream output, Hashtable extensions)
throws IOException
{
ByteArrayOutputStream buf = new ByteArrayOutputStream();
writeSelectedExtensions(buf, extensions, true);
writeSelectedExtensions(buf, extensions, false);
byte[] extBytes = buf.toByteArray();
TlsUtils.writeOpaque16(extBytes, output);
}
protected static void writeSelectedExtensions(OutputStream output, Hashtable extensions, boolean selectEmpty)
throws IOException
{
Enumeration keys = extensions.keys();
while (keys.hasMoreElements())
{
Integer key = (Integer)keys.nextElement();
int extension_type = key.intValue();
byte[] extension_data = (byte[])extensions.get(key);
if (selectEmpty == (extension_data.length == 0))
{
TlsUtils.checkUint16(extension_type);
TlsUtils.writeUint16(extension_type, output);
TlsUtils.writeOpaque16(extension_data, output);
}
}
}
protected static void writeSupplementalData(OutputStream output, Vector supplementalData)
throws IOException
{
ByteArrayOutputStream buf = new ByteArrayOutputStream();
for (int i = 0; i < supplementalData.size(); ++i)
{
SupplementalDataEntry entry = (SupplementalDataEntry)supplementalData.elementAt(i);
int supp_data_type = entry.getDataType();
TlsUtils.checkUint16(supp_data_type);
TlsUtils.writeUint16(supp_data_type, buf);
TlsUtils.writeOpaque16(entry.getData(), buf);
}
byte[] supp_data = buf.toByteArray();
TlsUtils.writeOpaque24(supp_data, output);
}
protected static int getPRFAlgorithm(TlsContext context, int ciphersuite) throws IOException
{
boolean isTLSv12 = TlsUtils.isTLSv12(context);
switch (ciphersuite)
{
case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB:
case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB:
case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB:
case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB:
case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB:
case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB:
case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB:
case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
{
if (isTLSv12)
{
return PRFAlgorithm.tls_prf_sha256;
}
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
{
if (isTLSv12)
{
return PRFAlgorithm.tls_prf_sha384;
}
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
{
if (isTLSv12)
{
return PRFAlgorithm.tls_prf_sha384;
}
return PRFAlgorithm.tls_prf_legacy;
}
default:
{
if (isTLSv12)
{
return PRFAlgorithm.tls_prf_sha256;
}
return PRFAlgorithm.tls_prf_legacy;
}
}
}
class HandshakeMessage extends ByteArrayOutputStream
{
HandshakeMessage(short handshakeType) throws IOException
{
this(handshakeType, 60);
}
HandshakeMessage(short handshakeType, int length) throws IOException
{
super(length + 4);
TlsUtils.writeUint8(handshakeType, this);
count += 3;
}
void writeToRecordStream() throws IOException
{
int length = count - 4;
TlsUtils.checkUint24(length);
TlsUtils.writeUint24(length, buf, 1);
writeHandshakeMessage(buf, 0, count);
buf = null;
}
}
}