All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.tls.TlsProtocol Maven / Gradle / Ivy

Go to download

The Bouncy Castle Java APIs for the TLS, including a JSSE provider. The APIs are designed primarily to be used in conjunction with the BC FIPS provider. The APIs may also be used with other providers although if being used in a FIPS context it is the responsibility of the user to ensure that any other providers used are FIPS certified and used appropriately.

There is a newer version: 2.0.19
Show newest version
package org.bouncycastle.tls;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.bouncycastle.tls.crypto.TlsCrypto;
import org.bouncycastle.tls.crypto.TlsSecret;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Integers;

public abstract class TlsProtocol
    implements TlsCloseable
{
    protected static final Integer EXT_RenegotiationInfo = Integers.valueOf(ExtensionType.renegotiation_info);
    protected static final Integer EXT_SessionTicket = Integers.valueOf(ExtensionType.session_ticket);

    /*
     * Connection States.
     * 
     * NOTE: Redirection of handshake messages to TLS 1.3 handlers assumes CS_START, CS_CLIENT_HELLO
     * are lower than any of the other values.
     */
    protected static final short CS_START = 0;
    protected static final short CS_CLIENT_HELLO = 1;
    protected static final short CS_SERVER_HELLO_RETRY_REQUEST = 2;
    protected static final short CS_CLIENT_HELLO_RETRY = 3;
    protected static final short CS_SERVER_HELLO = 4;
    protected static final short CS_SERVER_ENCRYPTED_EXTENSIONS = 5;
    protected static final short CS_SERVER_SUPPLEMENTAL_DATA = 6;
    protected static final short CS_SERVER_CERTIFICATE = 7;
    protected static final short CS_SERVER_CERTIFICATE_STATUS = 8;
    protected static final short CS_SERVER_CERTIFICATE_VERIFY = 9;
    protected static final short CS_SERVER_KEY_EXCHANGE = 10;
    protected static final short CS_SERVER_CERTIFICATE_REQUEST = 11;
    protected static final short CS_SERVER_HELLO_DONE = 12;
    protected static final short CS_CLIENT_END_OF_EARLY_DATA = 13;
    protected static final short CS_CLIENT_SUPPLEMENTAL_DATA = 14;
    protected static final short CS_CLIENT_CERTIFICATE = 15;
    protected static final short CS_CLIENT_KEY_EXCHANGE = 16;
    protected static final short CS_CLIENT_CERTIFICATE_VERIFY = 17;
    protected static final short CS_CLIENT_FINISHED = 18;
    protected static final short CS_SERVER_SESSION_TICKET = 19;
    protected static final short CS_SERVER_FINISHED = 20;
    protected static final short CS_END = 21;

    protected boolean isLegacyConnectionState()
    {
        switch (connection_state)
        {
        case CS_START:
        case CS_CLIENT_HELLO:
        case CS_SERVER_HELLO:
        case CS_SERVER_SUPPLEMENTAL_DATA:
        case CS_SERVER_CERTIFICATE:
        case CS_SERVER_CERTIFICATE_STATUS:
        case CS_SERVER_KEY_EXCHANGE:
        case CS_SERVER_CERTIFICATE_REQUEST:
        case CS_SERVER_HELLO_DONE:
        case CS_CLIENT_SUPPLEMENTAL_DATA:
        case CS_CLIENT_CERTIFICATE:
        case CS_CLIENT_KEY_EXCHANGE:
        case CS_CLIENT_CERTIFICATE_VERIFY:
        case CS_CLIENT_FINISHED:
        case CS_SERVER_SESSION_TICKET:
        case CS_SERVER_FINISHED:
        case CS_END:
            return true;

        case CS_SERVER_HELLO_RETRY_REQUEST:
        case CS_CLIENT_HELLO_RETRY:
        case CS_SERVER_ENCRYPTED_EXTENSIONS:
        case CS_SERVER_CERTIFICATE_VERIFY:
        case CS_CLIENT_END_OF_EARLY_DATA:
        default:
            return false;
        }
    }

    protected boolean isTLSv13ConnectionState()
    {
        switch (connection_state)
        {
        case CS_START:
        case CS_CLIENT_HELLO:
        case CS_SERVER_HELLO_RETRY_REQUEST:
        case CS_CLIENT_HELLO_RETRY:
        case CS_SERVER_HELLO:
        case CS_SERVER_ENCRYPTED_EXTENSIONS:
        case CS_SERVER_CERTIFICATE_REQUEST:
        case CS_SERVER_CERTIFICATE:
        case CS_SERVER_CERTIFICATE_VERIFY:
        case CS_SERVER_FINISHED:
        case CS_CLIENT_END_OF_EARLY_DATA:
        case CS_CLIENT_CERTIFICATE:
        case CS_CLIENT_CERTIFICATE_VERIFY:
        case CS_CLIENT_FINISHED:
        case CS_END:
            return true;

        case CS_SERVER_SUPPLEMENTAL_DATA:
        case CS_SERVER_CERTIFICATE_STATUS:
        case CS_SERVER_KEY_EXCHANGE:
        case CS_SERVER_HELLO_DONE:
        case CS_CLIENT_SUPPLEMENTAL_DATA:
        case CS_CLIENT_KEY_EXCHANGE:
        case CS_SERVER_SESSION_TICKET:
        default:
            return false;
        }
    }

    /*
     * 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();

    final RecordStream recordStream;
    final Object recordWriteLock = new Object();

    private int maxHandshakeMessageSize = -1;

    TlsHandshakeHash handshakeHash;

    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 boolean keyUpdateEnabled = false;
//    private volatile boolean keyUpdatePendingReceive = false;
    private volatile boolean keyUpdatePendingSend = false;
    private volatile boolean resumableHandshake = false;
    private volatile int appDataSplitMode = ADS_MODE_1_Nsub1;

    protected TlsSession tlsSession = null;
    protected SessionParameters sessionParameters = null;
    protected TlsSecret sessionMasterSecret = null;

    protected byte[] retryCookie = null;
    protected int retryGroup = -1;
    protected Hashtable clientExtensions = null;
    protected Hashtable serverExtensions = null;

    protected short connection_state = CS_START;
    protected boolean selectedPSK13 = false;
    protected boolean receivedChangeCipherSpec = false;
    protected boolean expectSessionTicket = false;

    protected boolean blocking;
    protected ByteQueueInputStream inputBuffers;
    protected ByteQueueOutputStream outputBuffer;

    protected TlsProtocol()
    {
        this.blocking = false;
        this.inputBuffers = new ByteQueueInputStream();
        this.outputBuffer = new ByteQueueOutputStream();
        this.recordStream = new RecordStream(this, inputBuffers, outputBuffer);
    }

    protected TlsProtocol(InputStream input, OutputStream output)
    {
        this.blocking = true;
        this.recordStream = new RecordStream(this, input, output);
    }

//    public boolean renegotiate() throws IOException
//    {
//        TlsContext context = getContext();
//        if (null == context.getSecurityParametersConnection() || isClosed())
//        {
//            throw new IllegalStateException("No connection");
//        }
//        if (!appDataReady)
//        {
//            throw new IllegalStateException("Initial handshake in progress");
//        }
//        return null == context.getSecurityParametersHandshake() && CS_END == connection_state;
//    }

    public void resumeHandshake() throws IOException
    {
        if (!blocking)
        {
            throw new IllegalStateException("Cannot use resumeHandshake() in non-blocking mode!");
        }
        if (!isHandshaking())
        {
            throw new IllegalStateException("No handshake in progress");
        }

        blockForHandshake();
    }

    protected void closeConnection() throws IOException
    {
        recordStream.close();
    }

    protected abstract TlsContext getContext();

    abstract AbstractTlsContext getContextAdmin();

    protected abstract TlsPeer getPeer();

    protected int getRenegotiationPolicy()
    {
        return RenegotiationPolicy.DENY;
    }

    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
    {
        switch (alertDescription)
        {
        /*
         * 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.
         */
        case AlertDescription.close_notify:
        {
            if (!appDataReady)
            {
                throw new TlsFatalAlert(AlertDescription.handshake_failure);
            }
            handleClose(false);
            break;
        }
        case AlertDescription.no_certificate:
        {
            throw new TlsFatalAlert(AlertDescription.unexpected_message);
        }
        case AlertDescription.no_renegotiation:
        {
            // TODO[reneg] Give peer the option to tolerate this
            throw new TlsFatalAlert(AlertDescription.handshake_failure);
        }
        }
    }

    protected void handleChangeCipherSpecMessage() throws IOException
    {
    }

    protected void handleClose(boolean user_canceled)
        throws IOException
    {
        if (!closed)
        {
            this.closed = true;

            if (!appDataReady)
            {
                cleanupHandshake();

                if (user_canceled)
                {
                    raiseAlertWarning(AlertDescription.user_canceled, "User canceled handshake");
                }
            }

            raiseAlertWarning(AlertDescription.close_notify, "Connection closed");

            closeConnection();

            getPeer().notifyConnectionClosed();
        }
    }

    protected void handleException(short alertDescription, String message, Throwable e)
        throws IOException
    {
        if ((appDataReady || isResumableHandshake()) && (e instanceof InterruptedIOException))
        {
            return;
        }

        if (!closed)
        {
            raiseAlertFatal(alertDescription, message, e);

            handleFailure();
        }
    }

    protected void handleFailure() throws IOException
    {
        this.closed = true;
        this.failedWithError = true;

        /*
         * 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();

        if (!appDataReady)
        {
            cleanupHandshake();
        }

        closeConnection();

        getPeer().notifyConnectionClosed();
    }

    protected abstract void handleHandshakeMessage(short type, HandshakeMessageInput buf)
        throws IOException;

    protected boolean handleRenegotiation() throws IOException
    {
        int renegotiationPolicy = RenegotiationPolicy.DENY;

        /*
         * Never renegotiate without secure renegotiation and server certificate authentication. Also, per RFC
         * 7627 5.4, renegotiation MUST be disabled for session resumption without extended_master_secret.
         */
        {
            SecurityParameters securityParameters = getContext().getSecurityParametersConnection();
            if (null != securityParameters &&
                securityParameters.isSecureRenegotiation() &&
                (!securityParameters.isResumedSession() || securityParameters.isExtendedMasterSecret()))
            {
                Certificate serverCertificate = ConnectionEnd.server == securityParameters.getEntity()
                    ?   securityParameters.getLocalCertificate()
                    :   securityParameters.getPeerCertificate();

                if (null != serverCertificate && !serverCertificate.isEmpty())
                {
                    renegotiationPolicy = getRenegotiationPolicy();
                }
            }
        }

        switch (renegotiationPolicy)
        {
        case RenegotiationPolicy.ACCEPT:
        {
            beginHandshake(true);
            return true;
        }
        case RenegotiationPolicy.IGNORE:
        {
            return false;
        }
        case RenegotiationPolicy.DENY:
        default:
        {
            refuseRenegotiation();
            return false;
        }
        }
    }

    protected void applyMaxFragmentLengthExtension(short maxFragmentLength) throws IOException
    {
        if (maxFragmentLength >= 0)
        {
            if (!MaxFragmentLength.isValid(maxFragmentLength))
            {
                throw new TlsFatalAlert(AlertDescription.internal_error);
            }

            int plainTextLimit = 1 << (8 + maxFragmentLength);
            recordStream.setPlaintextLimit(plainTextLimit);
        }
    }

    protected void checkReceivedChangeCipherSpec(boolean expected)
        throws IOException
    {
        if (expected != receivedChangeCipherSpec)
        {
            throw new TlsFatalAlert(AlertDescription.unexpected_message);
        }
    }

    protected void blockForHandshake() throws IOException
    {
        while (this.connection_state != CS_END)
        {
            if (isClosed())
            {
                // NOTE: Any close during the handshake should have raised an exception.
                throw new TlsFatalAlert(AlertDescription.internal_error);
            }

            safeReadRecord();
        }
    }

    protected void beginHandshake(boolean renegotiation)
        throws IOException
    {
        AbstractTlsContext context = getContextAdmin(); 
        TlsPeer peer = getPeer();

        this.maxHandshakeMessageSize = Math.max(1024, peer.getMaxHandshakeMessageSize());

        this.handshakeHash = new DeferredHash(context);
        this.connection_state = CS_START;
        this.selectedPSK13 = false;

        context.handshakeBeginning(peer);

        SecurityParameters securityParameters = context.getSecurityParametersHandshake();
        if (renegotiation != securityParameters.isRenegotiating())
        {
            throw new TlsFatalAlert(AlertDescription.internal_error);
        }

        securityParameters.extendedPadding = peer.shouldUseExtendedPadding();
    }

    protected void cleanupHandshake()
    {
        TlsContext context = getContext();
        if (null != context)
        {
            SecurityParameters securityParameters = context.getSecurityParameters();
            if (null != securityParameters)
            {
                securityParameters.clear();
            }
        }

        this.tlsSession = null;
        this.sessionParameters = null;
        this.sessionMasterSecret = null;

        this.retryCookie = null;
        this.retryGroup = -1;
        this.clientExtensions = null;
        this.serverExtensions = null;

        this.selectedPSK13 = false;
        this.receivedChangeCipherSpec = false;
        this.expectSessionTicket = false;
    }

    protected void completeHandshake()
        throws IOException
    {
        try
        {
            AbstractTlsContext context = getContextAdmin();
            SecurityParameters securityParameters = context.getSecurityParametersHandshake();

            if (!context.isHandshaking() ||
                null == securityParameters.getLocalVerifyData() ||
                null == securityParameters.getPeerVerifyData())
            {
                throw new TlsFatalAlert(AlertDescription.internal_error);
            }

            this.recordStream.finaliseHandshake();
            this.connection_state = CS_END;

            // TODO Prefer to set to null, but would need guards elsewhere
            this.handshakeHash = new DeferredHash(context);

            this.alertQueue.shrink();
            this.handshakeQueue.shrink();

            ProtocolVersion negotiatedVersion = securityParameters.getNegotiatedVersion();

            this.appDataSplitEnabled = !TlsUtils.isTLSv11(negotiatedVersion);
            this.appDataReady = true;

            this.keyUpdateEnabled = TlsUtils.isTLSv13(negotiatedVersion);

            if (blocking)
            {
                this.tlsInputStream = new TlsInputStream(this);
                this.tlsOutputStream = new TlsOutputStream(this);
            }

            if (this.sessionParameters == null)
            {
                this.sessionMasterSecret = securityParameters.getMasterSecret();

                this.sessionParameters = new SessionParameters.Builder()
                    .setCipherSuite(securityParameters.getCipherSuite())
                    .setExtendedMasterSecret(securityParameters.isExtendedMasterSecret())
                    .setLocalCertificate(securityParameters.getLocalCertificate())
                    .setMasterSecret(context.getCrypto().adoptSecret(this.sessionMasterSecret))
                    .setNegotiatedVersion(securityParameters.getNegotiatedVersion())
                    .setPeerCertificate(securityParameters.getPeerCertificate())
                    .setPSKIdentity(securityParameters.getPSKIdentity())
                    .setSRPIdentity(securityParameters.getSRPIdentity())
                    // TODO Consider filtering extensions that aren't relevant to resumed sessions
                    .setServerExtensions(this.serverExtensions)
                    .build();

                this.tlsSession = TlsUtils.importSession(securityParameters.getSessionID(), this.sessionParameters);
            }
            else
            {
                securityParameters.localCertificate = sessionParameters.getLocalCertificate();
                securityParameters.peerCertificate = sessionParameters.getPeerCertificate();
                securityParameters.pskIdentity = sessionParameters.getPSKIdentity();
                securityParameters.srpIdentity = sessionParameters.getSRPIdentity();
            }

            context.handshakeComplete(getPeer(), this.tlsSession);
        }
        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, off, len);
////            processHeartbeatQueue();
//            break;
//        }
        default:
            throw new TlsFatalAlert(AlertDescription.unexpected_message);
        }
    }

    private void processHandshakeQueue(ByteQueue queue)
        throws IOException
    {
        /*
         * We need the first 4 bytes, they contain type and length of the message.
         */
        while (queue.available() >= 4)
        {
            int header = queue.readInt32();

            short type = (short)(header >>> 24);
            if (!HandshakeType.isRecognized(type))
            {
                throw new TlsFatalAlert(AlertDescription.unexpected_message,
                    "Handshake message of unrecognized type: " + type);
            }

            int length = header & 0x00FFFFFF;
            if (length > maxHandshakeMessageSize)
            {
                throw new TlsFatalAlert(AlertDescription.internal_error,
                    "Handshake message length exceeds the maximum: " + HandshakeType.getText(type) + ", " + length
                        + " > " + maxHandshakeMessageSize);
            }

            int totalLength = 4 + length;
            if (queue.available() < totalLength)
            {
                // Not enough bytes in the buffer to read the full message.
                break;
            }

            /*
             * Check ChangeCipherSpec status
             */
            switch (type)
            {
            case HandshakeType.hello_request:
                break;

            default:
            {
                ProtocolVersion negotiatedVersion = getContext().getServerVersion();
                if (null != negotiatedVersion && TlsUtils.isTLSv13(negotiatedVersion))
                {
                    break;
                }

                checkReceivedChangeCipherSpec(HandshakeType.finished == type);
                break;
            }
            }

            HandshakeMessageInput buf = queue.readHandshakeMessage(totalLength);

            switch (type)
            {
            /*
             * These message types aren't included in the transcript.
             */
            case HandshakeType.hello_request:
            case HandshakeType.key_update:
                break;

            /*
             * Not included in the transcript for (D)TLS 1.3+
             */
            case HandshakeType.new_session_ticket:
            {
                ProtocolVersion negotiatedVersion = getContext().getServerVersion();
                if (null != negotiatedVersion && !TlsUtils.isTLSv13(negotiatedVersion))
                {
                    buf.updateHash(handshakeHash);
                }

                break;
            }

            /*
             * These message types are deferred to the handler to explicitly update the transcript.
             */
            case HandshakeType.certificate_verify:
            case HandshakeType.client_hello:
            case HandshakeType.finished:
            case HandshakeType.server_hello:
                break;

            /*
             * For all others we automatically update the transcript immediately. 
             */
            default:
            {
                buf.updateHash(handshakeHash);
                break;
            }
            }

            buf.skip(4);

            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[] alert = alertQueue.removeData(2, 0);
            short alertLevel = alert[0];
            short alertDescription = alert[1];

            handleAlertMessage(alertLevel, alertDescription);
        }
    }

    /**
     * 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
    {
        ProtocolVersion negotiatedVersion = getContext().getServerVersion();
        if (null == negotiatedVersion || TlsUtils.isTLSv13(negotiatedVersion))
        {
            // See RFC 8446 D.4.
            throw new TlsFatalAlert(AlertDescription.unexpected_message);
        }

        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.notifyChangeCipherSpecReceived();

            this.receivedChangeCipherSpec = true;

            handleChangeCipherSpecMessage();
        }
    }

    public 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 off 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.
     */
    public int readApplicationData(byte[] buf, int off, int len)
        throws IOException
    {
        // TODO Use method once available in bc-fips-java
//        Streams.validateBufferArguments(buf, off, len);
        {
            if (buf == null)
            {
                throw new NullPointerException();
            }
            int available = buf.length - off;
            int remaining = available - len;
            if ((off | len | available | remaining) < 0)
            {
                throw new IndexOutOfBoundsException();
            }
        }

        if (!appDataReady)
        {
            throw new IllegalStateException("Cannot read application data until initial handshake completed.");
        }

        if (len < 1)
        {
            return 0;
        }

        while (applicationDataQueue.available() < 1)
        {
            if (this.closed)
            {
                if (this.failedWithError)
                {
                    throw new IOException("Cannot read application data on failed TLS connection");
                }
                return -1;
            }

            /*
             * NOTE: Only called more than once when empty records are received, so no special
             * InterruptedIOException handling is necessary.
             */
            safeReadRecord();
        }

        len = Math.min(len, applicationDataQueue.available());
        applicationDataQueue.removeData(buf, off, len, 0);
        return len;
    }

    protected RecordPreview safePreviewRecordHeader(byte[] recordHeader)
        throws IOException
    {
        try
        {
            return recordStream.previewRecordHeader(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);
            }

            if (!getPeer().requiresCloseNotify())
            {
                handleClose(false);
                return;
            }
        }
        catch (TlsFatalAlertReceived e)
        {
            // Connection failure already handled at source
            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 boolean safeReadFullRecord(byte[] input, int inputOff, int inputLen)
        throws IOException
    {
        try
        {
            return recordStream.readFullRecord(input, inputOff, inputLen);
        }
        catch (TlsFatalAlert e)
        {
            handleException(e.getAlertDescription(), "Failed to process record", e);
            throw e;
        }
        catch (IOException e)
        {
            handleException(AlertDescription.internal_error, "Failed to process record", e);
            throw e;
        }
        catch (RuntimeException e)
        {
            handleException(AlertDescription.internal_error, "Failed to process record", e);
            throw new TlsFatalAlert(AlertDescription.internal_error, e);
        }
    }

    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);
        }
    }

    /**
     * Write some application data. Fragmentation is handled internally. Usable in both blocking/non-blocking
     * modes.
*
* In blocking mode, the output will be automatically sent via the underlying transport. In non-blocking * mode, call {@link #readOutput(byte[], int, int)} to get the output bytes to send to the peer.
*
* This method must not be called until after the initial handshake is complete. Attempting to call it * earlier will result in an {@link IllegalStateException}. * * @param buf The buffer containing application data to send * @param off The offset at which the application data begins * @param len The number of bytes of application data * @throws IllegalStateException If called before the initial handshake has completed. * @throws IOException If connection is already closed, or for encryption or transport errors. */ public void writeApplicationData(byte[] buf, int off, int len) throws IOException { // TODO Use method once available in bc-fips-java // Streams.validateBufferArguments(buf, off, len); { if (buf == null) { throw new NullPointerException(); } int available = buf.length - off; int remaining = available - len; if ((off | len | available | remaining) < 0) { throw new IndexOutOfBoundsException(); } } if (!appDataReady) { throw new IllegalStateException("Cannot write application data until initial handshake completed."); } synchronized (recordWriteLock) { while (len > 0) { if (closed) { throw new IOException("Cannot write application data on closed/failed TLS connection"); } /* * 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 (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; // NB: Fall through to next case label } case ADS_MODE_0_N: { safeWriteRecord(ContentType.application_data, TlsUtils.EMPTY_BYTES, 0, 0); break; } case ADS_MODE_1_Nsub1: default: { if (len > 1) { safeWriteRecord(ContentType.application_data, buf, off, 1); ++off; --len; } break; } } } else if (keyUpdateEnabled) { if (keyUpdatePendingSend) { send13KeyUpdate(false); } else if (recordStream.needsKeyUpdate()) { send13KeyUpdate(true); } } // Fragment data according to the current fragment limit. int toWrite = Math.min(len, recordStream.getPlaintextLimit()); safeWriteRecord(ContentType.application_data, buf, off, toWrite); off += toWrite; len -= toWrite; } } } public int getAppDataSplitMode() { return appDataSplitMode; } public 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; } public boolean isResumableHandshake() { return resumableHandshake; } public void setResumableHandshake(boolean resumableHandshake) { this.resumableHandshake = resumableHandshake; } 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); switch (type) { /* * These message types aren't included in the transcript. */ case HandshakeType.hello_request: case HandshakeType.key_update: break; /* * Not included in the transcript for (D)TLS 1.3+ */ case HandshakeType.new_session_ticket: { ProtocolVersion negotiatedVersion = getContext().getServerVersion(); if (null != negotiatedVersion && !TlsUtils.isTLSv13(negotiatedVersion)) { handshakeHash.update(buf, off, len); } break; } /* * These message types are deferred to the writer to explicitly update the transcript. */ case HandshakeType.client_hello: break; /* * For all others we automatically update the transcript. */ default: { handshakeHash.update(buf, off, len); break; } } 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); } if (!getPeer().requiresCloseNotify()) { handleClose(false); return; } handleFailure(); throw new TlsNoCloseNotifyException(); } public RecordPreview previewInputRecord(byte[] recordHeader) throws IOException { if (blocking) { throw new IllegalStateException("Cannot use previewInputRecord() in blocking mode!"); } if (inputBuffers.available() != 0) { throw new IllegalStateException("Can only use previewInputRecord() for record-aligned input."); } if (closed) { throw new IOException("Connection is closed, cannot accept any more input"); } return safePreviewRecordHeader(recordHeader); } public int previewOutputRecord() { if (blocking) { throw new IllegalStateException("Cannot use previewOutputRecord() in blocking mode!"); } ByteQueue buffer = outputBuffer.getBuffer(); int available = buffer.available(); if (available < 1) { return 0; } if (available >= RecordFormat.FRAGMENT_OFFSET) { int length = buffer.readUint16(RecordFormat.LENGTH_OFFSET); int recordSize = RecordFormat.FRAGMENT_OFFSET + length; if (available >= recordSize) { return recordSize; } } throw new IllegalStateException("Can only use previewOutputRecord() for record-aligned output."); } public RecordPreview previewOutputRecord(int applicationDataSize) throws IOException { if (!appDataReady) { throw new IllegalStateException("Cannot use previewOutputRecord() until initial handshake completed."); } if (blocking) { throw new IllegalStateException("Cannot use previewOutputRecord() in blocking mode!"); } if (outputBuffer.getBuffer().available() != 0) { throw new IllegalStateException("Can only use previewOutputRecord() for record-aligned output."); } if (closed) { throw new IOException("Connection is closed, cannot produce any more output"); } if (applicationDataSize < 1) { return new RecordPreview(0, 0); } if (appDataSplitEnabled) { switch (appDataSplitMode) { case ADS_MODE_0_N_FIRSTONLY: case ADS_MODE_0_N: { RecordPreview a = recordStream.previewOutputRecord(0); RecordPreview b = recordStream.previewOutputRecord(applicationDataSize); return RecordPreview.combineAppData(a, b); } case ADS_MODE_1_Nsub1: default: { RecordPreview a = recordStream.previewOutputRecord(1); if (applicationDataSize > 1) { RecordPreview b = recordStream.previewOutputRecord(applicationDataSize - 1); a = RecordPreview.combineAppData(a, b); } return a; } } } else { RecordPreview a = recordStream.previewOutputRecord(applicationDataSize); if (keyUpdateEnabled && (keyUpdatePendingSend || recordStream.needsKeyUpdate())) { int keyUpdateLength = HandshakeMessageOutput.getLength(1); int recordSize = recordStream.previewOutputRecordSize(keyUpdateLength); a = RecordPreview.extendRecordSize(a, recordSize); } return a; } } /** * Equivalent to offerInput(input, 0, input.length) * @see TlsProtocol#offerInput(byte[], int, int) * @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 { offerInput(input, 0, input.length); } /** * Offer input from an arbitrary source. Only allowed in non-blocking mode.
*
* 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.
*
* 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 * @param inputOff The offset within the input buffer that input begins * @param inputLen The number of bytes of input being offered * @throws IOException If an error occurs while decrypting or processing a record */ public void offerInput(byte[] input, int inputOff, int inputLen) 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"); } // Fast path if the input is arriving one record at a time if (inputBuffers.available() == 0 && safeReadFullRecord(input, inputOff, inputLen)) { if (closed) { if (!appDataReady) { // NOTE: Any close during the handshake should have raised an exception. throw new TlsFatalAlert(AlertDescription.internal_error); } } return; } inputBuffers.addBytes(input, inputOff, inputLen); // loop while there are enough bytes to read the length of the next record while (inputBuffers.available() >= RecordFormat.FRAGMENT_OFFSET) { byte[] recordHeader = new byte[RecordFormat.FRAGMENT_OFFSET]; if (RecordFormat.FRAGMENT_OFFSET != inputBuffers.peek(recordHeader)) { throw new TlsFatalAlert(AlertDescription.internal_error); } RecordPreview preview = safePreviewRecordHeader(recordHeader); if (inputBuffers.available() < preview.getRecordSize()) { // not enough bytes to read a whole record break; } // NOTE: This is actually reading from inputBuffers, so InterruptedIOException shouldn't be possible safeReadRecord(); if (closed) { if (!appDataReady) { // NOTE: Any close during the handshake should have raised an exception. throw new TlsFatalAlert(AlertDescription.internal_error); } break; } } } public int getApplicationDataLimit() { return recordStream.getPlaintextLimit(); } /** * 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.
*
* 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.
*
* 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."); } length = Math.min(length, applicationDataQueue.available()); if (length < 1) { return 0; } applicationDataQueue.removeData(buffer, offset, length, 0); return length; } /** * Retrieves received application data into a {@link ByteBuffer}. 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.
*
* Only allowed in non-blocking mode. * * @param buffer The {@link ByteBuffer} to hold the application data * @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(ByteBuffer buffer, int length) { if (blocking) { throw new IllegalStateException("Cannot use readInput() in blocking mode! Use getInputStream() instead."); } length = Math.min(length, applicationDataQueue.available()); if (length < 1) { return 0; } applicationDataQueue.removeData(buffer, length, 0); return 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.
*
* 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.
*
* 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; } /** * 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.
*
* Only allowed in non-blocking mode. * @param buffer The {@link ByteBuffer} to hold the encrypted data * @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(ByteBuffer buffer, 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, bytesToRead, 0); return bytesToRead; } protected boolean establishSession(TlsSession sessionToResume) { this.tlsSession = null; this.sessionParameters = null; this.sessionMasterSecret = null; if (null == sessionToResume || !sessionToResume.isResumable()) { return false; } SessionParameters sessionParameters = sessionToResume.exportSessionParameters(); if (null == sessionParameters) { return false; } ProtocolVersion sessionVersion = sessionParameters.getNegotiatedVersion(); if (null == sessionVersion || !sessionVersion.isTLS()) { return false; } boolean isEMS = sessionParameters.isExtendedMasterSecret(); if (sessionVersion.isSSL()) { if (isEMS) { return false; } } else if (!TlsUtils.isExtendedMasterSecretOptional(sessionVersion)) { if (!isEMS) { return false; } } TlsCrypto crypto = getContext().getCrypto(); TlsSecret sessionMasterSecret = TlsUtils.getSessionMasterSecret(crypto, sessionParameters.getMasterSecret()); if (null == sessionMasterSecret) { return false; } this.tlsSession = sessionToResume; this.sessionParameters = sessionParameters; this.sessionMasterSecret = sessionMasterSecret; return true; } protected void cancelSession() { if (this.sessionMasterSecret != null) { this.sessionMasterSecret.destroy(); this.sessionMasterSecret = null; } if (this.sessionParameters != null) { this.sessionParameters.clear(); this.sessionParameters = null; } this.tlsSession = null; } protected void invalidateSession() { if (this.tlsSession != null) { this.tlsSession.invalidate(); } cancelSession(); } protected void processFinishedMessage(ByteArrayInputStream buf) throws IOException { TlsContext context = getContext(); SecurityParameters securityParameters = context.getSecurityParametersHandshake(); boolean isServerContext = context.isServer(); byte[] verify_data = TlsUtils.readFully(securityParameters.getVerifyDataLength(), buf); assertEmpty(buf); byte[] expected_verify_data = TlsUtils.calculateVerifyData(context, handshakeHash, !isServerContext); /* * Compare both checksums. */ if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data)) { /* * Wrong checksum in the finished message. */ throw new TlsFatalAlert(AlertDescription.decrypt_error); } securityParameters.peerVerifyData = expected_verify_data; if (!securityParameters.isResumedSession() || securityParameters.isExtendedMasterSecret()) { if (null == securityParameters.getLocalVerifyData()) { securityParameters.tlsUnique = expected_verify_data; } } } protected void process13FinishedMessage(ByteArrayInputStream buf) throws IOException { TlsContext context = getContext(); SecurityParameters securityParameters = context.getSecurityParametersHandshake(); boolean isServerContext = context.isServer(); byte[] verify_data = TlsUtils.readFully(securityParameters.getVerifyDataLength(), buf); assertEmpty(buf); byte[] expected_verify_data = TlsUtils.calculateVerifyData(context, handshakeHash, !isServerContext); /* * Compare both checksums. */ if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data)) { /* * Wrong checksum in the finished message. */ throw new TlsFatalAlert(AlertDescription.decrypt_error); } securityParameters.peerVerifyData = expected_verify_data; securityParameters.tlsUnique = null; } 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) { // We are already processing an exception, so just ignore this } } 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 receive13KeyUpdate(ByteArrayInputStream buf) throws IOException { // TODO[tls13] This is interesting enough to notify the TlsPeer for possible logging/vetting if (!(appDataReady && keyUpdateEnabled)) { throw new TlsFatalAlert(AlertDescription.unexpected_message); } short requestUpdate = TlsUtils.readUint8(buf); assertEmpty(buf); if (!KeyUpdateRequest.isValid(requestUpdate)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } boolean updateRequested = (KeyUpdateRequest.update_requested == requestUpdate); TlsUtils.update13TrafficSecretPeer(getContext()); recordStream.notifyKeyUpdateReceived(); // this.keyUpdatePendingReceive &= updateRequested; this.keyUpdatePendingSend |= updateRequested; } protected void sendCertificateMessage(Certificate certificate, OutputStream endPointHash) throws IOException { TlsContext context = getContext(); SecurityParameters securityParameters = context.getSecurityParametersHandshake(); if (null != securityParameters.getLocalCertificate()) { throw new TlsFatalAlert(AlertDescription.internal_error); } if (null == certificate) { certificate = Certificate.EMPTY_CHAIN; } if (certificate.isEmpty() && !context.isServer() && securityParameters.getNegotiatedVersion().isSSL()) { String message = "SSLv3 client didn't provide credentials"; raiseAlertWarning(AlertDescription.no_certificate, message); } else { HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate); certificate.encode(context, message, endPointHash); message.send(this); } securityParameters.localCertificate = certificate; } protected void send13CertificateMessage(Certificate certificate) throws IOException { if (null == certificate) { throw new TlsFatalAlert(AlertDescription.internal_error); } TlsContext context = getContext(); SecurityParameters securityParameters = context.getSecurityParametersHandshake(); if (null != securityParameters.getLocalCertificate()) { throw new TlsFatalAlert(AlertDescription.internal_error); } HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate); certificate.encode(context, message, null); message.send(this); securityParameters.localCertificate = certificate; } protected void send13CertificateVerifyMessage(DigitallySigned certificateVerify) throws IOException { HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_verify); certificateVerify.encode(message); message.send(this); } protected void sendChangeCipherSpec() throws IOException { sendChangeCipherSpecMessage(); recordStream.enablePendingCipherWrite(); } protected void sendChangeCipherSpecMessage() throws IOException { byte[] message = new byte[]{ 1 }; safeWriteRecord(ContentType.change_cipher_spec, message, 0, message.length); } protected void sendFinishedMessage() throws IOException { TlsContext context = getContext(); SecurityParameters securityParameters = context.getSecurityParametersHandshake(); boolean isServerContext = context.isServer(); byte[] verify_data = TlsUtils.calculateVerifyData(context, handshakeHash, isServerContext); securityParameters.localVerifyData = verify_data; if (!securityParameters.isResumedSession() || securityParameters.isExtendedMasterSecret()) { if (null == securityParameters.getPeerVerifyData()) { securityParameters.tlsUnique = verify_data; } } HandshakeMessageOutput.send(this, HandshakeType.finished, verify_data); } protected void send13FinishedMessage() throws IOException { TlsContext context = getContext(); SecurityParameters securityParameters = context.getSecurityParametersHandshake(); boolean isServerContext = context.isServer(); byte[] verify_data = TlsUtils.calculateVerifyData(context, handshakeHash, isServerContext); securityParameters.localVerifyData = verify_data; securityParameters.tlsUnique = null; HandshakeMessageOutput.send(this, HandshakeType.finished, verify_data); } protected void send13KeyUpdate(boolean updateRequested) throws IOException { // TODO[tls13] This is interesting enough to notify the TlsPeer for possible logging/vetting if (!(appDataReady && keyUpdateEnabled)) { throw new TlsFatalAlert(AlertDescription.internal_error); } short requestUpdate = updateRequested ? KeyUpdateRequest.update_requested : KeyUpdateRequest.update_not_requested; HandshakeMessageOutput.send(this, HandshakeType.key_update, TlsUtils.encodeUint8(requestUpdate)); TlsUtils.update13TrafficSecretLocal(getContext()); recordStream.notifyKeyUpdateSent(); // this.keyUpdatePendingReceive |= updateRequested; this.keyUpdatePendingSend &= updateRequested; } protected void sendSupplementalDataMessage(Vector supplementalData) throws IOException { HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.supplemental_data); writeSupplementalData(message, supplementalData); message.send(this); } /** * Closes this connection. * * @throws IOException If something goes wrong during closing. */ public void close() throws IOException { handleClose(true); } public void flush() throws IOException { } boolean isApplicationDataReady() { return appDataReady; } public boolean isClosed() { return closed; } public boolean isConnected() { if (closed) { return false; } AbstractTlsContext context = getContextAdmin(); return null != context && context.isConnected(); } public boolean isHandshaking() { if (closed) { return false; } AbstractTlsContext context = getContextAdmin(); return null != context && context.isHandshaking(); } /** * @deprecated Will be removed. */ protected short processMaxFragmentLengthExtension(Hashtable clientExtensions, Hashtable serverExtensions, short alertDescription) throws IOException { return TlsUtils.processMaxFragmentLengthExtension(clientExtensions, serverExtensions, alertDescription); } protected void refuseRenegotiation() throws IOException { /* * RFC 5746 4.5 SSLv3 clients [..] SHOULD use a fatal handshake_failure alert. */ if (TlsUtils.isSSL(getContext())) { throw new TlsFatalAlert(AlertDescription.handshake_failure); } raiseAlertWarning(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, TlsContext context) { byte[] result = context.getNonceGenerator().generateNonce(32); 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 { TlsSecret preMasterSecret = keyExchange.generatePreMasterSecret(); if (preMasterSecret == null) { throw new TlsFatalAlert(AlertDescription.internal_error); } try { context.getSecurityParametersHandshake().masterSecret = TlsUtils.calculateMasterSecret(context, preMasterSecret); } finally { /* * RFC 2246 8.1. The pre_master_secret should be deleted from memory once the * master_secret has been computed. */ preMasterSecret.destroy(); } } protected static Hashtable readExtensions(ByteArrayInputStream input) throws IOException { if (input.available() < 1) { return null; } byte[] extBytes = TlsUtils.readOpaque16(input); assertEmpty(input); return readExtensionsData(extBytes); } protected static Hashtable readExtensionsData(byte[] extBytes) throws IOException { // Integer -> byte[] Hashtable extensions = new Hashtable(); if (extBytes.length > 0) { ByteArrayInputStream buf = new ByteArrayInputStream(extBytes); do { int extension_type = 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(Integers.valueOf(extension_type), extension_data)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter, "Repeated extension: " + ExtensionType.getText(extension_type)); } } while (buf.available() > 0); } return extensions; } protected static Hashtable readExtensionsData13(int handshakeType, byte[] extBytes) throws IOException { // Integer -> byte[] Hashtable extensions = new Hashtable(); if (extBytes.length > 0) { ByteArrayInputStream buf = new ByteArrayInputStream(extBytes); do { int extension_type = TlsUtils.readUint16(buf); if (!TlsUtils.isPermittedExtensionType13(handshakeType, extension_type)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter, "Invalid extension: " + ExtensionType.getText(extension_type)); } 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(Integers.valueOf(extension_type), extension_data)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter, "Repeated extension: " + ExtensionType.getText(extension_type)); } } while (buf.available() > 0); } return extensions; } protected static Hashtable readExtensionsDataClientHello(byte[] extBytes) throws IOException { /* * TODO[tls13] We are currently allowing any extensions to appear in ClientHello. It is * somewhat complicated to restrict what can appear based on the specific set of versions * the client is offering, and anyway could be fragile since clients may take a * "kitchen sink" approach to adding extensions independently of the offered versions. */ // Integer -> byte[] Hashtable extensions = new Hashtable(); if (extBytes.length > 0) { ByteArrayInputStream buf = new ByteArrayInputStream(extBytes); int extension_type = -1; boolean pre_shared_key_found = false; do { extension_type = 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(Integers.valueOf(extension_type), extension_data)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter, "Repeated extension: " + ExtensionType.getText(extension_type)); } pre_shared_key_found |= (ExtensionType.pre_shared_key == extension_type); } while (buf.available() > 0); if (pre_shared_key_found && (ExtensionType.pre_shared_key != extension_type)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter, "'pre_shared_key' MUST be last in ClientHello"); } } return extensions; } protected static Vector readSupplementalDataMessage(ByteArrayInputStream input) throws IOException { byte[] supp_data = TlsUtils.readOpaque24(input, 1); 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 { writeExtensions(output, extensions, 0); } protected static void writeExtensions(OutputStream output, Hashtable extensions, int bindersSize) throws IOException { if (null == extensions || extensions.isEmpty()) { return; } byte[] extBytes = writeExtensionsData(extensions, bindersSize); int lengthWithBinders = extBytes.length + bindersSize; TlsUtils.checkUint16(lengthWithBinders); TlsUtils.writeUint16(lengthWithBinders, output); output.write(extBytes); } protected static byte[] writeExtensionsData(Hashtable extensions) throws IOException { return writeExtensionsData(extensions, 0); } protected static byte[] writeExtensionsData(Hashtable extensions, int bindersSize) throws IOException { ByteArrayOutputStream buf = new ByteArrayOutputStream(); writeExtensionsData(extensions, bindersSize, buf); return buf.toByteArray(); } protected static void writeExtensionsData(Hashtable extensions, int bindersSize, ByteArrayOutputStream buf) throws IOException { /* * 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); writePreSharedKeyExtension(buf, extensions, bindersSize); } protected static void writePreSharedKeyExtension(OutputStream output, Hashtable extensions, int bindersSize) throws IOException { byte[] extension_data = (byte[])extensions.get(TlsExtensionsUtils.EXT_pre_shared_key); if (null != extension_data) { TlsUtils.checkUint16(ExtensionType.pre_shared_key); TlsUtils.writeUint16(ExtensionType.pre_shared_key, output); int lengthWithBinders = extension_data.length + bindersSize; TlsUtils.checkUint16(lengthWithBinders); TlsUtils.writeUint16(lengthWithBinders, output); output.write(extension_data); } } 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(); // NOTE: Must be last; handled by 'writePreSharedKeyExtension' if (ExtensionType.pre_shared_key == extension_type) { continue; } 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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy