current position:Home>Java implements HTTPS access through sslengine and NiO

Java implements HTTPS access through sslengine and NiO

2021-08-23 17:09:53 Explode the radish

    Java Use NIO Conduct HTTPS Protocol access , Cannot do without SSLContext and SSLEngine Two classes . All we need to do is Connect operation 、Connected operation 、Read and Write Add... To the operation SSL Relevant processing can .

One 、 Initialize before connecting to the server SSLContext And set certificate related operations .

1 public void Connect(String host, int port) {
2     mSSLContext = this.InitSSLContext();
3     super.Connect(host, port);  
4 }

     Create... Before connecting to the server SSLContext object , And set the certificate . If the server does not use the key generated by an external recognized certification authority , You can use public key based CA To set the certificate . If it is a recognized certification certificate, it generally only needs to load Java KeyStore that will do .

    1.1 Based on the public key CA

 1 public SSLContext InitSSLContext() throws NoSuchAlgorithmException{
 2   //  Create build x509 Object of certificate 
 3   CertificateFactory caf = CertificateFactory.getInstance("X.509");
 4   //  there CA_PATH It's server's ca certificate , You can save... Through the browser Cer certificate (Base64 and DER Fine )
 5   X509Certificate ca = (X509Certificate)caf.generateCertificate(new FileInputStream(CA_PATH));
 6   KeyStore caKs = KeyStore.getInstance("JKS");
 7   caKs.load(null, null);
 8   //  Set the certificate created above to the warehouse , Ahead `baidu-ca` Just an alias can be used arbitrarily without repetition .
 9   caKs.setCertificateEntry("baidu-ca", ca);
10   TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
11       tmf.init(caKs);
12   //  Finally create SSLContext, Pass the list of trusted certificates into .
13   SSLContext context = SSLContext.getInstance("TLSv1.2");
14   context.init(null, tmf.getTrustManagers(), null);
15   return context;
16 }

    1.2 load Java KeyStore

 1 public SSLContext InitSSLContext() throws NoSuchAlgorithmException{
 2   //  load java keystore  Warehouse 
 3   KeyStore caKs = KeyStore.getInstance("JKS");
 4   //  Generate good jks Load the certificate 
 5   caKs.load(new FileInputStream(CA_PATH), PASSWORD.toCharArray());
 6   //  Put the loaded certificate into the trust list 
 7   TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
 8   tmf.init(caKs);
 9   //  Finally create SSLContext, Pass the list of trusted certificates into .
10   SSLContext context = SSLContext.getInstance("TLSv1.2");
11   context.init(null, tmf.getTrustManagers(), null);
12   return context;
13 }

Two 、 After connecting to the server successfully , Need to create SSLEngine object , And carry out relevant settings and handshake processing .

     Generated through the first step SSLContext establish SSLSocketFactory And set the current SocketChannel Binding ( notes : Many other examples do not have this step , If there is only one HTTPS There is no problem in theory , But if you want to create a large number of HTTPS request “ Probably ” There is a problem , because SSLEngine Which is used internally Socket The operation data is uncertain , If my understanding is wrong, please correct me ).

     And then call creation SSLEngine object , And initialize the operation data Buffer, Then start to enter the handshake stage .( notes : Created here Buffer It is mainly used to encrypt application layer data into network data , Decrypt network data into application layer data :“ Ciphertext and plaintext ”).

 1 public final void OnConnected() {
 2   super.OnConnected();
 3   //  Set up socket, And create SSLEngine, Start shaking hands 
 4   SSLSocketFactory fx = mSSLContext.getSocketFactory();
 5   //  Here will be their own channel Pass in 
 6   fx.createSocket(mSocketChannel.GetSocket(), mHost, mPort, false);
 7   mSSLEngine = this.InitSSLEngine(mSSLContext);
 8   //  Initialize the BUFFER
 9   int appBufSize = mSSLEngine.getSession().getApplicationBufferSize();
10   int netBufSize = mSSLEngine.getSession().getPacketBufferSize();
11   mAppDataBuf = ByteBuffer.allocate(appBufSize);
12   mNetDataBuf = ByteBuffer.allocate(netBufSize);
13   pAppDataBuf = ByteBuffer.allocate(appBufSize);
14   pNetDataBuf = ByteBuffer.allocate(netBufSize);
15   //  Initialization complete , Ready to shake hands 
16   mSSLInitiated = true;
17   mSSLEngine.beginHandshake();
18   this.ProcessHandShake(null);
19 }

3、 ... and 、 Shake hands

     The following figure briefly shows the handshake process , Initiated by client , The handshake operation is finally completed through the data exchange of some columns . To successfully establish a connection with the server , The handshake process is a very important link , fortunately SSEngine Certificate verification has been implemented internally 、 Exchange and other steps , We just need to perform specific actions on top of it ( Handshake status processing ).

    3.1 Handshake related status ( come from getHandshakeStatus Method )

        NEED_WRAP  The current handshake state indicates that data needs to be encrypted , The application layer data to be sent is encrypted and output as network layer data , And execute the send operation .

        NEED_UNWRAP  The current handshake state indicates that the data needs to be decrypted , The received network layer data will be decrypted into application layer data .

        NEED_TASK  The current handshake status indicates that a task needs to be executed , Because some operations may be time-consuming , If you don't want to block the process, you need to start asynchronous tasks for execution .

        FINISHED  Current handshake completed

        NOT_HANDSHAKING  No handshake is needed , This is mainly when reconnecting , Skip the handshake process to speed up .

    3.2  How to handle handshake

         The following code shows the processing of various states in the handshake process , The main logic is to perform encryption operation if encryption is required , If decryption is required, perform decryption ( crap @[email protected]!).

 1 protected void ProcessHandShake(SSLEngineResult result){
 2  if(this.isClosed() || this.isShutdown()) return;
 3  //  The difference is to come here WRAP UNWRAP call , Or other calls 
 4  SSLEngineResult.HandshakeStatus status;
 5  if(result != null){
 6   status = result.getHandshakeStatus(); 
 7  }else{
 8   status = mSSLEngine.getHandshakeStatus();
 9  }
10  switch(status)
11  {
12   //  Need encryption 
13   case NEED_WRAP:
14       // Judge isOutboundDone, When true when , It indicates that there is no need to deal with any NEED_WRAP Operation .
15       //  Because... Has been explicitly called closeOutbound, And even if you execute wrap,
16       // SSLEngineReulst.STATUS It must be CLOSED, It doesn't make any sense 
17       if(mSSLEngine.isOutboundDone()){
18         //  If there is still data, send it out 
19         if(mNetDataBuf.position() > 0) {
20             mNetDataBuf.flip();
21             mSocketChannel.WriteAndFlush(mNetDataBuf);
22         }
23         break;
24       }
25       //  Perform the encryption process 
26       this.ProcessWrapEvent();
27       break;
28   //  Need to decrypt 
29   case NEED_UNWRAP:
30    // Judge inboundDone Is it true, true explain peer The client sent close_notify,
31    // peer Sent close_notify May also be unwrap The operation captured , The result is the return CLOSED
32    if(mSSLEngine.isInboundDone()){
33     //peer End sending off , At this point, you need to determine whether to call closeOutbound
34     if(mSSLEngine.isOutboundDone()){
35      return;
36     }
37     mSSLEngine.closeOutbound();
38    }
39    break;
40   case NEED_TASK:
41    //  Perform asynchronous tasks , I'm here to execute synchronously , You can get an asynchronous thread pool to .
42    Runnable task = mSSLEngine.getDelegatedTask();
43    if(task != null){
44     task.run();  
45     // executor.execute(task);  It is also possible to use asynchrony in this way ,
46     // But asynchrony needs to be on ProcessHandShake Do special processing for the call of , Because asynchronous , Like the following, this will directly lead to crazy calls .
47    }
48    this.ProcessHandShake(null);  //  Continue with the handshake 
49    break;
50   case FINISHED:
51    //  The handshake is complete 
52    mHandshakeCompleted = true;
53    this.OnHandCompleted();
54    return;
55   case NOT_HANDSHAKING:
56    //  You don't need to shake hands 
57    if(!mHandshakeCompleted)
58    {
59     mHandshakeCompleted = true;
60     this.OnHandCompleted();
61    }
62    return;
63  }
64 }

Four 、 The sending and receiving of data

     After the handshake is successful, normal data transmission and reception can be carried out , However, additional encryption is required when sending data , Decrypt the data after receiving it .

     It needs to be explained here , You also need to read data during the handshake , Because the data sent by the server needs us to read and decrypt . This operation directly uses the blocking read method in some other examples , I put it here OnRead Handle after the event is called , That's how it fits NIO Model .

    4.1  Encryption operation (SelectionKey.OP_WRITE)

 1 protected void ProcessWrapEvent(){
 2  if(this.isClosed() || this.isShutdown()) return;
 3  SSLEngineResult result = mSSLEngine.wrap(mAppDataBuf, mNetDataBuf);
 4  //  Handle result
 5  if(ProcessSSLStatus(result, true)){
 6   mNetDataBuf.flip();
 7   mSocketChannel.WriteAndFlush(mNetDataBuf);
 8   //  Empty after sending buffer
 9   mNetDataBuf.clear();
10  }
11  mAppDataBuf.clear();
12  //  If you don't shake hands , Then continue to call handshake processing 
13  if(!mHandshakeCompleted)
14    this.ProcessHandShake(result);
15 }

    4.2 Decryption operation (SelectionKey.OP_READ)

 1 protected void ProcessUnWrapEvent(){
 2  if(this.isClosed() || this.isShutdown()) return;
 3  do{
 4   //  Perform decryption operation 
 5   SSLEngineResult res = mSSLEngine.unwrap(pNetDataBuf, pAppDataBuf);
 6   if(!ProcessSSLStatus(res, false))  
 7       //  There is no need for `pNetDataBuf` To deal with , because ProcessSSLStatus It's already taken care of .
 8    return;  
 9   if(res.getStatus() == Status.CLOSED)
10    break;
11   //  When the handshake is not completed , You need to continue calling handshake processing 
12   if(!mHandshakeCompleted)
13    this.ProcessHandShake(res);
14  }while(pNetDataBuf.hasRemaining());
15  //  The data is decrypted , This can be emptied .
16  if(!pNetDataBuf.hasRemaining())
17    pNetDataBuf.clear();
18 }

 

      The article comes from my official account , If you are interested, you can pay attention to , The specific scanning attention is shown in the figure below .

copyright notice
author[Explode the radish],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210823170947712r.html

Random recommended