天天看点

SSLEngine + NIO 实现SSL握手协议

Java 提供了阻塞和非阻塞I/O。非阻塞的I/O,大大提高了服务器的扩展性和伸缩性。

SSLServerSocket和SSLSocket是阻塞的socket调用。这连个类分别继承自ServerSocket和Socket, 封装了SSL(Secure Socket Layer)和TLS(Transport Layer Security), 提供了安全套接字。ServerSocket和Socket是阻塞的现实, 因此SSLServerSocket和SSLSocket也是阻塞的实现。阻塞实现的套接字的好处的是编程简单,学习成本低;缺点是扩展性和并发性较差。没法满足大并发服务器的实现要求。

如果要实现非阻塞的安全套接字,需要将SSLEngine和SocketChannel结合使用。

安全通信模型包括:

1)数据一致性保护。数据一致性通过加密套件和信息摘要套件实现

2)认证。Client和Server互相认证

3)机密性。 数据被加密后传输,没有明文数据(plain text)在网络上传播。

SSLEngine负责入栈和出栈数据加密和解密以及加密解密算法的协商。加密解密的算法是通过握手协议协商完成的。

握手过程如下:

1. 客户端想服务器端发送信息。信息包括支持的最高版本的SSL和加密套件列表。

2. 服务器发送支持的SSL和加密套件。完成加密套件的协商。

3.服务器端生成密钥并用公钥加密发送给服务器,服务器用私钥解密。

4.客户端和服务器用第三步生成的密钥加密数据进行传输

客户端代码如下:

package com.net;

import java.io.IOException;

import java.io.InputStream;

import java.net.InetSocketAddress;

import java.net.Authenticator.RequestorType;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.SocketChannel;

import java.security.KeyStore;

import java.util.Iterator;

import java.util.Scanner;

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.SSLSession;

import javax.net.ssl.TrustManagerFactory;

import javax.net.ssl.SSLEngineResult.HandshakeStatus;

import javax.net.ssl.SSLEngineResult.Status;

public class SSLHandshakeClient {

    private static Logger logger = Logger.getLogger(SSLHandshakeClient.class.getName());

    private SocketChannel sc;

    private SSLEngine sslEngine;

    private Selector selector;

    private HandshakeStatus hsStatus;

    private Status status;

    private ByteBuffer myNetData;

    private ByteBuffer myAppData;

    private ByteBuffer peerNetData;

    private ByteBuffer peerAppData;

    private ByteBuffer dummy = ByteBuffer.allocate(0);

    public void run() throws Exception{

        char[] password = "123456".toCharArray();

        KeyStore trustStore = KeyStore.getInstance("JKS");

        InputStream in = this.getClass().getResourceAsStream("clienttruststore.jks");

        trustStore.load(in,password);

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

        tmf.init(trustStore);

        SSLContext sslContext = SSLContext.getInstance("SSL");

        sslContext.init(null, tmf.getTrustManagers(), null);

        sslEngine = sslContext.createSSLEngine();

        sslEngine.setUseClientMode(true);

        SSLSession session = sslEngine.getSession();

        myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());

        myNetData = ByteBuffer.allocate(session.getPacketBufferSize());

        peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());

        peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());

        peerNetData.clear();

        SocketChannel channel = SocketChannel.open();

        channel.configureBlocking(false);

        selector = Selector.open();

        channel.register(selector, SelectionKey.OP_CONNECT);

        channel.connect(new InetSocketAddress("localhost", 443));

        sslEngine.beginHandshake();

        hsStatus = sslEngine.getHandshakeStatus();

        while(true){

            selector.select();

            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while (it.hasNext()) {

                SelectionKey selectionKey = it.next();

                it.remove();

                handleSocketEvent(selectionKey);

            }

        }

    }

    private void handleSocketEvent(SelectionKey key) throws IOException{

        if(key.isConnectable()){

            sc = (SocketChannel)key.channel();

            if(sc.isConnectionPending()){

                sc.finishConnect();

            }

            doHandshake();

            sc.register(selector, SelectionKey.OP_READ);

        }

        if(key.isReadable()){

            sc = (SocketChannel)key.channel();

            doHandshake();

            if(hsStatus == HandshakeStatus.FINISHED){

                logger.info("Client handshake completes... ...");

                key.cancel();

                sc.close();

            }

        }

    }

    private void doHandshake() throws IOException{

        SSLEngineResult result;

        int count = 0;

        while(hsStatus != HandshakeStatus.FINISHED){

            logger.info("handshake status: " + hsStatus);

            switch (hsStatus) {

            case NEED_TASK:

                Runnable runnable;

                while((runnable=sslEngine.getDelegatedTask()) != null){

                    runnable.run();

                }

                hsStatus = sslEngine.getHandshakeStatus();

                break;

            case NEED_UNWRAP:

                count = sc.read(peerNetData);

                if(count < 0){

                    logger.info("no data is read for unwrap.");

                    break;

                }else{

                    logger.info("data read: " + count);

                }

                peerNetData.flip();

                peerAppData.clear();

                do {

                    result = sslEngine.unwrap(peerNetData, peerAppData);

                    logger.info("Unwrapping:\n" + result);

                    // During an handshake renegotiation we might need to perform

                    // several unwraps to consume the handshake data.

                } while (result.getStatus() == SSLEngineResult.Status.OK &&

                        result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&

                        result.bytesProduced() == 0);

                if (peerAppData.position() == 0 &&

                    result.getStatus() == SSLEngineResult.Status.OK &&

                    peerNetData.hasRemaining())

                {

                    result = sslEngine.unwrap(peerNetData, peerAppData);

                    logger.info("Unwrapping:\n" + result);

                }

                hsStatus = result.getHandshakeStatus();

                status = result.getStatus();

                assert status != status.BUFFER_OVERFLOW : "buffer not overflow." + status.toString();

                // Prepare the buffer to be written again.

                peerNetData.compact();

                // And the app buffer to be read.

                peerAppData.flip();

                break;

            case NEED_WRAP:

                myNetData.clear();

                result = sslEngine.wrap(dummy, myNetData);

                hsStatus = result.getHandshakeStatus();

                status = result.getStatus();

                while (status != Status.OK) {

                    logger.info("status: " + status);

                    switch (status) {

                    case BUFFER_OVERFLOW:

                        break;

                    case BUFFER_UNDERFLOW:

                        break;

                    }

                }

                myNetData.flip();

                count = sc.write(myNetData);

                if(count <=0){

                    logger.info("No data is written.");

                }else{

                    logger.info("Written data: " + count);

                }

                break;

            }

        }

    }

    public static void main(String[] args) throws Exception {

        new SSLHandshakeClient().run();

    }

}

服务器代码:

package com.net;

import java.io.IOException;

import java.io.InputStream;

import java.net.InetSocketAddress;

import java.net.ServerSocket;

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.security.KeyStore;

import java.util.Iterator;

import java.util.logging.Logger;

import javax.net.ssl.KeyManagerFactory;

import javax.net.ssl.SSLContext;

import javax.net.ssl.SSLEngine;

import javax.net.ssl.SSLEngineResult;

import javax.net.ssl.SSLSession;

import javax.net.ssl.SSLEngineResult.HandshakeStatus;

import javax.net.ssl.SSLEngineResult.Status;

public class SSLHandshakeServer {

    private static Logger logger = Logger.getLogger(SSLHandshakeServer.class.getName());

    private SocketChannel sc;

    private SSLEngine sslEngine;

    private Selector selector;

    private ByteBuffer myNetData;

    private ByteBuffer myAppData;

    private ByteBuffer peerNetData;

    private ByteBuffer peerAppData;

    private ByteBuffer dummy = ByteBuffer.allocate(0);

    private HandshakeStatus hsStatus;

    private Status status;

    public void run() throws Exception{

        char[] password = "123456".toCharArray();

        KeyStore keyStore = KeyStore.getInstance("JKS");

        InputStream in = this.getClass().getResourceAsStream("serverkeystore");

        keyStore.load(in, password);

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");

        kmf.init(keyStore, password);

        SSLContext sslContext = SSLContext.getInstance("SSL");

        sslContext.init(kmf.getKeyManagers(), null, null);

        sslEngine = sslContext.createSSLEngine();

        sslEngine.setUseClientMode(false);

        SSLSession session = sslEngine.getSession();

        myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());

        myNetData = ByteBuffer.allocate(session.getPacketBufferSize());

        peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());

        peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());

        peerNetData.clear();

        ServerSocketChannel serverChannel = ServerSocketChannel.open();

        serverChannel.configureBlocking(false);

        selector = Selector.open();

        ServerSocket serverSocket = serverChannel.socket();

        serverSocket.bind(new InetSocketAddress(443));

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        logger.info("Server listens on port 443... ...");

        while (true) {

            selector.select();

            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while (it.hasNext()) {

                SelectionKey selectionKey = it.next();

                it.remove();

                handleRequest(selectionKey);

            }

        }

    }

    private void handleRequest(SelectionKey key) throws Exception {

        if (key.isAcceptable()) {

            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

            SocketChannel channel = ssc.accept();

            channel.configureBlocking(false);

            channel.register(selector, SelectionKey.OP_READ);

        } else if (key.isReadable()) {

            sc = (SocketChannel) key.channel();

            logger.info("Server handshake begins... ...");

            sslEngine.beginHandshake();

            hsStatus = sslEngine.getHandshakeStatus();

            doHandshake();

            if(hsStatus == HandshakeStatus.FINISHED){

                key.cancel();

                sc.close();

            }

            logger.info("Server handshake completes... ...");

        }

    }

    private void doHandshake() throws IOException{

        SSLEngineResult result;

        while(hsStatus != HandshakeStatus.FINISHED){

            logger.info("handshake status: " + hsStatus);

            switch (hsStatus) {

            case NEED_TASK:

                Runnable runnable;

                while((runnable=sslEngine.getDelegatedTask()) != null){

                    runnable.run();

                }

                hsStatus = sslEngine.getHandshakeStatus();

                break;

            case NEED_UNWRAP:

                int count = sc.read(peerNetData);

                if(count < 0){

                    logger.info("no data is read for unwrap.");

                    break;

                }else{

                    logger.info("data read: " + count);

                }

                peerNetData.flip();

                peerAppData.clear();

                do {

                    result = sslEngine.unwrap(peerNetData, peerAppData);

                    logger.info("Unwrapping:\n" + result);

                    // During an handshake renegotiation we might need to perform

                    // several unwraps to consume the handshake data.

                } while (result.getStatus() == SSLEngineResult.Status.OK &&

                        result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&

                        result.bytesProduced() == 0);

                if (peerAppData.position() == 0 &&

                    result.getStatus() == SSLEngineResult.Status.OK &&

                    peerNetData.hasRemaining())

                {

                    result = sslEngine.unwrap(peerNetData, peerAppData);

                    logger.info("Unwrapping:\n" + result);

                }

                hsStatus = result.getHandshakeStatus();

                status = result.getStatus();

                assert status != status.BUFFER_OVERFLOW : "buffer not overflow." + status.toString();

                // Prepare the buffer to be written again.

                peerNetData.compact();

                // And the app buffer to be read.

                peerAppData.flip();

                break;

            case NEED_WRAP:

                myNetData.clear();

                result = sslEngine.wrap(dummy, myNetData);

                hsStatus = result.getHandshakeStatus();

                status = result.getStatus();

                while (status != Status.OK) {

                    logger.info("status: " + status);

                    switch (status) {

                    case BUFFER_OVERFLOW:

                        break;

                    case BUFFER_UNDERFLOW:

                        break;

                    }

                }

                myNetData.flip();

                sc.write(myNetData);

                break;

            }

        }

    }

    public static void main(String[] args) throws Exception {

        new SSLHandshakeServer().run();

    }

}

证书生成过程如下:

1. 生成keystore和自签名的certificate, 并生成相应公钥和私钥

%keytool -genkeypair -alias duke -keyalg RSA -validity 7 -keystore keystore 

Enter keystore password: password

What is your first and last name? [Unknown]: Duke

What is the name of your organizational unit? [Unknown]: Java Software

What is the name of your organization? [Unknown]: Sun Microsystems, Inc.

What is the name of your City or Locality? [Unknown]: Palo Alto

What is the name of your State or Province? [Unknown]: CA

What is the two-letter country code for this unit? [Unknown]: US

Is CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.",

L=Palo Alto, ST=CA, C=US correct?

[no]: yes

Enter key password for <duke> (RETURN if same as keystore password): <CR>

2.查看keystore

%keytool -list -v -keystore keystore

Enter keystore password: password

Keystore type: jks

Keystore provider: SUN

Your keystore contains 1 entry

Alias name: duke

Creation date: Dec 20, 2001

Entry type: keyEntry

Certificate chain length: 1

Certificate[1]:

Owner: CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.",L=Palo Alto, ST=CA, C=US

Issuer: CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Palo Alto, ST=CA, C=US

Serial number: 3c22adc1

Valid from: Thu Dec 20 19:34:25 PST 2001 until: Thu Dec 27 19:34:25 PST 2001

Certificate fingerprints: MD5: F1:5B:9B:A1:F7:16:CF:25:CF:F4:FF:35:3F:4C:9C:F0

SHA1: B2:00:50:DD:B6:CC:35:66:21:45:0F:96:AA:AF:6A:3D:E4:03:7C:74

3. 导出证书

%keytool -export -alias duke -keystore keystore -rfc -file duke.cer

Enter keystore password: password

Certificate stored in file <duke.cer>

% cat duke.cer

-----BEGIN CERTIFICATE-----MIICXjCCAccCBDwircEwDQYJKoZIhvcNAQEEBQAwdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xHzAdBgNVBAoTFlN1biBNaWNyb3N5c3RlbXMsIEluYy4xFjAUBgNVBAsTDUphdmEgU29mdHdhcmUxDTALBgNVBAMTBER1a2UwHhcNMDExMjIxMDMzNDI1WhcNMDExMjI4MDMzNDI1WjB2MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEfMB0GA1UEChMWU3VuIE1pY3Jvc3lzdGVtcywgSW5jLjEWMBQGA1UECxMNSmF2YSBTb2Z0d2FyZTENMAsGA1UEAxMERHVrZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1loObJzNXsi5aSr8N4XzDksD6GjTHFeqG9DUFXKEOQetfYXvA8F9uWtz8WInrqskLTNzwXgmNeWkoM7mrPpK6Rf5M3G1NXtYzvxyi473Gh1h9k7tjJvqSVKO7E1oFkQYeUPYifxmjbSMVirWZgvo2UmA1c76oNK+NhoHJ4qjeCUCAwEAATANBgkqhkiG9w0BAQQFAAOBgQCRPoQYw9rWWvfLPQuPXowvFmuebsTc28qI7iFWm6BJTT/qdmzti7B5MHOt9BeVEft3mMeBU0CS2guaBjDpGlf+zsK/UUi1w9C4mnwGDZzqY/NKKWtLxabZ5M+4MAKLZ92ePPKGpobM2CPLfM8ap4IgAzCbBKd8+CMp8yFmifze9Q==

-----END CERTIFICATE-----

4. 将第三步导出的证书导入到一个truststore

% keytool -import -alias dukecert -file duke.cer -keystore truststore

Enter keystore password: trustword

Owner: CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Palo Alto, ST=CA, C=US

Issuer: CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Palo Alto, ST=CA, C=US

Serial number: 3c22adc1

Valid from: Thu Dec 20 19:34:25 PST 2001 until: Thu Dec 27 19:34:25 PST 2001

Certificate fingerprints:

MD5: F1:5B:9B:A1:F7:16:CF:25:CF:F4:FF:35:3F:4C:9C:F0

SHA1: B2:00:50:DD:B6:CC:35:66:21:45:0F:96:AA:AF:6A:3D:E4:03:7C:74

Trust this certificate? [no]: yes

Certificate was added to keystore

5. 检查 truststore

% keytool -list -v -keystore truststore

Enter keystore password: trustword

Keystore type: jks

Keystore provider: SUN

Your keystore contains 1 entry

Alias name: dukecert

Creation date: Dec 20, 2001

Entry type: trustedCertEntry

Owner: CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Palo Alto, ST=CA, C=US

Issuer: CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Palo Alto, ST=CA, C=USSerial number: 3c22adc1

Valid from: Thu Dec 20 19:34:25 PST 2001 until: Thu Dec 27 19:34:25 PST 2001

Certificate fingerprints:

MD5: F1:5B:9B:A1:F7:16:CF:25:CF:F4:FF:35:3F:4C:9C:F0

SHA1: B2:00:50:DD:B6:CC:35:66:21:45:0F:96:AA:AF:6A:3D:E4:03:7C:74