Okhttp如何添加HTTPS自簽名證書信任


  • 背景

  本人目前遇到一個需求,甲方在內網使用HTTPS提供服務,但是這個HTTPS的證書不是CA證書,是自簽名的證書,我需要通過HTTP client的方式消費這些服務暴露的接口。為了方便測試不受信任的證書,我又使用openssl生成了一個自簽名的證書,並使用這個證書構建了一個非常簡單的NODEJS服務端。所以本涵蓋了三個部分的內容,我會按照我驗證的順序分別進行說明。

  • 使用okhttp添加證書,並使用https服務
  • 使用openssl生成自簽名證書
  • 使用nodejs加載證書提供HTTPS服務
  • 前述

    在我沒有到現場測試前,提前寫了一些服務,也就是使用HTTP協議進行API的訪問。主要實現的方式是基於Okhttp底層實現的Spring RestTemplate ,代碼如下所示:

 1 import lombok.extern.slf4j.Slf4j;
 2 import okhttp3.ConnectionPool;
 3 import okhttp3.OkHttpClient;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 import org.springframework.http.client.ClientHttpResponse;
 8 import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
 9 import org.springframework.web.client.DefaultResponseErrorHandler;
10 import org.springframework.web.client.RestTemplate;
11 
12 import java.io.IOException;
13 import java.util.concurrent.TimeUnit;
14 @Slf4j
15 @Configuration
16 public class RestConfig {
17 
18     @Autowired
19     private OkHttpClient okHttpClient;
20     @Bean
21     public RestTemplate restTemplate()
22     {
23         OkHttp3ClientHttpRequestFactory reqFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient);
24         RestTemplate rest = new RestTemplate(reqFactory);
25         rest.setErrorHandler(new RestConfig.ErrorHandler());
26         return rest;
27     }
28 
29     @Bean
30     public OkHttpClient okHttpClient() {
31         OkHttpClient clt = new OkHttpClient.Builder()
32                 .retryOnConnectionFailure(false)
33                 .connectionPool(pool())
34                 .connectTimeout(5, TimeUnit.SECONDS)
35                 .readTimeout(60, TimeUnit.SECONDS)
36                 .writeTimeout(60, TimeUnit.SECONDS)
37                 .build();
38 
39         return clt;
40     }
41 
42     @Bean
43     public ConnectionPool pool() {
44         return new ConnectionPool(5, 5, TimeUnit.MINUTES);
45     }
46 
47     public class ErrorHandler extends DefaultResponseErrorHandler {
48         @Override
49         public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
50             return super.hasError(clientHttpResponse);
51         }
52 
53         @Override
54         public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
55             //won't throw error so we can receive http status code in business service
56             log.error("error:{}",clientHttpResponse);
57         }
58     }
59 }
使用Okhttp作為Spring RestTemplate實現

    到現場測試,發現首次訪問頁面是這樣的。原來是HTTPS的服務。直觀體驗就是我們通常在訪問網站的時候,我模擬了一下如下圖:

 

    這個時候我只能硬着頭皮拿出代碼,填寫了HTTPS的地址進行測試,然后我收到了下面的錯誤提示:

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
沒有配置證書訪問https的錯誤提示

    通過這個錯誤信息查找,得到了以下兩種解決方案:

    •   通過信任全部設置允許客戶端所有的HTTPS 
    •   添加證書允許指定的信任服務通過
  • 信任全部連接

  顯而易見,第一種方案的安全性不能得到保證。但是為了參考,我下面還是給出第一種方案的代碼:

 1 import okhttp3.Headers;
 2 import okhttp3.OkHttpClient;
 3 import okhttp3.Request;
 4 import okhttp3.Response;
 5 import org.junit.Test;
 6 
 7 import javax.net.ssl.*;
 8 import java.io.IOException;
 9 import java.security.*;
10 import java.security.cert.CertificateException;
11 import java.security.cert.X509Certificate;
12 import java.util.Arrays;
13 
14 public class TrustAllDemo {
15     @Test
16     public void trustAll(){
17         final OkHttpClient client = getClient();
18         Request request = new Request.Builder()
19                 .url("https://localhost:3002")
20                 .build();
21 
22         try (Response response = client.newCall(request).execute()) {
23             if (!response.isSuccessful()) {
24                 Headers responseHeaders = response.headers();
25                 for (int i = 0; i < responseHeaders.size(); i++) {
26                     System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
27                 }
28                 throw new IOException("Unexpected code " + response);
29             }
30 
31             System.out.println(response.body().string());
32         } catch (IOException e) {
33             e.printStackTrace();
34         }
35     }
36 
37     private OkHttpClient getClient(){
38         try {
39             //參考okhttp的 API https://square.github.io/okhttp/3.x/okhttp/index.html?okhttp3/package-summary.html
40             //TrustManagerFactory 不是Okhttp的API內容,是JDK本身對秘鑰管理的支持類
41             TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm());
42             trustManagerFactory.init((KeyStore) null);
43             TrustManager[] trustManagersDefault = trustManagerFactory.getTrustManagers();
44             if (trustManagersDefault.length != 1 || !(trustManagersDefault[0] instanceof X509TrustManager)) {
45                 throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagersDefault));
46             }
47             X509TrustManager trustManager = (X509TrustManager) trustManagersDefault[0];
48             //初始化一個管理器的數組,用於初始化后面的上下文,這里只提供了一個X509管理器。 X509是一個秘鑰格式的標准。
49             final TrustManager[] trustManagers = {
50                     new X509TrustManager() {
51                         @Override
52                         public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
53                         }
54                         @Override
55                         public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
56                         }
57                         @Override
58                         public X509Certificate[] getAcceptedIssuers() {
59                             return new X509Certificate[]{};
60                         }
61                     }
62             };
63             //初始化SSL的上下文
64             SSLContext sslContext = SSLContext.getInstance("SSL");
65             sslContext.init(null,trustManagers, null);
66             SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
67 
68             OkHttpClient clt = new OkHttpClient.Builder()
69                     .sslSocketFactory(sslSocketFactory,trustManager)
70                     //永遠返回true,對所有的host都信任
71                     .hostnameVerifier((s,sslSession)->true)
72                     .retryOnConnectionFailure(false)
73                     .build();
74             return clt;
75         } catch (NoSuchAlgorithmException e) {
76             e.printStackTrace();
77         } catch (KeyStoreException e) {
78             e.printStackTrace();
79         } catch (KeyManagementException e) {
80             e.printStackTrace();
81         }
82         return null;
83     }
84 
85 }
信任所有連接
  • 添加證書    

    為了使用更安全的方式,我還是努力的查找了第二種方案。在okhttp的官網用例中,給出了通過證書加載的方式。鏈接如下:https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java

    我本人把代碼拿到了本地驗證了一下,沒有通過。然后通過瀏覽器導出Base64格式的證書,使用文本編輯工具打開比較了一下Base64文本的內容,發現是https的證書變了,因為內容和代碼中的內容並不相同。我使用新的證書文本測試了一下,例子是可以運行通過的。

 1 import java.io.IOException;
 2 import java.security.cert.X509Certificate;
 3 import okhttp3.Headers;
 4 import okhttp3.OkHttpClient;
 5 import okhttp3.Request;
 6 import okhttp3.Response;
 7 import okhttp3.tls.Certificates;
 8 import okhttp3.tls.HandshakeCertificates;
 9 import org.junit.Test;
10 
11 public final class CustomTrust {
12   final X509Certificate comodoRsaCertificationAuthority = Certificates.decodeCertificatePem("-----BEGIN CERTIFICATE-----\n" +
13           "MIIFaDCCBFCgAwIBAgISA0bCEx5CVEKiF4B9ZcKEzsQQMA0GCSqGSIb3DQEBCwUA\n" +
14           "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD\n" +
15           "EwJSMzAeFw0yMTA4MDMyMzUyMjZaFw0yMTExMDEyMzUyMjRaMBwxGjAYBgNVBAMT\n" +
16           "EXByaXZhdGVvYmplY3QuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" +
17           "AQEA68wPmz5RGUog4eaxMkvjTTZkLRBlsktWvtcx07oXVu8iwj+qk3xqbLpyX8eE\n" +
18           "Crtt5ZV8PhIu76LDQdur6O/Ufn1xRj/khBQUJrie2E6uB3LuDiPk1sI+JYFg070h\n" +
19           "5C8/D4GmGjKGsiWRLX1ieWX0ewHVDXZkhcM1sKQheXH8oZqYlgDfYgXdaiNMz9bG\n" +
20           "CMLHPgXRQ13x+OXpWERMNnYcLjAZFy36AQXrMCd7SzzV+2B8kzcfYXuEWH6AOAOi\n" +
21           "eZE5fQKtK/hYnEvt3yOXh5+DhZoLZvUCchDKhWYgx7kmZqgz5EyO4kcB/0PS6xrn\n" +
22           "H2HWO2gDA5E6agvdosh6MZdTJQIDAQABo4ICjDCCAogwDgYDVR0PAQH/BAQDAgWg\n" +
23           "MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0G\n" +
24           "A1UdDgQWBBRvM7ZqFFCCK7Kjndfxf1tX8S6yljAfBgNVHSMEGDAWgBQULrMXt1hW\n" +
25           "y65QCUDmH6+dixTCxjBVBggrBgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6\n" +
26           "Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iu\n" +
27           "b3JnLzBbBgNVHREEVDBSghVibG9nLnB1YmxpY29iamVjdC5jb22CEXByaXZhdGVv\n" +
28           "YmplY3QuY29tghBwdWJsaWNvYmplY3QuY29tghR3d3cucHVibGljb2JqZWN0LmNv\n" +
29           "bTBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEEAYLfEwEBATAoMCYGCCsGAQUF\n" +
30           "BwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCCAQUGCisGAQQB1nkCBAIE\n" +
31           "gfYEgfMA8QB2AJQgvB6O1Y1siHMfgosiLA3R2k1ebE+UPWHbTi9YTaLCAAABew6l\n" +
32           "tgwAAAQDAEcwRQIhANZ+tKy4/PANIi3JnTgf4I37wDH7oaIvw8o4MopzGx4UAiA+\n" +
33           "a+k+B12sM/4ZTpuqNgGShWKBJznqhrbU0vD2ncnYbwB3APZclC/RdzAiFFQYCDCU\n" +
34           "Vo7jTRMZM7/fDC8gC8xO8WTjAAABew6ltf0AAAQDAEgwRgIhAPatDDGXCeAsRaMW\n" +
35           "mO23JCzBVVKQTiq7PI42vPJhNEXuAiEAwgWM6S16tTDuNppPtd7P0Z5nr481bdd0\n" +
36           "SeUm/2fkh5MwDQYJKoZIhvcNAQELBQADggEBAAfQ36Dj51c+MI+g7c0k1i3+n6iA\n" +
37           "ozF5fb92A+4u+hfZD5nJaHkkwNGUMB4rnXojYLkJYee/DmVFkQVh1dG54jfz6/Gd\n" +
38           "IJfKVWP03N6W00igoA1+h88xtvt3fuXt1eIWQTVAQ78fKOPsUIpulhGErDDeDDQ7\n" +
39           "b/1J4OaA60HjfeFn/G0hqLL2pDKSn8nVRR/cO2OIG1Np2uoVI2wI7M+Fzb0xvznk\n" +
40           "R2ht2kLyNhGE+EmMiNtJokqX2aj7Wy0Z80Yc3eFHXUIWHqPy9hTXQ7e2YJmWcH8/\n" +
41           "FgI3TFczeMiGgLdM3jwKuEf0sftqCVyttLJ6E3zpKiD05OLQYm9vnLYv2F4=\n" +
42           "-----END CERTIFICATE-----");
43 
44   private final OkHttpClient client;
45 
46   public CustomTrust() {
47 
48     HandshakeCertificates certificates = new HandshakeCertificates.Builder()
49             .addTrustedCertificate(comodoRsaCertificationAuthority)
50             .build();
51 
52     client = new OkHttpClient.Builder()
53             .sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
54             .build();
55   }
56 
57   public void run() throws Exception {
58     Request request = new Request.Builder()
59             .url("https://publicobject.com/helloworld.txt")
60             .build();
61 
62     try (Response response = client.newCall(request).execute()) {
63       if (!response.isSuccessful()) {
64         Headers responseHeaders = response.headers();
65         for (int i = 0; i < responseHeaders.size(); i++) {
66           System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
67         }
68 
69         throw new IOException("Unexpected code " + response);
70       }
71 
72       System.out.println(response.body().string());
73     }
74   }
75 
76   public static void main(String... args) throws Exception {
77     new CustomTrust().run();
78   }
79 
80 }
使用導出證書文本測試官方用例

    集成到RestTemplate主要是通過構建 OkHttpClient 的時候設置一個 sslSocketFactory,等后續獲取客戶端實例的時候,就會為每個實例設置相應的屬性。這里主要是通過HandshakeCertificates這個工具類加載證書,並且構建了sslSocketFactory需要的兩個參數,當然這里也有其他的方式去構建。如下是通過證書創建 X509TrustManager ,用來初始化一個SSLContext來加載證書的演示:

 1 import okhttp3.OkHttpClient;
 2 import okhttp3.Request;
 3 import okhttp3.Response;
 4 
 5 import javax.net.ssl.*;
 6 import java.io.IOException;
 7 import java.io.InputStream;
 8 import java.security.GeneralSecurityException;
 9 import java.security.KeyStore;
10 import java.security.KeyStoreException;
11 import java.security.NoSuchAlgorithmException;
12 import java.security.cert.Certificate;
13 import java.security.cert.CertificateException;
14 import java.security.cert.CertificateFactory;
15 
16 
17 public class CustomTrust2 {
18     private OkHttpClient client;
19 
20     public CustomTrust2() {
21         X509TrustManager trustManager;
22         SSLSocketFactory sslSocketFactory;
23         try {
24             trustManager = trustManagerForCertificates(trustedCertificatesInputStream());
25             SSLContext sslContext = SSLContext.getInstance("TLS");
26             sslContext.init(null, new TrustManager[]{trustManager}, null);
27             sslSocketFactory = sslContext.getSocketFactory();
28         } catch (GeneralSecurityException | IOException e) {
29             throw new RuntimeException(e);
30         }
31         client = new OkHttpClient.Builder()
32                 .sslSocketFactory(sslSocketFactory, trustManager)
33                 .build();
34     }
35 
36     private X509TrustManager trustManagerForCertificates(InputStream trustedCertificatesInputStream) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
37         CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
38         final Certificate certificate = certificateFactory.generateCertificate(trustedCertificatesInputStream);
39         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
40         keyStore.load(null, null);
41         keyStore.setCertificateEntry("ca", certificate);
42         final TrustManagerFactory instance = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
43         instance.init(keyStore);
44         return (X509TrustManager) instance.getTrustManagers()[0];
45     }
46 
47     public void run() throws Exception {
48         Request request = new Request.Builder()
49                 .url("https://localhost:3002")
50                 .build();
51 
52         Response response = client.newCall(request).execute();
53         System.out.println(response.body().string());
54     }
55 
56     private InputStream trustedCertificatesInputStream() {
57 //        return Thread.currentThread().getContextClassLoader().getResourceAsStream("publicobject_com.cer");
58         return Thread.currentThread().getContextClassLoader().getResourceAsStream("local.crt");
59     }
60 
61     public SSLContext sslContextForTrustedCertificates(InputStream in) {
62         //... // Full source omitted. See sample.
63         return null;
64     }
65 
66     public static void main(String[] args) throws Exception {
67         new CustomTrust2().run();
68     }
69 }
使用TrustManagerFactory加載證書的代碼演示

    這里提一下addPlatformTrustedCertificates,我的理解是加載平台信任的證書,也就是對於我的機器,CA證書下的證書都是被信任的。如果打開了這個設置,我們去訪問baidu ,bing 常用的網站的時候,是沒有辦法驗證是不是加載了正確的證書。只有在加載是錯誤的證書,才會提示錯誤證書錯誤。

 1 import lombok.extern.slf4j.Slf4j;
 2 import okhttp3.ConnectionPool;
 3 import okhttp3.OkHttpClient;
 4 import okhttp3.tls.Certificates;
 5 import okhttp3.tls.HandshakeCertificates;
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.context.annotation.Bean;
 8 import org.springframework.context.annotation.Configuration;
 9 import org.springframework.http.client.ClientHttpResponse;
10 import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
11 import org.springframework.web.client.DefaultResponseErrorHandler;
12 import org.springframework.web.client.RestTemplate;
13 
14 import java.io.IOException;
15 import java.security.cert.X509Certificate;
16 import java.util.concurrent.TimeUnit;
17 @Slf4j
18 @Configuration
19 public class RestConfig {
20 
21     @Autowired
22     private OkHttpClient okHttpClient;
23     @Bean
24     public RestTemplate restTemplate()
25     {
26         OkHttp3ClientHttpRequestFactory reqFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient);
27         RestTemplate rest = new RestTemplate(reqFactory);
28         rest.setErrorHandler(new RestConfig.ErrorHandler());
29         return rest;
30     }
31 
32     @Bean
33     public OkHttpClient okHttpClient() {
34 
35         String cer = "-----BEGIN CERTIFICATE-----\n" +
36                 "MIIDsjCCApqgAwIBAgIUShTbcU1CzKA3VV/iqHbO5L7AYE4wDQYJKoZIhvcNAQEL\n" +
37                 "BQAwdDELMAkGA1UEBhMCemgxCzAJBgNVBAgMAnNoMQswCQYDVQQHDAJzaDELMAkG\n" +
38                 "A1UECgwCY2MxCzAJBgNVBAsMAmNjMREwDwYDVQQDDAhjaGFvY2hhbzEeMBwGCSqG\n" +
39                 "SIb3DQEJARYPY2hhb2NoYW9AcXEuY29tMB4XDTIxMDgzMDAzNDA1MVoXDTMxMDgy\n" +
40                 "ODAzNDA1MVowdDELMAkGA1UEBhMCemgxCzAJBgNVBAgMAnNoMQswCQYDVQQHDAJz\n" +
41                 "aDELMAkGA1UECgwCY2MxCzAJBgNVBAsMAmNjMREwDwYDVQQDDAhjaGFvY2hhbzEe\n" +
42                 "MBwGCSqGSIb3DQEJARYPY2hhb2NoYW9AcXEuY29tMIIBIjANBgkqhkiG9w0BAQEF\n" +
43                 "AAOCAQ8AMIIBCgKCAQEAvFLkQ0Wp10QVzWTQX+UbiFC5FE8YcfVuDfKNDt2xGRum\n" +
44                 "2cpvqcyKxRz/YVJcQd4lUoveJdPo/HlnO9wVmC+49MMqv8lwT9ZRXk4zvatF5074\n" +
45                 "TrNRtB7BgffJflLsBvSyXe7ynkkenAeSfrpcA9lteGDBBGpq7hJEctxy9S2+kN5l\n" +
46                 "8UL1lwNmUI8wivrNFzk05SwXZrii+saLcWZn/eoprGSCboCmhJCqcxOrwzVilFEG\n" +
47                 "eAKVz7tLuylbDeI0Q0QG7qhD1OmUVLoOt5cTb8TedptYZLoPeO4NlUeEHE+F9jou\n" +
48                 "kYDvqiTTJO7Dh6YPAdmJCYSEE7NB8dX73EQyKIg/JQIDAQABozwwOjAJBgNVHRME\n" +
49                 "AjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhvc3SHBH8AAAGHBMCo\n" +
50                 "D5QwDQYJKoZIhvcNAQELBQADggEBADj1MSfHXqjA+WNl3g7Uh/MsSV4VolqXzn++\n" +
51                 "jjaFCTwYURbwG/UglXc3KElTi+HqDRN/ExNgRHT/xnWVH43yhA16geymy2WR1BHO\n" +
52                 "k+k3VNmgbIwd9ocz9x5ol0sMJKqtAuLQlYp2NMvk9KBj7bNiorR5XWXybsMqDYiC\n" +
53                 "thwA436cHnrWRVzYFYhnI+x4Or7Jg/jnLEWmQfmOQqcQs2HG0MSqJcpidtdDMnOe\n" +
54                 "54SZv0T3Dcm2Shyt2Gf5c/gSnSAyb+PdB9O+QDJI0Z9HgqUx4atuQIqwc0LVTkwm\n" +
55                 "cVtfjmPS30lBMyGSwHBX578pEOk1CMdralIqTS5bAyucNL4ALDQ=\n" +
56                 "-----END CERTIFICATE-----";
57         final X509Certificate x509Certificate = Certificates.decodeCertificatePem(cer);
58         HandshakeCertificates certificates = new HandshakeCertificates.Builder()
59                 .addTrustedCertificate(x509Certificate)
60                 .addPlatformTrustedCertificates()
61                 .build();
62         final OkHttpClient clt = new OkHttpClient.Builder()
63                 .sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
64                 .retryOnConnectionFailure(false)
65                 .connectionPool(pool())
66                 .connectTimeout(5, TimeUnit.SECONDS)
67                 .readTimeout(60, TimeUnit.SECONDS)
68                 .writeTimeout(60, TimeUnit.SECONDS)
69                 .build();
70         return clt;
71     }
72 
73     private OkHttpClient trustWithCertificate(){
74         
75     }
76 
77     @Bean
78     public ConnectionPool pool() {
79         return new ConnectionPool(5, 5, TimeUnit.MINUTES);
80     }
81 
82     public class ErrorHandler extends DefaultResponseErrorHandler {
83         @Override
84         public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
85             return super.hasError(clientHttpResponse);
86         }
87 
88         @Override
89         public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
90             //won't throw error so we can receive http status code in business service
91             log.error("error:{}",clientHttpResponse);
92             log.error("clientHttpResponse.getRawStatusCode():{}",clientHttpResponse.getRawStatusCode());
93             throw new IOException(clientHttpResponse.getStatusCode()+"");
94         }
95     }
96 }
集成到RestTemplate  
  • 導出證書      

    導出證書的過程並不復雜,這里簡單描述一下:

    • 點擊瀏覽器(chrom)地址欄的鎖按鈕 
    • 選擇證書-》選擇詳細信息-》選擇復制到文件-》點擊下一頁-》選擇Base64編碼-》點下一頁-》選擇位置  
  • openssl

    由於我的生產環境是一個私有的證書,不被信任。我還是決定使用自簽名的證書來驗證一下,在我生產環境,以上方案是不是行得通。在*nix環境下,openssl是預置的,我是Windows,只能下載了,官網是沒有Windows的安裝包,這是一個其他的提供打包下載的網址。下載后的文件是 Win64OpenSSL_Light-1_1_1L.msi   http://slproweb.com/products/Win32OpenSSL.html  

    完成安裝之后,我們就可以使用openssl生成證書了。生成證書主要有以下幾個步驟:

    • 生成私鑰
    • 使用私鑰作為輸入,生成證書請求文件。這個步驟需要交互,輸入申請證書的一些必要信息如:郵箱,國家,地區,組織等等。同時也需要加入一些擴展信息,如證書是頒發給哪個地址/域名進行使用。
    • 使用私鑰,證書請求文件 生成簽名文件,相當於自己作為機構給自己頒發了個許可證。

    網上的文章很多講述這個,生成證書的格式也是各種各樣,pem,crt,key都有,但是如果使用Base64編碼,我們可以查看他們的報頭,看他們是什么文件。

    • 私鑰的報頭 -----BEGIN RSA PRIVATE KEY-----
    • 證書請求文件報頭 -----BEGIN CERTIFICATE REQUEST-----
    • 證書的報頭 -----BEGIN CERTIFICATE-----        

    接下來我們詳細說明一下如何配置並生成一個自簽名證書。

  • 生成私鑰

    生成私鑰使用的命令不需要復雜的配置。這個指令表示生成一個2048位長度的私鑰,使用默認的算法RSA。使用的指令如下:

openssl genrsa -out local.key 2048
  • 生成證書請求文件    

    生成證書請求文件我也走了一個彎路,常規的生成證書請求文件指令如下,我在交互過程都用了默認值:

openssl req -new -out local.csr -key local.key

    然后加載證書,啟動服務。使用客戶端訪問得到了下面的錯誤提示:

javax.net.ssl.SSLPeerUnverifiedException: Hostname localhost not verified:
    certificate: sha256/Zm1hvkjPhq1oZmygDIgWrMVhElJ03bsj+gquWyZ6FqM=
    DN: O=Internet Widgits Pty Ltd, ST=Some-State, C=AU
    subjectAltNames: []

    這段提示的意思是localhost 這個域沒有被證書驗證,如果支持該驗證還需要給證書添加相關的信息,然后我找到了下面的解決方案,通過編輯openssl.cnf 並在生成簽名請求文件時,指定這個配置文件。我的配置文件在 C:\Program Files\Common Files\SSL\openssl.cnf 。我截取了編輯的片段如下:

[ v3_req ]

# Extensions to add to a certificate request

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

subjectAltName = @alt_names

[alt_names]

# 改成自己的域名
DNS.1 = localhost
#DNS.2 = helpdesk.example.org
#DNS.3 = systems.example.net

# 改成自己的ip
IP.1 = 127.0.0.1
IP.2 = 192.168.15.148
View Code

    修改后的生成證書請求文件指令,這樣我們就加入了許可的IP地址或域名:

C:\Program Files\Common Files\SSL\cer>openssl req -new -out local.csr -key local.key -config ../openssl.cnf

     生成后的證書我們可以在訪問的時候看到,增加了IP/域信息。

  • 簽發證書

    簽發證書要求輸入證書請求文件和私鑰,相當於自己給了一個認證,通過這一步,會生成一個證書文件。指令如下:

C:\Program Files\Common Files\SSL\cer>openssl x509 -req -days 3650 -in local.csr -signkey local.key -out local.crt -extensions v3_req -extfile ../openssl.cnf
  •  使用NODEJS搭建HTTPS服務

    其實使用SpringBoot搭建一個服務也不難,但是使用Node實在是太簡單了,十多行代碼就可以完成服務。使用證書用Node搭建HTTPS的服務。需要提供私鑰文件,證書文件。證書文件就是在瀏覽器地址欄可以導出的證書(鎖頭圖標)。內容是一樣的。

 1 let express = require('express');
 2 let app = express();
 3 let fs = require('fs');
 4 let https = require('https');
 5 //前者是私鑰,后者是證書,也可以通過瀏覽器導出保存成文件加進來
 6 let cert = {key: fs.readFileSync('./cer/local.key', 'utf8'), cert: fs.readFileSync('./cer/local.crt', 'utf8')};
 7 let httpsServer = https.createServer(cert, app);
 8 app.get('/', function(req, res) {
 9     console.log(req.protocal);
10     if(req.protocol === 'https') {
11         res.send('https require');
12     }
13 });
14 
15 httpsServer.listen(3002, function() {
16     console.log('HTTPS Server is running');
17 });
Express 構建HTTPS服務

    通過運行nodejs服務,加載這個服務。我們的測試就可以順利的跑起來了。

  •  總結

    對於HTTPS提供的服務,如果我們使用Okhttp訪問的話,只要按照上述的方式導出證書,再通過相應的工具類加載證書就可以訪問了。

    對於很多常用網站其實是同時支持HTTP和HTTPS的,即使沒有設置sslSocketFactory 使用https訪問也會得到正確的訪問結果。

    我目前的方式適用於自簽名證書在局域網內的使用場景。

    由於我個人的測試需要,又在搭建HTTPS的服務,加載證書做了一些擴展,同時也了解學習了TLS和SSL協議的一些內容。

    如果對你有幫助,那就最好不過了。限於水平和理解能力,如果有錯誤也歡迎友好指出。

  • 參考

 https://square.github.io/okhttp/4.x/okhttp/okhttp3/  API文檔

https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java  官方的用例

https://blog.51cto.com/wushank/1915795  der pem cer crt key pfx等概念及區別

https://www.jianshu.com/p/cab185575b92  生成證書

https://blog.csdn.net/chennai1101/article/details/116296763  使用 TrustManagerFactory 加載證書


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM