简体   繁体   English

具有自签名证书的受TLS保护的TCP服务器和客户端

[英]TLS-secured TCP server and client with self-signed certificate

I'm working on an app that connects two Android devices via Wifi so they can exchange files/data using TCP. 我正在开发一个通过Wifi连接两个Android设备的应用程序,以便它们可以使用TCP交换文件/数据。 Since Android Oreo (API level 26) there's finally an official API for this: WifiManager.startLocalOnlyHotspot() . 自Android Oreo(API级别26)以来,终于有了一个官方的API: WifiManager.startLocalOnlyHotspot() This creates a Wifi hotspot/network without internet access. 这将创建一个无法访问互联网的Wifi热点/网络。 The documentation says: 该文件说:

Applications should also be aware that this network will be shared with other applications. 应用程序还应该知道该网络将与其他应用程序共享。 Applications are responsible for protecting their data on this network (eg, TLS ). 应用程序负责保护其在此网络上的数据 (例如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. 通过TCP连接两个设备时,我没有使用TLS的经验,因此我四处搜寻,发现了一些提及自签名证书的方法。 I'm not sure wether this is a good practice; 我不确定这是否是一种好习惯; I can't get it working anyway. 无论如何我都无法正常工作。 Any help is appreciated! 任何帮助表示赞赏!

What I did so far: 到目前为止,我做了什么:

  1. I created a self-signed certificate using OpenSSL as described in this answer : 如以下答案所述,我使用OpenSSL创建了自签名证书:

     openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 10 
  2. I created a new keystore with the latest (March 2018) Bouncy Castle provider .jar file and added cert.pem to it. 我使用最新的(2018年3月) Bouncy Castle提供程序.jar文件创建了一个新的密钥库,并向其中添加了cert.pem 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 
  3. I've added keystore_output_file to the src/res/raw/ folder of my app and initialized a SSLContext . 我已将keystore_output_file添加到应用程序的src/res/raw/文件夹中,并初始化了SSLContext Then my app creates a SSLServerSocket when acting as server or a SSLSocket when acting as client. 然后,我的应用程序在充当服务器时会创建一个SSLServerSocket ,而在充当客户端时会创建一个SSLSocket 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()); 

    ...continuing as server : ...继续作为服务器

     SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory(); SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port); SSLSocket sslClientSocket = (SSLSocket) sslServerSocket.accept(); 

    ...continuing as client : ...继续作为客户

     SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(ipAddress, port); sslSocket.startHandshake(); 

The Problem: 问题:

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. 根据服务器设备运行的是哪个版本的Android,会有不同的错误消息,但是两者都提到密码问题。 The Android versions of my testing devices are 4.3 and 7.1.1. 我的测试设备的Android版本为4.3和7.1.1。 The stacktraces are: 堆栈跟踪为:

Server: Android 7.1.1 伺服器: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)      

Server: Android 4.3 伺服器: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 谢谢

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.) (这就是keytool -import ...作用。)

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创建PKCS#12存储,然后通过keytool将其转换为BKS(与此处大致相同的原理)。

openssl pkcs12 -export -in cert.pem -inkey key.pem -out store.p12

Then, convert it into BKS using keytool -importkeystore (not just -import ). 然后,使用keytool -importkeystore (而不仅仅是-import )将其转换为BKS。 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

(Check the keytool documentation for the exact options you may also need.) (请查阅keytool文档以获取您可能还需要的确切选项。)

This should result in a store.jks keystore that also contains the private key. 这将导致store.jks密钥库也包含私钥。 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.) “(您已经拥有的一个可以在客户端上用作信任库 。)


A couple of side notes: 一些注意事项:

  • 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). 使用SSL / TLS检查的另一件事是证书中的身份 (不仅仅是相信该证书是真实的,并且是由您认识的一方签发的)。 This is not always checked by default in Java (depending on the options you use). 在Java中,默认情况下并不总是选中此选项(取决于您使用的选项)。
  • 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). 如果确实确实设法明确验证了远程设备的单个证书,则可以安全地执行此操作而无需检查证书中的名称(只要客户端可以检查它是否已连接到具有该确切证书的设备)。

    I'm not familiar enough with startLocalOnlyHotspot , but I suspect many of those details will depend on the IP address used by the hotspot. 我对startLocalOnlyHotspot不太熟悉,但是我怀疑其中许多细节将取决于热点使用的IP地址。 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). 我以为它还会嵌入某种形式的DHCP服务器来为客户端提供IP地址,但是我不确定客户端如何获取热点设备本身的IP地址(例如,可能存在某些mDNS集成) 。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM