使用自签名证书和SSLEngine(JSSE)进行SSL握手

8
我被委托实现一个自定义/独立的Java Web服务器,可以在同一端口上处理SSL和非SSL消息。
我已经实现了一个NIO服务器,并且非SSL请求工作得很好。但是SSL部分让我感到困难,我真的需要一些指导。
到目前为止,我已经完成了以下工作:
为了区分SSL和非SSL消息,我检查入站请求的第一个字节,以查看它是否为SSL/TLS消息。例如:
   byte a = read(buf);
   if (totalBytesRead==1 && (a>19 && a<25)){
       parseTLS(buf);
   }

在parseTLS()方法中,我会像这样实例化SSLEngine:
   java.security.KeyStore ks = java.security.KeyStore.getInstance("JKS");
   java.security.KeyStore ts = java.security.KeyStore.getInstance("JKS");

   ks.load(new java.io.FileInputStream(keyStoreFile), passphrase);
   ts.load(new java.io.FileInputStream(trustStoreFile), passphrase);

   KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
   kmf.init(ks, passphrase);

   TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
   tmf.init(ts);

   SSLContext sslc = SSLContext.getInstance("TLS");     
   sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);


   SSLEngine serverEngine = sslc.createSSLEngine();
   serverEngine.setUseClientMode(false);
   serverEngine.setEnableSessionCreation(true);
   serverEngine.setWantClientAuth(true);

一旦SSLEngine被实例化,我使用unwrap/wrap方法处理传入数据,直接使用官方JSSE示例中的代码:

   log("----");

   serverResult = serverEngine.unwrap(inNetData, inAppData);
   log("server unwrap: ", serverResult);
   runDelegatedTasks(serverResult, serverEngine);

   log("----");

   serverResult = serverEngine.wrap(outAppData, outNetData);
   log("server wrap: ", serverResult);
   runDelegatedTasks(serverResult, serverEngine);

握手的第一部分似乎运行良好。客户端发送一个握手消息,服务器用4个记录的消息进行响应:
handshake (22)
- server_hello (2) 
- certificate (11) 
- server_key_exchange (12)
- certificate_request (13) 
- server_hello_done (14)

下一步,客户端会发送一个包含三个部分的消息:
handshake (22)
 - certificate (11)
 - client_key_exchange (16)

change_cipher_spec (20)
 - client_hello (1)

handshake (22)
 *** Encrypted Message ****

SSLEngine解包客户端请求并解析记录,但wrap方法产生了0字节,并带有OK/NEED_UNWRAP的握手状态。换句话说,我没有任何东西可以发送回客户端,握手就会突然停止。
这就是我卡住的地方。
在调试器中,我可以看到SSLEngine,特别是ServerHandshaker,在客户端找不到任何对等证书。当我查看来自客户端的证书记录时,这显而易见,它的长度为0字节。但为什么?
我只能假设HelloServer响应存在问题,但我似乎无法找到原因。服务器似乎正在发送有效的证书,但客户端没有返回任何内容。我的密钥库有问题吗?还是信任库有问题?或者与我实例化SSLEngine的方式有关?我被难住了。
还有几点要注意:
上面代码片段中引用的密钥库和信任库是使用以下教程创建的: http://www.techbrainwave.com/?p=953 我正在使用Firefox 10和IE 9作为客户端来测试服务器。两个Web客户端的结果相同。
我正在使用Sun/Oracle JDK 6和随附其的Java Secure Socket Extension(JSSE)。
期待您可能提供的任何指导,但请不要告诉我我疯了或者使用Netty或Grizzly或其他现有解决方案。目前这不是一个选项。我只想知道我做错了什么。
谢谢您的帮助!

只是一个愚蠢的问题:当通过HTTPS连接到您的Web服务器时,您是否已在Firefox中生成并安装了客户端证书? - Robert
是的。FF提示我接受/拒绝证书。我点击了“我接受技术风险”,然后可以在“工具”->“选项”->“高级”->“加密”->“查看证书”->“服务器”下看到证书。事实上,如果没有这个证书,FF将不会发送第二个握手消息。 - Peter
这不是我要问的。我要求的是客户端证书。SSL客户端身份验证是可选的,通常在企业环境中使用,常与芯片卡结合使用。如果客户端没有安装证书(Firefox部分“您的证书”),它将不会发送证书。 - Robert
请查看这个StackOverflow的问题:http://stackoverflow.com/questions/14093546/java-nio-and-ssl#comment52845629_14093546 - rdalmeida
1个回答

9
你得到了NEED_UNWRAP,因此需要进行unwrap操作。这可能会出现BUFFER_UNDERFLOW,这意味着你需要进行读取并重试unwrap操作。
同样地,当你收到NEED_WRAP时,请进行wrap操作:这可能会导致BUFFER_OVERFLOW,这意味着你需要进行写入并重试wrap操作。
进行wrap或unwrap操作时可能要求你执行另一个操作:wrap或unwrap。
只需按照它所告诉你的去做即可。

1
@Peter 可能没有可读的内容,但仍可能有需要拆包的内容。按照我说的方式操作,让它告诉你何时需要读取,而不是假设每个拆包都需要读取。通常,服务器在握手过程中的第一个响应会包含三条不同的消息,因此您将连续收到 3 个 NEED_WRAP:相反,客户端可能会在单次读取中读取所有内容,但获取三个连续的 NEED_UNWRAPS。在整个握手过程中同样适用。 - user207421
1
@Peter 很不错。请确保您在每次包装和解包时都遵循这个原则,以便在对话过程中处理发生的部分和完整的重新握手。我见过的大多数实现都未通过此测试,包括一些著名的实现。我的通过了它;-) - user207421
我编写了一些隐藏SSLEngine复杂性并使其更易于使用的内容。它可能会对访问此线程的某些人有所帮助-https://github.com/kashifrazzaqui/sslfacade - keios
@kashif 如果它不支持会话恢复或重新握手,也不支持客户端证书,那么它的实际用途不大。在我看来,如果需要学习另一个API,那么也没有什么意义。可以将整个内容包装在“SSLSocketChannel”中,在商业产品中我已经这样做了。分散/聚集我可以不用,我认为在NIO中实现它并没有太大用处。 - user207421
@EJP,您是否有SSL引擎的可用实现? - Peter Penzov
显示剩余19条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接