简体   繁体   English

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

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

I am writing a program which needs to make HTTPS connections to a web server, where SSL Client authentication needs to be used. 我正在编写一个程序,需要与Web服务器建立HTTPS连接,需要使用SSL客户端身份验证。

The users of this program will use certificates from the windows environment to authenticate themselves. 该程序的用户将使用来自Windows环境的证书来验证自己。

I've found plenty of examples showing how to set up client authentication, and it works fine if I first export my certificate to pkcs12 format, but I don't wish to force my users to do that. 我发现了大量示例说明如何设置客户端身份验证,如果我首先将证书导出为pkcs12格式,它可以正常工作,但我不想强迫我的用户这样做。 However when I try to use the MSCAPI it always bombs out with an exception: 但是,当我尝试使用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

I can't really tell what might be wrong with the key from that exception. 我无法确定该异常中的密钥可能出现的问题。

I have made a tiny test program to reproduce the problem I am having: 我做了一个很小的测试程序来重现我遇到的问题:

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);

There is clearly something about the APIs I don't understand here. 很明显我在这里不了解的API。 How does it know which of the keys in the keystore I want to use? 它如何知道我想要使用的密钥库中的哪些密钥? I was expecting for Windows to prompt me, as it does for other applications which need to authenticate, but I suspect instead it is just choosing the first one it finds. 我期待Windows提示我,就像其他需要进行身份验证的应用程序一样,但我怀疑它只是选择它找到的第一个。

Do I need to implement my own key manager so it can choose which key will be used? 我是否需要实现自己的密钥管理器,以便选择使用哪个密钥?

If I iterate through the keystore, I can see the keys in it and can extract one by calling getKey. 如果我遍历密钥库,我可以看到其中的密钥,并可以通过调用getKey来提取密钥。 To complicate matters, there are multiple keys in the store with the same alias (but differing validity). 更复杂的是,商店中有多个具有相同别名的键(但有效性不同)。 Other (non-java) applications, eg. 其他(非Java)应用程序,例如。 Chrome, seem to be able to determine which keys to use somehow. Chrome,似乎能够以某种方式确定使用哪些密钥。

EDIT: I forgot to mention, I am using Java 1.7. 编辑:我忘了提,我使用的是Java 1.7。

The responsible for choosing which key in the keystore would be used is the KeyManager object. 负责选择将使用密钥库中的哪个密钥是KeyManager对象。 You can get a vector of those objects calling keyManagerFactory.getKeyManagers() . 您可以获取调用keyManagerFactory.getKeyManagers()的那些对象的向量。

The libraries usually get the first key entry they find in the store (could be compatible with the server certificate presented in this case). 这些库通常会获得他们在商店中找到的第一个密钥条目(可以与此案例中提供的服务器证书兼容)。 The MS-CAPI API does no different. MS-CAPI API没有什么不同。

To select which key in the keystore you want to use, you should do 3 things: 要选择要使用的密钥库中的哪个密钥,您应该执行以下三项操作:

  1. implement the interface X509KeyManager 实现接口X509KeyManager

  2. make the method chooseClientAlias of the above interface return the desired alias of your key 使上述接口的方法chooseClientAlias返回所需的密钥别名

  3. set the object to your SSLContext. 将对象设置为SSLContext。

Remind that your keystore must contain all the chain of certificates starting at your personal certificate up to a root authority. 提醒您的密钥库必须包含从您的个人证书开始到根权限的所有证书链。 You should use the certmgr.msc program to import the certificates and/or check if all of them are present in the folders Personal (your certificate), Intermediate Certification Authorities (any middle CA in your chain) and Trusted Root Certification Authorities . 您应该使用certmgr.msc程序导入证书和/或检查所有证书是否存在于个人 (您的证书), 中间证书颁发机构 (链中的任何中间CA)和受信任的根证书颁发机构文件夹中。

It's also important to set your trust keystore - it stores the root certificates you trust, this store would be used to verify the server certificate. 设置信任密钥库也很重要 - 它存储您信任的根证书,该存储将用于验证服务器证书。 In case of MS-CAPI you would use the KeyStore.getInstance("Windows-ROOT") command to get it (the certs in the Trusted Root Certification Authorities folder). 在MS-CAPI的情况下,您将使用KeyStore.getInstance(“Windows-ROOT”)命令来获取它( 受信任的根证书颁发机构文件夹中的证书 )。

Modifying your code to accomplish that: 修改您的代码以实现此目的:

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);

You may want to try using a different context. 您可能想尝试使用不同的上下文。 The security provided for your Windows store may not recognize "TLS". 为Windows应用商店提供的安全性可能无法识别“TLS”。 Maybe "SSL_TLS" instead? 也许“SSL_TLS”呢?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM