11

I am trying to run an HTTPS Server on an Android device using NanoHttpd (my final goal is to run WSS server on Android). I successfully ran HTTP Server and Websocket using NanoHttpd on Android. I generated the key on MAC using this command and copied it onto my device:

keytool -genkey -keystore key.keystore -storepass keypass -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider

I wrote the following code:

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

I tested this on Chrome 38 and 42 with "Minimum SSL/TLS" flag set to "SSLv3". But when I want to connect to the server I keep receiving "ERR_SSL_VERSION_OR_CIPHER_MISMATCH" error.

I tried different instances of protocol (SSL/TLS), on multiple machines, and browsers. I tried NanoHttpd SSLServerSocketFactory method. But the error is the same.

I already looked at some samples including: https://github.com/NanoHttpd/nanohttpd/issues/139

Does anyone have any comment on this?

user3183066
  • 111
  • 1
  • 1
  • 3

4 Answers4

10

After Hours of toil, I've got it!

Here is MY (working) code:

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

Here is the actual NanoHttpd Test case which illustrates exactly how its done Nano style.

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();
    }
}
Denys Vitali
  • 530
  • 6
  • 19
Decoded
  • 1,057
  • 13
  • 17
  • 1
    ONE NOTE: Nano tries to load the keystore as a resource stream, so the KeyStore MUST reside in src/main/resources (which is why I've set that as a hard path). Seems like this is a security feature ;). – Decoded Mar 04 '16 at 06:26
  • 2
    Watch out on publicly posting your passwords – Denys Vitali Mar 31 '16 at 13:59
  • you mean you stored the file `res` folder? I'm not able to find the the `.jks` file. – KVISH Apr 04 '16 at 02:48
  • 1
    Um, what? `src/main/resources/key001.jks` is valid at build time, but not run time. – Jeffrey Blattman Aug 01 '16 at 19:57
  • 1
    Thanks for the warning @DenysVitali, luckily these aren't REAL production passwords ;), all just for testing purposes. – Decoded Aug 17 '16 at 19:55
  • @JeffreyBlattman have you made any changes to your Java installation (i.e. regarding security jars??), and have you correctly installed your certificates on your rooted android device? – Decoded Aug 17 '16 at 19:56
  • Will it work for any IP address accessed from the desktop browser? That IP wont be fix – Abhishek Batra Apr 17 '18 at 18:43
  • Yes @Abishek it should, once your browser downloads the certificate – Decoded Jun 20 '18 at 18:41
4

The other answers didn't work for me. I had to create a BKS-V1 Keystore using a KeyStore Explorer and save it to android assets folder as "keystore.bks". Alternatively, You can also use the following code to make the KeyStore file then just open it using KeyStore Explorer and change its type to 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 

I used the following code to make it work.

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) {
    }
}

To use it just write the following code and you will have an HTTPS server running on your Android device.

Server server = new Server(8080);
server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);

This code is made possible thanks to the example provided in the following Github issue comment.

https://github.com/NanoHttpd/nanohttpd/issues/535#issuecomment-479269044

Ravi Patel
  • 2,136
  • 3
  • 32
  • 48
  • Ravi Patel, I used this to create https server, but when I hit request form web application I get handshake failed, web application doesn't have client certificate as it is one way authentication. How can we achieve https with only server side certificate. – wasim.shariff Apr 08 '21 at 08:48
  • @wasim you can try disabling the certificate verification on client side. It may solve the issue. – Ravi Patel Apr 08 '21 at 17:15
  • from Client web application server certificate is validated. So we will have only server certificate. Is it because the certificate I'm using is self signed? – wasim.shariff Apr 28 '21 at 04:03
  • Yes, It can happen if you are using self-signed certificate. – Ravi Patel May 01 '21 at 07:04
  • Ravi, thanks for the reply. Is there a way to know if the nanohttp server has stopped? Like do we have any broadcast so that if server is stopped after few min/hr we can start again. – wasim.shariff Jul 19 '21 at 07:42
  • @wasim You can run it in a Foreground Service, and it won't be stopped. I don't think there is an official way to detect it. – Ravi Patel Jul 19 '21 at 08:33
  • @RaviPatel it works when i change the key type to BKS-V1. But i got another issue: javax.net.ssl.SSLHandshakeException: Handshake failed at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:286) Could you please tell me how to resolve? – kollein Feb 07 '22 at 13:43
  • @kollein Does this error happen when you open the server URL on your browser? – Ravi Patel Feb 07 '22 at 18:48
  • @RaviPatel actually, i'm building an android app and i serve it as a local server. it works when i import the certificate into the keystore. Thanks! – kollein Feb 09 '22 at 07:43
0

Modified from Decoded's solution since I was not able to use JKS type of keystore.

Instead I use Keystore Explorer to generate a BKS key. Select BKS-V1 as type of the new KeyStore, then setup the NanoHTTPD server before start:

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(); 
Homer Wang
  • 531
  • 6
  • 8
0

This link has solution: https://www.baeldung.com/nanohttpd#https

use keytool generate 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

move keystore.jks to project src/main/resources

and use following code:

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
}
chikadance
  • 3,591
  • 4
  • 41
  • 73