首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用MSCAPI提供程序进行客户端SSL身份验证

如何使用MSCAPI提供程序进行客户端SSL身份验证
EN

Stack Overflow用户
提问于 2013-12-02 14:17:40
回答 2查看 2K关注 0票数 5

我正在编写一个程序,需要使HTTPS连接到web服务器,其中需要使用SSL客户端身份验证。

此程序的用户将使用windows环境中的证书来验证自己。

我发现了大量示例,说明如何设置客户端身份验证,如果我首先将证书导出为pkcs12格式,则可以很好地工作,但我不希望强迫用户这样做。然而,当我尝试使用MSCAPI时,它总是失败,并出现一个异常:

代码语言:javascript
复制
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

我真的不能从那个异常中判断出键可能出了什么问题。

我做了一个小的测试程序来重现我遇到的问题:

代码语言:javascript
复制
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似乎能够以某种方式确定使用哪些键。

编辑:我忘了提一下,我使用的是Java1.7。

EN

回答 2

Stack Overflow用户

发布于 2017-03-29 15:40:56

负责选择密钥库中哪个密钥将被使用的是KeyManager对象。您可以获得这些调用keyManagerFactory.getKeyManagers()的对象的向量。

库通常获取它们在存储中找到的第一个密钥条目(可能与本例中提供的服务器证书兼容)。MS-CAPI API没有什么不同。

要选择密钥库中要使用的密钥,您应该做3件事:

  1. 实现上述接口的方法chooseClientAlias返回您的键的所需别名
  2. 将对象设置为您的SSLContext。

请注意,您的密钥库必须包含从您的个人证书到根颁发机构的所有证书链。您应该使用certmgr.msc程序导入证书和/或检查是否所有证书都存在于个人(您的证书)、中间证书颁发机构(链中的任何中间CA )和受信任的根证书颁发机构文件夹中。

设置您的信任密钥库也很重要-它存储您信任的根证书,此存储将用于验证服务器证书。对于MS-CAPI,您可以使用KeyStore.getInstance("Windows- Root ")命令来获取它(受信任的根证书颁发机构文件夹中的证书)。

修改你的代码来实现这一点:

代码语言:javascript
复制
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);
票数 4
EN

Stack Overflow用户

发布于 2016-04-21 05:18:44

您可能希望尝试使用不同的上下文。为您的Windows应用商店提供的安全性可能无法识别"TLS“。也许用"SSL_TLS“来代替?

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20321984

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档