具有自签名证书的 TLS 安全 TCP 服务器和客户端 [英] TLS-secured TCP server and client with self-signed certificate
问题描述
我正在开发一款通过 Wifi 连接两个 Android 设备的应用,以便它们可以使用 TCP 交换文件/数据.由于 Android Oreo(API 级别 26)终于有了一个官方 API:WifiManager.startLocalOnlyHotspot()
.这会创建一个 Wifi 热点/网络, 没有互联网访问权限.文档说:
I'm working on an app that connects two Android devices via Wifi so they can exchange files/data using TCP. Since Android Oreo (API level 26) there's finally an official API for this: WifiManager.startLocalOnlyHotspot()
. This creates a Wifi hotspot/network without internet access. The documentation says:
应用程序还应该知道该网络将与其他应用程序共享.应用程序负责保护其在此网络上的数据(例如,TLS).
Applications should also be aware that this network will be shared with other applications. Applications are responsible for protecting their data on this network (e.g., TLS).
在通过 TCP 连接两个设备时,我没有使用 TLS 的经验,所以我四处搜索并找到了一些提到自签名证书的方法.我不确定这是否是一个好习惯;无论如何我无法让它工作.任何帮助表示赞赏!
I have no experience in using TLS when it comes to connecting two devices via TCP, so I searched around and found some approaches mentioning self-signed certificates. I'm not sure wether this is a good practice; I can't get it working anyway. Any help is appreciated!
到目前为止我做了什么:
我使用 OpenSSL 创建了一个自签名证书,如本 答案:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 10
我使用最新的(2018 年 3 月)Bouncy Castle 提供程序 .jar 文件创建了一个新的密钥库 并将 cert.pem
添加到其中.以下代码段源自this 很棒的博客文章,更具体地说来自 示例应用.
I created a new keystore with the latest (March 2018) Bouncy Castle provider .jar file and added cert.pem
to it. The following snippet is derived from this great blog article, more specifically from the sample app it features.
ALIAS=`openssl x509 -inform PEM -subject_hash -noout -in cert.pem`
keytool -import -v -trustcacerts \
-alias $ALIAS \
-file cert.pem \
-keystore keystore_output_file \
-storetype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath bcprov-jdk15on-159.jar \
-storepass my_keystore_password
我已将 keystore_output_file
添加到我的应用程序的 src/res/raw/
文件夹并初始化了一个 SSLContext
.然后我的应用程序在充当服务器时创建一个 SSLServerSocket
或在充当客户端时创建一个 SSLSocket
.最初我在这里找到了这种方法.
I've added keystore_output_file
to the src/res/raw/
folder of my app and initialized a SSLContext
. Then my app creates a SSLServerSocket
when acting as server or a SSLSocket
when acting as client. Originally I found this approach here.
// Exception handling omitted
KeyStore keyStore = KeyStore.getInstance("BKS");
InputStream certStore = context.getResources().openRawResource(R.raw.keystore_output_file);
keyStore.load(certStore, "my_keystore_password".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "my_key_password".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
...继续作为服务器:
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
SSLSocket sslClientSocket = (SSLSocket) sslServerSocket.accept();
...继续作为客户:
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(ipAddress, port);
sslSocket.startHandshake();
问题:
连接失败.根据服务器设备运行的 Android 版本,有不同的错误消息,但都提到了密码问题.我的测试设备的 Android 版本是 4.3 和 7.1.1.堆栈跟踪是:
The connection fails. There are different error messages depending on what version of Android the server device is running, but both are mentioning problems with ciphers. The Android versions of my testing devices are 4.3 and 7.1.1. The stacktraces are:
服务器:Android 7.1.1
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:429)
at com.android.org.conscrypt.OpenSSLSocketImpl.waitForHandshake(OpenSSLSocketImpl.java:682)
at com.android.org.conscrypt.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:644)
at com.candor.tlstcptest.ServerWorkerThread.run(ServerWorkerThread.java:46)
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x8b0f8a40: Failure in SSL library, usually a protocol error
error:100000b8:SSL routines:OPENSSL_internal:NO_SHARED_CIPHER (external/boringssl/src/ssl/s3_srvr.c:1059 0x99b4286a:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)
服务器:Android 4.3
javax.net.ssl.SSLException: Could not find any key store entries to support the enabled cipher suites.
at org.apache.harmony.xnet.provider.jsse.OpenSSLServerSocketImpl.checkEnabledCipherSuites(OpenSSLServerSocketImpl.java:232)
at org.apache.harmony.xnet.provider.jsse.OpenSSLServerSocketImpl.accept(OpenSSLServerSocketImpl.java:177)
at com.candor.tlstcptest.ServerThread.run(ServerThread.java:71)
现在我卡住了,我什至不知道如何开始解决这个问题,而且我还没有在网上找到有用的信息.我想知道是否真的没有关于这个主题的官方文档......正如我所说,感谢任何帮助!谢谢
Now I'm stuck, I don't even know how to start to resolve this problem and I haven't yet found helpful information online. I wonder if there is really no offical documentation on this topic... As I said, any help is appreciated! Thanks
推荐答案
这里的问题是您创建的密钥库只包含证书,而不包含其私钥.(这就是 keytool -import ...
所做的.)
The problem here is that you've created a keystore that only contains the certificate, not its private key. (This is what keytool -import ...
does.)
创建具有私钥条目(及其相应证书)的密钥库的一种方法是从 OpenSSL 创建一个 PKCS#12 存储库,然后通过 keytool 将其转换为 BKS(或多或少与
One way to create a keystore that has a private key entry (with its corresponding certificate) would be to create a PKCS#12 store from OpenSSL and then convert it into BKS via keytool (more or less the same principle as here).
openssl pkcs12 -export -in cert.pem -inkey key.pem -out store.p12
然后,使用keytool -importkeystore
(不仅仅是-import
)将其转换为BKS.我还没有尝试过确切的命令,但这应该是这样的:
Then, convert it into BKS using keytool -importkeystore
(not just -import
). I haven't tried the exact command, but this should be something like this:
keytool -importkeystore \
-srckeystore store.p12 -srcstoretype PKCS12 \
-destkeystore store.jks -deststoretype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath bcprov-jdk15on-159.jar
(检查 keytool代码>文档
,了解您可能还需要的确切选项.)
(Check the keytool
documentation for the exact options you may also need.)
这应该会产生一个 store.jks
密钥库,它也包含私钥.该密钥库只能在服务器端用作密钥库,而不应用作客户端的信任库."(您已经拥有的可用作 信任库,在客户端.)
This should result in a store.jks
keystore that also contains the private key. That keystore should only be used on the server side, as the keystore, not as the truststore on the client side." (The one you already had can be used as a truststore, on the client side.)
一些旁注:
- 使用 SSL/TLS 检查的另一件事是证书中的身份(不仅仅是相信证书是真实的并由您认识的一方发行).这在 Java 中并不总是默认选中(取决于您使用的选项).
对于这种特定类型的应用程序(基本上是临时连接),您可能希望在安装时在服务器上生成证书/私钥对,向用户显示其指纹,然后使用松散的信任管理器在显示指纹的客户端上,它必须以交互方式对其进行验证(并可能记住它).
- Another thing to check with SSL/TLS is the identity in the certificate (not just trusting that the certificate is genuine and issued by a party you know). This is not always checked by default in Java (depending on the options you use).
For this particular type of application (essentially ad-hoc connections), you may want to generate the certificate/private key pair on the server upon installation, show its fingerprint to the user, then have a looser trust manager on the client that displays the fingerprint it has to validate it interactively (and possibly remember it).
如果您确实设法明确地验证远程设备的单个证书,那么您可能无需检查证书中的名称即可安全地进行验证(只要客户端可以检查它是否连接到具有该确切名称的设备)证书).
If you do indeed manage to validate an individual certificate for the remote device explicitly, you might be able to do it securely without checking the name in the certificate (as long as the client can check it's connecting to a device with that exact certificate).
我对 startLocalOnlyHotspot
不够熟悉,但我怀疑其中许多细节将取决于热点使用的 IP 地址.我想它还会嵌入某种形式的 DHCP 服务器来为客户端提供 IP 地址,但我不确定客户端如何获取热点设备本身的 IP 地址(例如,可能有一些 mDNS 集成).
I'm not familiar enough with startLocalOnlyHotspot
, but I suspect many of those details will depend on the IP address used by the hotspot. I'd imagine it also embeds some form of DHCP server to give the client an IP address, but I'm not sure how the client can get the IP address of the hotspot device itself (there might be some mDNS integration, for example).
这篇关于具有自签名证书的 TLS 安全 TCP 服务器和客户端的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!