Under the hood: HTTPS in Loklak

For some time now, loklak natively offers HTTPS support. On most one-click-deployments, that is not really necessary, as there’s usually a HTTP-proxy in front, which forwards all the trafic. This HTTP-proxies can then use their own HTTPS implementation. Also, Loklak is usually run with normal user previleges, which means it can’t open a socket on the normal HTTP and HTTPS ports, but only on ports greater than 1024.

Still, in some setups it might be desireable to not have an extra HTTP-proxy installed but still benefit from a secure connection. Espeacially for user-login and similar things.

These are the current options that can be set in conf/config.properties or data/settings/customized_config.properties:

https.mode=(off|on|redirect|only)
https.keysource=(keystore|key-cert)

keystore.name=keystore.jks
keystore.password=123456

https.key=/etc/ssl/private/loklak_key.pem
https.cert=/etc/ssl/certs/loklak_cert_full_chain.pem

The first setting has four option:

  1. off: the default. Only HTTP
  2. on: HTTP and HTTPS
  3. redirect: redirect all HTTP requests to HTTPS
  4. only: only HTTPS

The second lets us choose where to get our keys from. In java, the usual way is to use a key-store. That’s a file, protected by a password (the next two options). Buf if the keysource is set to “key-cert”, we can also use PEM-formated keys and certs, which is generally more common for non-java applications (like apache, nginx etc.).

If choosen, we specify .pem files (the last two options). If a whole certificate chain is required, all the certificates have to be in one file, just copied together.

Loklak will create a keystore from the .pem files using the bouncycastle library. It will not write it to disk. Here’s the code for that:

//generate random password
char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 20; i++) {
    char c = chars[random.nextInt(chars.length)];
    sb.append(c);
}
String password = keystoreManagerPass = sb.toString();

//get key and cert
File keyFile = new File(DAO.getConfig("https.key", ""));
if(!keyFile.exists() || !keyFile.isFile() || !keyFile.canRead()){
   throw new Exception("Could not find key file");
}
File certFile = new File(DAO.getConfig("https.cert", ""));
if(!certFile.exists() || !certFile.isFile() || !certFile.canRead()){
   throw new Exception("Could not find cert file");
}

Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

byte[] keyBytes = Files.readAllBytes(keyFile.toPath());
byte[] certBytes = Files.readAllBytes(certFile.toPath());

PEMParser parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(certBytes)));
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate((X509CertificateHolder) parser.readObject());

parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(keyBytes)));
PrivateKey key = new JcaPEMKeyConverter().setProvider("BC").getPrivateKey((PrivateKeyInfo) parser.readObject());

keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);

keyStore.setCertificateEntry(cert.getSubjectX500Principal().getName(), cert);
keyStore.setKeyEntry("defaultKey",key, password.toCharArray(), new Certificate[] {cert})

 

A last interesting option is:

httpsclient.trustselfsignedcerts=(none|peer|all)

Loklak is by default configured to trust any HTTPS-connection, even if the certificate is wrong. That was done so people behind HTTPS-proxies can still use Loklak.

But it is also possible to make Loklak honor certificates. If “none” is selected, it will behave like most applications: if the certificate is wrong, close the connection. But even then, it’s possible to import certificates system-wide. Loklak will then accept those connections.

It’s also possible to make Loklak work with peers with broken/self-signed certificates (so the connection is atleast not plain text) but still require good certificates from other sources (for example twitter). That’s the “peer” option.

Creating a HttpConnection in java that does not check the certificates is actually much more tricky than creating a save on. Here’s the code to create a connection manager cm that ignores certificates if you need one at some point:

boolean trustAllCerts = ...;
   
   Registry<ConnectionSocketFactory> socketFactoryRegistry = null;
   if(trustAllCerts){
   try {
      SSLConnectionSocketFactory trustSelfSignedSocketFactory = new SSLConnectionSocketFactory(
             new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
               new TrustAllHostNameVerifier());
   socketFactoryRegistry = RegistryBuilder
               .<ConnectionSocketFactory> create()
               .register("http", new PlainConnectionSocketFactory())
               .register("https", trustSelfSignedSocketFactory)
               .build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
   Log.getLog().warn(e);
}
   }
     
   PoolingHttpClientConnectionManager cm = (trustAllCerts && socketFactoryRegistry != null) ? 
          new PoolingHttpClientConnectionManager(socketFactoryRegistry):
          new PoolingHttpClientConnectionManager();
Under the hood: HTTPS in Loklak