HTTPS Server on Android Device Using NanoHttpd
Asked Answered
N

4

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?

Nativeborn answered 7/7, 2015 at 13:56 Comment(3)
On which Android version do you execute the server?Attested
It's a LG VS985 phone with Android version 5.0.1Nativeborn
you get this to work?Glottochronology
P
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();
    }
}
Pope answered 3/3, 2016 at 7:6 Comment(8)
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 ;).Pope
Watch out on publicly posting your passwordsBeaune
you mean you stored the file res folder? I'm not able to find the the .jks file.Glottochronology
Um, what? src/main/resources/key001.jks is valid at build time, but not run time.Motorboat
Thanks for the warning @DenysVitali, luckily these aren't REAL production passwords ;), all just for testing purposes.Pope
@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?Pope
Will it work for any IP address accessed from the desktop browser? That IP wont be fixBalbo
Yes @Abishek it should, once your browser downloads the certificatePope
Q
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

Quinn answered 12/12, 2019 at 12:23 Comment(9)
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.Venepuncture
@wasim you can try disabling the certificate verification on client side. It may solve the issue.Quinn
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?Venepuncture
Yes, It can happen if you are using self-signed certificate.Quinn
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.Venepuncture
@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.Quinn
@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?Threadbare
@Threadbare Does this error happen when you open the server URL on your browser?Quinn
@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!Threadbare
W
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(); 
Winder answered 9/8, 2018 at 9:21 Comment(0)
C
-1

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
}
Cambogia answered 25/6, 2020 at 10:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.