-
背景
本人目前遇到一个需求,甲方在内网使用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 加载证书