Java TLS套接字:找不到可信证书

时间:2014-04-17 09:21:22

标签: java sockets ssl apple-push-notifications javaapns

让我快速解释一下我要做的事情。我试图在java中构建我自己的Apple推送通知服务(用于测试目的)。这项服务的工作得益于TLS套接字。

我有一个java客户端来创建一个TLS套接字来向APN发送推送通知。我更改了主机URL以将套接字重定向到localhost:2195。现在我试图编写一个java套接字服务器来获取通知请求。

但是,我在握手期间遇到异常,无法找到解决方法。

注意:我在双方都使用相同的证书,它是一个标准的.p12文件,用于向APN发送推送通知。

这是客户端(简化):

KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(certificatePath), password.toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(ks, password.toCharArray());

TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509"); 
tmf.init((KeyStore)null);

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

SSLSocketFactory ssf = sc.getSocketFactory(); 
SSLSocket socket = (SSLSocket) ssf.createSocket(InetAddress.getLocalHost(), 2195);
socket.startHandshake();

这是服务器:

KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(certificatePath), password.toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(ks, password.toCharArray());

SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), null, null);

SSLServerSocketFactory ssf = context.getServerSocketFactory();
serverSocket = (SSLServerSocket) ssf.createServerSocket(2195);

以下是例外:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found

我猜客户端并不信任服务器的证书。我尝试将客户端的TrustManager设置为接受服务器的p12并且它工作正常,但是我需要在没有编辑客户端的情况下工作(因为它与真正的APN一起工作)。

客户端需要哪种证书才能信任服务器?

提前致谢。

1 个答案:

答案 0 :(得分:2)

编辑:我错了! tmf.init(null)与sslctx.init(,null,)一样使用默认密钥库! 该缺省值通常是JRE / lib / security中的cacerts文件,它信任许多已建立的CA. 所以现在我认为我们可以确信真正的服务器在已建立的CA下使用证书(等等 是你的客户信任的,而p12中的证书显然没有; 但这里有两种可能性:

  • 它是自我签名的,或由未知,模糊或未经证实的CA颁发

  • 它是由'中间'CA下的'真正'CA发布的,需要链证书(或几个) 并且您的p12中没有链证书。请注意,这仍然适用于客户端身份验证 到真实的服务器,因为真正的服务器可以轻松地将链证书“预加载” 在它的信任库中,即使它们不在Java中。

要区分这些,请查看keytool -keystore file -storetype pkcs12 -list -v 并查看您拥有的证书或证书顺序。

然后可能有几种解决方法:

  1. 如果您只缺少已建立的CA的链证书,请获取并添加它们。 keytool只允许您替换整个链,因此您必须获得所有需要的证书; openssl(如果你有或得到它)可以从pkcs12中分出密钥和证书, 替换或添加单个证书,并将它们重新组合在一起。

  2. 为服务器创建不同的存储和密钥,并从已建立的CA获取证书(链)。 通常需要花费一些钱,并要求您证明对服务器域名的控制。 (您的客户可以而且应该仍然使用此p12。双方不必相同。)

  3. 找到信任锚(来自p12,或来自CA之类的其他地方)并拥有它 在客户端显式加载的信任库中。你通过使用有效地尝试了这个 p12作为信任库,并说你不想那样。

  4. 将信任锚放在客户端的默认信任库中,以便客户端继续 使用默认值。如果您不介意修改您的JRE(而不是其他用户或应用程序 在你的系统上很麻烦)只需添加到JRE / lib / security / cacerts。或者,假设您可以设置 系统属性,将锚点放在商店中或将其保留在p12中 并将javax.net.ssl.trustStore {,Password,Type}设置为指向该商店。 (如果你复制,你应该只接受证书; p12是一个密钥和证书而不仅仅是证书。 不要只是-importkeystore; -importcert一个证书文件,如果需要,用-exportcert创建。) (您可以在代码中使用System.setProperty,但这会改变您的代码。如果您从中运行 命令行你可以使用'java -Dname = value ...'。对于其他情况YMMV。)

  5. 有一种可能的“类型”问题:如果证书是使用ExtendedKeyUsage扩展名发布的 并且该值仅指定TLSclient而不指定TLSserver(CA可以选择这样做) 然后将它用于服务器可能将无效 - 看来JSSE强制执行EKU限制。 但如果这是一个问题,你会得到一个非常不同的例外。 你也可以在上面的keytool -list -v中看到这个。

    由于您(正确地)想要将此p12用于您的客户端,因此您的服务器逻辑同样需要 相信它。 (将其用于传出身份验证不会自动使其对传入身份验证信任。) 但是,只有当/实际完成clientAuth时,这不是默认值;你的服务器代码 接受连接之前SSLServerSocket上的.setNeedClientAuth(true)? 可能的方法与上述相同,但跳过#2不适用。 如果客户端和服务器都使用相同的JRE,那么cacerts会更容易一些。

    最后,是的TrustManager'PKIX'比'SunX509'更新,通常更有特色。 但是对于基本测试'是我们信任库中的信任锚',它们是等价的。

    再次抱歉误导。