我正在尝试使用 NanoHttpd 在 Android 设备上运行 HTTPS 服务器(我的最终目标是在 Android 上运行 WSS 服务器)。我在 Android 上使用 NanoHttpd 成功运行了 HTTP Server 和 Websocket。我使用此命令在 MAC 上生成密钥并将其复制到我的设备上:
keytool -genkey -keystore key.keystore -storepass keypass -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
我写了以下代码:
keyStore = KeyStore.getInstance("BKS");
keyStore.load(stream, keyStorePwd.toCharArray());
keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePwd.toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(keyManagerFactory.getKeyManagers(), null, null);
server.makeSecure(sc.getServerSocketFactory());
server.start();
我在 Chrome 38 和 42 上对此进行了测试,并将“最小 SSL/TLS”标志设置为“SSLv3”。但是当我想连接到服务器时,我不断收到“ERR_SSL_VERSION_OR_CIPHER_MISMATCH”错误。
我在多台计算机和浏览器上尝试了不同的协议实例(SSL/TLS)。我尝试了 NanoHttpd SSLServerSocketFactory 方法。但错误是一样的。
我已经看过一些样本,包括: https://github.com/NanoHttpd/nanohttpd/issues/139
有人对此有何评论吗?
经过几个小时的辛苦,我得到了!
这是我的(工作)代码:
// I placed this block right below my class declaration so it runs
// as soon as the class is defined. (this is for localhost testing ONLY!!!!)
static {
//for localhost testing only
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
if (hostname.equals("localhost")) {
return true;
}
return false;
}
});
}
// then in an init function, I set it all up here
this.secureAppServer = new NanoHTTPD(9043);
File f =new File("src/main/resources/key001.jks");
System.setProperty("javax.net.ssl.trustStore", f.getAbsolutePath());
this.secureAppServer.setServerSocketFactory(new SecureServerSocketFactory(NanoHTTPD.makeSSLSocketFactory("/" +f.getName(), "myawesomepassword".toCharArray()), null));
this.secureAppServer.start();
这是实际的 NanoHttpd 测试用例,它准确地说明了它是如何完成 Nano 风格的。
package fi.iki.elonen;
import java.io.File;
/*
* #%L
* NanoHttpd-Core
* %%
* Copyright (C) 2012 - 2015 nanohttpd
* %%
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the nanohttpd nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
import java.io.IOException;
import java.util.Arrays;
import javax.net.ssl.SSLServerSocket;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import fi.iki.elonen.NanoHTTPD.SecureServerSocketFactory;
public class SSLServerSocketFactoryTest extends HttpServerTest {
@Test
public void testSSLConnection() throws ClientProtocolException, IOException {
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpTrace httphead = new HttpTrace("https://localhost:9043/index.html");
HttpResponse response = httpclient.execute(httphead);
HttpEntity entity = response.getEntity();
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
Assert.assertEquals(9043, this.testServer.getListeningPort());
Assert.assertTrue(this.testServer.isAlive());
}
@Test
public void testCreatePassesTheProtocolsToServerSocket() throws IOException {
// first find the supported protocols
SecureServerSocketFactory secureServerSocketFactory = new SecureServerSocketFactory(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), null);
SSLServerSocket socket = (SSLServerSocket) secureServerSocketFactory.create();
String[] protocols = socket.getSupportedProtocols();
// remove one element from supported protocols
if (protocols.length > 0) {
protocols = Arrays.copyOfRange(protocols, 0, protocols.length - 1);
}
// test
secureServerSocketFactory = new SecureServerSocketFactory(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), protocols);
socket = (SSLServerSocket) secureServerSocketFactory.create();
Assert.assertArrayEquals("Enabled protocols specified in the factory were not set to the socket.", protocols, socket.getEnabledProtocols());
}
@Before
public void setUp() throws Exception {
System.setProperty("javax.net.ssl.trustStore", new File("src/test/resources/keystore.jks").getAbsolutePath());
this.testServer = new TestServer(9043);
this.testServer.setServerSocketFactory(new SecureServerSocketFactory(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), null));
this.tempFileManager = new TestTempFileManager();
this.testServer.start();
try {
long start = System.currentTimeMillis();
Thread.sleep(100L);
while (!this.testServer.wasStarted()) {
Thread.sleep(100L);
if (System.currentTimeMillis() - start > 2000) {
Assert.fail("could not start server");
}
}
} catch (InterruptedException e) {
}
}
@After
public void tearDown() {
this.testServer.stop();
}
}
其他答案对我不起作用。我必须使用 KeyStore Explorer 创建 BKS-V1 密钥库,并将其保存到 android assets 文件夹中,作为“keystore.bks”。或者,您也可以使用以下代码制作 KeyStore 文件,然后使用 KeyStore Explorer 打开它,并将其类型更改为 BKS-V1。
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.bks -storepass myKeyStorePass -validity 360 -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999
我使用以下代码使其工作。
package com.example.myappname
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import fi.iki.elonen.NanoHTTPD;
public class Server extends NanoHTTPD {
public Server(int port) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
super(port);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream keyStoreStream = context.get().getAssets().open("keystore.bks");
keyStore.load(keyStoreStream, "myKeyStorePass".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "myCertificatePass".toCharArray());
makeSecure(NanoHTTPD.makeSSLSocketFactory(keyStore, keyManagerFactory), null);
}
@Override
public Response serve(IHTTPSession session) {
}
}
要使用它,只需编写以下代码,您的 Android 设备上就会运行一个 HTTPS 服务器。
Server server = new Server(8080);
server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
由于以下 Github 问题评论中提供的示例,该代码才得以实现。
https://github.com/NanoHttpd/nanohttpd/issues/535#issuecomment-479269044
根据 Decoded 的解决方案进行修改,因为我无法使用 JKS 类型的密钥库。
我使用 Keystore Explorer 生成 BKS 密钥。 选择 BKS-V1 作为新 KeyStore 的类型,然后在启动前设置 NanoHTTPD 服务器:
androidWebServer = new AndroidWebServer(port);
File f = new File("src/main/resources/localkey.bks");
System.setProperty("javax.net.ssl.trustStore", f.getAbsolutePath());
androidWebServer.setServerSocketFactory(new AndroidWebServer.SecureServerSocketFactory(AndroidWebServer.makeSSLSocketFactory("/" + f.getName(), "yourKeyStorePass".toCharArray()), null));
androidWebServer.start();
此链接有解决方案:https://www.baeldung.com/nanohttpd#https
使用keytool生成jks
keytool -genkey -keyalg RSA -alias selfsigned
-keystore keystore.jks -storepass your_password -validity 360
-keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999
将 keystore.jks 移至项目 src/main/resources
并使用以下代码:
public class HttpsExample extends NanoHTTPD {
public HttpsExample() throws IOException {
super(8080);
makeSecure(NanoHTTPD.makeSSLSocketFactory(
"/keystore.jks", "password".toCharArray()), null);
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
}
// main and serve methods
}