JSSE(Java Secure Socket Extension)简单使用(三)

客户端和服务器端证书的生成,请看:

http://my.oschina.net/xinxingegeya/blog/266826

http://www.ibm.com/developerworks/cn/java/j-lo-socketkeytool/

参考文章:http://www.ibm.com/developerworks/cn/java/j-lo-socketkeytool/

参考文章:http://willtea.iteye.com/blog/1841281


双向认证

从别人那摘来的,讲的不错:

证书,就是用来自己证明身份的证书,你可以自己生成一个,但是没有用。为什么呢?因为数字证书必须由第三方的权威机构签名后才有效。 

权威机构就那几家,你可以打开你的任何浏览器,在选项里面,找高ssl相关的配置,可以看到每个浏览器里面,都有默认的几十家权威机构的数字签名文件。 

对于java环境,这些默认的机构也存在,它们的签名保存在keystore里面,我们自己生成的数字证书,都是自签名的,自己签自己。 

这种默认是不被浏览器或是jvm环境接受的,原因上面说了,因为我们自己签名不算数的,“自己”这个机构不在默认的keystore里面。 

所以,现在问题来了,怎么样让客户端信任服务端这个自签名的证书呢? 

答案你已经知道了,将自己的证书,导进需要信任你的证书的keystore里面。 

jvm有自已默认的keystore,在java的安装目录里面,是jre/lib/security/xxx那个。 

/usr/lib/jvm/jre/lib/security/cacerts 

里面包含了很多第三方的权威机构的数字证书。 

但是我们在做项目的时候,不想污染这个默认的库,会产生安全问题。 

所以我们用keytool自己生成一个全新的keystore,其实就是一个文件,你知道的。 

对于自签名的证书,肯定是不可能通过验证的 

如果我们把自签名的证书导进这个cacerts里面 

那么我们自己签的所有证书,就都在自己的java环境下面可以认证通过了。 

但是如果你想让别人的环境下面也认你这个证书,就要在别人的环境下面把你的证书给加进信任列表 

如果对方是个浏览器,就把证书导出来,让对方加进浏览器的信任列表 

如果对方是java,就让对方将你的证书导入到他们的keystore 

当然,如果你的证书是找Verisign签过名的,那就完全没问题了。 

但是Verisign非常贵,一年好多钱,所以只有生产系统上才用。因为,我们不可能让所有的用户都把服务端自签名证书导到他们的环境里,那是不现实的。而且,浏览器会弹窗报警,说服务端提供的证书不是受信的第三方签名的,用户一看有危险就闪人了。 

所以,说了这么半天,你应该明白了一个道理:SSL并不存在服务端和客户端,只存在谁需要信任谁 

那么我问个问题,双向握手时,需要几个证书?单向握手时,需要几个证书? 

如果服务端强制要求客户端提供证书 

那么他那边必定要给你生成一个能用的证书,在他的信任列表里面 

否则你自己生成的客户端证书是没有用的,必须找对方签名才可以 

因为对方的环境里面不可能信任你自生成的证书 

如果是双向握手,为什么需要给你两个证书? 

因为除了客户端要验证服务端身份, 服务器段也要验证客户端身份, 只有允许的客户端, 才能发起请求 

所以,如果服务端需要验证客户端身份,那对方必须给你一个他信任的客户端证书, 

要么就是:你自己生成一个客户端证书,让对方去签名。 

总之必须由对方提供,你自己生成的自签名证书,是不可能在对方的信任列表里的。 

如果手里现在只有一个证书,应该有两种情况:1. 这个是服务端证书,只需要单向握 2. 需要双向握手,对方少给你一个证书 

我文章中单向握手的客户端的例子 

客户端不向服务端提供客户端证书 

客户端只验证服务端证书 

即clientWithoutCert()这个列子 


理解了原理最重要 

后续的问题就都不难了。 


原理特别简单:你自己做张信用卡,自己签个名,去商店买东西,没人要 

于是你找银行给你背书:此信用卡有效,和银行账户绑定,你四处用,就okay了 

但是银行给你签名要花钱,你只是试着玩,于是你找朋友商店,让他给你签个名,你这张卡在他的店里面有效,于是,你的卡在他的店里就能用了 

所以不存在谁是客户端,谁是服务端,只是看谁签名,谁信任谁,就这样。把这个印在脑子里,就特别简单。 

keystore没什么神奇的,就是一个钱包,里面有你自己的卡(自己的证书),可以拿去用,还有几张证书(第三方机构的证书),别人在你这用卡的时候,你对证书查,发现别人的卡是你这证书上面机构发的,你就信任。 


服务器端

指定服务器端的证书库和信任库

 /**
         * 指定服务端程序要使用的证书库和密码:
         * 这是单向认证,只需客户端热证服务器端
         */
        System.setProperty("javax.net.ssl.keyStore", SERVER_KEY_STORE);
        System.setProperty("javax.net.ssl.keyStorePassword", SERVER_KEY_STORE_PASSWORD);

        /**
         * 指定服务器端要信任的证书库和密码
         */
        System.setProperty("javax.net.ssl.trustStore", SERVER_TRUST_KEY_STORE);
        System.setProperty("javax.net.ssl.trustStorePassword", SERVER_TRUST_KEY_STORE_PASSWORD);

注意serverSocket.setNeedClientAuth(true)表示要认证客户端身份

package ssl5;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import java.io.*;

/**
 * Created with IntelliJ IDEA.
 * User: ASUS
 * Date: 14-5-20
 * Time: 下午12:07
 * To change this template use File | Settings | File Templates.
 */
public class SSLServer {

    // 服务器端授权的用户名和密码
    private static final String USER_NAME = "principal";
    private static final String PASSWORD = "credential";
    // 服务器端保密内容
    private static final String SECRET_CONTENT = "This is confidential content from server X, for your eye!";

    /**
     * 服务器端使用的证书库和信任库,还有密码
     */
    private static String SERVER_KEY_STORE = "E:\\javassl2\\sslserverkeys";
    private static String SERVER_TRUST_KEY_STORE = "E:\\javassl2\\sslservertrust";
    private static String SERVER_KEY_STORE_PASSWORD = "123456";
    private static String SERVER_TRUST_KEY_STORE_PASSWORD = "123456";

    private SSLServerSocket serverSocket = null;

    public SSLServer() throws Exception {
        /**
         * 指定服务端程序要使用的证书库和密码:
         * 这是单向认证,只需客户端热证服务器端
         */
        System.setProperty("javax.net.ssl.keyStore", SERVER_KEY_STORE);
        System.setProperty("javax.net.ssl.keyStorePassword", SERVER_KEY_STORE_PASSWORD);

        /**
         * 指定服务器端要信任的证书库和密码
         */
        System.setProperty("javax.net.ssl.trustStore", SERVER_TRUST_KEY_STORE);
        System.setProperty("javax.net.ssl.trustStorePassword", SERVER_TRUST_KEY_STORE_PASSWORD);

        // 使用默认方式获取套接字工厂实例
        SSLServerSocketFactory socketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        serverSocket = (SSLServerSocket) socketFactory.createServerSocket(7070);
        /**
         * 设置要验证客户端身份
         */
        serverSocket.setNeedClientAuth(true);
    }

    private void runServer() {
        while (true) {
            try {
                System.out.println("Waiting for connection...");
                // 服务器端套接字进入阻塞状态,等待来自客户端的连接请求
                SSLSocket socket = (SSLSocket) serverSocket.accept();

                // 获取服务器端套接字输入流
                BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                // 从输入流中读取客户端用户名和密码
                String userName = input.readLine();
                String password = input.readLine();

                // 获取服务器端套接字输出流
                PrintWriter output = new PrintWriter(
                        new OutputStreamWriter(socket.getOutputStream()));

                // 对请求进行认证,如果通过则将保密内容发送给客户端
                if (userName.equals(USER_NAME) && password.equals(PASSWORD)) {
                    output.println("Welcome, " + userName);
                    output.println(SECRET_CONTENT);
                } else {
                    output.println("Authentication failed, you have no access to server X...");
                }

                // 关闭流资源和套接字资源
                output.close();
                input.close();
                socket.close();

            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

    public static void main(String args[]) throws Exception {
        SSLServer server = new SSLServer();
        server.runServer();
    }
}


客户端

指定客户端的证书库和信任库

        /**
         * 客户端指定信任库和密码
         * 如果服务端程序 setNeedClientAuth(true) 要求验证客户端身份,则我们还需要指定证书库和密码:
         */
        System.setProperty("javax.net.ssl.trustStore", CLIENT_TRUST_KEY_STORE);
        System.setProperty("javax.net.ssl.trustStorePassword", CLIENT_TRUST_KEY_STORE_PASSWORD);
        /**
         * 客户端指定证书库和密码
         * 服务器要验证客户端的身份
         */
        System.setProperty("javax.net.ssl.keyStore", CLIENT_KEY_STORE);
        System.setProperty("javax.net.ssl.keyStorePassword", CLIENT_KEY_STORE_PASSWORD);

前面代码serverSocket.setNeedClientAuth(true);指定要验证客户端。

package ssl5;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;

/**
 * Created with IntelliJ IDEA.
 * User: ASUS
 * Date: 14-5-20
 * Time: 下午12:06
 * To change this template use File | Settings | File Templates.
 */
public class SSLClient {

    private static String CLIENT_KEY_STORE = "E:\\javassl2\\sslclientkeys";
    private static String CLIENT_TRUST_KEY_STORE = "E:\\javassl2\\sslclienttrust";
    private static String CLIENT_KEY_STORE_PASSWORD = "123456";
    private static String CLIENT_TRUST_KEY_STORE_PASSWORD = "123456";

    private SSLSocket socket = null;

    public SSLClient() throws IOException {
        /**
         * 客户端指定信任库和密码
         * 如果服务端程序 setNeedClientAuth(true) 要求验证客户端身份,则我们还需要指定证书库和密码:
         */
        System.setProperty("javax.net.ssl.trustStore", CLIENT_TRUST_KEY_STORE);
        System.setProperty("javax.net.ssl.trustStorePassword", CLIENT_TRUST_KEY_STORE_PASSWORD);
        /**
         * 客户端指定证书库和密码
         * 服务器要验证客户端的身份
         */
        System.setProperty("javax.net.ssl.keyStore", CLIENT_KEY_STORE);
        System.setProperty("javax.net.ssl.keyStorePassword", CLIENT_KEY_STORE_PASSWORD);
        // 通过套接字工厂,获取一个客户端套接字
        SSLSocketFactory socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        socket = (SSLSocket) socketFactory.createSocket("localhost", 7070);
    }

    public void connect() {
        try {
            // 获取客户端套接字输出流
            PrintWriter output = new PrintWriter(
                    new OutputStreamWriter(socket.getOutputStream()));
            // 将用户名和密码通过输出流发送到服务器端
            String userName = "principal";
            output.println(userName);
            String password = "credential";
            output.println(password);
            output.flush();

            // 获取客户端套接字输入流
            BufferedReader input = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));
            // 从输入流中读取服务器端传送的数据内容,并打印出来
            String response = input.readLine();
            response += "\n " + input.readLine();
            System.out.println(response);

            // 关闭流资源和套接字资源
            output.close();
            input.close();
            socket.close();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        } finally {
            System.exit(0);
        }
    }

    public static void main(String args[]) throws IOException {
        new SSLClient().connect();
    }
}



你可能感兴趣的:(安全套接字编程)