JAVA自带的SSL以及X509库只能使用SSL证书,不能生成SSL证书。因此我们使用“Bouncy Castle”这个算法库来实现SSL证书的生成。然后使用X509KeyManager来构建符合浏览器规范的SSL证书链,以便自建代理服务器时,信任该CA就可以浏览HTTPS内容而不会有安全提醒。
浏览器生成的CA,使用X509的V3版。V3版和V1版的主要不同是可以生成扩展字段。详细信息可以参考Bouncy Castle的WIKI上面的解释。参考了burpsuite,发现CA的扩展字段需要Subject Key Identifier,再加上Basic Constraints声明是CA及最大中间CA数即可兼容大部分主流浏览器。
注意一点,就是Windows系统的证书验证似乎只检查公钥私钥,根证书使用相同的密钥对每次动态生成都可以通过信任CA的认证;而Firefox则严格很多,需要把CA保存下来,每次都使用同样的CA才能匹配信任列表中的对应CA。
生成密钥对:
KeyPairGenerator caKeyPairGen = KeyPairGenerator.getInstance("RSA", "BC"); caKeyPairGen.initialize(1024, new SecureRandom()); KeyPair keypair = caKeyPairGen.genKeyPair(); caPriKey = keypair.getPrivate(); caPubKey = keypair.getPublic();
生成CA:
public static X509Certificate createAcIssuerCert( PublicKey pubKey, PrivateKey privKey) throws Exception { X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator(); // // signers name // String issuer = "CN=My CA, OU=My CA, O=My, L=My, ST=AMy, C=CN"; // // subjects name - the same as we are self signed. // String subject = issuer; // // create the certificate - version 3 // v3CertGen.setSerialNumber(BigInteger.valueOf(0x1234ABCDL)); v3CertGen.setIssuerDN(new X509Principal(issuer)); v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 30*aDay)); v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + 36500*aDay)); v3CertGen.setSubjectDN(new X509Principal(subject)); v3CertGen.setPublicKey(pubKey); v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption"); // Is a CA v3CertGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true)); v3CertGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(pubKey)); X509Certificate cert = v3CertGen.generateX509Certificate(privKey); cert.checkValidity(new Date()); cert.verify(pubKey); return cert; }
使用CA签发网站证书:
public static X509Certificate createClientCert( PublicKey pubKey, PrivateKey caPrivKey, PublicKey caPubKey, String host) throws Exception { X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator(); // // issuer // String issuer = "CN=My CA, OU=My CA, O=My, L=My, ST=My, C=CN"; // // subjects name table. // Hashtable attrs = new Hashtable(); Vector order = new Vector(); attrs.put(X509Principal.C, "CN"); attrs.put(X509Principal.O, "My"); attrs.put(X509Principal.OU, "My"); attrs.put(X509Principal.CN, host); order.addElement(X509Principal.C); order.addElement(X509Principal.O); order.addElement(X509Principal.OU); order.addElement(X509Principal.CN); // // create the certificate - version 3 // v3CertGen.reset(); v3CertGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); v3CertGen.setIssuerDN(new X509Principal(issuer)); v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 10*aDay)); v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + 3650*aDay)); v3CertGen.setSubjectDN(new X509Principal(order, attrs)); v3CertGen.setPublicKey(pubKey); v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption"); X509Certificate cert = v3CertGen.generateX509Certificate(caPrivKey); cert.checkValidity(new Date()); cert.verify(caPubKey); return cert; }
对应的X509KeyManager每次从host_port.cert文件中读取对应的网站证书,然后加上CA证书构成证书链返回:
public final class MyKeyManager implements X509KeyManager { private String entryname; private String port; private X509Certificate caCert; private X509Certificate clientCert; private PrivateKey privatekey; MyKeyManager(String host, String port, X509Certificate caCert) { this.port = port; this.entryname = host; this.caCert = caCert; try { String certFileName = host + "_" + port + ".cert"; FileInputStream caCertFis = new FileInputStream(certFileName); ObjectInputStream oos = new ObjectInputStream(caCertFis); privatekey = (PrivateKey) oos.readObject(); clientCert = (X509Certificate) oos.readObject(); oos.close(); caCertFis.close(); } catch (Exception ex) { ex.printStackTrace(); } } public String[] getClientAliases(String string, Principal[] prncpls) { throw new UnsupportedOperationException("Not supported yet."); } public String chooseClientAlias(String[] strings, Principal[] prncpls, Socket socket) { throw new UnsupportedOperationException("Not supported yet."); } public String[] getServerAliases(String string, Principal[] prncpls) { return (new String[] { entryname }); } public String chooseServerAlias(String string, Principal[] prncpls, Socket socket) { return entryname; } public X509Certificate[] getCertificateChain(String string) { X509Certificate x509certificates[] = new X509Certificate[2]; x509certificates[0] = clientCert; x509certificates[1] = caCert; return x509certificates; } public PrivateKey getPrivateKey(String string) { return this.privatekey; } }
使用实例:
SSLContext sslcontext; sslcontext = SSLContext.getInstance("SSL"); X509KeyManager[] x509km = new X509KeyManager[]{ new AsanKeyManager(host , port , KeyMaker.getCAcert())}; sslcontext.init(x509km, null, null); SSLServerSocketFactory sslserversocketfactory = sslcontext.getServerSocketFactory();
这样动态生成证书,只要在浏览器第一次浏览HTTPS内容时,信任该CA,以后就不会出现安全警告了。当然,真正的安全程度,靠的是你的代理程序的数据传输的安全性啦。
挽尊
PS:某些破烂这么久了还是解决不了ssl
用自己软件的就飘了吧,管别人,哦呵呵