簡體   English   中英

如何使用MSCAPI提供程序進行客戶端SSL身份驗證

[英]How to use MSCAPI provider for client SSL authentication

我正在編寫一個程序,需要與Web服務器建立HTTPS連接,需要使用SSL客戶端身份驗證。

該程序的用戶將使用來自Windows環境的證書來驗證自己。

我發現了大量示例說明如何設置客戶端身份驗證,如果我首先將證書導出為pkcs12格式,它可以正常工作,但我不想強迫我的用戶這樣做。 但是,當我嘗試使用MSCAPI時,它總是會出現異常情況:

javax.net.ssl.SSLHandshakeException: Error signing certificate verify
        at sun.security.ssl.Alerts.getSSLException(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
        at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
        at sun.security.ssl.ClientHandshaker.serverHelloDone(Unknown Source)
        at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source)
        at sun.security.ssl.Handshaker.processLoop(Unknown Source)
        at sun.security.ssl.Handshaker.process_record(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
        at sun.security.ssl.AppInputStream.read(Unknown Source)
        at java.io.BufferedInputStream.fill(Unknown Source)
        at java.io.BufferedInputStream.read1(Unknown Source)
        at java.io.BufferedInputStream.read(Unknown Source)
        at sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source)
        at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
        at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(Unknown Source)
        at com.example.Win2.main(Win2.java:62)
Caused by: java.security.SignatureException: Bad Key.

        at sun.security.mscapi.RSASignature.signHash(Native Method)
        at sun.security.mscapi.RSASignature.engineSign(RSASignature.java:390)
        at java.security.Signature$Delegate.engineSign(Unknown Source)
        at java.security.Signature.sign(Unknown Source)
        at sun.security.ssl.RSASignature.engineSign(Unknown Source)
        at java.security.Signature$Delegate.engineSign(Unknown Source)
        at java.security.Signature.sign(Unknown Source)
        at sun.security.ssl.HandshakeMessage$CertificateVerify.<init>(Unknown Source)
        ... 16 more

我無法確定該異常中的密鑰可能出現的問題。

我做了一個很小的測試程序來重現我遇到的問題:

String passwd = .....";
URL url = new URL("https://.........");

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, passwd.toCharArray());
keyManagerFactory.init(keyStore, passwd.toCharArray());

SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory.getKeyManagers(), null, null);
SSLSocketFactory socketFactory = context.getSocketFactory();

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(socketFactory);

很明顯我在這里不了解的API。 它如何知道我想要使用的密鑰庫中的哪些密鑰? 我期待Windows提示我,就像其他需要進行身份驗證的應用程序一樣,但我懷疑它只是選擇它找到的第一個。

我是否需要實現自己的密鑰管理器,以便選擇使用哪個密鑰?

如果我遍歷密鑰庫,我可以看到其中的密鑰,並可以通過調用getKey來提取密鑰。 更復雜的是,商店中有多個具有相同別名的鍵(但有效性不同)。 其他(非Java)應用程序,例如。 Chrome,似乎能夠以某種方式確定使用哪些密鑰。

編輯:我忘了提,我使用的是Java 1.7。

負責選擇將使用密鑰庫中的哪個密鑰是KeyManager對象。 您可以獲取調用keyManagerFactory.getKeyManagers()的那些對象的向量。

這些庫通常會獲得他們在商店中找到的第一個密鑰條目(可以與此案例中提供的服務器證書兼容)。 MS-CAPI API沒有什么不同。

要選擇要使用的密鑰庫中的哪個密鑰,您應該執行以下三項操作:

  1. 實現接口X509KeyManager

  2. 使上述接口的方法chooseClientAlias返回所需的密鑰別名

  3. 將對象設置為SSLContext。

提醒您的密鑰庫必須包含從您的個人證書開始到根權限的所有證書鏈。 您應該使用certmgr.msc程序導入證書和/或檢查所有證書是否存在於個人 (您的證書), 中間證書頒發機構 (鏈中的任何中間CA)和受信任的根證書頒發機構文件夾中。

設置信任密鑰庫也很重要 - 它存儲您信任的根證書,該存儲將用於驗證服務器證書。 在MS-CAPI的情況下,您將使用KeyStore.getInstance(“Windows-ROOT”)命令來獲取它( 受信任的根證書頒發機構文件夾中的證書 )。

修改您的代碼以實現此目的:

URL url = new URL("https://.........");

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, null);
keyManagerFactory.init(keyStore);

/* You must also set your trust store */
KeyStore ts = KeyStore.getInstance("Windows-ROOT");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);

/* Here you can implement a way to set your key alias 
** You can run through all key entries and implement a way
** to prompt the user to choose one - for simplicity I just set a
** name*/
String alias = "user1_alias";

/* Get your current KeyManager from the factory */
final X509KeyManager okm = (X509KeyManager)keyManagerFactory.getKeyManagers()[0];

/* Implement the Interface X509KeyManager */
X509KeyManager km = new X509KeyManager() {
    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
         /* Implement your own logic to choose the alias
            according to the validity if the case, 
            or use the entry id or any other way, you can get 
            those values outside this class*/
         return alias;
    }

    public X509Certificate[] getCertificateChain(String alias) {
         return okm.getCertificateChain(alias);
    }
   /* Implement the other methods of the interface using the okm object */
};
SSLContext context = SSLContext.getInstance("TLS");
/* set the keymanager in the SSLContext */
context.init(new KeyManager[]{km}, tmf.getTrustManagers(), new SecureRandom());
SSLSocketFactory socketFactory = context.getSocketFactory();

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(socketFactory);

您可能想嘗試使用不同的上下文。 為Windows應用商店提供的安全性可能無法識別“TLS”。 也許“SSL_TLS”呢?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM