Android 设备上使用 NanoHttpd 的 HTTPS 服务器

问题描述 投票:0回答:4

我正在尝试使用 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

有人对此有何评论吗?

android ssl websocket https nanohttpd
4个回答
10
投票

经过几个小时的辛苦,我得到了!

这是我的(工作)代码:

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

4
投票

其他答案对我不起作用。我必须使用 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


0
投票

根据 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(); 

-1
投票

此链接有解决方案: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
}
© www.soinside.com 2019 - 2024. All rights reserved.