[英]TLS-secured TCP server and client with self-signed certificate
我正在开发一个通过Wifi连接两个Android设备的应用程序,以便它们可以使用TCP交换文件/数据。 自Android Oreo(API级别26)以来,终于有了一个官方的API: WifiManager.startLocalOnlyHotspot()
。 这将创建一个无法访问互联网的Wifi热点/网络。 该文件说:
应用程序还应该知道该网络将与其他应用程序共享。 应用程序负责保护其在此网络上的数据 (例如TLS )。
通过TCP连接两个设备时,我没有使用TLS的经验,因此我四处搜寻,发现了一些提及自签名证书的方法。 我不确定这是否是一种好习惯; 无论如何我都无法正常工作。 任何帮助表示赞赏!
到目前为止,我做了什么:
如以下答案所述,我使用OpenSSL创建了自签名证书:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 10
我使用最新的(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
我已将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
密钥库也包含私钥。 该密钥库只能在服务器端用作密钥库,而不能在客户端用作信任库。 “(您已经拥有的一个可以在客户端上用作信任库 。)
一些注意事项:
对于这种特定类型的应用程序(基本上是临时连接),您可能希望在安装时在服务器上生成证书/专用密钥对,向用户显示其指纹,然后在客户端上显示一个松散的信任管理器,以显示指纹,它必须以交互方式验证它(并且可能会记住它)。
如果确实确实设法明确验证了远程设备的单个证书,则可以安全地执行此操作而无需检查证书中的名称(只要客户端可以检查它是否已连接到具有该确切证书的设备)。
我对startLocalOnlyHotspot
不太熟悉,但是我怀疑其中许多细节将取决于热点使用的IP地址。 我以为它还会嵌入某种形式的DHCP服务器来为客户端提供IP地址,但是我不确定客户端如何获取热点设备本身的IP地址(例如,可能存在某些mDNS集成) 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.