Connecting to a server that uses a self signed certificate

Connecting to a server that uses a self signed certificate

There are several hurdles to overcome before you can connect to a server using a self-signed certificate via SSL from android. First, you’d not need to have bought a signed certificate. These will be accepted. Self-signed certificates will raise a java.security.cert.CertPathValidatorException carrying the message “Trust anchor for certification path not found”. This means that a chain of trust (that is, a list of certificates starting with one of a so-called “certificate authority”) could not be established.

Certificates are basically RSA public keys (there are other ciphers as well, however, for simplicitiy, you may assume that these work just like RSA) bound to a certain object (most likely a computer or an institution using a computer). That means, there’s no objection against publishing a certificate. However, an attacker could simply supply a modified certificate and so make your application trust a different server, which is exactly what we wanted to avoid. A way to mitigate this risk is “pinning”. Make your Application accept only certain keys. That way, an attacker would have to dig through binary (JVM, in this case) code and modify it so that their certificate would be accepted.

However, we’re not so much interested in general public key security considerations, we’d simply want to load a PEM certificate into an android application environment, so that aforementioned exception will be suppressed. Certainly, this could be done in an easy way, however, then somebody would come and break security. So here some remarks on how to accomplish this.

1. Android will only accept a “BouncyCastle” Keystore.

So you’d have to convert your certificate to this format. Luckily, there’s the so-called “keytool” of java which manages most of this process. Sadly, the documentation of this tool is a little involved. In my opinion, the most important details in using “keytool” are:

  • Maybe you’d have to load the BouncyCastle “Provider”. This happens by supplying the corresponding .jar file from this page:

https://www.bouncycastle.org/latest_releases.html

you just need to add the parameters

-provider org.bouncycastle.jce.provider.BouncyCastleProvider

-providerpath xyz.jar

to the keytool.

  • in any case, probably your installation is a little more recent than the android environment. Android expects a “version 1” BKS keystore. You’d select this type with

-storetype bks-v1

Any other keystore type will raise a java.io.IOException “Wrong version of key store”.

-storepass …

You need to have a password on the keystore. Android will not accept “null” passwords.

-keystore …

You’ll need to save the key somewhere. Just give a filename here. Somewhat surprisingly, the keytool does not expect you to “initialize” a keystore. It will happily create a new one if it is not there. However, it will complain whenever it recognizes you’re trying to load a certificate a second time.

When, after supplying all these parameters to “keytool -importcert”, the keytool does not display an error message, it is ready to read a PEM certificate through standard input. That means, you can copy it from somewhere else and paste it into your terminal window. You should not be using a “pipe” or a similar construction, since the keytool will ask you if you’d want to trust “this” certificate, which is displayed as a series of somewhat technical messages. It’s quite hard to tell if you’d trust this, however, if you don’t answer “yes”, the certificate will not be imported. When using a “pipe”, the keytool can’t read the “yes” and will simply abort there.

You should now have a keystore with the supplied certificate imported and know its filename.

2. The Keystore has to be loaded into your android environment.

The file you just generated has to be loaded inside android. First, you should make it a “resource”, that is, move it to a place usually called “src/main/res”, from the root of your project directory. I’d place it in a directory called “raw” there, since it is a binary file which I would not expect to be present in more than one instance. If there’s other binary files of this kind, I’d maybe just create a directory “crypto” or “keys”. However, we just want to use one certificate, so we stay with “raw”.

To load the Keystore file, you’ll have to call “KeyStore.load” on an input stream and supply the password you set above. First, we’ll get the resource

Resources res = ctx.getResources();

String packageName = ctx.getApplicationContext().getPackageName();

int id = res.getIdentifier(“raw/trusted_keys”, “raw”, packageName);

InputStream ins = res.openRawResource(id);

Where “ctx” is an Android Application context. You can get a context from inside an android Activity by calling getApplicationContext(). So the code above may very well be part of onStart, however, I put it as static code in a different class so I’d not have to bother with ever reading it again. When this code has run without exception, “ins” will be an InputStream pointing to your keystore file. After this, you can simply use “load”.

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

ks.load(ins,"".toCharArray());

using getDefaultType is a little more portable than just using the String “BKS” (BouncyCastle Key Store - which is the value that will appear on android). I just used a literal password, since the security implications appear to be quite harmless here. It - obviously - has to be the same password that was used to generate the keystore.

After these steps, ks will hold a keystore which contains the supplied certificate.

3. Using the keystore for an xmpp connection (smack 4.1.1)

Now the stored key (certificate) can be used in a client to start a secure transmission. There’s several ways of negotiating such an encrypted transmission, among them there’s starttls, which is used in xmpp. To have smack use the key, a so-called SSL Context is used. First, however, we’ll have to create a “trust manager”:

TrustManagerFactory tmf =

TrustManagerFactory

.getInstance(TrustManagerFactory.getDefaultAlgorithm());

tmf.init(ks);

where ks is the Key store from above. A Trust Manager is basically a keystore for server keys. It’s only use here is to hold the server’s public key. Now we can create a new SSL context that uses the Trust Manager - the first parameter here, “null”, is for client certificates, which are fancy, but rarely used:

SSLContext sslctx = SSLContext.getInstance(“TLS”);

sslctx.init(null, tmf.getTrustManagers(), new SecureRandom());

This SSL context now can be used in Smack:

configBuilder.setCustomSSLContext(sslctx);

Using these steps, you should be able to build an encrypted connection to an xmpp server and authenticate there. It works with jabberd 2.2.17 on my machine.

Smack 4.1.1 also appears to contain a bug in the Digest-MD5 implementation (it appears to always send “=”, which is invalid Base64), so this mechanism should be unregistered. To this end, you have to overload XMPPTCPConnection, since unregisterSASLMechanism is protected. Also, the mechanics of initialization changed quite a lot, so do not expect this to work on other versions of smack. However, with slight variations, this code should work there too.

This is my first contribution to this community, I hope it’ll help others a little. Comments and criticism are welcome.

1 Like

A post was split to a new topic: Self signed certified