我正在编写一个程序,需要与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。答案 0 :(得分:3)
负责选择将使用密钥库中的哪个密钥是 KeyManager 对象。 您可以获取调用 keyManagerFactory.getKeyManagers()的那些对象的向量。
库通常会获得他们在商店中找到的第一个密钥条目(可能与本案例中提供的服务器证书兼容)。 MS-CAPI API没有什么不同。
要选择要使用的密钥库中的哪个密钥,您应该做三件事:
实现接口 X509KeyManager
使上述界面的方法chooseClientAlias返回您所需的别名
将对象设置为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);
答案 1 :(得分:0)
您可能想尝试使用其他上下文。为Windows应用商店提供的安全性可能无法识别“TLS”。也许是“SSL_TLS”?