Java TLS 套接字:未找到受信任的证书
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23129224/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java TLS socket : No trusted certificate found
提问by Owumaro
Let me explain quickly what I'm trying to do. I'm trying to build my own Apple's Push Notification service in java (for testing purposes). This service works thanks to TLS socket.
让我快速解释一下我要做什么。我正在尝试用 Java 构建我自己的 Apple 推送通知服务(用于测试目的)。这项服务的工作归功于 TLS 套接字。
I have a java client to create a TLS socket to send push notifications to the APNs. I changed the host url to redirect the socket to localhost:2195. Now I'm trying to write a java socket server to get the notification request.
我有一个 java 客户端来创建一个 TLS 套接字来向 APNs 发送推送通知。我更改了主机 url 以将套接字重定向到 localhost:2195。现在我正在尝试编写一个 java 套接字服务器来获取通知请求。
However, I get an exception during the handshake and can't find how to fix it.
但是,我在握手过程中遇到异常并且无法找到解决方法。
Note : I'm using the same certificate on both sides, it's a standard .p12 file that works to send push notifications to the APNs.
注意:我在双方使用相同的证书,它是一个标准的 .p12 文件,用于向 APNs 发送推送通知。
Here is the client (simplified) :
这是客户端(简化):
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();
Here is the server :
这是服务器:
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);
And here is the exception :
这是例外:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
I guess the client isn't trusting the server's certificate. I tryed to set the client's TrustManager to accept the server's p12 and it worked, however I need this to work without editing the client (since it's working that way with the real APNs).
我猜客户端不信任服务器的证书。我尝试将客户端的 TrustManager 设置为接受服务器的 p12 并且它可以工作,但是我需要在不编辑客户端的情况下使其工作(因为它与真正的 APN 一样工作)。
What kind of certificate needs the server to be trusted by the client ?
服务器需要什么样的证书才能被客户端信任?
Thanks in advance.
提前致谢。
采纳答案by dave_thompson_085
EDIT: I WAS WRONG! tmf.init(null) DOES use the default keystore just like sslctx.init(,null,) ! That default is normally the cacerts file in JRE/lib/security which DOES trust many established CAs so now I think we can be confident the real server is using a cert under an established CA (and so is trusted by your client) while the cert in your p12 apparently does not; but there are two possibilities here:
编辑:我错了!tmf.init(null) 确实像 sslctx.init(,null,) 一样使用默认密钥库!该默认值通常是 JRE/lib/security 中的 cacerts 文件,它确实信任许多已建立的 CA,所以现在我认为我们可以确信真实服务器正在使用已建立的 CA 下的证书(因此您的客户端信任)而证书在您的 p12 中显然没有;但这里有两种可能性:
it is selfsigned, or issued by an unknown, obscure, or unproven CA
it is issued by a 'real' CA under an 'intermediate' CA that needs a chain cert (or several) and you do not have the chain cert(s) in your p12. Note this could still work for client auth to the real server, because the real server can easily have the chain cert(s) 'preloaded' in its truststore even though they aren't in Java's.
它是自签名的,或由未知的、不知名的或未经证实的 CA 颁发的
它是由“中间”CA 下的“真实”CA 颁发的,该CA 需要一个(或多个)链证书,而您的p12 中没有链证书。请注意,这仍然适用于真实服务器的客户端身份验证,因为真实服务器可以轻松地在其信任库中“预加载”链证书,即使它们不在 Java 中。
To distinguish these, look at keytool -keystore file -storetype pkcs12 -list -v
and see what cert or sequence of certs you have there.
要区分这些,请查看keytool -keystore file -storetype pkcs12 -list -v
并查看您拥有的证书或证书序列。
Then there may be several approaches to solution:
那么可能有几种解决方法:
if you are only missing chain cert(s) for an established CA get them and add them. keytool only allows you to replace the whole chain so you must get all needed certs; openssl (if you have or get it) can break out the key and cert(s) from a pkcs12, replace or add individual certs, and join them back together.
create a different store and key for the server and get it a cert (chain) from an established CA. Usually costs some money and requires you prove control of the server's domain name. (Your client can and should still use this p12. The two sides needn't be the same.)
locate the trust anchor (from the p12, or from somewhere else like the CA) and have it in a truststore the client explicitly loads. You effectively tried this by using the p12 as the truststore and say you don't want that.
put the trust anchor in the client's default truststore, so the client continues using the default. If you don't mind modifying your JRE (and no other user or application on your system is bothered) just add to JRE/lib/security/cacerts. Or, assuming you can set system properties, put the anchor in a store or just leave it in the p12 and set javax.net.ssl.trustStore{,Password,Type} to point to that store. (If you copy you should take only the cert; a p12 is a KEY AND cert not just a cert. Don't just -importkeystore; -importcert a cert file, created with -exportcert if necessary.) (You can System.setProperty in your code, but that's changing your code. If you run from commandline you can use 'java -Dname=value...'. For other cases YMMV.)
如果您只缺少已建立 CA 的链证书,请获取它们并添加它们。keytool 只允许您替换整个链,因此您必须获得所有需要的证书;openssl(如果您有或得到它)可以从 pkcs12 中分解出密钥和证书,替换或添加单个证书,然后将它们重新组合在一起。
为服务器创建不同的存储和密钥,并从已建立的 CA 获取证书(链)。通常会花费一些钱,并且需要您证明对服务器域名的控制权。(你的客户可以而且应该仍然使用这个 p12。这两个方面不必相同。)
找到信任锚(从 p12,或从其他地方,如 CA)并将其放在客户端显式加载的信任库中。您通过使用 p12 作为信任库有效地尝试了这一点,并说您不想要那个。
将信任锚放在客户端的默认信任库中,以便客户端继续使用默认值。如果您不介意修改您的 JRE(并且不会打扰您系统上的其他用户或应用程序),只需将其添加到 JRE/lib/security/cacerts。或者,假设您可以设置系统属性,将锚点放在商店中或将其留在 p12 中并设置 javax.net.ssl.trustStore{,Password,Type} 指向该商店。(如果你复制你应该只拿证书;p12 是一个密钥和证书而不仅仅是一个证书。不要只是 -importkeystore;-importcert 一个证书文件,如果需要的话,用 -exportcert 创建。)(你可以 System.setProperty在您的代码中,但这会改变您的代码。如果您从命令行运行,您可以使用“java -Dname=value...”。对于其他情况,YMMV。)
There is one possible 'type' issue: if the cert was issued with ExtendedKeyUsage extension and that value specifies only TLSclient and not TLSserver (which the CA can choose to do) then using it for server probablywon't work -- it appears JSSE enforces EKU restrictions. But if that is a problem you'll get a very different Exception. And you can see this also in the keytool -list -v above.
有一个可能的“类型”问题:如果证书是使用 ExtendedKeyUsage 扩展发布的,并且该值仅指定 TLSclient 而不是 TLSserver(CA 可以选择这样做),那么将它用于服务器可能不起作用——它似乎是 JSSE强制执行 EKU 限制。但是如果这是一个问题,你会得到一个非常不同的异常。您也可以在上面的 keytool -list -v 中看到这一点。
Since you (rightly) want to use this p12 for your client, your server logic similarly needs to trust it. (Using it for outgoing auth does NOT automatically make it trusted for incoming auth.) But only if/when clientAuth is actually done, which is not the default; does your server code .setNeedClientAuth(true) on the SSLServerSocket before accepting the connection? Possible approaches are equivalent to the above except skipping #2 as inapplicable. If both client and server use the same JRE, that makes the cacerts way a little easier.
由于您(正确地)希望将此 p12 用于您的客户端,因此您的服务器逻辑同样需要信任它。(将它用于传出身份验证并不会自动使其受传入身份验证的信任。)但只有当/当 clientAuth 实际完成时,这不是默认值;在接受连接之前,您的服务器代码 .setNeedClientAuth(true) 在 SSLServerSocket 上吗?除了跳过#2 不适用之外,可能的方法等同于上述方法。如果客户端和服务器都使用相同的 JRE,那么 cacerts 会更容易一些。
Finally, yes TrustManager 'PKIX' is newer and generally more featureful than 'SunX509'. But for the basic test 'is the trust anchor in our truststore' they are equivalent.
最后,是的,TrustManager 'PKIX' 比'SunX509' 更新并且通常功能更多。但是对于基本测试“是我们信任库中的信任锚”,它们是等效的。
Sorry again for the mislead.
再次对误导表示抱歉。