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

2 comments

  1. LitmusBlue says:

    挽尊

    PS:某些破烂这么久了还是解决不了ssl

    1. creke says:

      用自己软件的就飘了吧,管别人,哦呵呵

Leave a comment