記一次Tomcat證書由jks更換爲pfx的艱辛過程

背景介紹

https不瞭解,對https雙向認證更是一臉懵java

客戶方要求系統提供https的服務,一年前申請某網站的免費證書,下載後包含了各類web容器的證書,應用程序的web容器爲tomcat8,最後選用了tomcat下的證書,爲jks格式。
image.pngweb

一年後免費服務到期,須要更換證書,客戶申請了阿里雲的證書,下載下來的格式爲pfx,客戶說只有這種格式的。坑已挖好。apache

操做記錄

百度搜索結果說能夠將pfx轉換成jks
命令以下:瀏覽器

keytool -importkeystore -srckeystore xxx.pfx -destkeystore xxx.jks -srcstoretype PKCS12 -deststoretype JKS -srcstorepass xxx -deststorepass xxx -srcalias alias -destalias destalias

由於原來tomcat中配置了jks相關的信息,想着直接將新的jks的文件名和密碼保持一致,直接替換原文件就能夠了。結果被坑。tomcat

按原來的文件名、密碼生成新證書後,啓動tomcat,事實證實想的仍是太簡單。
報錯信息以下:服務器

15-Nov-2019 12:20:20.221 SEVERE [main] org.apache.coyote.AbstractProtocol.init Failed to initialize end point associated with ProtocolHandler ["http-nio-443"]
 java.security.UnrecoverableKeyException: Cannot recover key
    at sun.security.provider.KeyProtector.recover(Unknown Source)
    at sun.security.provider.JavaKeyStore.engineGetKey(Unknown Source)
    at sun.security.provider.JavaKeyStore$JKS.engineGetKey(Unknown Source)
    at java.security.KeyStore.getKey(Unknown Source)
    at sun.security.ssl.SunX509KeyManagerImpl.<init>(Unknown Source)
    at sun.security.ssl.KeyManagerFactoryImpl$SunX509.engineInit(Unknown Source)
    at javax.net.ssl.KeyManagerFactory.init(Unknown Source)
    at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:617)
    at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:546)
    .
    .
    .
    Caused by: org.apache.catalina.LifecycleException: Protocol handler initialization failed
    at org.apache.catalina.connector.Connector.initInternal(Connector.java:962)
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:102)
    ... 12 more
Caused by: java.security.UnrecoverableKeyException: Cannot recover key
    at sun.security.provider.KeyProtector.recover(Unknown Source)
    at sun.security.provider.JavaKeyStore.engineGetKey(Unknown Source)

處理辦法:
將新的jks的密碼和pfx的密碼保持一致,修改tomcat配置文件中的密碼。app

其實tomcat能夠直接配置pfx格式證書,須要指定 keystoreType="PKCS12"
keystoreFile="/證書路徑/名稱.pfx" keystoreType="PKCS12" keystorePass="證書密碼"
但此次的客戶有作https雙向認證,以前的代碼已經寫死支持jks證書庫,因此須要生成jkside

遇到的異常說明

密碼錯誤異常

15-Nov-2019 11:59:55.017 SEVERE [main] org.apache.coyote.AbstractProtocol.init Failed to initialize end point associated with ProtocolHandler ["http-nio-443"]
 java.io.IOException: Keystore was tampered with, or password was incorrect
    at sun.security.provider.JavaKeyStore.engineLoad(Unknown Source)
    at sun.security.provider.JavaKeyStore$JKS.engineLoad(Unknown Source)
    .
    .
    .
    Caused by: java.security.UnrecoverableKeyException: Password verification failed
    ... 25 more

pfx和jks密碼不匹配

異常信息以下:post

15-Nov-2019 12:20:20.221 SEVERE [main] org.apache.coyote.AbstractProtocol.init Failed to initialize end point associated with ProtocolHandler ["http-nio-443"]
 java.security.UnrecoverableKeyException: Cannot recover key

雙向認證

示例代碼

import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyStore;


/**
 * #2
 * HTTPS 雙向認證 - use truststore
 * 原生方式
 *
 * @Author soft_xiang
 * @Date 7/11/2017
 */
public class HttpsTruststoreNativeDemo {
    // 客戶端證書路徑,用了本地絕對路徑,須要修改 調用方證書
    private final static String CLIENT_CERT_FILE = "D:\\xxx\\https\\xxx-ip.p12";
    // 客戶端證書密碼
    private final static String CLIENT_PWD = "pwd";
    // 信任庫路徑 (被調用方證書合集)
    private final static String TRUST_STRORE_FILE = "D:\\xxx\\https\\xxx.jks";
    // 信任庫密碼
    private final static String TRUST_STORE_PWD = "xxx";


    private static String readResponseBody(InputStream inputStream) throws IOException {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            StringBuffer sb = new StringBuffer();
            String buff = null;
            while ((buff = br.readLine()) != null) {
                sb.append(buff + "\n");
            }
            return sb.toString();
        } finally {
            inputStream.close();
        }
    }

    public static void httpsCall() throws Exception {
        // 初始化密鑰庫
        KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance("SunX509");
        KeyStore keyStore = getKeyStore(CLIENT_CERT_FILE, CLIENT_PWD, "PKCS12");
        keyManagerFactory.init(keyStore, CLIENT_PWD.toCharArray());

        // 初始化信任庫
        TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance("SunX509");
        KeyStore trustkeyStore = getKeyStore(TRUST_STRORE_FILE, TRUST_STORE_PWD, "JKS");
        trustManagerFactory.init(trustkeyStore);

        // 初始化SSL上下文
        SSLContext ctx = SSLContext.getInstance("SSL");
        ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory
                .getTrustManagers(), null);
        SSLSocketFactory sf = ctx.getSocketFactory();

        HttpsURLConnection.setDefaultSSLSocketFactory(sf);
        String url = "post url";
        URL urlObj = new URL(url);
        HttpsURLConnection con = (HttpsURLConnection) urlObj.openConnection();
        con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
        con.setRequestProperty("Accept-Language", "zh-CN;en-US,en;q=0.5");
        con.setRequestProperty("Content-Type", "text/xml");
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Length", "0");
        con.setDoInput(true);
        con.setDoOutput(true);
        DataOutputStream os = new DataOutputStream(con.getOutputStream());
        os.write("".getBytes("UTF-8"), 0, 0);
        os.flush();
        os.close();
        con.connect();
        InputStream is = con.getInputStream();
        Integer code = con.getResponseCode();
        final String contentType = con.getContentType();
        System.out.println(code + ":" + contentType);
        String response = readResponseBody(is);
        System.out.println(response);
    }

    /**
     * 得到KeyStore
     *
     * @param keyStorePath
     * @param password
     * @return
     * @throws Exception
     */
    private static KeyStore getKeyStore(String keyStorePath, String password, String type)
            throws Exception {
        FileInputStream is = new FileInputStream(keyStorePath);
        KeyStore ks = KeyStore.getInstance(type);
        ks.load(is, password.toCharArray());
        is.close();
        return ks;
    }


    public static void main(String[] args) throws Exception {
        httpsCall();
    }

}

雙向認證示例中可能碰到的問題

image.png
在容許調用的服務器,直接瀏覽器打開須要訪問的地址,調用出現401,sap問題,協調sap解決網站

image.png
證書庫密碼錯誤

image.png
證書錯誤,確認sap加入信任庫的證書和代碼中調用的證書庫中的證書是同一個

經常使用命令

keytool -import -alias xxx -file "xxx.der" -keystore xxx.jks -storepass pass
將證書導入信任庫

keytool -export -alias xxx -keystore abc.jks -storepass pass -file xxx.cer
將證書庫abc.jks中別名爲xxx的證書導出爲xxx.cer,cer客戶端證書

keytool -import -alias xxx -file xxx.cer -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit -trustcacerts將客戶端證書導入jdk的默認信任庫(cacerts爲jdk默認信任庫,導入以前記得備份)

相關文章
相關標籤/搜索