我目前正在开发一个涉及客户端和服务器之间相互SSL的项目.我能够使用P12和JKS文件执行此操作,但现在需要使用智能卡令牌执行相同的操作.
首先,我很困惑,我无法从卡中获取私钥(我们在这里谈论不可提取的密钥),但现在我认为我理解整个过程的基础知识(请纠正我,如果我错了,请):
我不是直接读取私钥,而是让智能卡通过提供程序处理密钥(我们在Linux中使用OnePin OpenSC PKCS11,在Windows中使用SunMSCAPI).这样,密钥永远不会从卡中实际提取出来.
由于我们所讨论的客户端非常复杂,我们决定创建一个更简单的测试项目,首先尝试使用智能卡的令牌进行签名和加密等基本加密操作.
我设法使用以下代码在Linux上签名:
Provider sunMSC = Security.getProvider("SunMSCAPI"); final String alias = "Some Alias of a KeyEntry"; final byte[] msg = "Hello World".getBytes(); KeyStore.CallbackHandlerProtection chp = new KeyStore.CallbackHandlerProtection(new FixedPINCallbackHandler("12345")); KeyStore.Builder builder = KeyStore.Builder.newInstance("Windows-MY", sunMSC, chp); KeyStore store = builder.getKeyStore(); PrivateKey pk = (PrivateKey)store.getKey(alias, null); Signature signature = Signature.getInstance("SHA1withRSA", sunMSC); signature.initSign(pk); signature.update(msg); final byte[] sig = signature.sign();
我可以使用相应的证书验证该签名.该FixedPINCallbackHandler
班是非常简单的,只是设置每个的密码PasswordCallback
,它在遇到handle
-方法与给定的PIN(12345在本例的情况下).我认为PIN并不重要,因为当执行上述代码时,Linux和Windows都会使用读卡器的驱动程序询问PIN(我尝试了有效和无效的PIN,给我相同的结果.如果以下情况,Windows会抱怨但是,你没有在回调处理代码中设置任何键.
问题是上面的代码在Linux中完全正常工作在WIndows中不起作用.它碰到上面代码片段中的最后一行(当signature.sign()
被调用时)时,会抛出以下异常:
Exception in thread "main" java.security.SignatureException: Ungültiger Schlüssel 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 (my code).
(我来自德国,上面的堆栈跟踪中的"UngültigerSchlüssel"意味着"无效密钥").
这个类PrivateKey
是sun.security.mscapi.RSAPrivateKey
.
当我试图建立与服务器的连接时,我总会得到这样的东西:
*** CertificateVerify DAV-WS-Mirror, WRITE: TLSv1 Handshake, length = 134 DAV-WS-Mirror, WRITE: TLSv1 Change Cipher Spec, length = 1 *** Finished verify_data: { 122, 176, 195, 78, 166, 117, 103, 191, 102, 9, 101, 255 } *** DAV-WS-Mirror, WRITE: TLSv1 Handshake, length = 40 DAV-WS-Mirror, received EOFException: error DAV-WS-Mirror, handling exception: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake DAV-WS-Mirror, SEND TLSv1 ALERT: fatal, description = handshake_failure DAV-WS-Mirror, WRITE: TLSv1 Alert, length = 24 DAV-WS-Mirror, called closeSocket() [DEBUG] [2013-12-04 15:11:40,809] [HTTPSender-146] - [javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake]
(DAV-WS-Mirror只是一个线程的名称,如果有人想知道).
万一这很重要,这就是pkcs15-tool -k
告诉我智能卡上的私钥的原因:
Private RSA Key [Private Key] Object Flags : [0x3], private, modifiable Usage : [0x4], sign Access Flags : [0x1D], sensitive, alwaysSensitive, neverExtract, local ModLength : 2048 Key ref : 1 (0x1) Native : yes Path : 3f005015 Auth ID : 01 ID : 45
我尝试了第二个智能卡(实际上是我们需要支持的卡的类型,这与我拥有的卡不同),但是获得了相同的堆栈跟踪.
我尝试安装无限强度策略文件(JCE),但堆栈跟踪保持不变(它们可能已经安装).
不同版本的Java.最终的软件必须与Java 6一起使用,但我想排除不同版本的任何问题(是的,客户端运行在32位版本上).
我真的很想让这个代码在Windows和Linux中运行(因为客户端软件主要针对Windows机器,Windows部分更重要),后来能够在客户端和服务器之间创建相互认证的SSL连接使用智能卡.
我可以自由选择库,所以如果有替代方案,我绝对没有离开SunMSCAPI的问题.
如果您还有任何疑问,请随时提出,我会尽可能快速彻底地回答.
非常感谢任何帮助,谢谢你们提前.
我终于通过简单地安装OpenSC并使用他们的DLL而不是MSCAPI(我在SO上发现这篇文章之后)设法在Windows中签名数据.
代码保持不变,但Security.getProvider("SunMSCAPI")
它现在使用的new sun.security.pkcs11.SunPKCS11(new FileInputStream(new File(configFile)))
地方configFile
只是包含以下内容的文件的完全限定路径:
name=OpenSC library=C:\\opensc-pkcs11.dll slot=1
正如我之前提到的,使用此代码,我可以对数据进行签名.
然后,我尝试创建与我的服务器软件相互认证的SSL连接.我从SSL调试日志中看到的最后一件事是(剪辑):
Server write IV: 0000: 79 85 7B D7 D0 5C 53 E6 5E CE 49 EA DF 94 BD 1D y....\S.^.I..... DAV-WS-Mirror, SEND TLSv1 ALERT: fatal, description = handshake_failure DAV-WS-Mirror, WRITE: TLSv1 Alert, length = 2 DAV-WS-Mirror, called closeSocket() DAV-WS-Mirror, handling exception: javax.net.ssl.SSLHandshakeException: Error signing certificate verify DAV-WS-Mirror, IOException in getSession(): javax.net.ssl.SSLHandshakeException: Error signing certificate verify
我猜测客户端需要验证它是否拥有与握手期间提供给服务器的公共证书相关的私钥,但是提供商(OpenSC)未能签署挑战(同样,我要求这是一个有根据的猜测).
我们使用完全相同的读卡器使用Firefox测试完全相同的智能卡,并且能够完成握手.
有谁知道如何做到这一点?