Java TLS 套接字:未找到受信任的证书 [英] Java TLS socket : No trusted certificate found

查看:34
本文介绍了Java TLS 套接字:未找到受信任的证书的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 客户端来创建一个 TLS 套接字来向 APNs 发送推送通知.我更改了主机 url 以将套接字重定向到 localhost:2195.现在我正在尝试编写一个 Java 套接字服务器来获取通知请求.

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.

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

However, I get an exception during the handshake and can't find how to fix it.

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

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.

这是客户端(简化版):

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();

这里是服务器:

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 以这种方式工作).

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).

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

What kind of certificate needs the server to be trusted by the client ?

提前致谢.

推荐答案

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

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:

  • 它是自签名的,或由未知、不知名或未经证实的 CA 颁发

  • it is selfsigned, or issued by an unknown, obscure, or unproven CA

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

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.

要区分这些,看keytool -keystore file -storetype pkcs12 -list -v并查看您在那里拥有什么证书或证书序列.

To distinguish these, look at keytool -keystore file -storetype pkcs12 -list -v and see what cert or sequence of certs you have there.

那么可能有几种解决方法:

Then there may be several approaches to solution:

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

  1. 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.

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

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.)

找到信任锚(从 p12,或从其他地方,如 CA)并拥有它在客户端显式加载的信任库中.您通过使用p12 作为信任库并说您不想要它.

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.

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

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.)

存在一种可能的类型"问题:如果证书是使用 ExtendedKeyUsage 扩展发布的并且该值仅指定 TLSclient 而不是 TLSserver(CA 可以选择这样做)然后将它用于服务器 可能 将不起作用——JSSE 似乎强制执行 EKU 限制.但是如果这是一个问题,你会得到一个非常不同的异常.您也可以在上面的 keytool -list -v 中看到这一点.

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 probably won'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.

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

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.

最后,是的,TrustManager 'PKIX' 比'SunX509' 更新并且通常功能更多.但是对于基本测试是我们信任库中的信任锚",它们是等效的.

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.

再次为误导道歉.

这篇关于Java TLS 套接字:未找到受信任的证书的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆