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

com.viaoa.comm.ssl.SSLBase Maven / Gradle / Ivy

package com.viaoa.comm.ssl;

import java.nio.ByteBuffer;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;

/**
 * SSL base class used by client and server to be able to send/receive data that is first encrypted.
 * @author vvia
 *
 */
public abstract class SSLBase {
    private static Logger LOG = Logger.getLogger(SSLBase.class.getName());
    
    
    // https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html
    
    /**
     * Preferred encryption cipher to use for SSL sockets.
     */
    public static final String[] PREFERRED_CIPHER_NAMES = new String[] { "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" };  // 20171118
    //was: public static final String[] PREFERRED_CIPHER_NAMES = new String[] { "SSL_RSA_WITH_RC4_128_MD5" };

    
    
    protected SSLContext sslContext;
    protected SSLEngine sslEngine;

    // used to store encrypted data
    private byte[] bsWrap;
    private ByteBuffer bbWrap;

    // lock used when waiting on handshake data
    private final Object lock = new Object();
    // used during handshaking to 
    private byte[] bsBlank;
    // used by getInput(..) to allow for blocking, to wait on receiveInput(..) to return data.
    private final Object lockGetInput = new Object();

    protected final String host;
    protected final int port;

    public SSLBase(String host, int port) {
        this.host = host;
        this.port = port;
    }

    protected SSLContext getSSLContext() throws Exception {
        if (sslContext == null) {
            sslContext = createSSLContext();
        }
        return sslContext;
    }

    protected SSLEngine getSSLEngine() throws Exception {
        if (sslEngine == null) {
            sslEngine = createSSLEngine();
            sslEngine.setEnabledCipherSuites(PREFERRED_CIPHER_NAMES);
            sslEngine.beginHandshake();
        }
        return sslEngine;
    }

    /**
     * This will reset and cause the SSL handshaking to have to be done again.
     * Should not be needed, except for testing.
     */
    public void resetSSL() throws Exception {
        sslEngine.getSession().invalidate();
        sslEngine.beginHandshake();
    }

    /**
     * This can be called to initialize the SSLEngine, etc.
     * This is not required.
     */
    public void initialize() throws Exception {
        //log("initialize");
        getSSLEngine();
    }
    
    
    /**
     * This is the only way to send data to the client computer. 
     */
    public void output(final byte[] bs, final int offset, final int len) throws Exception {
        //log("ouput");
        getSSLEngine();
        int consumed = 0;
        for (;;) {
            for (;;) {
                needUnwrap();
                if (!needWrap()) break;
            }
            consumed += wrap(bs, offset + consumed, len - consumed, false);
            if (consumed >= len) break;
        }
    }

    // check to see if ssl handshake needs input data
    private void needUnwrap() throws Exception {
        for (int i=0;; i++) {
            synchronized (lock) {
                SSLEngineResult.HandshakeStatus hs = getSSLEngine().getHandshakeStatus();
                if (hs != hs.NEED_UNWRAP) break;
                try {
                    log("need_unwrap, i="+i);
                    lock.wait(250); // wait for input to unwrap
                }
                catch (Exception e) {
                }
            }
        }
    }

    // check to see if ssl handshake needs output data
    private boolean needWrap() throws Exception {
        SSLEngineResult.HandshakeStatus hs = getSSLEngine().getHandshakeStatus();
        if (hs != hs.NEED_WRAP) return false;
        log("need_wrap");
        if (bsBlank == null) bsBlank = new byte[0];
        wrap(bsBlank, 0, 0, true);
        return true;
    }

    /**
     * used by needWrap(..) and output(..) to encrypt data and call sendOutput(..) If the data is not
     * all encrypted, then it will continue to call output(..) with the remaining data.
     */
    private int wrap(final byte[] bs, final int offset, final int len, final boolean bHandshakeOnly) throws Exception {
        // log("wrap");
        int consumed = 0;
        for (;;) {
            if (bsWrap == null) {
                int max = getSSLEngine().getSession().getPacketBufferSize();
                bsWrap = new byte[max];
                bbWrap = ByteBuffer.wrap(bsWrap, 0, max);
            }
            else bbWrap.clear();

            ByteBuffer bb = ByteBuffer.wrap(bs, offset, len);
            SSLEngineResult result = getSSLEngine().wrap(bb, bbWrap);

            if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
                bsWrap = new byte[bsWrap.length + 1024];
                bbWrap = ByteBuffer.wrap(bsWrap, 0, bsWrap.length);
                continue;
            }

            if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
                Runnable runnable;
                while ((runnable = sslEngine.getDelegatedTask()) != null) {
                    runnable.run();
                }
            }

            consumed = result.bytesConsumed();
            if (result.bytesProduced() > 0) {
                sendOutput(bsWrap, 0, result.bytesProduced(), bHandshakeOnly);
            }
            break;
        }
        return consumed;
    }

    protected void input(final byte[] bs, final int len, final boolean bHandshakeOnly) throws Exception {
        //log("input");
        ByteBuffer bb = ByteBuffer.wrap(bs, 0, len);

        // this will use the same buffer to unwrap the data. This assumes that the unwrapped data is <=
        // the encrypted data.
        ByteBuffer bb2 = ByteBuffer.wrap(bs, 0, bs.length);

        synchronized (lock) {
            SSLEngineResult result = getSSLEngine().unwrap(bb, bb2);
            switch (result.getStatus()) {
            case BUFFER_OVERFLOW: // should never happen for unwrap
                throw new SSLException("Buffer_Overflow, should not happen for an unwrap");
            case BUFFER_UNDERFLOW: // not enough data to do SSL, should never happen for unwrap: since
                                   // we make sure all data is in buffer
                throw new SSLException("Buffer_Underflow, should not happen for an unwrap");
            }

            if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
                Runnable runnable;
                while ((runnable = getSSLEngine().getDelegatedTask()) != null) {
                    runnable.run();
                }
            }
            if (!bHandshakeOnly) {
                receiveInput(bs, 0, result.bytesProduced());
            }
            lock.notifyAll();
        }
    }

    /**
     * Implemented by Server/Client to set up correct ssl context.
     */
    protected abstract SSLContext createSSLContext() throws Exception;

    protected abstract SSLEngine createSSLEngine() throws Exception;
    
    
    /**
     * This is the encrypted data that needs to go to the client. This should be overwritten to call the
     * SSLCLient input(..) method.
     */
    protected abstract void sendOutput(final byte[] bs, final int offset, final int len, final boolean bHandshakeOnly) throws Exception;

    private byte[] bsGetInput;

    /**
     * This is unencrypted data from the client, called from input(..)
     */
    protected void receiveInput(final byte[] bs, final int offset, final int len) throws Exception {
        //log("receiveInput");
        synchronized (lockGetInput) {
            bsGetInput = new byte[len];
            if (len > 0) System.arraycopy(bs, offset, bsGetInput, 0, len);
            lockGetInput.notifyAll();
        }
    }

    /**
     * performs a blocking read.
     */
    public byte[] input() throws Exception {
        //log("input");
        int consumed = 0;

        byte[] bs;
        for (;;) {
            for (;;) {
                needUnwrap();
                if (!needWrap()) break;
            }
            synchronized (lockGetInput) {
                if (bsGetInput != null) {
                    bs = bsGetInput;
                    bsGetInput = null;
                    break;
                }
                try {
                    lockGetInput.wait(500);
                }
                catch (Exception e) {
                    // TODO: handle exception
                }
            }
        }
        return bs;
    }
    
    protected void log(String msg) {
        System.out.println(msg);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy