【问题标题】:HTTPS Server on Android Device Using NanoHttpdAndroid 设备上使用 NanoHttpd 的 HTTPS 服务器
【发布时间】:2015-07-07 13:56:07
【问题描述】:

我正在尝试使用 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 上对此进行了测试,并将“Minimum SSL/TLS”标志设置为“SSLv3”。但是当我想连接到服务器时,我不断收到“ERR_SSL_VERSION_OR_CIPHER_MISMATCH”错误。

我在多台机器和浏览器上尝试了不同的协议实例 (SSL/TLS)。我尝试了 NanoHttpd SSLServerSocketFactory 方法。但错误是一样的。

我已经查看了一些示例,包括: https://github.com/NanoHttpd/nanohttpd/issues/139

有人对此有什么意见吗?

【问题讨论】:

  • 您在哪个 Android 版本上执行服务器?
  • 这是一部 LG VS985 手机,Android 版本为 5.0.1
  • 你让它工作了吗?

标签: android ssl https wss nanohttpd


【解决方案1】:

经过几个小时的辛勤工作,我成功了!

这是我的(工作)代码:

// 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();
    }
}

【讨论】:

  • 一个注意事项:Nano 尝试将密钥库作为资源流加载,因此密钥库必须驻留在 src/main/resources 中(这就是我将其设置为硬路径的原因)。似乎这是一项安全功能;)。
  • 注意公开发布您的密码
  • 您的意思是您将文件存储在res 文件夹中?我找不到.jks 文件。
  • 嗯,什么? src/main/resources/key001.jks 在构建时有效,但在运行时无效。
  • 感谢@DenysVitali 的警告,幸运的是这些不是真正的生产密码;),所有这些都只是为了测试目的。
【解决方案2】:

其他答案对我不起作用。我必须使用KeyStore Explorer 创建一个 BKS-V1 密钥库并将其作为“keystore.bks”保存到 android assets 文件夹中。或者,您也可以使用以下代码制作 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

【讨论】:

  • Ravi Patel,我用它来创建 https 服务器,但是当我点击请求表单 web 应用程序时,握手失败,web 应用程序没有客户端证书,因为它是一种身份验证方式。我们如何仅使用服务器端证书来实现 https。
  • @wasim 您可以尝试在客户端禁用证书验证。它可能会解决问题。
  • 来自客户端 Web 应用程序服务器证书已验证。所以我们将只有服务器证书。是因为我使用的证书是自签名的吗?
  • 是的,如果您使用的是自签名证书,就会发生这种情况。
  • 拉维,感谢您的回复。有没有办法知道 nanohttp 服务器是否已停止?就像我们有任何广播一样,如果服务器在几分钟/小时后停止,我们可以重新开始。
【解决方案3】:

从 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(); 

【讨论】:

    【解决方案4】:

    此链接有解决方案: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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-22
      相关资源
      最近更新 更多