Java TLS 套接字:未找到受信任的证书

Owu*_*aro 6 java sockets ssl apple-push-notifications javaapns

让我快速解释一下我要做什么。我正在尝试用 Java 构建我自己的 Apple 推送通知服务(用于测试目的)。这项服务的工作归功于 TLS 套接字。

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

但是,我在握手过程中遇到异常并且无法找到解决方法。

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

这是客户端(简化):

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();
Run Code Online (Sandbox Code Playgroud)

这是服务器:

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);
Run Code Online (Sandbox Code Playgroud)

这是例外:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
Run Code Online (Sandbox Code Playgroud)

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

服务器需要什么样的证书才能被客户端信任?

提前致谢。

dav*_*085 5

编辑:我错了!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。)

有一个可能的“类型”问题:如果证书是使用 ExtendedKeyUsage 扩展颁发的,并且该值仅指定 TLSclient 而不是 TLSserver(CA 可以选择这样做),那么将其用于服务器可能无法工作 - 看起来是 JSSE执行 EKU 限制。但如果这是一个问题,你会得到一个非常不同的异常。您也可以在上面的 keytool -list -v 中看到这一点。

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

最后,是的,TrustManager 'PKIX' 比 'SunX509' 更新且通常功能更丰富。但对于基本测试“是我们信任库中的信任锚”,它们是等效的。

再次抱歉造成误导。