Java 如何使用MSCAPI提供程序进行客户端SSL身份验证
我正在编写一个程序,需要将HTTPS连接到需要使用SSL客户端身份验证的web服务器 此程序的用户将使用windows环境中的证书进行身份验证 我已经找到了很多示例,展示了如何设置客户机身份验证,如果我首先将证书导出为pkcs12格式,效果会很好,但我不想强迫用户这样做。但是,当我尝试使用MSCAPI时,它总是会爆炸,但有一个例外:Java 如何使用MSCAPI提供程序进行客户端SSL身份验证,java,security,authentication,ssl,Java,Security,Authentication,Ssl,我正在编写一个程序,需要将HTTPS连接到需要使用SSL客户端身份验证的web服务器 此程序的用户将使用windows环境中的证书进行身份验证 我已经找到了很多示例,展示了如何设置客户机身份验证,如果我首先将证书导出为pkcs12格式,效果会很好,但我不想强迫用户这样做。但是,当我尝试使用MSCAPI时,它总是会爆炸,但有一个例外: javax.net.ssl.SSLHandshakeException: Error signing certificate verify at s
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
很明显,这里有些关于API的东西我不理解。它如何知道我要使用密钥库中的哪些密钥?我原以为Windows会提示我,就像其他需要验证的应用程序一样,但我怀疑它只是在选择它找到的第一个应用程序
我是否需要实现自己的密钥管理器,以便它可以选择使用哪个密钥
如果我遍历密钥库,我可以看到其中的密钥,并且可以通过调用getKey来提取一个密钥。更复杂的是,存储中有多个具有相同别名(但有效性不同)的密钥。其他(非java)应用程序,例如Chrome,似乎能够确定以某种方式使用哪些键
编辑:我忘了提到,我正在使用Java 1.7。您可能想尝试使用不同的上下文。为Windows应用商店提供的安全性可能无法识别“TLS”。可能改为“SSL_TLS”?负责选择密钥库中使用哪个密钥的是KeyManager对象。 您可以通过调用keyManagerFactory.getKeyManagers()获得这些对象的向量 这些库通常会获得它们在存储中找到的第一个密钥项(可能与本例中提供的服务器证书兼容)。MS-CAPI API也不例外 要在密钥库中选择要使用的密钥,应执行3项操作:
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);
有关于这次整合的消息吗?你找到解决办法了吗?@olator不,我没有。到目前为止,需要这样做的用户数量不足以保证投入更多的时间,因此这些用户不得不忍受将证书导出为pkcs12格式。如果可以的话,我真的很想在这方面有所改进!
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);