Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java')
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java1184
1 files changed, 1184 insertions, 0 deletions
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java
new file mode 100644
index 00000000..96aff625
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java
@@ -0,0 +1,1184 @@
+package org.spongycastle.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.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.prng.RandomGenerator;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.Integers;
+
+/**
+ * An implementation of all high level protocols in TLS 1.0/1.1.
+ */
+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);
+
+ private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
+
+ /*
+ * Our Connection states
+ */
+ 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;
+
+ /*
+ * Queues for data from some protocols.
+ */
+ private ByteQueue applicationDataQueue = new ByteQueue();
+ private ByteQueue alertQueue = new ByteQueue(2);
+ private ByteQueue handshakeQueue = new ByteQueue();
+// private ByteQueue heartbeatQueue = new ByteQueue();
+
+ /*
+ * The Record Stream we use
+ */
+ protected 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 splitApplicationDataRecords = true;
+ 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;
+
+ public TlsProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
+ {
+ this.recordStream = new RecordStream(this, input, output);
+ this.secureRandom = secureRandom;
+ }
+
+ protected abstract AbstractTlsContext getContext();
+
+ protected abstract TlsPeer getPeer();
+
+ protected void handleChangeCipherSpecMessage() throws IOException
+ {
+ }
+
+ protected abstract void handleHandshakeMessage(short type, byte[] buf)
+ throws IOException;
+
+ protected void handleWarningMessage(short description)
+ throws IOException
+ {
+ }
+
+ 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 completeHandshake()
+ throws IOException
+ {
+ try
+ {
+ /*
+ * We will now read data, until we have completed the handshake.
+ */
+ while (this.connection_state != CS_END)
+ {
+ if (this.closed)
+ {
+ // TODO What kind of exception/alert?
+ }
+
+ safeReadRecord();
+ }
+
+ this.recordStream.finaliseHandshake();
+
+ this.splitApplicationDataRecords = !TlsUtils.isTLSv11(getContext());
+
+ /*
+ * If this was an initial handshake, we are now ready to send and receive application data.
+ */
+ if (!appDataReady)
+ {
+ this.appDataReady = true;
+
+ 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.cipherSuite)
+ .setCompressionAlgorithm(this.securityParameters.compressionAlgorithm)
+ .setMasterSecret(this.securityParameters.masterSecret)
+ .setPeerCertificate(this.peerCertificate)
+ // TODO Consider filtering extensions that aren't relevant to resumed sessions
+ .setServerExtensions(this.serverExtensions)
+ .build();
+
+ this.tlsSession = new TlsSessionImpl(this.tlsSession.getSessionID(), this.sessionParameters);
+ }
+
+ getContext().setResumableSession(this.tlsSession);
+ }
+
+ getPeer().notifyHandshakeComplete();
+ }
+ finally
+ {
+ cleanupHandshake();
+ }
+ }
+
+ protected void processRecord(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 ContentType.alert:
+ {
+ alertQueue.addData(buf, offset, len);
+ processAlert();
+ break;
+ }
+ case ContentType.application_data:
+ {
+ if (!appDataReady)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ applicationDataQueue.addData(buf, offset, len);
+ processApplicationData();
+ break;
+ }
+ case ContentType.change_cipher_spec:
+ {
+ processChangeCipherSpec(buf, offset, len);
+ break;
+ }
+ case ContentType.handshake:
+ {
+ handshakeQueue.addData(buf, offset, len);
+ processHandshake();
+ break;
+ }
+ case ContentType.heartbeat:
+ {
+ if (!appDataReady)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ // TODO[RFC 6520]
+// heartbeatQueue.addData(buf, offset, len);
+// processHeartbeat();
+ }
+ 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 = 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 HandshakeType.hello_request:
+ break;
+ case HandshakeType.finished:
+ {
+ if (this.expected_verify_data == null)
+ {
+ this.expected_verify_data = createVerifyData(!getContext().isServer());
+ }
+
+ // NB: Fall through to next case label
+ }
+ default:
+ recordStream.updateHandshakeData(beginning, 0, 4);
+ recordStream.updateHandshakeData(buf, 0, len);
+ break;
+ }
+
+ /*
+ * Now, parse the message.
+ */
+ handleHandshakeMessage(type, buf);
+ read = true;
+ }
+ }
+ }
+ while (read);
+ }
+
+ 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 = alertQueue.removeData(2, 0);
+ short level = tmp[0];
+ short description = tmp[1];
+
+ getPeer().notifyAlertReceived(level, description);
+
+ if (level == AlertLevel.fatal)
+ {
+ /*
+ * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
+ * without proper close_notify messages with level equal to warning.
+ */
+ invalidateSession();
+
+ this.failedWithError = true;
+ this.closed = true;
+
+ recordStream.safeClose();
+
+ throw new IOException(TLS_ERROR_MESSAGE);
+ }
+ else
+ {
+
+ /*
+ * RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own
+ * and close down the connection immediately, discarding any pending writes.
+ */
+ // TODO Can close_notify be a fatal alert?
+ if (description == AlertDescription.close_notify)
+ {
+ handleClose(false);
+ }
+
+ /*
+ * If it is just a warning, we continue.
+ */
+ handleWarningMessage(description);
+ }
+ }
+ }
+
+ /**
+ * 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(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.size() > 0
+ || handshakeQueue.size() > 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ recordStream.receivedReadCipherSpec();
+
+ this.receivedChangeCipherSpec = true;
+
+ handleChangeCipherSpecMessage();
+ }
+ }
+
+ protected int applicationDataAvailable()
+ throws IOException
+ {
+ return applicationDataQueue.size();
+ }
+
+ /**
+ * 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
+ {
+ if (len < 1)
+ {
+ return 0;
+ }
+
+ 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;
+ }
+
+ safeReadRecord();
+ }
+
+ len = Math.min(len, applicationDataQueue.size());
+ applicationDataQueue.removeData(buf, offset, len, 0);
+ return len;
+ }
+
+ protected void safeReadRecord()
+ throws IOException
+ {
+ try
+ {
+ if (!recordStream.readRecord())
+ {
+ // TODO It would be nicer to allow graceful connection close if between records
+// this.failWithError(AlertLevel.warning, AlertDescription.close_notify);
+ throw new EOFException();
+ }
+ }
+ catch (TlsFatalAlert e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to read record", e);
+ }
+ throw e;
+ }
+ catch (IOException e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e);
+ }
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e);
+ }
+ throw e;
+ }
+ }
+
+ protected void safeWriteRecord(short type, byte[] buf, int offset, int len)
+ throws IOException
+ {
+ try
+ {
+ recordStream.writeRecord(type, buf, offset, len);
+ }
+ catch (TlsFatalAlert e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to write record", e);
+ }
+ throw e;
+ }
+ catch (IOException e)
+ {
+ if (!closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e);
+ }
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ if (!closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * 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");
+ }
+
+ while (len > 0)
+ {
+ /*
+ * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
+ * potentially useful as a traffic analysis countermeasure.
+ *
+ * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting.
+ */
+
+ if (this.splitApplicationDataRecords)
+ {
+ /*
+ * Protect against known IV attack!
+ *
+ * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
+ */
+ safeWriteRecord(ContentType.application_data, buf, offset, 1);
+ ++offset;
+ --len;
+ }
+
+ if (len > 0)
+ {
+ // Fragment data according to the current fragment limit.
+ int toWrite = Math.min(len, recordStream.getPlaintextLimit());
+ safeWriteRecord(ContentType.application_data, buf, offset, toWrite);
+ offset += toWrite;
+ len -= toWrite;
+ }
+ }
+ }
+
+ protected void writeHandshakeMessage(byte[] buf, int off, int len) throws IOException
+ {
+ while (len > 0)
+ {
+ // Fragment data according to the current fragment limit.
+ int toWrite = Math.min(len, recordStream.getPlaintextLimit());
+ safeWriteRecord(ContentType.handshake, buf, off, toWrite);
+ off += toWrite;
+ len -= toWrite;
+ }
+ }
+
+ /**
+ * @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. Can be used for normal closure too.
+ *
+ * @param alertLevel
+ * See {@link AlertLevel} for values.
+ * @param alertDescription
+ * See {@link AlertDescription} for values.
+ * @throws IOException
+ * If alert was fatal.
+ */
+ protected void failWithError(short alertLevel, short alertDescription, String message, Exception cause)
+ throws IOException
+ {
+ /*
+ * Check if the connection is still open.
+ */
+ if (!closed)
+ {
+ /*
+ * Prepare the message
+ */
+ this.closed = true;
+
+ if (alertLevel == AlertLevel.fatal)
+ {
+ /*
+ * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
+ * without proper close_notify messages with level equal to warning.
+ */
+ // TODO This isn't quite in the right place. Also, as of TLS 1.1 the above is obsolete.
+ invalidateSession();
+
+ this.failedWithError = true;
+ }
+ raiseAlert(alertLevel, alertDescription, message, cause);
+ recordStream.safeClose();
+ if (alertLevel != AlertLevel.fatal)
+ {
+ return;
+ }
+ }
+
+ throw new IOException(TLS_ERROR_MESSAGE);
+ }
+
+ 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
+ {
+ byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf);
+
+ assertEmpty(buf);
+
+ /*
+ * Compare both checksums.
+ */
+ if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data))
+ {
+ /*
+ * Wrong checksum in the finished message.
+ */
+ throw new TlsFatalAlert(AlertDescription.decrypt_error);
+ }
+ }
+
+ protected void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause)
+ throws IOException
+ {
+ getPeer().notifyAlertRaised(alertLevel, alertDescription, message, cause);
+
+ byte[] error = new byte[2];
+ error[0] = (byte)alertLevel;
+ error[1] = (byte)alertDescription;
+
+ safeWriteRecord(ContentType.alert, error, 0, 2);
+ }
+
+ protected void raiseWarning(short alertDescription, String message)
+ throws IOException
+ {
+ raiseAlert(AlertLevel.warning, alertDescription, message, null);
+ }
+
+ protected void sendCertificateMessage(Certificate certificate)
+ throws IOException
+ {
+ if (certificate == null)
+ {
+ certificate = Certificate.EMPTY_CHAIN;
+ }
+
+ if (certificate.getLength() == 0)
+ {
+ TlsContext context = getContext();
+ if (!context.isServer())
+ {
+ ProtocolVersion serverVersion = getContext().getServerVersion();
+ if (serverVersion.isSSL())
+ {
+ String message = serverVersion.toString() + " client didn't provide credentials";
+ raiseWarning(AlertDescription.no_certificate, message);
+ 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();
+
+ if (isServer)
+ {
+ return TlsUtils.calculateVerifyData(context, ExporterLabel.server_finished,
+ getCurrentPRFHash(getContext(), recordStream.getHandshakeHash(), TlsUtils.SSL_SERVER));
+ }
+
+ return TlsUtils.calculateVerifyData(context, ExporterLabel.client_finished,
+ getCurrentPRFHash(getContext(), recordStream.getHandshakeHash(), TlsUtils.SSL_CLIENT));
+ }
+
+ /**
+ * Closes this connection.
+ *
+ * @throws IOException If something goes wrong during closing.
+ */
+ public void close()
+ throws IOException
+ {
+ handleClose(true);
+ }
+
+ protected void handleClose(boolean user_canceled)
+ throws IOException
+ {
+ if (!closed)
+ {
+ if (user_canceled && !appDataReady)
+ {
+ raiseWarning(AlertDescription.user_canceled, "User canceled handshake");
+ }
+ this.failWithError(AlertLevel.warning, AlertDescription.close_notify, "Connection closed", null);
+ }
+ }
+
+ protected void flush()
+ throws IOException
+ {
+ recordStream.flush();
+ }
+
+ protected short processMaxFragmentLengthExtension(Hashtable clientExtensions, Hashtable serverExtensions, short alertDescription)
+ throws IOException
+ {
+ short maxFragmentLength = TlsExtensionsUtils.getMaxFragmentLengthExtension(serverExtensions);
+ if (maxFragmentLength >= 0 && !this.resumedSession)
+ {
+ if (maxFragmentLength != TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions))
+ {
+ throw new TlsFatalAlert(alertDescription);
+ }
+ }
+ return maxFragmentLength;
+ }
+
+ /**
+ * Make sure the InputStream 'buf' now empty. Fail otherwise.
+ *
+ * @param buf The InputStream to check.
+ * @throws IOException If 'buf' is not empty.
+ */
+ 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
+ {
+ // 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.
+ */
+ if (pre_master_secret != null)
+ {
+ Arrays.fill(pre_master_secret, (byte)0);
+ }
+ }
+ }
+
+ /**
+ * 'sender' only relevant to SSLv3
+ */
+ 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);
+
+ // Integer -> byte[]
+ Hashtable extensions = new Hashtable();
+
+ while (buf.available() > 0)
+ {
+ Integer extension_type = Integers.valueOf(TlsUtils.readUint16(buf));
+ byte[] extension_data = TlsUtils.readOpaque16(buf);
+
+ /*
+ * RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
+ */
+ 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();
+
+ Enumeration keys = extensions.keys();
+ while (keys.hasMoreElements())
+ {
+ Integer key = (Integer)keys.nextElement();
+ int extension_type = key.intValue();
+ byte[] extension_data = (byte[])extensions.get(key);
+
+ TlsUtils.checkUint16(extension_type);
+ TlsUtils.writeUint16(extension_type, buf);
+ TlsUtils.writeOpaque16(extension_data, buf);
+ }
+
+ byte[] extBytes = buf.toByteArray();
+
+ TlsUtils.writeOpaque16(extBytes, 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_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.TLS_DHE_PSK_WITH_AES_256_CCM:
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_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.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.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.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_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_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.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.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.TLS_PSK_WITH_AES_256_CCM:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+ 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.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_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);
+ // Reserve space for length
+ count += 3;
+ }
+
+ void writeToRecordStream() throws IOException
+ {
+ // Patch actual length back in
+ int length = count - 4;
+ TlsUtils.checkUint24(length);
+ TlsUtils.writeUint24(length, buf, 1);
+ writeHandshakeMessage(buf, 0, count);
+ buf = null;
+ }
+ }
+}