4

I am trying to get two way SSL authentication working between a Python server and an Android client application. I have access to both the server and client, and would like to implement client authentication using my own certificate. So far I have been able to verify the server certificate and connect without client authentication.

What sort of certificate does the client need and how do I get it to automatically send it to the server during the handshake process? Here is the client and server side code that I have so far. Is my approach wrong?

Server Code

while True: # Keep listening for clients
    c, fromaddr = sock.accept()

    ssl_sock = ssl.wrap_socket(c,
            keyfile = "serverPrivateKey.pem",
            certfile = "servercert.pem",
            server_side = True,
            # Require the client to provide a certificate
            cert_reqs = ssl.CERT_REQUIRED,
            ssl_version = ssl.PROTOCOL_TLSv1,
            ca_certs = "clientcert.pem", #TODO must point to a file of CA certificates??
            do_handshake_on_connect = True,
            ciphers="!NULL:!EXPORT:AES256-SHA")

    print ssl_sock.cipher()
    thrd = sock_thread(ssl_sock)
    thrd.daemon = True
    thrd.start()

I suspect I may be using the wrong file for ca_certs...?

Client Code

    private boolean connect() {
    try {
        KeyStore keystore = KeyStore.getInstance("BKS"); // Stores the client certificate, to be sent to server
        KeyStore truststore = KeyStore.getInstance("BKS"); // Stores the server certificate we want to trust
        // TODO: change hard coded password... THIS IS REAL BAD MKAY
        truststore.load(mSocketService.getResources().openRawResource(R.raw.truststore), "test".toCharArray());
        keystore.load(mSocketService.getResources().openRawResource(R.raw.keystore), "test".toCharArray());

        // Use the key manager for client authentication. Keys in the key manager will be sent to the host
        KeyManagerFactory keyFManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyFManager.init(keystore, "test".toCharArray());

        // Use the trust manager to determine if the host I am connecting to is a trusted host
        TrustManagerFactory trustMFactory = TrustManagerFactory.getInstance(TrustManagerFactory
                .getDefaultAlgorithm());
        trustMFactory.init(truststore);

        // Create the socket factory and add both the trust manager and key manager
        SSLCertificateSocketFactory socketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory
                .getDefault(5000, new SSLSessionCache(mSocketService));
        socketFactory.setTrustManagers(trustMFactory.getTrustManagers());
        socketFactory.setKeyManagers(keyFManager.getKeyManagers());

        // Open SSL socket directly to host, host name verification is NOT performed here due to
        // SSLCertificateFactory implementation
        mSSLSocket = (SSLSocket) socketFactory.createSocket(mHostname, mPort);
        mSSLSocket.setSoTimeout(TIMEOUT);

        // Most SSLSocketFactory implementations do not verify the server's identity, allowing man-in-the-middle
        // attacks. This implementation (SSLCertificateSocketFactory) does check the server's certificate hostname,
        // but only for createSocket variants that specify a hostname. When using methods that use InetAddress or
        // which return an unconnected socket, you MUST verify the server's identity yourself to ensure a secure
        // connection.
        verifyHostname();
        // Safe to proceed with socket now
...

I have generated a client private key, a client certificate, a server private key, and a server certificate using openssl. I then added the client certificate to keystore.bks (which I store in /res/raw/keystore.bks) I then added the server certificate to the truststore.bks

So now when the client tries to connect I am getting this error server side:

ssl.SSLError: [Errno 1] _ssl.c:504: error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate

And when I try to do this in the android client

SSLSession s = mSSLSocket.getSession();
s.getPeerCertificates();

I get this error:

javax.net.ssl.SSLPeerUnverifiedException: No peer certificate

So obviously the keystore I am using doesn't appear to have a correct peer certificate in it and thus isn't sending one to the server.

What should I put in the keystore to prevent this exception?

Furthermore, is this method of two way SSL authentication safe and effective?

pfista
  • 349
  • 1
  • 4
  • 13
  • Can i ask which version of Android you're running? – IainS Jun 27 '13 at 16:19
  • @lainS I am testing on both 4.1.2 and 4.2.2 – pfista Jun 27 '13 at 16:21
  • http://stackoverflow.com/questions/4064810/using-client-server-certificates-for-two-way-authentication-ssl-socket-on-androi – ivan_pozdeev Jul 08 '13 at 09:45
  • Did you ever come up with a solution to this? I am having a similar issue. I add my client cert to a keystore, but it never gets sent to the server. I have tried wrapping the x509keymanager class, and I can see it doesn't even get called at all. – Derek Jul 03 '14 at 20:50

2 Answers2

1

The server needs to trust the client certificate. The usual way to do this is to create a CA, then have it sign a server certificate and a client certificate. Each one would have the CA certificate in their respective trust stores. Then you need to initialize the SSLContext with something like this:

KeyStore trustStore = loadTrustStore();
KeyStore keyStore = loadKeyStore();

TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);

KeyManagerFactory kmf = KeyManagerFactory
                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());

SSLContext sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

You can then use the SSLContext to create socket factories as needed. They will be initialized with the proper keys and certificates.

Nikolay Elenkov
  • 52,576
  • 10
  • 84
  • 84
0

update

I'm having trouble getting a peer certificate added to the keystore, and having that certificate sent to the server.

Done a little research that may aid your journey. mvsjes2 reported that incorrect ports can cause SSLPeerUnverifiedException to be thrown. Verify you are using port 443 as I can't see in your code where you set's the port.

Also check out emmby answer which I found also insightful.

Original post

So obviously the keystore I am using doesn't appear to have a correct peer certificate in it and thus isn't sending one to the server.

Try looking at this article about using the connect to Unknown Certificates authority, which will let you use certificate that is not default with Android.. You might have to include a public/intermediate certificate in your application to ensure that existing/older devices will be able to connect with your server. Follow the article and see if that solved the problem of yours.

Furthermore, is this method of two way SSL authentication safe and effective?

Only if you can get it work first! As long as your code can checks the validity of the SSL certificate it should be effective enough. Safety wise... well as long the ssl certificate itself is created with a strong RSA signature/key it will make it more secure than just plain ol' http. Let me know if you continue to have issues.

Community
  • 1
  • 1
Jerad
  • 709
  • 5
  • 11
  • This doesn't answer my question. I already added the server certificate to my trust store and am able to connect without _client_ authentication. I'm having trouble getting a peer certificate added to the keystore, and having that certificate sent to the server. – pfista Jul 03 '13 at 17:40
  • I added a few resources that might help. If that still doesn't work, at least other will think of alternatives. "if have eliminated the impossible, whatever remains, however improbable, must be the truth". – Jerad Jul 03 '13 at 18:43
  • I appreciate you trying to help, but this doesn't really address my problem. The port in my case is irrelevant because I have access to and control over the server. I am not writing an HttpClient. Furthermore, I am getting the SSLPeerUnverifiedException in the first place because, I think, I am not putting the correct client certificate / certificate chain / I dont know what I need in the keystore – pfista Jul 03 '13 at 19:46