客户端和服务器端证书的生成,请看:
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(); } }