具有自签名证书的 TLS 安全 TCP 服务器和客户端 [英] TLS-secured TCP server and client with self-signed certificate

查看:44
本文介绍了具有自签名证书的 TLS 安全 TCP 服务器和客户端的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一款通过 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!

到目前为止我做了什么:

  1. 我使用 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屋!

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