我很抱歉,这太长了。如果您熟悉在 Java 中执行客户端身份验证,您可能可以略过/跳到底部。我花了很长时间从不同的来源找到所有相关的信息。也许这会帮助别人。
我正在研究概念验证,以使用 Windows 密钥库从 Java 客户端进行客户端身份验证。我创建了一个可以请求或要求客户端证书的 servlet。它只返回一个 HTML 响应,其中包含证书信息,包括主题 DN 和序列号。我已经对浏览器(主要是 Chrome 和 IE)进行了许多实验,以验证它是否正常工作。我使用 SSL 生成的证书和我公司内部 CA 颁发的证书都获得了成功的客户端身份验证。
我的下一步是拥有一个与 Java 中的 MSCAPI 密钥库一起工作的 Java 客户端。我走这条路的原因是我们要使用的证书是由我们的内部 CA 颁发的,并且会自动添加到 Windows 中的个人密钥库中,并标记为不可导出。我知道如果您是工作站的管理员,可以使用一些免费工具将私钥从密钥库中取出。在这种情况下,由于各种原因,这不是一个可行的选择。这是我最终得到的代码:
private static SSLSocketFactory getFactory() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException
{
KeyStore roots = KeyStore.getInstance("Windows-ROOT", new SunMSCAPI());
KeyStore personal = KeyStore.getInstance("Windows-MY", new SunMSCAPI());
roots.load(null,null);
personal.load(null,null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(roots);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(personal, "".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}
这可行,但是当我连接时,Java 客户端正在使用我之前生成的客户端证书进行身份验证。我没有看到强制选择特定密钥的明显方法,所以我认为它只是选择了主机信任的第一个密钥。然后我删除了我从 Windows 密钥库创建的这些证书,它不再进行身份验证。我三重检查了服务器端是否仍然可以与浏览器一起使用,并且确实如此。然后我将这些证书读取到个人密钥库中,并从服务器端删除了我的伪 CA 的信任。仍然在浏览器中工作,而不是在客户端。
我在我的 VM 参数中添加了 -Djavax.net.debug=ssl:handshake 并开始查看输出。我看到的一件事是我添加的每个证书的“找到密钥:[别名]”行,但不是通过 certmgr.msc 的“自我注册”功能添加的证书。最初我认为这是因为它们是可导出的,但我删除了它们并添加了一个作为不可导出的,Java 仍然可以使用它。我不知道为什么 SunMSCAPI 不会使用这些证书。我和内部CA创建的都是sha256RSA。两者都启用了客户端身份验证。当我添加以下代码时,我发现 isKeyEntry() 对于我要使用的所有密钥对都是错误的:
Enumeration<String> aliases = personal.aliases();
while (aliases.hasMoreElements())
{
String alias = aliases.nextElement();
System.out.println(alias + " " + personal.isKeyEntry(alias));
}
我在这里找到了其他有类似问题但没有明确答案的人。