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