HTTPS单向双向认证

240 阅读3分钟

生成证书

此内容仅供参考转载需要标注作者。内容相关密钥均为测试密钥。

生成 CA

#生成 CA 私钥: ca.key
openssl genrsa -out ca.key 2048
#生成 CA 的证书请求文件: ca.crt
openssl req -new -out ca.csr -key ca.key
#生成 CA 根证书
openssl x509 -req -in ca.csr -out ca.crt -signkey ca.key -CAcreateserial -days 3650

生成中间 CA

#生成中间CA私钥:intermediate.key
openssl genrsa -out intermediate.key 2048
#生成中间CA的证书请求文件:intermediate.csr
openssl req -new -out intermediate.csr -key intermediate.key
#使用根CA签发中间CA证书:intermediate.crt
openssl ca -extensions v3_ca -in intermediate.csr -out intermediate.crt -cert ca.crt -keyfile ca.key -days 3650

使用中间 CA 签发服务端证书

#生成服务端私钥:server.key
openssl genrsa -out server.key 2048
#生成服务端的证书请求文件:server.csr
openssl req -new -out server.csr -key server.key
#使用中间CA签发服务端证书:server.crt
openssl ca -in server.csr -out server.crt -cert intermediate.crt -keyfile intermediate.key -days 3650

使用中间 CA 签发客户端证书

#生成客户端私钥:client.key
openssl genrsa -out client.key 2048
#生成中间CA的证书请求文件:client.csr
openssl req -new -out client.csr -key client.key
#使用根CA签发中间CA证书:intermediate.crt
openssl ca -in client.csr -out client.crt -cert intermediate.crt -keyfile intermediate.key -days 3650

单向认证

NGINX 配置

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    # HTTPS server
    #
    server {
        listen       443 ssl;
        server_name  localhost;

        ssl_certificate     cert/server.crt;
        ssl_certificate_key  cert/server.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;
        
        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://localhost:9091/;
            proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
        }
    }

}

JAVA 代码测试

static void loadCustomCaCertsTest() throws IOException {
    // Create a new HttpURLConnection to the HTTPS server.
    URL url = new URL("https://127.0.0.1/test");
    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

    //Execute the request
    connection.connect();

    // Check the response code
    int responseCode = connection.getResponseCode();
    if (responseCode != 200) {
        throw new IOException("HTTP error code: " + responseCode);
    }

    // Read the response body.
    InputStream responseInputStream = connection.getInputStream();
    byte[] responseBytes = new byte[responseInputStream.available()];
    responseInputStream.read(responseBytes);
    String responseString = new String(responseBytes);

    System.out.println(responseString);
}

使用ssl连接时,遇到不信任的证书,应用程序一般都会拒绝连接。 浏览网站时,我们可以通过在浏览器的设置中导入证书,把证书加入到信任列表中。 而在JAVA直接进行SSL连接应用时,默认没有一个界面来导入证书。JAVA进行不信任的ssl连接时,会报如下异常

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

解决方法

使用keytool导入intermediate.crt或server.crt

Java 使用了一种叫 keystore 的文件来存储证书 (默认是位于 $JAVA_HOME/jre/lib/security/cacerts ) 。

该文件使用 keytool 工具去管理 (该工具默认位于 $JAVA_HOME/bin/keytool )。

使用这种方式不需要改java代码。

keytool -import -alias <证书别名> -keystore $JAVA_HOME/jre/lib/security/cacerts -file your.crt
使用java代码导入intermediate.crt或server.crt

这种方式需要修改代码,代码中加入ca证书

static void loadCaCertsInCodeTest() throws Exception {
    byte[] certBytes = Base64.decode("MIIC/zCCAeegAwIBAgIBAzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApTTkIg\n" +
            "SU9UIENBMB4XDTIzMDgwNDA2NDQxOVoXDTMzMDgwMTA2NDQxOVowHzEdMBsGA1UE\n" +
            "AwwUU05CIElPVCBJTlRFUk1FRElBVEUwggEiMb0GCSqGSIb3DQEBAQUAA4IBDwAw\n" +
            "ggEKAoIBAQDBPSiCv4989QXgeOiWCt2fLUGpCjDKcPoIxnYLDXyPYhy2Lq2J10e5\n" +
            "bO8F52qeAH4OpJnxt5cNvVmayfaFh63iVKqaYdPXhiZHlXWrAids0Wocw5hb0+pd\n" +
            "71O9raqoqvofYIwQ7LJqK/e61kA8hP0dVlo2niNCubhEri3tQWTrbKmKax9JJldn\n" +
            "1MVFqMuUl8C4RqKwLyiLaWS+6N+fN8S1BQArcbZ/PFZ366Vcwh4patXdM1aUtdc9\n" +
            "/+CIqUNG5BEI/LkerI+ajKn4FIauPyyUbm7UDavJj2U4uI3mww3O/ylnSMTiVwBh\n" +
            "hutw7THLhROv4NcSlwDTg7TrmFGO/bdJAgMBAAGjUDBOMB0GA1UdDgQWBBS4d9HP\n" +
            "tyMGIndimcfuqfILbNva4zAfBgNVHSMEGDAWg2RiWJPDGD340cgnJGtuIxS4selj\n" +
            "FzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA0ZqpLBqbiAuaFQSD/\n" +
            "zn+KwzHCb2UzZUVehOYGiqlT8L6StOVnuu2a9BEdOzx0pSQmfRsV2/p33Zszs2U9\n" +
            "KwdJqF5g/OLj6nIzTkh0RjZXaJsCaqq6Ka5Ktykrb7pATyiiSensTN8mvgTb9I7X\n" +
            "6XQI/Oi54Wgz41CYuyEwIFF/rbaX1/AZfSGU/lr6kXEjxm6n5FC/mRZdAt7RK4/g\n" +
            "C+YhDCbgjDoCIZvaE0aC/HJGXKDgF+ID4bbTfr1ECDUeMJe15jQjW9UyZGAQNeY0\n" +
            "5y7LaInrlS3zNFxzuEUlRg3vvw6EqESV5tE8QecYTVg1zCQE7PIG4YRFLZgSeXvm\n" +
            "ombN");
    InputStream certInputStream = new ByteArrayInputStream(certBytes);
    Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(certInputStream);

    KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
    truststore.load(null, null);
    truststore.setCertificateEntry("testintermediateca", certificate);

    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(truststore);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagerFactory.getTrustManagers(), new java.security.SecureRandom());


    // Create a new HttpURLConnection to the HTTPS server.
    URL url = new URL("https://192.168.188.130/test");
    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    connection.setSSLSocketFactory(sslContext.getSocketFactory());

    //Execute the request
    connection.connect();

    // Check the response code
    int responseCode = connection.getResponseCode();
    if (responseCode != 200) {
        throw new IOException("HTTP error code: " + responseCode);
    }

    // Read the response body.
    InputStream responseInputStream = connection.getInputStream();
    byte[] responseBytes = new byte[responseInputStream.available()];
    responseInputStream.read(responseBytes);
    String responseString = new String(responseBytes);

    System.out.println(responseString);
}

双向认证

NGINX 配置

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    # HTTPS server
    #
    server {
        listen       443 ssl;
        server_name  localhost;

        ssl_certificate     cert/server.crt;
        ssl_certificate_key  cert/server.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;


        ssl_client_certificate cert/client_ca.crt;
        ssl_verify_client on;
        ssl_verify_depth 2;
        
        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://localhost:9091/;
            proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
        }
    }

}

JAVA 代码测试

用openssl把client.key和client.crt转成p12文件

openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile client_ca.crt



public static void test() throws Exception {
    /**
     * 1、服务端证书放在代码中
     * 2、把客户端证书和key转成p12文件
     */
    // Load client certificate and private key from keystore
    String filePath = "client.p12";
    char[] keystorePassword = "888888".toCharArray();
    KeyStore keystore = KeyStore.getInstance("PKCS12");
    InputStream keystoreFile = MutualAuthenticationTestApp.class.getClassLoader().getResourceAsStream(filePath);
    keystore.load(keystoreFile, keystorePassword);

    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keystore, keystorePassword);

    byte[] certBytes = Base64.decode("MIIC/zCCAeegAwIBAgIBAzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApTTkIg\n" +
            "SU9UIENBMB4XDTIzMDgwNDA2NDQxOVoXDTMzMDgwMTA2NDQxOVowHzEdMBsGA1UE\n" +
            "AwwUU05CIElPVCBJTlRFUk1FRElBVEUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n" +
            "ggEKAoIBAQDBPSiCv1989QXgeOiWCt2fLUGpCJDKcPoIxnYLDXyPYhy2Lq2J10e5\n" +
            "bO8F52qeAH4OpJnxt5cNvVmayfaFh63iVKqDYdPXhiZHlXWrAids0Wocw5hb0+pd\n" +
            "71O9raqoqvofYIwQ7LJqK/e61kA8hP0dVlo2niNCubhEri3tQWTrbKmKax9JJldn\n" +
            "1MVFqMuUl8C4RqKwLyiLaWS+6N+fN8S1BQArobZ/PFZ366Vcwh4patXdM1aUtdc9\n" +
            "/+CIqUNG5tEI/LkerI+ajKn4FIauPyyUbm7UDasJj2U4uI3mww3O/ylnSMTiVwBh\n" +
            "hutw7THLhROv4NcSlwDTg7TrmFGO/bdJAgMBAAGjUDBOMB0GA1UdDgQWBBS4d9HP\n" +
            "tyMGIndimcfuqfILbNva4zAfBgNVHSMEGDAWgBRiWJPDGD340cgnJGtuIxS4selj\n" +
            "FzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA0ZqpLBqbiAuaFQSD/\n" +
            "zn+KwzHCb2UzZUVehOYGiqlT8L63tOVnuu2a9BEdOzx0pSQmfRsV2/p33Zszs2U9\n" +
            "KwdJqF5g/OLj6nIzTkh0RjZXaJsCaqq6Ka5Ktykrb7pATyiiSensTN8mvgTb9I7X\n" +
            "6XQI/Oi54Wgz41CYuyEwIFF/rbaX1/AZfSGU/lr6kXEjxm6n5FC/mRZdAt7RK4/g\n" +
            "C+YhDCbgjDoCIZvaE0aC/HJGXKDgF+ID4bbTfr1ECDUeMJe15jQjW9UyZGAQNeY0\n" +
            "5y7LaInrlS3zNFxzuEUlRg3vvw6EqESV5tE8QecYTVg1zCQE7PIG4YRFLZgSeXvm\n" +
            "ombN");
    InputStream certInputStream = new ByteArrayInputStream(certBytes);
    Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(certInputStream);

    KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
    truststore.load(null, null);
    truststore.setCertificateEntry("testintermediateca", certificate);

    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(truststore);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new java.security.SecureRandom());


    // Create a new HttpURLConnection to the HTTPS server.
    //URL url = new URL("https://192.168.188.130/test");
    URL url = new URL("https://transit-qa.xxxxxxxx.com:8443/api/kms/V3/getTk");
    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    connection.setSSLSocketFactory(sslContext.getSocketFactory());

    //Execute the request
    connection.connect();

    // Check the response code
    int responseCode = connection.getResponseCode();
    if (responseCode != 200) {
        log.info("HTTP error code: " + responseCode);
        //throw new IOException("HTTP error code: " + responseCode);
    }

    // Read the response body.
    InputStream responseInputStream = connection.getInputStream();
    byte[] responseBytes = new byte[responseInputStream.available()];
    responseInputStream.read(responseBytes);
    String responseString = new String(responseBytes);

    log.info(responseString);
}

curl测试

curl -v --cert ./client.crt --cacert ./client_ca.crt --key ./client.key https://192.168.188.130/test

参考资料

Nginx之多级证书的双向认证