Nio ssl socket server

Source: Internet
Author: User
Tags compact

Learning the combination of NiO and SSL

Reference: http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#KeyClasses

NIO has socketchannel but does not have sslsocketchannel. According to the document, if you want to implement the SSL socketchannel, a lot of code implementation will be involved, increasing the complexity of the API; the implementation of SSL should not depend on NiO or the traditional stream-based Io. It should be free for programmers to combine themselves, so no related APIs are provided in the java standard, it is left to the programmer for implementation. To support NiO for SSL communication, Java has been added.Javax.net. SSL. sslengineThis sslengine is used to support NiO SSL. It acts like a state machine that maintains various statuses and the next state in SSL communication.

 

For more information about SSL communication, see the SSL communication process in this document, which consists of three steps: handshake, dialog, and closing. The main contents of the handshake part are negotiation protocols, mutual authentication, generation and exchange of symmetric keys, in which mutual authentication and symmetric key exchange are completed by asymmetric encryption. During the conversation, the actual plaintext is encrypted by the previously generated symmetric key. When the conversation ends, send an ending signal to each other to end communication.

 

In the above process, it is not only a simple exchange of data between the Communications parties, but more importantly, it is necessary to send or accept specific data in a specific State according to the requirements of the SSL protocol, in addition, the data is processed, that is, SSL information is included between the TCP header and the communication body, and the body is encrypted in a specific form. Sslengine is the role that completes management, encapsulates application data to the network, parses network data, and passes it to the application.

 

First, let's take a look at the status and work of sslengine. Let's assume it is from an SSL server. First, in the handshake phase, you need to shake hands with the client program for authentication and symmetric key generation. During this period, there was no actual data exchange at the application layer, but only the exchange of SSL protocol data. Without looking at the actual content and meaning, in the handshake process, that is, step 1 to Step 13. After the sslengine of the server is initialized, it always waits for client requests (waiting to receive data). In this case, its status is need_unwrap, which means that sslengine is waiting to parse an SSL data packet, when the server receives the data packet, the data packet in NIO is always placed in a buffer instead of a read stream. We send the buffer to sslengine and call its unwrap method. sslengine will parse the data packet, extract the information about the SSL handshake and change its status. here it will change to the need_wrap status, which means packaging, it needs to write the corresponding SSL reply content into the packet and return to the client, that is, what is done in the step2-6. Similarly, sslengine always switches between the unpackage and package statuses most of the time. Especially during actual communication, it is noted that both the unwrap and wrap functions have a source buffer and a destination buffer, because sslengine not only extracts SSL-related content, but also decrypts network data and passes the plaintext to the application. This is actually the source of the two function names, but during the handshake process, there is no actual data, but only the SSL protocol information, so there is always nothing in that destination buffer. We can consider SSL communication as a gift exchange. sslengine split the package and gave it to you, or he picked up the gift package and sent it away, but there was no gift in the package during the SSL handshake, sslengine only removes an empty package or sends an empty package. There is no other status, there is a finished
The status is the status of Step 13 on the server, indicating that the handshake is complete. When the actual data is switched, that is, the status of Step 14 is not_handshake, it indicates that the current handshake is not in progress. Generally, you only need to call the unwrap function to decrypt the SSL data packets from the network when the socket is readable. When the socket is writable, the wrap function is called to encrypt and send the plaintext data. There is another status of need_task. First, you must know that sslengine is asynchronous, and both the wrap and unwrap functions will return immediately. For example, after the server receives the first request from the client, it will call unwrap, but in fact, sslengine will do a lot of work, such as accessing the keystore file. These operations are time-consuming, but the function actually returns immediately. At this time, sslengine will enter the need_task status, instead of entering the need_wrap status immediately, you must have sslengine complete the work at hand before proceeding to the next step. In this case, you can call the getdelegatedtask () method of sslengine to obtain the unfinished work, it is a runnable object. You can call its run method to wait for it to complete. If you are a high-concurrency server, you can also do other things at this time and wait for this job to complete, next we will do the wrap job. In addition, there is a very error-prone location. The next state of a need_unwrap state may be need_unwrap, and calling the unwrap method once does not necessarily unpackage all the content in the buffer, there may also be content that needs to be resolved in one unwrap to get everything done, this situation I encountered occurs in the process of step7-11 with NiO server and the old sslsocket communication, the client only sends the data to the server once, and the server needs to unwrap twice in a row to completely process the client data.

 

In addition to the preceding four states, the sslengine state is described, and four other states are used to describe the result state after each call of wrap and unwrap. Buffer_overflow indicates that the target buffer does not have enough space to store the unwrapped content. This is often because your destination buffer is too small or there is no clear before the buffer is written; buffer_underflow indicates that the source buffer does not have enough content for sslengine to unpack. This is often because there may be data that has not yet arrived, or no flip before the buffer is read; closed indicates that a communication section is trying to end this SSL communication; OK, you know.

 

After learning about the status of sslengine and the principles of wrap and unwrap, you will not have to worry about completing a NiO-based sslsocket.

First, the socket of NiO is basically implemented through selector. The socket accept, read, and write events are registered on the selector, And the select () statement can be executed continuously, for an SSL server socket, it is only a common serversocket. First, it only cares about the accept event, so first, it registers an event on the selector.

After serversocket receives an SSL client request, it starts to shake hands. This process is synchronous, so do not register the Read and Write events on the selector, after the handshake is completed, the two events are registered and the socket is set to non-blocking. The unwrap method is called before the select to socket is readable, And the wrap method is called before writing.

Each socket has two sets of buffer, Appin, netin, appout, and netout. netxx indicates that the data is read or written from the socket, and they are encrypted, appxx represents the data content that the application can understand. They can be converted to netxx through the sslengine wrap and unwrap methods.

 

Code sticking

package com.red.nio.ssl;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.ClosedChannelException;import java.nio.channels.SelectableChannel;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.nio.charset.Charset;import java.nio.charset.CharsetDecoder;import java.nio.charset.CharsetEncoder;import java.security.GeneralSecurityException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.UnrecoverableKeyException;import java.security.cert.CertificateException;import java.util.Iterator;import javax.net.ssl.KeyManagerFactory;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;import javax.net.ssl.TrustManagerFactory;import javax.net.ssl.SSLEngineResult.HandshakeStatus;public class SSLNewServer {private static boolean logging = true;private boolean handshakeDone = false;private Selector selector;private SSLEngine sslEngine;private SSLContext sslContext;private ByteBuffer appOut; // clear text buffer for outprivate ByteBuffer appIn; // clear text buffer for inprivate ByteBuffer netOut; // encrypted buffer for outprivate ByteBuffer netIn; // encrypted buffer for inprivate CharsetEncoder encoder = Charset.forName("UTF8").newEncoder();private CharsetDecoder decoder = Charset.forName("UTF8").newDecoder();public SSLNewServer() {try{createServerSocket();} catch (IOException e){System.out.println("initializing server failed");e.printStackTrace();}try{createSSLContext();} catch (GeneralSecurityException e){System.out.println("initializing SSL context failed");e.printStackTrace();} catch (IOException e){System.out.println("reading keystore or truststore file failed");e.printStackTrace();}createSSLEngines();createBuffers();}private void createBuffers(){SSLSession session = sslEngine.getSession();int appBufferMax = session.getApplicationBufferSize();int netBufferMax = session.getPacketBufferSize();appOut = ByteBuffer.wrap("This is an SSL Server".getBytes());//server only reply this sentence appIn = ByteBuffer.allocate(appBufferMax + 10);//appIn is bigger than the allowed max application buffer siznetOut = ByteBuffer.allocateDirect(netBufferMax);//direct allocate for better performancenetIn = ByteBuffer.allocateDirect(netBufferMax);}//the ssl context initializationprivate void createSSLContext() throws GeneralSecurityException, FileNotFoundException, IOException{KeyStore ks = KeyStore.getInstance("JKS");KeyStore ts = KeyStore.getInstance("JKS");char[] passphrase = "123456".toCharArray();ks.load(new FileInputStream("ssl/kserver.keystore"), passphrase);ts.load(new FileInputStream("ssl/tserver.keystore"), passphrase);KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");kmf.init(ks, passphrase);TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");tmf.init(ts);SSLContext sslCtx = SSLContext.getInstance("SSL");sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);sslContext = sslCtx;}//create the server socket, bind it to port 1234, set unblock and register the "accept" onlyprivate void createServerSocket() throws IOException{selector = Selector.open();ServerSocketChannel ssc = ServerSocketChannel.open();ssc.socket().bind(new InetSocketAddress(1234));ssc.configureBlocking(false);ssc.register(selector, SelectionKey.OP_ACCEPT);}private void createSSLEngines() {sslEngine = sslContext.createSSLEngine();sslEngine.setUseClientMode(false);//work in a server modesslEngine.setNeedClientAuth(true);//need client authentication}public void selecting() {while (true){try{selector.select();} catch (IOException e){e.printStackTrace();}Iterator<SelectionKey> iter = selector.selectedKeys().iterator();while (iter.hasNext()){SelectionKey key = (SelectionKey) iter.next();iter.remove();try{handle(key);} catch (SSLException e){// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e){// TODO Auto-generated catch blocke.printStackTrace();}}}}private void handle(SelectionKey key) throws IOException{if(key.isAcceptable()) {try{SocketChannel sc = ((ServerSocketChannel)key.channel()).accept();doHandShake(sc);//if it is an accept event, do the handshake in a blocking mode} catch (ClosedChannelException e){// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e){// TODO Auto-generated catch blocke.printStackTrace();}}else if(key.isReadable()) {if (sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING){SocketChannel sc = (SocketChannel) key.channel();sc.read(netIn);netIn.flip();SSLEngineResult engineResult = sslEngine.unwrap(netIn, appIn);log("server unwrap: ", engineResult);doTask();//runDelegatedTasks(engineResult, sslEngine);netIn.compact();if (engineResult.getStatus() == SSLEngineResult.Status.OK){System.out.println("text recieved");appIn.flip();// ready for readingSystem.out.println(decoder.decode(appIn));appIn.compact();}else if(engineResult.getStatus() == SSLEngineResult.Status.CLOSED) {doSSLClose(key);}}}else if(key.isWritable()) {SocketChannel sc = (SocketChannel) key.channel();//if(!sslEngine.isOutboundDone()) {//netOut.clear();SSLEngineResult engineResult = sslEngine.wrap(appOut, netOut);log("server wrap: ", engineResult);doTask();//runDelegatedTasks(engineResult, sslEngine);if (engineResult.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING){System.out.println("text sent");}netOut.flip();sc.write(netOut);netOut.compact();//}}}/*public static HandshakeStatus runDelegatedTasks(SSLEngineResult engineResult, SSLEngine sslEngine){if (engineResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK){Runnable runnable;while ((runnable = sslEngine.getDelegatedTask()) != null){System.out.println("\trunning delegated task...");runnable.run();}HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();if (hsStatus == HandshakeStatus.NEED_TASK){//throw new Exception("handshake shouldn't need additional tasks");System.out.println("handshake shouldn't need additional tasks");}System.out.println("\tnew HandshakeStatus: " + hsStatus);}return sslEngine.getHandshakeStatus();}*//* * Logging code */private static boolean resultOnce = true;public static void log(String str, SSLEngineResult result){if (!logging){return;}if (resultOnce){resultOnce = false;System.out.println("The format of the SSLEngineResult is: \n"+ "\t\"getStatus() / getHandshakeStatus()\" +\n"+ "\t\"bytesConsumed() / bytesProduced()\"\n");}HandshakeStatus hsStatus = result.getHandshakeStatus();log(str + result.getStatus() + "/" + hsStatus + ", " + result.bytesConsumed() + "/"+ result.bytesProduced() + " bytes");if (hsStatus == HandshakeStatus.FINISHED){log("\t...ready for application data");}}public static void log(String str){if (logging){System.out.println(str);}}private void doHandShake(SocketChannel sc) throws IOException{sslEngine.beginHandshake();//explicitly begin the handshakeHandshakeStatus hsStatus = sslEngine.getHandshakeStatus();while (!handshakeDone){switch(hsStatus){case FINISHED://the status become FINISHED only when the ssl handshake is finished//but we still need to send data, so do nothing herebreak;case NEED_TASK://do the delegate task if there is some extra work such as checking the keystore during the handshakehsStatus = doTask();break;case NEED_UNWRAP://unwrap means unwrap the ssl packet to get ssl handshake informationsc.read(netIn);netIn.flip();hsStatus = doUnwrap();break;case NEED_WRAP://wrap means wrap the app packet into an ssl packet to add ssl handshake informationhsStatus = doWrap();sc.write(netOut);netOut.clear();break;case NOT_HANDSHAKING://now it is not in a handshake or say byebye status. here it means handshake is over and ready for ssl talksc.configureBlocking(false);//set the socket to unblocking modesc.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);//register the read and write eventhandshakeDone = true;break;}}}private HandshakeStatus doTask() {Runnable runnable;while ((runnable = sslEngine.getDelegatedTask()) != null){System.out.println("\trunning delegated task...");runnable.run();}HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();if (hsStatus == HandshakeStatus.NEED_TASK){//throw new Exception("handshake shouldn't need additional tasks");System.out.println("handshake shouldn't need additional tasks");}System.out.println("\tnew HandshakeStatus: " + hsStatus);return hsStatus;}private HandshakeStatus doUnwrap() throws SSLException{HandshakeStatus hsStatus;do{//do unwrap until the state is change to "NEED_WRAP"SSLEngineResult engineResult = sslEngine.unwrap(netIn, appIn);log("server unwrap: ", engineResult);hsStatus = doTask();}while(hsStatus ==  SSLEngineResult.HandshakeStatus.NEED_UNWRAP && netIn.remaining()>0);System.out.println("\tnew HandshakeStatus: " + hsStatus);netIn.clear();return hsStatus;}private HandshakeStatus doWrap() throws SSLException{HandshakeStatus hsStatus;SSLEngineResult engineResult = sslEngine.wrap(appOut, netOut);log("server wrap: ", engineResult);hsStatus = doTask();System.out.println("\tnew HandshakeStatus: " + hsStatus);netOut.flip();return hsStatus;}//close an ssl talk, similar to the handshake stepsprivate void doSSLClose(SelectionKey key) throws IOException {SocketChannel sc = (SocketChannel) key.channel();key.cancel();try{sc.configureBlocking(true);} catch (IOException e){// TODO Auto-generated catch blocke.printStackTrace();}HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();while(handshakeDone) {switch(hsStatus) {case FINISHED:break;case NEED_TASK:hsStatus = doTask();break;case NEED_UNWRAP:sc.read(netIn);netIn.flip();hsStatus = doUnwrap();break;case NEED_WRAP:hsStatus = doWrap();sc.write(netOut);netOut.clear();break;case NOT_HANDSHAKING:handshakeDone = false;sc.close();break;}}}public static void main(String[] args) {SSLNewServer sns = new SSLNewServer();sns.selecting();}}

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.