转载请注明出处:http://www.cnblogs.com/apm70
最近在对Andriod客户端与服务器socket通讯链路进行SSL加密时,服务器端在读取“.jks”证书时总是报下面的异常,很是苦恼。
1 java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big. 2 at sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:109) 3 at java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:305) 4 at test.socket.SocketServerWithSSL.loadKeyStore(SocketServerWithSSL.java:85) 5 at test.socket.SocketServerWithSSL.init(SocketServerWithSSL.java:38) 6 at test.socket.SocketServerWithSSL.main(SocketServerWithSSL.java:130) 7 Caused by: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big. 8 at sun.security.util.DerInputStream.getLength(DerInputStream.java:544) 9 at sun.security.util.DerValue.init(DerValue.java:347) 10 at sun.security.util.DerValue.<init>(DerValue.java:303) 11 at sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:104) 12 ... 4 more
开始以为是服务器端代码写得有问题,但经过排查,否定了这个假设。看来问题出在证书格式上面了。
在开发的过程中图方便,让客户端的同学把双向认证的证书密钥都一并生成了,也就是说两对密钥都是由客户端的同学生成的,那会不会是他们生成的文件有问题呢?答案是肯定的。
当时客户端提供的两对密钥文件是酱紫滴:
根据客户端滴同事反馈,andriod客户端在读取client端的两个.bks文件没有问题,看来问题就出在服务器端的两个.jks文件了。
那就来验证下吧,先尝试着自己生成两对.keystore文件先,生成这个用来验证下通讯正常的情况。
利用keytool工具生成我们所需要的文件。
1.生成服务器端私钥:
keytool -genkey -alias serverkey -keystore kserver.keystore
其中红色标注的部分可自定义,kserver.keystore就是存储服务器私钥的容器,下同。
在输入keystore密码及姓名、组织等参数后,输入y确认即可。
2.导出服务器端公钥(证书):
keytool -export -alias serverkey -keystore kserver.keystore –file server.crt
输入密码后即可导出公钥
3.将服务器端的公钥(证书)导入到客户端的Trust Keystore中:
keytool -import -alias serverkey -file server.crt -keystore tclient.keystore
在输入keystore密码后,输入y确认即可。
4.生成客户端私钥:
keytool -genkey -alias clientkey -keystore kclient.keystore
5.导出客户端公钥(证书):
keytool -export -alias clientkey -keystore kclient.keystore –file client.crt
6.将客户端的公钥(证书)导入到服务器端的Trust Keystore中:
keytool -import -alias clientkey –file client.crt -keystore tserver.keystore
具体测试代码见本文末尾。
使用测试客户端(非andriod客户端,因为andriod证书生成还需BKS包)及服务端通讯正常,再一次证明代码逻辑正常。
将验证正确的两对.keystore及两个.crt文件交给客户端同事生成.bks(for client)及.jks(for server),出现问题。看来开始猜测得没错。
定位到问题,接下来就来解决它吧。
从.keystore文件的测试情况看,密钥对是没有问题的,问题出在存储密钥的容器上,即.bks及.jks上,由于服务器端的.jks是客户端生成的(使用了BKS包),导致服务器端在读取.jks文件时异常。
我们来梳理下公钥(证书)的交换及导入过程:
由于客户端(android)与服务器端生成证书的格式不同,客户端最终生成的证书格式为.bks(andriod必需),服务器端为.keystore。
根据这次生成的证书联调,结果正常。问题解决。
附测试SSL服务端及客户端代码:
1.客户端:
1 /** 2 * 3 */ 4 package test.socket; 5 6 import java.io.BufferedInputStream; 7 import java.io.BufferedOutputStream; 8 import java.io.FileInputStream; 9 import java.io.IOException; 10 import java.io.InputStream; 11 import java.io.OutputStream; 12 import java.security.KeyStore; 13 14 import javax.net.ssl.KeyManagerFactory; 15 import javax.net.ssl.SSLContext; 16 import javax.net.ssl.SSLSocket; 17 import javax.net.ssl.TrustManagerFactory; 18 19 /** 20 * @author Administrator 21 * 22 */ 23 public class SocketSSLClient { 24 25 private static final String DEFAULT_HOST = "127.0.0.1"; 26 private static final int DEFAULT_PORT = 7777; 27 28 private static final String CLIENT_KEY_STORE_PASSWORD = "123456"; 29 private static final String CLIENT_TRUST_KEY_STORE_PASSWORD = "123456"; 30 31 private static final String PRIVATE_KEY_CLIENT = "D:\\test\\kclient.keystore"; 32 private static final String TRUST_KEY_CLIENT = "D:\\test\\tclient.keystore"; 33 34 private SSLSocket sslSocket; 35 36 /** 37 * 启动客户端程序 38 * 39 * @param args 40 */ 41 public static void main(String[] args) { 42 SocketSSLClient client = new SocketSSLClient(); 43 client.init(); 44 client.process(); 45 } 46 47 public void process() { 48 String msg = "hello world"; 49 if (sslSocket == null) { 50 System.out.println("ERROR"); 51 return; 52 } 53 try { 54 InputStream input = sslSocket.getInputStream(); 55 OutputStream output = sslSocket.getOutputStream(); 56 57 BufferedInputStream bis = new BufferedInputStream(input); 58 BufferedOutputStream bos = new BufferedOutputStream(output); 59 60 bos.write(msg.getBytes()); 61 bos.flush(); 62 63 byte[] buffer = new byte[20]; 64 bis.read(buffer); 65 System.out.println(new String(buffer)); 66 67 sslSocket.close(); 68 } catch (IOException e) { 69 System.out.println(e); 70 } 71 } 72 73 public void init() { 74 try { 75 SSLContext ctx = SSLContext.getInstance("SSL"); 76 77 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 78 TrustManagerFactory tmf = TrustManagerFactory 79 .getInstance("SunX509"); 80 81 KeyStore ks = KeyStore.getInstance("JKS"); 82 KeyStore tks = KeyStore.getInstance("JKS"); 83 84 ks.load(new FileInputStream(PRIVATE_KEY_CLIENT), 85 CLIENT_KEY_STORE_PASSWORD.toCharArray()); 86 tks.load(new FileInputStream(TRUST_KEY_CLIENT), 87 CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray()); 88 89 kmf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray()); 90 tmf.init(tks); 91 92 ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 93 94 sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket( 95 DEFAULT_HOST, DEFAULT_PORT); 96 } catch (Exception e) { 97 System.out.println(e); 98 } 99 } 100 101 }
2.服务器端:
1 /** 2 * 3 */ 4 package test.socket; 5 6 import java.io.BufferedInputStream; 7 import java.io.BufferedOutputStream; 8 import java.io.FileInputStream; 9 import java.io.InputStream; 10 import java.io.OutputStream; 11 import java.net.Socket; 12 import java.security.KeyStore; 13 14 import javax.net.ssl.KeyManagerFactory; 15 import javax.net.ssl.SSLContext; 16 import javax.net.ssl.SSLServerSocket; 17 import javax.net.ssl.TrustManagerFactory; 18 19 /** 20 * @author Administrator 21 * 22 */ 23 public class SocketSSLServer implements Runnable { 24 25 private static final int DEFAULT_PORT = 7777; 26 27 private static final String SERVER_KEY_STORE_PASSWORD = "123456"; 28 private static final String SERVER_TRUST_KEY_STORE_PASSWORD = "123456"; 29 30 private static final String PRIVATE_KEY_SERVER = "D:\\test\\kserver.keystore"; 31 private static final String TRUST_KEY_SERVER = "D:\\test\\tserver.keystore"; 32 33 private SSLServerSocket serverSocket; 34 35 /** 36 * 启动程序 37 * 38 * @param args 39 */ 40 public static void main(String[] args) { 41 SocketSSLServer server = new SocketSSLServer(); 42 server.init(); 43 Thread thread = new Thread(server); 44 thread.start(); 45 } 46 47 public synchronized void start() { 48 if (serverSocket == null) { 49 System.out.println("ERROR"); 50 return; 51 } 52 while (true) { 53 try { 54 Socket s = serverSocket.accept(); 55 InputStream input = s.getInputStream(); 56 OutputStream output = s.getOutputStream(); 57 58 BufferedInputStream bis = new BufferedInputStream(input); 59 BufferedOutputStream bos = new BufferedOutputStream(output); 60 61 byte[] buffer = new byte[20]; 62 bis.read(buffer); 63 System.out.println("------receive:--------" 64 + new String(buffer).toString()); 65 66 bos.write("yes".getBytes()); 67 bos.flush(); 68 69 s.close(); 70 } catch (Exception e) { 71 System.out.println(e); 72 } 73 } 74 } 75 76 public void init() { 77 try { 78 SSLContext ctx = SSLContext.getInstance("SSL"); 79 80 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 81 TrustManagerFactory tmf = TrustManagerFactory 82 .getInstance("SunX509"); 83 84 KeyStore ks = KeyStore.getInstance("JKS"); 85 KeyStore tks = KeyStore.getInstance("JKS"); 86 87 ks.load(new FileInputStream(PRIVATE_KEY_SERVER), 88 SERVER_KEY_STORE_PASSWORD.toCharArray()); 89 tks.load(new FileInputStream(TRUST_KEY_SERVER), 90 SERVER_TRUST_KEY_STORE_PASSWORD.toCharArray()); 91 92 kmf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray()); 93 tmf.init(tks); 94 95 ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 96 97 serverSocket = (SSLServerSocket) ctx.getServerSocketFactory() 98 .createServerSocket(DEFAULT_PORT); 99 serverSocket.setNeedClientAuth(true); 100 } catch (Exception e) { 101 System.out.println(e); 102 } 103 } 104 105 public void run() { 106 // TODO Auto-generated method stub 107 start(); 108 } 109 }