-
背景
本人目前遇到一個需求,甲方在內網使用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 }
到現場測試,發現首次訪問頁面是這樣的。原來是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
- 添加證書允許指定的信任服務通過
-
信任全部連接
顯而易見,第一種方案的安全性不能得到保證。但是為了參考,我下面還是給出第一種方案的代碼:

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 }
這里提一下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 }
-
導出證書
導出證書的過程並不復雜,這里簡單描述一下:
-
- 點擊瀏覽器(chrom)地址欄的鎖按鈕
- 選擇證書-》選擇詳細信息-》選擇復制到文件-》點擊下一頁-》選擇Base64編碼-》點下一頁-》選擇位置
- 點擊瀏覽器(chrom)地址欄的鎖按鈕
-
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
修改后的生成證書請求文件指令,這樣我們就加入了許可的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 });
通過運行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 加載證書