我正在开发一款通过Wifi连接两台Android设备的应用,以便他们可以使用TCP交换文件/数据。自Android Oreo(API级别26)以来,最终有一个官方API:WifiManager.startLocalOnlyHotspot()
。这将创建一个Wifi热点/网络,无需互联网访问。文档说:
应用程序还应该知道该网络将与其他应用程序共享。应用程序负责在此网络上保护其数据(例如, TLS )。
在通过TCP连接两台设备时,我没有使用TLS的经验,因此我搜索了一些提及自签名证书的方法。我不确定这是一个好习惯;无论如何,我无法让它发挥作用。任何帮助表示赞赏!
到目前为止我做了什么:
我使用OpenSSL创建了一个自签名证书,如answer所述:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 10
我使用最新的(2018年3月)Bouncy Castle provider .jar file创建了一个新的密钥库,并添加了cert.pem
。以下代码段源自this精彩博客文章,更具体地来自其sample app功能。
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
。最初我发现了这种方法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。堆栈跟踪是:
服务器: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)
现在我卡住了,我甚至不知道如何开始解决这个问题,我还没有在网上找到有用的信息。我想知道是否真的没有关于这个主题的官方文档...正如我所说,任何帮助表示赞赏!感谢
答案 0 :(得分:1)
这里的问题是您创建了一个仅包含证书的密钥库,而不是其私钥。 (这是keytool -import ...
所做的。)
创建具有私钥条目(具有相应证书)的密钥库的一种方法是从OpenSSL创建PKCS#12存储,然后通过keytool将其转换为BKS(与{{或多或少相同的原则) 3}})。
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
(查看here以了解您可能还需要的确切选项。)
这应该导致store.jks
密钥库也包含私钥。 该密钥库只应在服务器端使用,作为密钥库,而不是客户端的信任库。“(您已拥有的密钥库可用作keytool
documentation,on客户方。)
几个旁注:
对于这种特定类型的应用程序(主要是ad-hoc连接),您可能希望在安装时在服务器上生成证书/私钥对,向用户显示其指纹,然后拥有一个更宽松的信任管理器在显示指纹的客户端上,它必须以交互方式验证它(并且可能记住它)。
如果您确实设法明确验证远程设备的单个证书,您可以安全地执行此操作而无需检查证书中的名称(只要客户端可以检查它是否连接到具有该确切名称的设备证书)。
我对startLocalOnlyHotspot
不够熟悉,但我怀疑其中许多细节将取决于热点使用的IP地址。我想它也嵌入了某种形式的DHCP服务器来给客户端一个IP地址,但我不确定客户端如何获取热点设备本身的IP地址(例如,可能有一些mDNS集成)