参考:
####一. SSLSocket是什么?
- JDK文档指出,SSLSocket扩展Socket并提供使用SSL或TLS协议的安全套接字。
- 这种套接字是正常的流套接字,但是它们在基础网络传输协议(如TCP)上添加了安全保护层。
- 具体安全方面的讨论见下一篇。本篇重点关注SSLSocket及相关几个类的使用。
####二.与SSLSocket相关的工具类
SSLSocket来自jsse(Java Secure Socket Extension)
- SSLContext: 此类的实例表示安全套接字协议的实现, 它是SSLSocketFactory、SSLServerSocketFactory和SSLEngine的工厂。
- SSLSocket: 扩展自Socket
- SSLServerSocket: 扩展自ServerSocket
- SSLSocketFactory: 抽象类,扩展自SocketFactory, SSLSocket的工厂
- SSLServerSocketFactory: 抽象类,扩展自ServerSocketFactory, SSLServerSocket的工厂
- KeyStore: 表示密钥和证书的存储设施
- KeyManager: 接口,JSSE密钥管理器
- TrustManager: 接口,信任管理器(?翻译得很拗口)
- X590TrustedManager: TrustManager的子接口,管理X509证书,验证远程安全套接字
####三.SSLContext的使用
public static void main(String[] args) throws Exception { X509TrustManager x509m = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } }; // 获取一个SSLContext实例 SSLContext s = SSLContext.getInstance("SSL"); // 初始化SSLContext实例 s.init(null, new TrustManager[] { x509m }, new java.security.SecureRandom()); // 打印这个SSLContext实例使用的协议 System.out.println("缺省安全套接字使用的协议: " + s.getProtocol()); // 获取SSLContext实例相关的SSLEngine SSLEngine e = s.createSSLEngine(); System.out .println("支持的协议: " + Arrays.asList(e.getSupportedProtocols())); System.out.println("启用的协议: " + Arrays.asList(e.getEnabledProtocols())); System.out.println("支持的加密套件: " + Arrays.asList(e.getSupportedCipherSuites())); System.out.println("启用的加密套件: " + Arrays.asList(e.getEnabledCipherSuites())); }
运行结果如下:
SSLContext.getProtocol(): 返回当前SSLContext对象的协议名称SSLContext.init(): 初始化当前SSLContext对象。 三个参数均可以为null。 详见JDK文档。SSLEngine.getSupportedProtocols()等几个方法可以返回些 Engine上支持/已启用的协议、支持/已启用的加密套件
####四. SSLSocket和SSLServerSocket的使用
这两个类的用法跟Socket/ServerSocket的用法比较类似。看下面的例子(主要为了验证SSLSocket的用法 ,I/O和多线程处理比较随意)
4.1 SSLServerSocket服务器创建 (1)新建一个SSLServerSocket,并开始监听来自客户端的连接
// 抛出异常 // javax.net.ssl.SSLException: No available certificate or key corresponds // to the SSL cipher suites which are enabled. public static void notOk() throws IOException { SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory .getDefault(); SSLServerSocket server = (SSLServerSocket) factory .createServerSocket(10000); System.out.println("ok"); server.accept(); }
server.accept()处抛出异常, 提示缺少证书。与ServerSocket不同, SSLServerSocket需要证书来进行安全验证。
4.2使用keytool工具生成一个证书 步骤如下, 得到一个名为cmkey的证书文件
重新完善上面的代码。 主要增加两个功能: 使用名为cmkey的证书初始化SSLContext, echo客户端的消息。 代码如下
// 启动一个ssl server socket // 配置了证书, 所以不会抛出异常 public static void sslSocketServer() throws Exception { // key store相关信息 String keyName = "cmkey"; char[] keyStorePwd = "123456".toCharArray(); char[] keyPwd = "123456".toCharArray(); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); // 装载当前目录下的key store. 可用jdk中的keytool工具生成keystore InputStream in = null; keyStore.load(in = Test2.class.getClassLoader().getResourceAsStream( keyName), keyPwd); in.close(); // 初始化key manager factory KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(keyStore, keyPwd); // 初始化ssl context SSLContext context = SSLContext.getInstance("SSL"); context.init(kmf.getKeyManagers(), new TrustManager[] { new MyX509TrustManager() }, new SecureRandom()); // 监听和接收客户端连接 SSLServerSocketFactory factory = context.getServerSocketFactory(); SSLServerSocket server = (SSLServerSocket) factory .createServerSocket(10002); System.out.println("ok"); Socket client = server.accept(); System.out.println(client.getRemoteSocketAddress()); // 向客户端发送接收到的字节序列 OutputStream output = client.getOutputStream(); // 当一个普通 socket 连接上来, 这里会抛出异常 // Exception in thread "main" javax.net.ssl.SSLException: Unrecognized // SSL message, plaintext connection? InputStream input = client.getInputStream(); byte[] buf = new byte[1024]; int len = input.read(buf); System.out.println("received: " + new String(buf, 0, len)); output.write(buf, 0, len); output.flush(); output.close(); input.close(); // 关闭socket连接 client.close(); server.close(); }
这样就可以正常启动SSLServerSocket服务器
4.2 SSLSocket链接服务器 (1)我们先使用一个普通的Socket尝试连接服务器端
// 通过socket连接服务器 public static void socket() throws UnknownHostException, IOException { Socket s = new Socket("localhost", 10002); System.out.println(s); System.out.println("ok"); OutputStream output = s.getOutputStream(); InputStream input = s.getInputStream(); output.write("alert".getBytes()); System.out.println("sent: alert"); output.flush(); byte[] buf = new byte[1024]; int len = input.read(buf); System.out.println("received:" + new String(buf, 0, len)); }
结果客户端和服务器端都出错。 客户端的错误是接收到乱码。
服务器则抛出异常 javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
4.3改成SSLSocket, 但是不使用证书。 客户端抛出sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
// 不使用证书, 通过ssl socket连接服务器 // 抛出异常, 提示找不到证书 public static void sslSocket() throws UnknownHostException, IOException { SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory .getDefault(); SSLSocket s = (SSLSocket) factory.createSocket("localhost", 10002); System.out.println("ok"); OutputStream output = s.getOutputStream(); InputStream input = s.getInputStream(); output.write("alert".getBytes()); System.out.println("sent: alert"); output.flush(); byte[] buf = new byte[1024]; int len = input.read(buf); System.out.println("received:" + new String(buf, 0, len)); }
程序客户在不持有证书的情况下直接进行连接,服务器端会产生运行时异常javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown,不允许进行连接。 我们可以指定像下面这样执行客户端,服务器端可以成功echo客户端的发出的字符串"alert"
java -Djavax.net.ssl.trustStore=cmkey Client
这里的cmkey即前面生成的证书文件。
4.4 使用SSLContext对SSLSocket初始化(仍旧不使用证书)
public static void sslSocket2() throws Exception { SSLContext context = SSLContext.getInstance("SSL"); // 初始化 context.init(null, new TrustManager[] { new Test2.MyX509TrustManager() }, new SecureRandom()); SSLSocketFactory factory = context.getSocketFactory(); SSLSocket s = (SSLSocket) factory.createSocket("localhost", 10002); System.out.println("ok"); OutputStream output = s.getOutputStream(); InputStream input = s.getInputStream(); output.write("alert".getBytes()); System.out.println("sent: alert"); output.flush(); byte[] buf = new byte[1024]; int len = input.read(buf); System.out.println("received:" + new String(buf, 0, len)); }
服务器端可以成功echo客户端的发出的字符串"alert"。 完整代码见附件。
####五.SSLSocket加载证书(非原文部分) 对于想要在实现SSLSocket加载证书的SSLContext,可以看看这里
/** * 获得SSLSocketFactory. * @param password * 密码 * @param keyStorePath * 密钥库路径 * @param trustStorePath * 信任库路径 * @return SSLSocketFactory * @throws Exception */ public static SSLContext getSSLContext(String password, String keyStorePath, String trustStorePath) throws Exception { // 实例化密钥库 KeyManagerFactory keyManagerFactory = KeyManagerFactory .getInstance(KeyManagerFactory.getDefaultAlgorithm()); // 获得密钥库 KeyStore keyStore = getKeyStore(password, keyStorePath); // 初始化密钥工厂 keyManagerFactory.init(keyStore, password.toCharArray()); // 实例化信任库 TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); // 获得信任库 KeyStore trustStore = getKeyStore(password, trustStorePath); // 初始化信任库 trustManagerFactory.init(trustStore); // 实例化SSL上下文 SSLContext ctx = SSLContext.getInstance("TLS"); // 初始化SSL上下文 ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); // 获得SSLSocketFactory return ctx; }
###这里我们使用一个常见的SSLTrustManager来构建我们的Socket
public class SSLTrustManager implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager ,HostnameVerifier{ public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } public boolean isServerTrusted( java.security.cert.X509Certificate[] certs) { return true; } public boolean isClientTrusted( java.security.cert.X509Certificate[] certs) { return true; } public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException { return; } public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException { return; } @Override public boolean verify(String urlHostName, SSLSession session) { //允许所有主机 return true; } //封装 public static HttpURLConnection connectTrustAllServer(String strUrl) throws Exception { return connectTrustAllServer(strUrl,null); } //封装 public static HttpURLConnection connectTrustAllServer(String strUrl,Proxy proxy) throws Exception { javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1]; javax.net.ssl.TrustManager tm = new SSLTrustManager(); trustAllCerts[0] = tm; javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext .getInstance("TLS"); sc.init(null, trustAllCerts, null); javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc .getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((HostnameVerifier) tm); URL url = new URL(strUrl); HttpURLConnection urlConn = null; if(proxy==null) { urlConn = (HttpURLConnection) url.openConnection(); }else{ urlConn = (HttpURLConnection) url.openConnection(proxy); } urlConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"); return urlConn; } //用于双向认证 public static HttpURLConnection connectProxyTrustCA(String strUrl,Proxy proxy) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException { HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslsession) { return true; } }); String clientKeyStoreFile = "D:/JDK8Home/twt/sslClientKeys"; String clientKeyStorePwd = "123456"; String catServerKeyPwd = "123456"; String serverTrustKeyStoreFile = "D:/JDK8Home/twt/sslClientTrust"; String serverTrustKeyStorePwd = "123456"; KeyStore serverKeyStore = KeyStore.getInstance("JKS"); serverKeyStore.load(new FileInputStream(clientKeyStoreFile), clientKeyStorePwd.toCharArray()); KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS"); serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(serverKeyStore, catServerKeyPwd.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(serverTrustKeyStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); URL url = new URL(strUrl); HttpURLConnection httpURLConnection = null; if(proxy==null) { httpURLConnection = (HttpURLConnection) url.openConnection(); }else{ httpURLConnection = (HttpURLConnection) url.openConnection(proxy); } httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"); return httpURLConnection; } /** * 用于单向认证 * server侧只需要自己的keystore文件,不需要truststore文件 * client侧不需要自己的keystore文件,只需要truststore文件(其中包含server的公钥)。 * 此外server侧需要在创建SSLServerSocket之后设定不需要客户端证书:setNeedClientAuth(false) * @param strUrl * @param proxy * @return * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws CertificateException * @throws FileNotFoundException * @throws IOException * @throws UnrecoverableKeyException * @throws KeyManagementException */ public static HttpURLConnection connectProxyTrustCA2(String strUrl,Proxy proxy) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException { HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslsession) { return true; } }); String serverTrustKeyStoreFile = "D:/JDK8Home/twt/sslClientTrust"; String serverTrustKeyStorePwd = "123456"; KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS"); serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(serverTrustKeyStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); URL url = new URL(strUrl); HttpURLConnection httpURLConnection = null; if(proxy==null) { httpURLConnection = (HttpURLConnection) url.openConnection(); }else{ httpURLConnection = (HttpURLConnection) url.openConnection(proxy); } httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"); return httpURLConnection; } //用于双向认证 public static SSLSocket createTlsConnect(Socket socketClient) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException { String protocol = "TLS"; String serverKey = "D:/JDK8Home/twt/sslServerKeys"; String serverTrust = "D:/JDK8Home/twt/sslServerTrust"; String serverKeyPwd = "123456"; //私钥密码 String serverTrustPwd = "123456"; //信任证书密码 String serverKeyStorePwd = "123456"; // keystore存储密码 KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(serverKey),serverKeyPwd.toCharArray()); KeyStore tks = KeyStore.getInstance("JKS"); tks.load(new FileInputStream(serverTrust), serverTrustPwd.toCharArray()); KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); km.init(keyStore, serverKeyStorePwd.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(tks); SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(km.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); //第一项是用来做服务器验证的 SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); SSLSocket clientSSLSocket = (SSLSocket) sslSocketFactory.createSocket(socketClient,socketClient.getInetAddress().getHostAddress(),socketClient.getPort(), true); clientSSLSocket.setNeedClientAuth(false); clientSSLSocket.setUseClientMode(false); return clientSSLSocket; } /** * 用于单向认证 * server侧只需要自己的keystore文件,不需要truststore文件 * client侧不需要自己的keystore文件,只需要truststore文件(其中包含server的公钥)。 * 此外server侧需要在创建SSLServerSocket之后设定不需要客户端证书:setNeedClientAuth(false) * @param socketClient * @return * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws CertificateException * @throws FileNotFoundException * @throws IOException * @throws UnrecoverableKeyException * @throws KeyManagementException */ public static SSLSocket createTlsConnect2(Socket socketClient) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException { String protocol = "TLS"; String serverKey = "D:/JDK8Home/twt/sslServerKeys"; String serverKeyPwd = "123456"; //私钥密码 String serverKeyStorePwd = "123456"; // keystore存储密码 KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(serverKey),serverKeyPwd.toCharArray()); KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); km.init(keyStore, serverKeyStorePwd.toCharArray()); SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(km.getKeyManagers(), null, new SecureRandom()); //第一项是用来做服务器验证的 SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); SSLSocket clientSSLSocket = (SSLSocket) sslSocketFactory.createSocket(socketClient,socketClient.getInetAddress().getHostAddress(),socketClient.getPort(), true); clientSSLSocket.setNeedClientAuth(false); clientSSLSocket.setUseClientMode(false); return clientSSLSocket; } public static SSLSocket getTlsTrustAllSocket(Socket remoteHost,boolean isClient) { SSLSocket remoteSSLSocket = null; SSLContext context = SSLTrustManager.getTrustAllSSLContext(isClient); try { remoteSSLSocket = (SSLSocket) context.getSocketFactory().createSocket(remoteHost, remoteHost.getInetAddress().getHostName(),remoteHost.getPort(), true); remoteSSLSocket.setTcpNoDelay(true); remoteSSLSocket.setSoTimeout(5000); remoteSSLSocket.setNeedClientAuth(false); //remoteSSLSocket.setUseClientMode(isClient); } catch (IOException e) { e.printStackTrace(); } return remoteSSLSocket; } public static SSLContext getTrustAllSSLContext(boolean isClient) { javax.net.ssl.SSLContext sc = null; try { javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1]; javax.net.ssl.TrustManager tm = new SSLTrustManager(); trustAllCerts[0] = tm; sc = javax.net.ssl.SSLContext .getInstance("TLS"); if(isClient) { sc.init(null, trustAllCerts, null); }else{ String protocol = "TLS"; String serverKeyPath = "D:/JDK8Home/twt/sslServerKeys"; String serverKeyPwd = "123456"; //私钥密码 String serverKeyStorePwd = "123456"; // keystore存储密码 KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(serverKeyPath),serverKeyPwd.toCharArray()); KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); km.init(keyStore, serverKeyStorePwd.toCharArray()); KeyManager[] keyManagers = km.getKeyManagers(); keyManagers = Arrays.copyOf(keyManagers, keyManagers.length+1); keyManagers[keyManagers.length-1] = new SSLTrustKeyManager(); SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(keyManagers, null, new SecureRandom()); //第一项是用来做服务器验证的 sc.init(km.getKeyManagers(), null, null); } } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return sc; } public static class SSLTrustKeyManager extends X509ExtendedKeyManager { @Override public String chooseClientAlias(String[] as, Principal[] aprincipal, Socket socket) { System.out.println(Arrays.asList(as)); return null; } @Override public String chooseServerAlias(String s, Principal[] aprincipal, Socket socket) { System.out.println(s); return null; } @Override public X509Certificate[] getCertificateChain(String s) { System.out.println(s); return null; } @Override public String[] getClientAliases(String s, Principal[] aprincipal) { System.out.println(s); return null; } @Override public PrivateKey getPrivateKey(String s) { System.out.println(s); return null; } @Override public String[] getServerAliases(String s, Principal[] aprincipal) { System.out.println(s); return null; } } }