• JAVA动态生成兼容浏览器的HTTPS(SSL)证书

    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,以后就不会出现安全警告了。当然,真正的安全程度,靠的是你的代理程序的数据传输的安全性啦。