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); 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; /* * Different modes to handle the known IV weakness */ protected static final short ADS_MODE_1_Nsub1 = 0; // 1/n-1 record splitting protected static final short ADS_MODE_0_N = 1; // 0/n record splitting protected static final short ADS_MODE_0_N_FIRSTONLY = 2; // 0/n record splitting on first data fragment only /* * Queues for data from some protocols. */ private ByteQueue applicationDataQueue = new ByteQueue(0); private ByteQueue alertQueue = new ByteQueue(2); private ByteQueue handshakeQueue = new ByteQueue(0); // private ByteQueue heartbeatQueue = new ByteQueue(); /* * The Record Stream we use */ 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 handleChangeCipherSpecMessage() throws IOException { } protected abstract void handleHandshakeMessage(short type, ByteArrayInputStream buf) throws IOException; protected void handleWarningMessage(short description) 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) { // NOTE: Any close during the handshake should have raised an exception. 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 this was an initial handshake, we are now ready to send and receive application data. */ 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()) .setMasterSecret(this.securityParameters.getMasterSecret()) .setPeerCertificate(this.peerCertificate) .setPSKIdentity(this.securityParameters.getPSKIdentity()) .setSRPIdentity(this.securityParameters.getSRPIdentity()) // TODO Consider filtering extensions that aren't relevant to resumed sessions .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 { /* * Have a look at the protocol type, and add it to the correct queue. */ 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; } // case ContentType.heartbeat: // { // if (!appDataReady) // { // throw new TlsFatalAlert(AlertDescription.unexpected_message); // } // // TODO[RFC 6520] //// heartbeatQueue.addData(buf, offset, len); //// processHeartbeat(); // break; // } default: // Record type should already have been checked throw new TlsFatalAlert(AlertDescription.internal_error); } } private void processHandshakeQueue(ByteQueue queue) throws IOException { while (queue.available() >= 4) { /* * We need the first 4 bytes, they contain type and length of the message. */ 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; /* * Check if we have enough bytes in the buffer to read the full message. */ if (queue.available() < totalLength) { break; } checkReceivedChangeCipherSpec(connection_state == CS_END || type == HandshakeType.finished); /* * 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: { TlsContext ctx = getContext(); if (this.expected_verify_data == null && ctx.getSecurityParameters().getMasterSecret() != null) { this.expected_verify_data = createVerifyData(!ctx.isServer()); } // NB: Fall through to next case label } default: queue.copyTo(recordStream.getHandshakeHashUpdater(), totalLength); break; } queue.removeData(4); ByteArrayInputStream buf = queue.readFrom(length); /* * Now, parse the message. */ handleHandshakeMessage(type, buf); } } private void processApplicationDataQueue() { /* * There is nothing we need to do here. * * This function could be used for callbacks when application data arrives in the future. */ } private void processAlertQueue() throws IOException { while (alertQueue.available() >= 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. */ if (description == AlertDescription.close_notify) { if (!appDataReady) { throw new TlsFatalAlert(AlertDescription.handshake_failure); } 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.available() > 0 || handshakeQueue.available() > 0) { throw new TlsFatalAlert(AlertDescription.unexpected_message); } recordStream.receivedReadCipherSpec(); this.receivedChangeCipherSpec = true; handleChangeCipherSpecMessage(); } } protected int applicationDataAvailable() { return applicationDataQueue.available(); } /** * 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.available() == 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.available()); applicationDataQueue.removeData(buf, offset, len, 0); return len; } protected void safeCheckRecordHeader(byte[] recordHeader) throws IOException { try { recordStream.checkRecordHeader(recordHeader); } catch (TlsFatalAlert e) { this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to read record", e); throw e; } catch (IOException e) { this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e); throw e; } catch (RuntimeException e) { this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e); throw e; } } protected void safeReadRecord() throws IOException { try { if (!recordStream.readRecord()) { if (!appDataReady) { throw new TlsFatalAlert(AlertDescription.handshake_failure); } throw new TlsNoCloseNotifyException(); } } catch (TlsFatalAlert e) { if (!closed) { this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to read record", e); } throw e; } catch (IOException e) { if (!closed) { this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e); } throw e; } catch (RuntimeException e) { if (!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 (!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. * </p> * @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.appDataSplitEnabled) { /* * Protect against known IV attack! * * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE. */ switch (appDataSplitMode) { case ADS_MODE_0_N_FIRSTONLY: this.appDataSplitEnabled = false; // fall through intended! 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) { // 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 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 { // Fragment data according to the current fragment limit. int toWrite = Math.min(len - total, recordStream.getPlaintextLimit()); safeWriteRecord(ContentType.handshake, buf, off + total, toWrite); total += toWrite; } while (total < len); } /** * @return An OutputStream which can be used to send data. Only allowed in blocking mode. */ public OutputStream getOutputStream() { if (!blocking) { throw new IllegalStateException("Cannot use OutputStream in non-blocking mode! Use offerOutput() instead."); } return this.tlsOutputStream; } /** * @return An InputStream which can be used to read data. Only allowed in blocking mode. */ public InputStream getInputStream() { if (!blocking) { throw new IllegalStateException("Cannot use InputStream in non-blocking mode! Use offerInput() instead."); } return this.tlsInputStream; } /** * Should be called in non-blocking mode when the input data reaches EOF. */ 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(); } /** * Offer input from an arbitrary source. Only allowed in non-blocking mode.<br> * <br> * After this method returns, the input buffer is "owned" by this object. Other code * must not attempt to do anything with it.<br> * <br> * This method will decrypt and process all records that are fully available. * If only part of a record is available, the buffer will be retained until the * remainder of the record is offered.<br> * <br> * If any records containing application data were processed, the decrypted data * can be obtained using {@link #readInput(byte[], int, int)}. If any records * containing protocol data were processed, a response may have been generated. * You should always check to see if there is any available output after calling * this method by calling {@link #getAvailableOutputBytes()}. * @param input The input buffer to offer * @throws IOException If an error occurs while decrypting or processing a record */ 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); // loop while there are enough bytes to read the length of the next record 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) { // not enough bytes to read a whole record safeCheckRecordHeader(recordHeader); break; } safeReadRecord(); if (closed) { if (connection_state != CS_END) { // NOTE: Any close during the handshake should have raised an exception. throw new TlsFatalAlert(AlertDescription.internal_error); } break; } } } /** * Gets the amount of received application data. A call to {@link #readInput(byte[], int, int)} * is guaranteed to be able to return at least this much data.<br> * <br> * Only allowed in non-blocking mode. * @return The number of bytes of available application data */ public int getAvailableInputBytes() { if (blocking) { throw new IllegalStateException("Cannot use getAvailableInputBytes() in blocking mode! Use getInputStream().available() instead."); } return applicationDataAvailable(); } /** * Retrieves received application data. Use {@link #getAvailableInputBytes()} to check * how much application data is currently available. This method functions similarly to * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data * is available, nothing will be copied and zero will be returned.<br> * <br> * Only allowed in non-blocking mode. * @param buffer The buffer to hold the application data * @param offset The start offset in the buffer at which the data is written * @param length The maximum number of bytes to read * @return The total number of bytes copied to the buffer. May be less than the * length specified if the length was greater than the amount of available data. */ 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) { // readApplicationData() only throws if there is no data available, so this should never happen throw new RuntimeException(e.toString()); // early JDK fix. } } /** * Offer output from an arbitrary source. Only allowed in non-blocking mode.<br> * <br> * After this method returns, the specified section of the buffer will have been * processed. Use {@link #readOutput(byte[], int, int)} to get the bytes to * transmit to the other peer.<br> * <br> * This method must not be called until after the handshake is complete! Attempting * to call it before the handshake is complete will result in an exception. * @param buffer The buffer containing application data to encrypt * @param offset The offset at which to begin reading data * @param length The number of bytes of data to read * @throws IOException If an error occurs encrypting the data, or the handshake is not complete */ 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); } /** * Gets the amount of encrypted data available to be sent. A call to * {@link #readOutput(byte[], int, int)} is guaranteed to be able to return at * least this much data.<br> * <br> * Only allowed in non-blocking mode. * @return The number of bytes of available encrypted data */ public int getAvailableOutputBytes() { if (blocking) { throw new IllegalStateException("Cannot use getAvailableOutputBytes() in blocking mode! Use getOutputStream() instead."); } return outputBuffer.getBuffer().available(); } /** * Retrieves encrypted data to be sent. Use {@link #getAvailableOutputBytes()} to check * how much encrypted data is currently available. This method functions similarly to * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data * is available, nothing will be copied and zero will be returned.<br> * <br> * Only allowed in non-blocking mode. * @param buffer The buffer to hold the encrypted data * @param offset The start offset in the buffer at which the data is written * @param length The maximum number of bytes to read * @return The total number of bytes copied to the buffer. May be less than the * length specified if the length was greater than the amount of available data. */ 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; } /** * 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, Throwable 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 { if (expected_verify_data == null) { throw new TlsFatalAlert(AlertDescription.internal_error); } 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, Throwable 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.isEmpty()) { TlsContext context = getContext(); if (!context.isServer()) { ProtocolVersion serverVersion = getContext().getServerVersion(); if (serverVersion.isSSL()) { String errorMessage = serverVersion.toString() + " client didn't provide credentials"; raiseWarning(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); } /** * 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(); } 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 { /* * RFC 5746 4.5 SSLv3 clients that refuse renegotiation SHOULD use a fatal * handshake_failure alert. */ if (TlsUtils.isSSL(getContext())) { throw new TlsFatalAlert(AlertDescription.handshake_failure); } raiseWarning(AlertDescription.no_renegotiation, "Renegotiation not supported"); } /** * 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(); /* * NOTE: There are reports of servers that don't accept a zero-length extension as the last * one, so we write out any zero-length ones first as a best-effort workaround. */ 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); // 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; } } }