Search code examples
androidsslhttpsssl-certificaterestlet

"NO SERVER CERTIFICATE FOUND" with HTTPS Restlet server on Android


I'm using Restlet 2.3.2 and Android 4.4.4.

I previously successfully ran an Android server application using Restlet with HTTP, but now, I'm running into problems trying to do the same with HTTPS.

Trying to connect to this server with my Chrome browser returns an ERR_CONNECTION_CLOSE error. And as you can see, there is an exception in logcat stating NO SERVER CERTIFICATE FOUND. So, I guess that my error is around the certificate generation...

Here's my Android server code :

// Creating a minimal Restlet returning "Hello World"
Restlet restlet = new Restlet() {
    @Override
    public void handle(Request request, Response response) {
        response.setEntity("Hello World!", MediaType.TEXT_PLAIN);
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final File keystore3 = new File("/storage/sdcard0/qlinks.bks");
    Log.d(TAG, "Keystore3 exists: " + keystore3.exists());
    Log.d(TAG, "Keystore3 can read: " + keystore3.canRead());

    Engine.getInstance().getRegisteredServers().add(new HttpsServerHelper(null));
    Engine.setLogLevel(Level.FINEST);

    // Create a new Component.
    Component component = new Component();

    // Add a new HTTPS server listening on port 8080
    Server server = component.getServers().add(Protocol.HTTPS, 8080);  
    Series<Parameter> parameters = server.getContext().getParameters();
    parameters.add("sslContextFactory", "org.restlet.engine.ssl.DefaultSslContextFactory");
    parameters.add("keyStorePath", "/storage/sdcard0/qlinks.bks");
    parameters.add("keyStorePassword", "password");
    parameters.add("keyPassword", "password");
    parameters.add("keyStoreType", "BKS");

    parameters.add("keyManagerAlgorithm", KeyManagerFactory.getDefaultAlgorithm());

    // Attach the sample application.
    component.getDefaultHost().attach("/test", restlet);

    // Start the component.
    try {
        server.start();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Here's the logcat output :

I/System.out(11955): debugger has settled (1315)
D/q_links.RestletServer(11955): Keystore3 exists: true
D/q_links.RestletServer(11955): Keystore3 can read: true
D/dalvikvm(11955): GC_FOR_ALLOC freed 249K, 3% free 9142K/9416K, paused 39ms, total 40ms
W/System.err(11955): Starting the internal [HTTPS/1.1] server on port 8080
[...]
W/System.err(11955): NIO controller selected 1 key(s) !
W/System.err(11955): ReadableSocketChannel created from: java.nio.SocketChannelImpl@424e2cc0,Interest= NONE , Ready=NONE , Canceling=false. Registration: Interest= NONE , Ready=NONE , Canceling=false
W/System.err(11955): ReadableSslChannel created from: org.restlet.ext.nio.internal.connection.Connection$1@424e5dc0. Registration: Interest= NONE , Ready=NONE , Canceling=false
W/System.err(11955): Connection state (old | new) : OPENING | OPEN
W/System.err(11955): InboundWay#setMessageState: START
W/System.err(11955): InboundWay#setIoState: INTEREST
W/System.err(11955): Old connection NIO interest: Interest= NONE , Ready=NONE , Canceling=false
W/System.err(11955): New connection NIO interest: Interest= READ , Ready=NONE , Canceling=false
W/System.err(11955): Connection from "/192.168.2.77:56441" accepted. New count: 1
W/System.err(11955): helper.control()
W/System.err(11955): controlConnections()
W/System.err(11955): Connection status: OPEN | true | Interest= READ , Ready=NONE , Canceling=false | org.apache.harmony.xnet.provider.jsse.SSLEngineImpl@420f5280 | null
W/System.err(11955): registerKeys()
W/System.err(11955): Registering new NIO interest with selector: Interest= READ , Ready=NONE , Canceling=false
W/System.err(11955): updateKeys()
W/System.err(11955): selectKeys(60000)
W/System.err(11955): NIO controller about to sleep 60000 ms, selecting among 2 keys...
W/System.err(11955): NIO controller selected 2 key(s) !
W/System.err(11955): ReadableSocketChannel created from: java.nio.SocketChannelImpl@424f8670,Interest= NONE , Ready=NONE , Canceling=false. Registration: Interest= NONE , Ready=NONE , Canceling=false
W/System.err(11955): ReadableSslChannel created from: org.restlet.ext.nio.internal.connection.Connection$1@424f8cd0. Registration: Interest= NONE , Ready=NONE , Canceling=false
W/System.err(11955): Connection state (old | new) : OPENING | OPEN
W/System.err(11955): InboundWay#setMessageState: START
W/System.err(11955): InboundWay#setIoState: INTEREST
W/System.err(11955): Old connection NIO interest: Interest= NONE , Ready=NONE , Canceling=false
W/System.err(11955): New connection NIO interest: Interest= READ , Ready=NONE , Canceling=false
W/System.err(11955): Connection from "/192.168.2.77:56442" accepted. New count: 2
W/System.err(11955): NIO selection detected for key: Interest= READ , Ready=NONE , Canceling=false
W/System.err(11955): Server connection (state | empty | registration): OPEN | true | Interest= READ , Ready=READ , Canceling=false | org.apache.harmony.xnet.provider.jsse.SSLEngineImpl@420f5280 | null
W/System.err(11955): InboundWay#setIoState: PROCESSING
W/System.err(11955): Processing IO for inbound way: PROCESSING, START, java.nio.ByteArrayBuffer[position=0,limit=16384,capacity=16384], FILLING, true
W/System.err(11955): Beginning process of buffer java.nio.ByteArrayBuffer[position=0,limit=16384,capacity=16384], FILLING, true
W/System.err(11955): 0 bytes drained from buffer at pre-processing, 16384 remaining bytes
W/System.err(11955): Filling buffer java.nio.ByteArrayBuffer[position=0,limit=16384,capacity=16384], FILLING, true
W/System.err(11955): Beginning process of buffer java.nio.ByteArrayBuffer[position=0,limit=18437,capacity=18437], FILLING, true
W/System.err(11955): 0 bytes drained from buffer at pre-processing, 18437 remaining bytes
W/System.err(11955): Filling buffer java.nio.ByteArrayBuffer[position=0,limit=18437,capacity=18437], FILLING, true
W/System.err(11955): 179 bytes filled into buffer
W/System.err(11955): Filling buffer java.nio.ByteArrayBuffer[position=179,limit=18437,capacity=18437], FILLING, false
W/System.err(11955): Draining buffer java.nio.ByteArrayBuffer[position=0,limit=179,capacity=18437], DRAINING, false
W/System.err(11955): SSL engine result: SSLEngineReport: Status = OK  HandshakeStatus = NEED_TASK
W/System.err(11955):                  bytesConsumed = 179 bytesProduced = 0
W/System.err(11955): SSL connection: OPEN | true | Interest= READ , Ready=READ , Canceling=false | org.apache.harmony.xnet.provider.jsse.SSLEngineImpl@420f5280 | null
W/System.err(11955): 179 bytes drained from buffer, 0 remaining bytes
W/System.err(11955): Draining buffer java.nio.ByteArrayBuffer[position=179,limit=179,capacity=18437], DRAINING, true
W/System.err(11955): Filling buffer java.nio.ByteArrayBuffer[position=0,limit=18437,capacity=18437], FILLING, true
W/System.err(11955): Ending process of buffer java.nio.ByteArrayBuffer[position=0,limit=18437,capacity=18437], FILLING, true. Result: 179, try again: false, can loop: true, total filled: 179
W/System.err(11955): Handling SSL result: OK
W/System.err(11955): Handling SSL handshake: NEED_TASK
W/System.err(11955): InboundWay#setIoState: IDLE
W/System.err(11955): Running delegated tasks...
W/System.err(11955): 179 bytes filled into buffer
D/dalvikvm(11955): GC_FOR_ALLOC freed 245K, 3% free 12858K/13128K, paused 44ms, total 44ms
W/System.err(11955): Done running delegated tasks
W/System.err(11955): Ending process of buffer java.nio.ByteArrayBuffer[position=0,limit=16384,capacity=16384], FILLING, true. Result: 0, try again: true, can loop: false, total filled: 179
W/System.err(11955): Handling SSL result: OK
W/System.err(11955): Handling SSL handshake: NEED_WRAP
W/System.err(11955): OutboundWay#setIoState: READY
W/System.err(11955): NIO controller woke up
W/System.err(11955): Handling SSL result: OK
W/System.err(11955): Handling SSL handshake: NEED_WRAP
W/System.err(11955): Inbound way selected. Done for : IDLE, START, java.nio.ByteArrayBuffer[position=0,limit=16384,capacity=16384], FILLING, true
W/System.err(11955): Entering into a connection READY loop
W/System.err(11955): Processing IO for outbound way: READY, IDLE, java.nio.ByteArrayBuffer[position=0,limit=16384,capacity=16384], FILLING, true
W/System.err(11955): Beginning process of buffer java.nio.ByteArrayBuffer[position=0,limit=16384,capacity=16384], FILLING, true
W/System.err(11955): Beginning process of buffer java.nio.ByteArrayBuffer[position=0,limit=18437,capacity=18437], FILLING, true
W/System.err(11955): 0 bytes drained from buffer at pre-processing, 18437 remaining bytes
W/System.err(11955): Filling buffer java.nio.ByteArrayBuffer[position=0,limit=18437,capacity=18437], FILLING, true
W/System.err(11955): Error while processing a connection
W/System.err(11955): javax.net.ssl.SSLException: Error occured in delegated task:javax.net.ssl.SSLHandshakeException: NO SERVER CERTIFICATE FOUND
W/System.err(11955):    at org.apache.harmony.xnet.provider.jsse.HandshakeProtocol.fatalAlert(HandshakeProtocol.java:319)
W/System.err(11955):    at org.apache.harmony.xnet.provider.jsse.HandshakeProtocol.wrap(HandshakeProtocol.java:271)
W/System.err(11955):    at org.apache.harmony.xnet.provider.jsse.SSLEngineImpl.wrap(SSLEngineImpl.java:694)
W/System.err(11955):    at javax.net.ssl.SSLEngine.wrap(SSLEngine.java:462)
W/System.err(11955):    at org.restlet.ext.nio.internal.channel.WritableSslChannel.onFill(WritableSslChannel.java:125)
W/System.err(11955):    at org.restlet.ext.nio.internal.buffer.Buffer.process(Buffer.java:593)
W/System.err(11955):    at org.restlet.ext.nio.internal.channel.WritableBufferedChannel.write(WritableBufferedChannel.java:118)
W/System.err(11955):    at org.restlet.ext.nio.internal.channel.WritableSslChannel.write(WritableSslChannel.java:143)
W/System.err(11955):    at org.restlet.ext.nio.internal.buffer.Buffer.drain(Buffer.java:333)
W/System.err(11955):    at org.restlet.ext.nio.internal.way.OutboundWay.onDrain(OutboundWay.java:257)
W/System.err(11955):    at org.restlet.ext.nio.internal.way.HttpsServerOutboundWay.preProcess(HttpsServerOutboundWay.java:81)
W/System.err(11955):    at org.restlet.ext.nio.internal.buffer.Buffer.process(Buffer.java:528)
W/System.err(11955):    at org.restlet.ext.nio.internal.way.Way.processIoBuffer(Way.java:498)
W/System.err(11955):    at org.restlet.ext.nio.internal.way.OutboundWay.processIoBuffer(OutboundWay.java:463)
W/System.err(11955):    at org.restlet.ext.nio.internal.way.Way.onSelected(Way.java:451)
W/System.err(11955):    at org.restlet.ext.nio.internal.connection.Connection.onSelected(Connection.java:664)
W/System.err(11955):    at org.restlet.util.SelectionRegistration.onSelected(SelectionRegistration.java:316)
W/System.err(11955):    at org.restlet.ext.nio.internal.controller.ConnectionController.onSelected(ConnectionController.java:213)
W/System.err(11955):    at org.restlet.ext.nio.internal.controller.ServerConnectionController.onSelected(ServerConnectionController.java:109)
W/System.err(11955):    at org.restlet.ext.nio.internal.controller.ConnectionController.selectKeys(ConnectionController.java:302)
W/System.err(11955):    at org.restlet.ext.nio.internal.controller.ConnectionController.doRun(ConnectionController.java:165)
W/System.err(11955):    at org.restlet.ext.nio.internal.controller.Controller.run(Controller.java:152)
W/System.err(11955):    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
W/System.err(11955):    at java.util.concurrent.FutureTask.run(FutureTask.java:234)
W/System.err(11955):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
W/System.err(11955):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
W/System.err(11955):    at java.lang.Thread.run(Thread.java:841)
W/System.err(11955): Caused by: org.apache.harmony.xnet.provider.jsse.AlertException: javax.net.ssl.SSLHandshakeException: NO SERVER CERTIFICATE FOUND
W/System.err(11955):    at org.apache.harmony.xnet.provider.jsse.HandshakeProtocol.fatalAlert(HandshakeProtocol.java:308)
W/System.err(11955):    at org.apache.harmony.xnet.provider.jsse.ServerHandshakeImpl.processClientHello(ServerHandshakeImpl.java:459)
W/System.err(11955):    at org.apache.harmony.xnet.provider.jsse.ServerHandshakeImpl$1.run(ServerHandshakeImpl.java:123)
W/System.err(11955):    at org.apache.harmony.xnet.provider.jsse.DelegatedTask.run(DelegatedTask.java:36)
W/System.err(11955):    at org.restlet.ext.nio.internal.connection.SslConnection$1.run(SslConnection.java:417)
W/System.err(11955):    ... 3 more
W/System.err(11955): Caused by: javax.net.ssl.SSLHandshakeException: NO SERVER CERTIFICATE FOUND
W/System.err(11955):    ... 8 more
W/System.err(11955): Closing connection to /192.168.2.77:56441 immediately
W/System.err(11955): InboundWay#setMessageState: IDLE
W/System.err(11955): OutboundWay#setIoState: IDLE
W/System.err(11955): Connection state (old | new) : OPEN | CLOSED
W/System.err(11955): Connection to /192.168.2.77:56441 is now closed

Here's how I generated my certificate :

> keytool -genkey -alias qlinks -keystore qlinks.keystore -validity 365
> keytool -export -alias qlinks -keystore qlinks.keystore -file qlinks.cer
> keytool -import -alias qlinks -file qlinks.cer -keystore qlinks.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/Downloads/bcprov-jdk15on-146.jar
> adb push qlinks.bks /storage/sdcard0/

Here are the jars I've out into my /libs directory from my Android project :

android-support-v4.jar
org.jsslutils.jar
org.restlet.ext.nio.jar
org.restlet.jar

Solution

  • As I suspected, my certificate was not generated properly.

    I'm quite certain there is an easier way, but here is what I did (with the help of this other answer) :

    > openssl genrsa -des3 -passout pass:1 -out qlinks.pass.key 2048
    > openssl rsa -passin pass:1 -in qlinks.pass.key -out qlinks.key
    > openssl req -new -key qlinks.key -out qlinks.csr
    > openssl x509 -req -days 365 -in qlinks.csr -signkey qlinks.key -out qlinks.crt
    > keytool -keystore keystore -import -alias jetty -file qlinks.crt -trustcacerts
    > openssl pkcs12 -inkey qlinks.key -in qlinks.crt -export -out qlinks.pkcs12
    > keytool -importkeystore -srckeystore qlinks.pkcs12 -srcstoretype PKCS12 -destkeystore keystore
    

    Next, I had to use a neat tool called portecle to convert my JKS keystore (the keystore file) to BKS, because Android only supports BouncyCastle keystores, AFAIK.

    Finally, I pushed the file to the target :

    adb push keystore /storage/sdcard0/qlinks.bks