繁体   English   中英

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

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

我正在开发一个通过Wifi连接两个Android设备的应用程序,以便它们可以使用TCP交换文件/数据。 自Android Oreo(API级别26)以来,终于有了一个官方的API: WifiManager.startLocalOnlyHotspot() 这将创建一个无法访问互联网的Wifi热点/网络。 该文件说:

应用程序还应该知道该网络将与其他应用程序共享。 应用程序负责保护其在此网络上的数据 (例如TLS )。

通过TCP连接两个设备时,我没有使用TLS的经验,因此我四处搜寻,发现了一些提及自签名证书的方法。 我不确定这是否是一种好习惯; 无论如何我都无法正常工作。 任何帮助表示赞赏!

到目前为止,我做了什么:

  1. 如以下答案所述,我使用OpenSSL创建了自签名证书:

     openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 10 
  2. 我使用最新的(2018年3月) Bouncy Castle提供程序.jar文件创建了一个新的密钥库,并向其中添加了cert.pem 以下代码片段摘自这篇出色的博客文章,更具体地讲,其功能来自于示例应用程序

     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. 我已将keystore_output_file添加到应用程序的src/res/raw/文件夹中,并初始化了SSLContext 然后,我的应用程序在充当服务器时会创建一个SSLServerSocket ,而在充当客户端时会创建一个SSLSocket 我最初是在这里找到这种方法的。

     // 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。 堆栈跟踪为:

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

现在我被困住了,我什至不知道如何开始解决这个问题,而且我还没有在线找到有用的信息。 我想知道是否真的没有有关此主题的官方文档。 谢谢

这里的问题是您创建的密钥库仅包含证书,而不包含其私钥。 (这就是keytool -import ...作用。)

创建具有私钥项(及其相应证书)的密钥库的一种方法是从OpenSSL创建PKCS#12存储,然后通过keytool将其转换为BKS(与此处大致相同的原理)。

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

然后,使用keytool -importkeystore (而不仅仅是-import )将其转换为BKS。 我没有尝试过确切的命令,但这应该是这样的:

keytool -importkeystore \
        -srckeystore store.p12 -srcstoretype PKCS12 \
        -destkeystore store.jks -deststoretype BKS \
        -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
        -providerpath bcprov-jdk15on-159.jar

(请查阅keytool文档以获取您可能还需要的确切选项。)

这将导致store.jks密钥库也包含私钥。 该密钥库只能在服务器端用作密钥库,而不能在客户端用作信任库。 “(您已经拥有的一个可以在客户端上用作信任库 。)


一些注意事项:

  • 使用SSL / TLS检查的另一件事是证书中的身份 (不仅仅是相信该证书是真实的,并且是由您认识的一方签发的)。 在Java中,默认情况下并不总是选中此选项(取决于您使用的选项)。
  • 对于这种特定类型的应用程序(基本上是临时连接),您可能希望在安装时在服务器上生成证书/专用密钥对,向用户显示其指纹,然后在客户端上显示一个松散的信任管理器,以显示指纹,它必须以交互方式验证它(并且可能会记住它)。

    如果确实确实设法明确验证了远程设备的单个证书,则可以安全地执行此操作而无需检查证书中的名称(只要客户端可以检查它是否已连接到具有该确切证书的设备)。

    我对startLocalOnlyHotspot不太熟悉,但是我怀疑其中许多细节将取决于热点使用的IP地址。 我以为它还会嵌入某种形式的DHCP服务器来为客户端提供IP地址,但是我不确定客户端如何获取热点设备本身的IP地址(例如,可能存在某些mDNS集成) 。

暂无
暂无

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

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