无法使用java SSLEnigine解密ssl包

无法使用java SSLEnigine解密ssl包,java,ssl,encryption,Java,Ssl,Encryption,我有一个node.js服务器,它自动开始向连接的客户端发送数据(用java实现)。客户端在接收数据时打印数据。我有两个场景,一个作品在另一个作品中没有 Server.js工作正常 导致客户: Server.js不起作用 导致客户: 问题似乎是,当服务器发送数据时,客户端无法解密数据,而连接刚刚建立。问题出在客户端,因为我已经使用node.js tls客户端进行了测试,它可以正常工作 . 我认为问题在于,在第二种情况下,当服务器发送“Hi”时,客户机还没有准备好,但我如何知道客户机已经准备好

我有一个node.js服务器,它自动开始向连接的客户端发送数据(用java实现)。客户端在接收数据时打印数据。我有两个场景,一个作品在另一个作品中没有

  • Server.js工作正常
  • 导致客户:

  • Server.js不起作用
  • 导致客户:

    问题似乎是,当服务器发送数据时,客户端无法解密数据,而连接刚刚建立。问题出在客户端,因为我已经使用node.js tls客户端进行了测试,它可以正常工作 . 我认为问题在于,在第二种情况下,当服务器发送“Hi”时,客户机还没有准备好,但我如何知道客户机已经准备好

    NioSSLClient.java

    package alkarn.github.io.sslengine.example;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.nio.channels.spi.SelectorProvider;
    import java.security.SecureRandom;
    import java.util.Iterator;
    
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLEngineResult;
    import javax.net.ssl.SSLException;
    import javax.net.ssl.SSLSession;
    
    public class NioSslClient extends NioSslPeer {
        
        private String remoteAddress;
        private int port;
        private SSLEngine engine;
        private SocketChannel socketChannel;
        private Selector selector;  
        private SelectionKey k;  
        private boolean active;
    
        
        public NioSslClient(String protocol, String remoteAddress, int port) throws Exception  {
            this.remoteAddress = remoteAddress;
            this.port = port;
    
            SSLContext context = SSLContext.getInstance(protocol);
            context.init(null, createTrustManagers("/home/javier/Escritorio/tls_server/server.truststore", "example"), new SecureRandom());
            engine = context.createSSLEngine(remoteAddress, port);
            engine.setUseClientMode(true);
    
            SSLSession session = engine.getSession();
            myAppData = ByteBuffer.allocate(1024);
            myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
            peerAppData = ByteBuffer.allocate(1024);
            peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
            
            selector = SelectorProvider.provider().openSelector();
        }
    
    
        public boolean connect() throws Exception {
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress(remoteAddress, port));
            while (!socketChannel.finishConnect()) {
                // not
            }
            active = true;
            engine.beginHandshake();
            if(doHandshake(socketChannel, engine)) {
                k = socketChannel.register(selector, SelectionKey.OP_READ, engine);
                return true;
            }
            return false;
        }
    
        
        @Override
        protected void read(SocketChannel socketChannel, SSLEngine engine) throws Exception  {
            log.debug("About to read from the server...");
            peerNetData.clear();
            boolean exitReadLoop = false;
            while (!exitReadLoop) {
                int bytesRead = socketChannel.read(peerNetData);
                if (bytesRead > 0) {
                    peerNetData.flip();
                    while (peerNetData.hasRemaining()) {
                        peerAppData.clear();
                        SSLEngineResult result = engine.unwrap(peerNetData, peerAppData);
                        switch (result.getStatus()) {
                        case OK:
                            peerAppData.flip();
                            log.debug("Server response: " + new String(peerAppData.array()));
                            exitReadLoop = true;
                            break;
                        case BUFFER_OVERFLOW:
                            peerAppData = enlargeApplicationBuffer(engine, peerAppData);
                            break;
                        case BUFFER_UNDERFLOW:
                            peerNetData = handleBufferUnderflow(engine, peerNetData);
                            break;
                        case CLOSED:
                            closeConnection(socketChannel, engine);
                            return;
                        default:
                            throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                        }
                    }
                } else if (bytesRead < 0) {
                    handleEndOfStream(socketChannel, engine);
                    return;
                }
            }
        }
    
        
        public void start() throws Exception {
            log.debug("Initialized and waiting to connect...");
            while (active) {
                selector.select();
                Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
                while (selectedKeys.hasNext()) {
                    SelectionKey key = selectedKeys.next();
                    selectedKeys.remove();
                    if (!key.isValid()) {
                        continue;
                    }
                    if (key.isAcceptable()) {
                        //nop
                    } else if (key.isReadable()) {
                        read((SocketChannel) key.channel(), (SSLEngine) key.attachment());
                    }
                }
            }
            log.debug("Goodbye!");
        }
        
        public void stop() {
            log.debug("Will now close server...");
            active = false;
            executor.shutdown();
            selector.wakeup();
        }
        
    }
    
    
    Demo.java

    \\more
    @override
    public void run(){
        try{
              client = new NioSslClient("TLSv1.2", "localhost", 8000);
              client.conect();
              client.start();
        } catch(....){...}
    }
    \\more
    
    public static void main(String[] args) throws Exception {
        ClientRunnable clientRunnable = new ClientRunnable();
        new Thread(clientRunnable).start();
    }
    
    package alkarn.github.io.sslengine.example;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.BufferOverflowException;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    import java.security.KeyStore;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import javax.net.ssl.KeyManager;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLEngineResult;
    import javax.net.ssl.SSLEngineResult.HandshakeStatus;
    import javax.net.ssl.SSLException;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    
    import org.apache.log4j.Logger;
    
    /**
     * A class that represents an SSL/TLS peer, and can be extended to create a client or a server.
     * <p/>
     * It makes use of the JSSE framework, and specifically the {@link SSLEngine} logic, which
     * is described by Oracle as "an advanced API, not appropriate for casual use", since
     * it requires the user to implement much of the communication establishment procedure himself.
     * More information about it can be found here: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine
     * <p/>
     * {@link NioSslPeer} implements the handshake protocol, required to establish a connection between two peers,
     * which is common for both client and server and provides the abstract {@link NioSslPeer#read(SocketChannel, SSLEngine)} and
     * {@link NioSslPeer#write(SocketChannel, SSLEngine, String)} methods, that need to be implemented by the specific SSL/TLS peer
     * that is going to extend this class.
     *
     * @author <a href="mailto:alex.a.karnezis@gmail.com">Alex Karnezis</a>
     */
    public abstract class NioSslPeer {
    
        /**
         * Class' logger.
         */
        protected final Logger log = Logger.getLogger(getClass());
    
        /**
         * Will contain this peer's application data in plaintext, that will be later encrypted
         * using {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} and sent to the other peer. This buffer can typically
         * be of any size, as long as it is large enough to contain this peer's outgoing messages.
         * If this peer tries to send a message bigger than buffer's capacity a {@link BufferOverflowException}
         * will be thrown.
         */
        protected ByteBuffer myAppData;
    
        /**
         * Will contain this peer's encrypted data, that will be generated after {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)}
         * is applied on {@link NioSslPeer#myAppData}. It should be initialized using {@link SSLSession#getPacketBufferSize()},
         * which returns the size up to which, SSL/TLS packets will be generated from the engine under a session.
         * All SSLEngine network buffers should be sized at least this large to avoid insufficient space problems when performing wrap and unwrap calls.
         */
        protected ByteBuffer myNetData;
    
        /**
         * Will contain the other peer's (decrypted) application data. It must be large enough to hold the application data
         * from any peer. Can be initialized with {@link SSLSession#getApplicationBufferSize()} for an estimation
         * of the other peer's application data and should be enlarged if this size is not enough.
         */
        protected ByteBuffer peerAppData;
    
        /**
         * Will contain the other peer's encrypted data. The SSL/TLS protocols specify that implementations should produce packets containing at most 16 KB of plaintext,
         * so a buffer sized to this value should normally cause no capacity problems. However, some implementations violate the specification and generate large records up to 32 KB.
         * If the {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} detects large inbound packets, the buffer sizes returned by SSLSession will be updated dynamically, so the this peer
         * should check for overflow conditions and enlarge the buffer using the session's (updated) buffer size.
         */
        protected ByteBuffer peerNetData;
    
        /**
         * Will be used to execute tasks that may emerge during handshake in parallel with the server's main thread.
         */
        protected ExecutorService executor = Executors.newSingleThreadExecutor();
    
        protected abstract void read(SocketChannel socketChannel, SSLEngine engine) throws Exception;
    
        protected abstract void write(SocketChannel socketChannel, SSLEngine engine, String message) throws Exception;
    
        /**
         * Implements the handshake protocol between two peers, required for the establishment of the SSL/TLS connection.
         * During the handshake, encryption configuration information - such as the list of available cipher suites - will be exchanged
         * and if the handshake is successful will lead to an established SSL/TLS session.
         *
         * <p/>
         * A typical handshake will usually contain the following steps:
         *
         * <ul>
         *   <li>1. wrap:     ClientHello</li>
         *   <li>2. unwrap:   ServerHello/Cert/ServerHelloDone</li>
         *   <li>3. wrap:     ClientKeyExchange</li>
         *   <li>4. wrap:     ChangeCipherSpec</li>
         *   <li>5. wrap:     Finished</li>
         *   <li>6. unwrap:   ChangeCipherSpec</li>
         *   <li>7. unwrap:   Finished</li>
         * </ul>
         * <p/>
         * Handshake is also used during the end of the session, in order to properly close the connection between the two peers.
         * A proper connection close will typically include the one peer sending a CLOSE message to another, and then wait for
         * the other's CLOSE message to close the transport link. The other peer from his perspective would read a CLOSE message
         * from his peer and then enter the handshake procedure to send his own CLOSE message as well.
         *
         * @param socketChannel - the socket channel that connects the two peers.
         * @param engine - the engine that will be used for encryption/decryption of the data exchanged with the other peer.
         * @return True if the connection handshake was successful or false if an error occurred.
         * @throws IOException - if an error occurs during read/write to the socket channel.
         */
        protected boolean doHandshake(SocketChannel socketChannel, SSLEngine engine) throws IOException {
    
            log.debug("About to do handshake...");
    
            SSLEngineResult result;
            HandshakeStatus handshakeStatus;
    
            // NioSslPeer's fields myAppData and peerAppData are supposed to be large enough to hold all message data the peer
            // will send and expects to receive from the other peer respectively. Since the messages to be exchanged will usually be less
            // than 16KB long the capacity of these fields should also be smaller. Here we initialize these two local buffers
            // to be used for the handshake, while keeping client's buffers at the same size.
            int appBufferSize = engine.getSession().getApplicationBufferSize();
            ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize);
            ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize);
            myNetData.clear();
            peerNetData.clear();
    
            handshakeStatus = engine.getHandshakeStatus();
            while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                switch (handshakeStatus) {
                case NEED_UNWRAP:
                    if (socketChannel.read(peerNetData) < 0) {
                        if (engine.isInboundDone() && engine.isOutboundDone()) {
                            return false;
                        }
                        try {
                            engine.closeInbound();
                        } catch (SSLException e) {
                            log.error("This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream.");
                        }
                        engine.closeOutbound();
                        // After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client.
                        handshakeStatus = engine.getHandshakeStatus();
                        break;
                    }
                    peerNetData.flip();
                    try {
                        result = engine.unwrap(peerNetData, peerAppData);
                        peerNetData.compact();
                        handshakeStatus = result.getHandshakeStatus();
                    } catch (SSLException sslException) {
                        log.error("A problem was encountered while processing the data that caused the SSLEngine to abort. Will try to properly close connection...");
                        engine.closeOutbound();
                        handshakeStatus = engine.getHandshakeStatus();
                        break;
                    }
                    switch (result.getStatus()) {
                    case OK:
                        break;
                    case BUFFER_OVERFLOW:
                        // Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap.
                        peerAppData = enlargeApplicationBuffer(engine, peerAppData);
                        break;
                    case BUFFER_UNDERFLOW:
                        // Will occur either when no data was read from the peer or when the peerNetData buffer was too small to hold all peer's data.
                        peerNetData = handleBufferUnderflow(engine, peerNetData);
                        break;
                    case CLOSED:
                        if (engine.isOutboundDone()) {
                            return false;
                        } else {
                            engine.closeOutbound();
                            handshakeStatus = engine.getHandshakeStatus();
                            break;
                        }
                    default:
                        throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                    }
                    break;
                case NEED_WRAP:
                    myNetData.clear();
                    try {
                        result = engine.wrap(myAppData, myNetData);
                        handshakeStatus = result.getHandshakeStatus();
                    } catch (SSLException sslException) {
                        log.error("A problem was encountered while processing the data that caused the SSLEngine to abort. Will try to properly close connection...");
                        engine.closeOutbound();
                        handshakeStatus = engine.getHandshakeStatus();
                        break;
                    }
                    switch (result.getStatus()) {
                    case OK :
                        myNetData.flip();
                        while (myNetData.hasRemaining()) {
                            socketChannel.write(myNetData);
                        }
                        break;
                    case BUFFER_OVERFLOW:
                        // Will occur if there is not enough space in myNetData buffer to write all the data that would be generated by the method wrap.
                        // Since myNetData is set to session's packet size we should not get to this point because SSLEngine is supposed
                        // to produce messages smaller or equal to that, but a general handling would be the following:
                        myNetData = enlargePacketBuffer(engine, myNetData);
                        break;
                    case BUFFER_UNDERFLOW:
                        throw new SSLException("Buffer underflow occured after a wrap. I don't think we should ever get here.");
                    case CLOSED:
                        try {
                            myNetData.flip();
                            while (myNetData.hasRemaining()) {
                                socketChannel.write(myNetData);
                            }
                            // At this point the handshake status will probably be NEED_UNWRAP so we make sure that peerNetData is clear to read.
                            peerNetData.clear();
                        } catch (Exception e) {
                            log.error("Failed to send server's CLOSE message due to socket channel's failure.");
                            handshakeStatus = engine.getHandshakeStatus();
                        }
                        break;
                    default:
                        throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                    }
                    break;
                case NEED_TASK:
                    Runnable task;
                    while ((task = engine.getDelegatedTask()) != null) {
                        executor.execute(task);
                    }
                    handshakeStatus = engine.getHandshakeStatus();
                    break;
                case FINISHED:
                    break;
                case NOT_HANDSHAKING:
                    break;
                default:
                    throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
                }
            }
    
            return true;
    
        }
    
        protected ByteBuffer enlargePacketBuffer(SSLEngine engine, ByteBuffer buffer) {
            return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
        }
    
        protected ByteBuffer enlargeApplicationBuffer(SSLEngine engine, ByteBuffer buffer) {
            return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
        }
    
        /**
         * Compares <code>sessionProposedCapacity<code> with buffer's capacity. If buffer's capacity is smaller,
         * returns a buffer with the proposed capacity. If it's equal or larger, returns a buffer
         * with capacity twice the size of the initial one.
         *
         * @param buffer - the buffer to be enlarged.
         * @param sessionProposedCapacity - the minimum size of the new buffer, proposed by {@link SSLSession}.
         * @return A new buffer with a larger capacity.
         */
        protected ByteBuffer enlargeBuffer(ByteBuffer buffer, int sessionProposedCapacity) {
            if (sessionProposedCapacity > buffer.capacity()) {
                buffer = ByteBuffer.allocate(sessionProposedCapacity);
            } else {
                buffer = ByteBuffer.allocate(buffer.capacity() * 2);
            }
            return buffer;
        }
    
        /**
         * Handles {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}. Will check if the buffer is already filled, and if there is no space problem
         * will return the same buffer, so the client tries to read again. If the buffer is already filled will try to enlarge the buffer either to
         * session's proposed size or to a larger capacity. A buffer underflow can happen only after an unwrap, so the buffer will always be a
         * peerNetData buffer.
         *
         * @param buffer - will always be peerNetData buffer.
         * @param engine - the engine used for encryption/decryption of the data exchanged between the two peers.
         * @return The same buffer if there is no space problem or a new buffer with the same data but more space.
         * @throws Exception
         */
        protected ByteBuffer handleBufferUnderflow(SSLEngine engine, ByteBuffer buffer) {
            if (engine.getSession().getPacketBufferSize() < buffer.limit()) {
                return buffer;
            } else {
                ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer);
                buffer.flip();
                replaceBuffer.put(buffer);
                return replaceBuffer;
            }
        }
    
        /**
         * This method should be called when this peer wants to explicitly close the connection
         * or when a close message has arrived from the other peer, in order to provide an orderly shutdown.
         * <p/>
         * It first calls {@link SSLEngine#closeOutbound()} which prepares this peer to send its own close message and
         * sets {@link SSLEngine} to the <code>NEED_WRAP</code> state. Then, it delegates the exchange of close messages
         * to the handshake method and finally, it closes socket channel.
         *
         * @param socketChannel - the transport link used between the two peers.
         * @param engine - the engine used for encryption/decryption of the data exchanged between the two peers.
         * @throws IOException if an I/O error occurs to the socket channel.
         */
        protected void closeConnection(SocketChannel socketChannel, SSLEngine engine) throws IOException  {
            engine.closeOutbound();
            doHandshake(socketChannel, engine);
            socketChannel.close();
        }
    
        /**
         * In addition to orderly shutdowns, an unorderly shutdown may occur, when the transport link (socket channel)
         * is severed before close messages are exchanged. This may happen by getting an -1 or {@link IOException}
         * when trying to read from the socket channel, or an {@link IOException} when trying to write to it.
         * In both cases {@link SSLEngine#closeInbound()} should be called and then try to follow the standard procedure.
         *
         * @param socketChannel - the transport link used between the two peers.
         * @param engine - the engine used for encryption/decryption of the data exchanged between the two peers.
         * @throws IOException if an I/O error occurs to the socket channel.
         */
        protected void handleEndOfStream(SocketChannel socketChannel, SSLEngine engine) throws IOException  {
            try {
                engine.closeInbound();
            } catch (Exception e) {
                log.error("This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream.");
            }
            closeConnection(socketChannel, engine);
        }
    
        /**
         * Creates the key managers required to initiate the {@link SSLContext}, using a JKS keystore as an input.
         *
         * @param filepath - the path to the JKS keystore.
         * @param keystorePassword - the keystore's password.
         * @param keyPassword - the key's passsword.
         * @return {@link KeyManager} array that will be used to initiate the {@link SSLContext}.
         * @throws Exception
         */
        protected KeyManager[] createKeyManagers(String filepath, String keystorePassword, String keyPassword) throws Exception {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            InputStream keyStoreIS = new FileInputStream(filepath);
            try {
                keyStore.load(keyStoreIS, keystorePassword.toCharArray());
            } finally {
                if (keyStoreIS != null) {
                    keyStoreIS.close();
                }
            }
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, keyPassword.toCharArray());
            return kmf.getKeyManagers();
        }
    
        /**
         * Creates the trust managers required to initiate the {@link SSLContext}, using a JKS keystore as an input.
         *
         * @param filepath - the path to the JKS keystore.
         * @param keystorePassword - the keystore's password.
         * @return {@link TrustManager} array, that will be used to initiate the {@link SSLContext}.
         * @throws Exception
         */
        protected TrustManager[] createTrustManagers(String filepath, String keystorePassword) throws Exception {
            KeyStore trustStore = KeyStore.getInstance("JKS");
            InputStream trustStoreIS = new FileInputStream(filepath);
            try {
                trustStore.load(trustStoreIS, keystorePassword.toCharArray());
            } finally {
                if (trustStoreIS != null) {
                    trustStoreIS.close();
                }
            }
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(trustStore);
            return trustFactory.getTrustManagers();
        }
    
    }
    
    
    NioSslPeer.java

    \\more
    @override
    public void run(){
        try{
              client = new NioSslClient("TLSv1.2", "localhost", 8000);
              client.conect();
              client.start();
        } catch(....){...}
    }
    \\more
    
    public static void main(String[] args) throws Exception {
        ClientRunnable clientRunnable = new ClientRunnable();
        new Thread(clientRunnable).start();
    }
    
    package alkarn.github.io.sslengine.example;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.BufferOverflowException;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    import java.security.KeyStore;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import javax.net.ssl.KeyManager;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLEngineResult;
    import javax.net.ssl.SSLEngineResult.HandshakeStatus;
    import javax.net.ssl.SSLException;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    
    import org.apache.log4j.Logger;
    
    /**
     * A class that represents an SSL/TLS peer, and can be extended to create a client or a server.
     * <p/>
     * It makes use of the JSSE framework, and specifically the {@link SSLEngine} logic, which
     * is described by Oracle as "an advanced API, not appropriate for casual use", since
     * it requires the user to implement much of the communication establishment procedure himself.
     * More information about it can be found here: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine
     * <p/>
     * {@link NioSslPeer} implements the handshake protocol, required to establish a connection between two peers,
     * which is common for both client and server and provides the abstract {@link NioSslPeer#read(SocketChannel, SSLEngine)} and
     * {@link NioSslPeer#write(SocketChannel, SSLEngine, String)} methods, that need to be implemented by the specific SSL/TLS peer
     * that is going to extend this class.
     *
     * @author <a href="mailto:alex.a.karnezis@gmail.com">Alex Karnezis</a>
     */
    public abstract class NioSslPeer {
    
        /**
         * Class' logger.
         */
        protected final Logger log = Logger.getLogger(getClass());
    
        /**
         * Will contain this peer's application data in plaintext, that will be later encrypted
         * using {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} and sent to the other peer. This buffer can typically
         * be of any size, as long as it is large enough to contain this peer's outgoing messages.
         * If this peer tries to send a message bigger than buffer's capacity a {@link BufferOverflowException}
         * will be thrown.
         */
        protected ByteBuffer myAppData;
    
        /**
         * Will contain this peer's encrypted data, that will be generated after {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)}
         * is applied on {@link NioSslPeer#myAppData}. It should be initialized using {@link SSLSession#getPacketBufferSize()},
         * which returns the size up to which, SSL/TLS packets will be generated from the engine under a session.
         * All SSLEngine network buffers should be sized at least this large to avoid insufficient space problems when performing wrap and unwrap calls.
         */
        protected ByteBuffer myNetData;
    
        /**
         * Will contain the other peer's (decrypted) application data. It must be large enough to hold the application data
         * from any peer. Can be initialized with {@link SSLSession#getApplicationBufferSize()} for an estimation
         * of the other peer's application data and should be enlarged if this size is not enough.
         */
        protected ByteBuffer peerAppData;
    
        /**
         * Will contain the other peer's encrypted data. The SSL/TLS protocols specify that implementations should produce packets containing at most 16 KB of plaintext,
         * so a buffer sized to this value should normally cause no capacity problems. However, some implementations violate the specification and generate large records up to 32 KB.
         * If the {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} detects large inbound packets, the buffer sizes returned by SSLSession will be updated dynamically, so the this peer
         * should check for overflow conditions and enlarge the buffer using the session's (updated) buffer size.
         */
        protected ByteBuffer peerNetData;
    
        /**
         * Will be used to execute tasks that may emerge during handshake in parallel with the server's main thread.
         */
        protected ExecutorService executor = Executors.newSingleThreadExecutor();
    
        protected abstract void read(SocketChannel socketChannel, SSLEngine engine) throws Exception;
    
        protected abstract void write(SocketChannel socketChannel, SSLEngine engine, String message) throws Exception;
    
        /**
         * Implements the handshake protocol between two peers, required for the establishment of the SSL/TLS connection.
         * During the handshake, encryption configuration information - such as the list of available cipher suites - will be exchanged
         * and if the handshake is successful will lead to an established SSL/TLS session.
         *
         * <p/>
         * A typical handshake will usually contain the following steps:
         *
         * <ul>
         *   <li>1. wrap:     ClientHello</li>
         *   <li>2. unwrap:   ServerHello/Cert/ServerHelloDone</li>
         *   <li>3. wrap:     ClientKeyExchange</li>
         *   <li>4. wrap:     ChangeCipherSpec</li>
         *   <li>5. wrap:     Finished</li>
         *   <li>6. unwrap:   ChangeCipherSpec</li>
         *   <li>7. unwrap:   Finished</li>
         * </ul>
         * <p/>
         * Handshake is also used during the end of the session, in order to properly close the connection between the two peers.
         * A proper connection close will typically include the one peer sending a CLOSE message to another, and then wait for
         * the other's CLOSE message to close the transport link. The other peer from his perspective would read a CLOSE message
         * from his peer and then enter the handshake procedure to send his own CLOSE message as well.
         *
         * @param socketChannel - the socket channel that connects the two peers.
         * @param engine - the engine that will be used for encryption/decryption of the data exchanged with the other peer.
         * @return True if the connection handshake was successful or false if an error occurred.
         * @throws IOException - if an error occurs during read/write to the socket channel.
         */
        protected boolean doHandshake(SocketChannel socketChannel, SSLEngine engine) throws IOException {
    
            log.debug("About to do handshake...");
    
            SSLEngineResult result;
            HandshakeStatus handshakeStatus;
    
            // NioSslPeer's fields myAppData and peerAppData are supposed to be large enough to hold all message data the peer
            // will send and expects to receive from the other peer respectively. Since the messages to be exchanged will usually be less
            // than 16KB long the capacity of these fields should also be smaller. Here we initialize these two local buffers
            // to be used for the handshake, while keeping client's buffers at the same size.
            int appBufferSize = engine.getSession().getApplicationBufferSize();
            ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize);
            ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize);
            myNetData.clear();
            peerNetData.clear();
    
            handshakeStatus = engine.getHandshakeStatus();
            while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                switch (handshakeStatus) {
                case NEED_UNWRAP:
                    if (socketChannel.read(peerNetData) < 0) {
                        if (engine.isInboundDone() && engine.isOutboundDone()) {
                            return false;
                        }
                        try {
                            engine.closeInbound();
                        } catch (SSLException e) {
                            log.error("This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream.");
                        }
                        engine.closeOutbound();
                        // After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client.
                        handshakeStatus = engine.getHandshakeStatus();
                        break;
                    }
                    peerNetData.flip();
                    try {
                        result = engine.unwrap(peerNetData, peerAppData);
                        peerNetData.compact();
                        handshakeStatus = result.getHandshakeStatus();
                    } catch (SSLException sslException) {
                        log.error("A problem was encountered while processing the data that caused the SSLEngine to abort. Will try to properly close connection...");
                        engine.closeOutbound();
                        handshakeStatus = engine.getHandshakeStatus();
                        break;
                    }
                    switch (result.getStatus()) {
                    case OK:
                        break;
                    case BUFFER_OVERFLOW:
                        // Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap.
                        peerAppData = enlargeApplicationBuffer(engine, peerAppData);
                        break;
                    case BUFFER_UNDERFLOW:
                        // Will occur either when no data was read from the peer or when the peerNetData buffer was too small to hold all peer's data.
                        peerNetData = handleBufferUnderflow(engine, peerNetData);
                        break;
                    case CLOSED:
                        if (engine.isOutboundDone()) {
                            return false;
                        } else {
                            engine.closeOutbound();
                            handshakeStatus = engine.getHandshakeStatus();
                            break;
                        }
                    default:
                        throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                    }
                    break;
                case NEED_WRAP:
                    myNetData.clear();
                    try {
                        result = engine.wrap(myAppData, myNetData);
                        handshakeStatus = result.getHandshakeStatus();
                    } catch (SSLException sslException) {
                        log.error("A problem was encountered while processing the data that caused the SSLEngine to abort. Will try to properly close connection...");
                        engine.closeOutbound();
                        handshakeStatus = engine.getHandshakeStatus();
                        break;
                    }
                    switch (result.getStatus()) {
                    case OK :
                        myNetData.flip();
                        while (myNetData.hasRemaining()) {
                            socketChannel.write(myNetData);
                        }
                        break;
                    case BUFFER_OVERFLOW:
                        // Will occur if there is not enough space in myNetData buffer to write all the data that would be generated by the method wrap.
                        // Since myNetData is set to session's packet size we should not get to this point because SSLEngine is supposed
                        // to produce messages smaller or equal to that, but a general handling would be the following:
                        myNetData = enlargePacketBuffer(engine, myNetData);
                        break;
                    case BUFFER_UNDERFLOW:
                        throw new SSLException("Buffer underflow occured after a wrap. I don't think we should ever get here.");
                    case CLOSED:
                        try {
                            myNetData.flip();
                            while (myNetData.hasRemaining()) {
                                socketChannel.write(myNetData);
                            }
                            // At this point the handshake status will probably be NEED_UNWRAP so we make sure that peerNetData is clear to read.
                            peerNetData.clear();
                        } catch (Exception e) {
                            log.error("Failed to send server's CLOSE message due to socket channel's failure.");
                            handshakeStatus = engine.getHandshakeStatus();
                        }
                        break;
                    default:
                        throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                    }
                    break;
                case NEED_TASK:
                    Runnable task;
                    while ((task = engine.getDelegatedTask()) != null) {
                        executor.execute(task);
                    }
                    handshakeStatus = engine.getHandshakeStatus();
                    break;
                case FINISHED:
                    break;
                case NOT_HANDSHAKING:
                    break;
                default:
                    throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
                }
            }
    
            return true;
    
        }
    
        protected ByteBuffer enlargePacketBuffer(SSLEngine engine, ByteBuffer buffer) {
            return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
        }
    
        protected ByteBuffer enlargeApplicationBuffer(SSLEngine engine, ByteBuffer buffer) {
            return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
        }
    
        /**
         * Compares <code>sessionProposedCapacity<code> with buffer's capacity. If buffer's capacity is smaller,
         * returns a buffer with the proposed capacity. If it's equal or larger, returns a buffer
         * with capacity twice the size of the initial one.
         *
         * @param buffer - the buffer to be enlarged.
         * @param sessionProposedCapacity - the minimum size of the new buffer, proposed by {@link SSLSession}.
         * @return A new buffer with a larger capacity.
         */
        protected ByteBuffer enlargeBuffer(ByteBuffer buffer, int sessionProposedCapacity) {
            if (sessionProposedCapacity > buffer.capacity()) {
                buffer = ByteBuffer.allocate(sessionProposedCapacity);
            } else {
                buffer = ByteBuffer.allocate(buffer.capacity() * 2);
            }
            return buffer;
        }
    
        /**
         * Handles {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}. Will check if the buffer is already filled, and if there is no space problem
         * will return the same buffer, so the client tries to read again. If the buffer is already filled will try to enlarge the buffer either to
         * session's proposed size or to a larger capacity. A buffer underflow can happen only after an unwrap, so the buffer will always be a
         * peerNetData buffer.
         *
         * @param buffer - will always be peerNetData buffer.
         * @param engine - the engine used for encryption/decryption of the data exchanged between the two peers.
         * @return The same buffer if there is no space problem or a new buffer with the same data but more space.
         * @throws Exception
         */
        protected ByteBuffer handleBufferUnderflow(SSLEngine engine, ByteBuffer buffer) {
            if (engine.getSession().getPacketBufferSize() < buffer.limit()) {
                return buffer;
            } else {
                ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer);
                buffer.flip();
                replaceBuffer.put(buffer);
                return replaceBuffer;
            }
        }
    
        /**
         * This method should be called when this peer wants to explicitly close the connection
         * or when a close message has arrived from the other peer, in order to provide an orderly shutdown.
         * <p/>
         * It first calls {@link SSLEngine#closeOutbound()} which prepares this peer to send its own close message and
         * sets {@link SSLEngine} to the <code>NEED_WRAP</code> state. Then, it delegates the exchange of close messages
         * to the handshake method and finally, it closes socket channel.
         *
         * @param socketChannel - the transport link used between the two peers.
         * @param engine - the engine used for encryption/decryption of the data exchanged between the two peers.
         * @throws IOException if an I/O error occurs to the socket channel.
         */
        protected void closeConnection(SocketChannel socketChannel, SSLEngine engine) throws IOException  {
            engine.closeOutbound();
            doHandshake(socketChannel, engine);
            socketChannel.close();
        }
    
        /**
         * In addition to orderly shutdowns, an unorderly shutdown may occur, when the transport link (socket channel)
         * is severed before close messages are exchanged. This may happen by getting an -1 or {@link IOException}
         * when trying to read from the socket channel, or an {@link IOException} when trying to write to it.
         * In both cases {@link SSLEngine#closeInbound()} should be called and then try to follow the standard procedure.
         *
         * @param socketChannel - the transport link used between the two peers.
         * @param engine - the engine used for encryption/decryption of the data exchanged between the two peers.
         * @throws IOException if an I/O error occurs to the socket channel.
         */
        protected void handleEndOfStream(SocketChannel socketChannel, SSLEngine engine) throws IOException  {
            try {
                engine.closeInbound();
            } catch (Exception e) {
                log.error("This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream.");
            }
            closeConnection(socketChannel, engine);
        }
    
        /**
         * Creates the key managers required to initiate the {@link SSLContext}, using a JKS keystore as an input.
         *
         * @param filepath - the path to the JKS keystore.
         * @param keystorePassword - the keystore's password.
         * @param keyPassword - the key's passsword.
         * @return {@link KeyManager} array that will be used to initiate the {@link SSLContext}.
         * @throws Exception
         */
        protected KeyManager[] createKeyManagers(String filepath, String keystorePassword, String keyPassword) throws Exception {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            InputStream keyStoreIS = new FileInputStream(filepath);
            try {
                keyStore.load(keyStoreIS, keystorePassword.toCharArray());
            } finally {
                if (keyStoreIS != null) {
                    keyStoreIS.close();
                }
            }
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, keyPassword.toCharArray());
            return kmf.getKeyManagers();
        }
    
        /**
         * Creates the trust managers required to initiate the {@link SSLContext}, using a JKS keystore as an input.
         *
         * @param filepath - the path to the JKS keystore.
         * @param keystorePassword - the keystore's password.
         * @return {@link TrustManager} array, that will be used to initiate the {@link SSLContext}.
         * @throws Exception
         */
        protected TrustManager[] createTrustManagers(String filepath, String keystorePassword) throws Exception {
            KeyStore trustStore = KeyStore.getInstance("JKS");
            InputStream trustStoreIS = new FileInputStream(filepath);
            try {
                trustStore.load(trustStoreIS, keystorePassword.toCharArray());
            } finally {
                if (trustStoreIS != null) {
                    trustStoreIS.close();
                }
            }
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(trustStore);
            return trustFactory.getTrustManagers();
        }
    
    }
    
    

    最后,我直接使用javax.net.ssl.SSLSocket