Nginx Tomcat 構造https服務 ssl支持改進

由于蘋果對加密算法的要求

ATS要求服務器必須支持傳輸層安全(TLS)協議1.2以上版本;證書必須使用SHA256或更高的哈希算法簽名;必須使用2048位以上RSA密鑰或256位以上ECC算法等等,不滿足條件的證書,ATS都會拒絕連接。

前一篇文章構造的自簽名證書的加密算法過低不符合要求,這篇文章做一下改進。

1.openssl genrsa -aes256 -out ca.key 8192
2.openssl req -sha256 -new -x509 -days 1826 -key ca.key -out ca.crt
3.openssl pkcs12 -export -clcerts -in ca.crt -inkey ca.key -out root.p12
4.keytool -import -v -trustcacerts -storepass holly.wang -alias root -file ca.crt -keystore root.jks

說一下證書相關的東東(SSL(TLS)、X.509、PEM、DER、CRT、CER、KEY、CSR、P12等)

SSL

Secure Sockets Layer,現在應該叫"TLS",但由于習慣問題,我們還是叫"SSL"比較多.http協議默認情況下是不加密內容的,這樣就很可能在內容傳播的時候被別人監聽到,對于安全性要求較高的場合,必須要加密,https就是帶加密的http協議,而https的加密是基于SSL的,它執行的是一個比較下層的加密,也就是說,在加密前,你的服務器程序在干嘛,加密后也一樣在干嘛,不用動,這個加密對用戶和開發者來說都是透明的.維基百科解釋 | RFC定義的標準

OpenSSL

OpenSSL 是一個安全套接字層密碼庫,囊括主要的密碼算法、常用的密鑰和證書封裝管理功能及SSL協議,并提供豐富的應用程序供測試或其它目的使用。
  OpenSSL被曝出現嚴重安全漏洞后,發現多數通過SSL協議加密的網站使用名為OpenSSL的開源軟件包。OpenSSL漏洞不僅影響以https開頭的網站,黑客還可利用此漏洞直接對個人電腦發起“心臟出血”(Heartbleed)攻擊。據分析,Windows上有大量軟件使用了存在漏洞的OpenSSL代碼庫,可能被黑客攻擊抓取用戶電腦上的內存數據。

證書標準X.509

X.509 - 這是一種證書標準,主要定義了證書中應該包含哪些內容.數字證書的格式遵循X.509標準。X.509是由國際電信聯盟(ITU-T)制定的數字證書標準

編碼格式

同樣的X.509證書,可能有不同的編碼格式,目前有以下兩種編碼格式.
  PEM - Privacy Enhanced Mail,Apache和NGINX服務器偏向于使用這種編碼格式.打開看文本格式,以"-----BEGIN..."開頭, "-----END..."結尾,內容是BASE64編碼。
查看PEM格式證書的信息:

openssl x509 -in certificate.pem -text -noout

DER - Distinguished Encoding Rules,Java和Windows服務器偏向于使用這種編碼格式.打開看是二進制格式,不可讀.查看DER格式證書的信息:

openssl x509 -in certificate.der **-inform der** -text -noout

相關的文件擴展名

CRT - certificate的三個字母,證書的意思,常見于NGINX系統,有可能是PEM編碼,也有可能是DER編碼,大多數應該是PEM編碼.
  CER - 還是certificate,還是證書,常見于Windows系統,同樣的,可能是PEM編碼,也可能是DER編碼,大多數應該是DER編碼.
  KEY - 通常用來存放一個私鑰,并非X.509證書,編碼同樣的,可能是PEM,也可能是DER.
PEM查看KEY的辦法:

openssl rsa -in mykey.key -text -noout

DER查看KEY的辦法:

openssl rsa -in mykey.key -text -noout  -inform der

CSR - Certificate Signing Request,即證書簽名請求,這個并不是證書,而是向權威證書頒發機構獲得簽名證書的申請,其核心內容是一個公鑰(當然還附帶了一些別的信息),在生成這個申請的時候,同時也會生成一個私鑰,私鑰要自己保管好.
PEM查看CSR的辦法:

openssl req -noout -text -in my.csr 

DER查看CSR的辦法:

openssl req -noout -text -in my.csr -inform der

PFX/P12 - predecessor of PKCS#12,對ngnix服務器來說,一般CRT和KEY是分開存放在不同文件中的,但Windows的IIS則將它們存在一個PFX文件中,(因此這個文件包含了證書及私鑰)這樣會不會不安全?應該不會,PFX通常會有一個"提取密碼",你想把里面的東西讀取出來的話,它就要求你提供提取密碼,
生成服務端p12格式根證書

openssl pkcs12 -export -clcerts -in server-cert.cer -inkey server-key.key -out server.p12

PFX使用的時DER編碼
把PFX轉換為PEM編碼方式

openssl pkcs12 -in for-iis.pfx -out for-iis.pem -nodes

這個時候會提示你輸入提取代碼. for-iis.pem就是可讀的文本.
生成pfx的命令類似這樣

openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt

其中CACert.crt是CA(權威證書頒發機構)的根證書,有的話也通過-certfile參數一起帶進去.這么看來,PFX其實是個證書密鑰庫.

JKS - 即Java Key Storage,這是Java的專利,跟OpenSSL關系不大,利用Java的一個叫"keytool"的工具,可以將PFX轉為JKS,當然了,keytool也能直接生成JKS

 keytool -import -v -trustcacerts -storepass 123456 -alias server -file server-cert.cer -keystore
server.jks

證書編碼的轉換

PEM轉為DER openssl x509 -in cert.crt -outform der -out cert.der
DER轉為PEM openssl x509 -in cert.crt -inform der -outform pem -out cert.pem
(提示:要轉換KEY文件也類似,只不過把x509換成rsa,要轉CSR的話,把x509換成req)

這里附上java利用httpclient實現實現對服務端的https請求
第一種方式 繞過證書的方式(主要是生成 SSLContext 的時候對所有證書都是信任方式)

/** 
     * 模擬請求 
     *  
     * @param url       資源地址 
     * @param map   參數列表 
     * @param encoding  編碼 
     * @return 
     * @throws NoSuchAlgorithmException  
     * @throws KeyManagementException  
     * @throws IOException  
     * @throws ClientProtocolException  
     */  
    @SuppressWarnings("deprecation")
    public static String ignoreVerifySSL(String url,String json) {  
        String body = "";  
        try {
            //采用繞過驗證的方式處理https請求  
            SSLContext sslcontext = createIgnoreVerifySSL();  
      
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext,
                    new String[] { "SSLv3","TLSv1","TLSv1.1", "TLSv1.2" } ,//SSLv3 TLSv1 TLSv1.1 TLSv1.2
                    null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());
   
            CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build();
   
            HttpPost httpPost = new HttpPost(url);
            StringEntity stringEntity = new StringEntity(json);
            httpPost.setEntity(stringEntity);
            httpPost.setHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON);
    
            //執行請求操作,并拿到結果(同步阻塞)  
            CloseableHttpResponse response = client.execute(httpPost);  
            //獲取結果實體  
            HttpEntity entity = response.getEntity();  
            if (entity != null) {  
                //按指定編碼轉換結果實體為String類型  
                body = EntityUtils.toString(entity, HTTP.UTF_8);
            }  
            EntityUtils.consume(entity);  
            //釋放鏈接  
            response.close();  
        } catch (ParseException | IOException | KeyManagementException | NoSuchAlgorithmException e) {
            log.error(e);
        }
        return body;  
    }
    
    /** 
     * 繞過驗證 
     *   
     * @return 
     * @throws NoSuchAlgorithmException  
     * @throws KeyManagementException  
     */  
    public static SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {  
        SSLContext sc = SSLContext.getInstance("TLS");  
      
        // 實現一個X509TrustManager接口,用于繞過驗證,不用修改里面的方法  
        X509TrustManager trustManager = new X509TrustManager() {  
            @Override  
            public void checkClientTrusted(  
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,  
                    String paramString) throws CertificateException {  
            }  
      
            @Override  
            public void checkServerTrusted(  
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,  
                    String paramString) throws CertificateException {  
            }  
      
            @Override  
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {  
                return null;  
            }  
        };  
      
        sc.init(null, new TrustManager[] { trustManager }, null);  
        return sc;  
    }

第二種方式信任自簽名證書

 /** 
     * 設置信任自簽名證書 
     *   
     * @param keyStorePath      密鑰庫路徑 
     * @param keyStorepass      密鑰庫密碼 
     * @return 
     */  
    public static SSLContext custom(String keyStorePath, String keyStorepass){  
        SSLContext sc = null;  
        FileInputStream instream = null;  
        KeyStore trustStore = null;  
        try {  
            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());  
            instream = new FileInputStream(new File(keyStorePath));  
            trustStore.load(instream, keyStorepass.toCharArray());  
            // 相信自己的CA和所有自簽名的證書  
            sc = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();  
        } catch (KeyStoreException | NoSuchAlgorithmException| CertificateException | IOException | KeyManagementException e) {  
            e.printStackTrace();  
        } finally {  
            try {  
                instream.close();  
            } catch (IOException e) {  
            }  
        }  
        return sc;  
    }
    /** 
     * 模擬請求 
     *  
     * @param url       資源地址 
     * @param map   參數列表 
     * @param encoding  編碼 
     * @return 
     * @throws ParseException 
     * @throws IOException 
     * @throws KeyManagementException  
     * @throws NoSuchAlgorithmException  
     * @throws ClientProtocolException  
     */  
    public static String send(String url, String json,String encoding) throws ClientProtocolException, IOException {  
        String body = "";  
          
        //tomcat是我自己的密鑰庫的密碼,你可以替換成自己的  
        //如果密碼為空,則用"nopassword"代替  
        SSLContext sslcontext = custom("D:\\work\\jiezhike\\root.jks", "holly.wang");  
          
      /* // 設置協議http和https對應的處理socket鏈接工廠的對象  
       Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
               .register("http", PlainConnectionSocketFactory.INSTANCE)  
               .register("https", new SSLConnectionSocketFactory(sslcontext))  
               .build();  
       PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);  
       HttpClients.custom().setConnectionManager(connManager);  */
       
       // Allow TLSv1 protocol only
       SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
               sslcontext,
               new String[] { "SSLv3","TLSv1","TLSv1.1", "TLSv1.2" } ,//SSLv3 TLSv1 TLSv1.1 TLSv1.2
               null,
               SSLConnectionSocketFactory.getDefaultHostnameVerifier());
      
           //創建自定義的httpclient對象  
//        CloseableHttpClient client = HttpClients.custom().setConnectionManager(connManager).build();  
        CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build();  
//      CloseableHttpClient client = HttpClients.createDefault();  
          
        //創建post方式請求對象
        HttpPost httpPost = new HttpPost(url);
        StringEntity stringEntity = new StringEntity(json);
        httpPost.setEntity(stringEntity);
        httpPost.setHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON);
        
        //執行請求操作,并拿到結果(同步阻塞)  
        CloseableHttpResponse response = client.execute(httpPost);  
        //獲取結果實體  
        HttpEntity entity = response.getEntity();  
        if (entity != null) {  
            //按指定編碼轉換結果實體為String類型  
            body = EntityUtils.toString(entity, encoding);  
        }  
        EntityUtils.consume(entity);  
        //釋放鏈接  
        response.close();  
        return body;  
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容