我目前正在开发一个涉及客户端和服务器之间相互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的问题。
如果您还有任何疑问,请随时提出,我会尽可能快速彻底地回答。
非常感谢任何帮助,谢谢你们。
我终于设法在Windows中签名数据,只需安装OpenSC并使用他们的DLL而不是MSCAPI(在我发现this article之后)。
代码保持不变,但代替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测试完全相同的智能卡,并且能够完成握手。
有谁知道如何做到这一点?