Root certificaat in je certificate chain?

Als je een certificaat aanvraagt krijg je bij de oplevering daarvan vaak een zip-bestand met daarin het certificaat, één of meer intermediate certificaten en een root-certificaat.

Het is echter raadzaam om het root-certificaat niet in te regelen in de webserver. Dat scheelt namelijk bandbreedte bij het opzetten van de SSL handshake en het voegt niks toe.

Dat zit zo; een browser is standaard voorzien van een verzameling root-certificaten die door de browser worden vertrouwd. Certificaten die zijn uitgegeven met die root-certificaten worden dus ook vertrouwd. En certificaten die zijn uitgegeven door intermediate certificaten die zijn ondertekend met een van de bekende root-certificaten worden ook vertrouwd. Enzovoort. Een certificaat uit een keten waarvan het root-certificaat niet wordt vertrouwd wordt door de browser niet vertrouwd, ook niet als je het meestuurt. Dus waarom zou je het meesturen?

Sterker nog; door het wel mee te sturen zorg je voor extra traffic bij het opzetten van de SSL verbinding. In een setup waar ik aan werkte was er sprake van een keten van in totaal 4 certificaten; het domein-certificaat, 2 intermediate certificaten en 1 rootcertificaat.

Met het volgende commando kon ik zien hoe veel bytes er werden verzonden bij het opzetten van de SSL handshake:

$ echo | openssl s_client -connect www.domein.nl:443 \
2>/dev/null | grep handshake
SSL handshake has read 6107 bytes and written 512 bytes

Volgens OpenSSL waren er dus 6107 bytes ontvangen en 512 bytes verstuurd. Dat lijkt niet veel, maar dat zijn dus al de nodige roundtrips alvorens er ook maar 1 HTTP request kan worden uitgevoerd.

Door het root-certificaat weg te laten uit de certificate chain werd dat al een stuk minder:

$ echo | openssl s_client -connect www.domein.nl:443 \
2>/dev/null | grep handshake
SSL handshake has read 5022 bytes and written 512 bytes

Dat scheelt dus dik 1000 bytes wellicht een aantal roundtrips.

Om te zien hoe veel TCP-pakketjes er worden verstuurd bij het opzetten van een SSL handshake kun je het volgende commando uitvoeren:

# tcpdump -ttttt -i any 'port 443 and host www.domein.nl'

Als je 2 terminal-vensters open hebt op een testclient kun je in de ene de tcpdump starten en in een ander venster kun je een SSL-verbinding opzetten:

openssl s_client -connect www.domein.nl:443

Dit commando blijft ‘hangen’ omdat het na het opzetten van de SSL-connectie wacht op input. Dat geeft je even de tijd om de output van tcpdump te bekijken.

In mijn situatie scheelde dit precies 1 roundtrip en een verwaarloosbare hoeveelheid tijd. Hoewel het in dit geval niet veel oplevert laat ik voortaan de root-certificaten achterwege. Hebben toch geen nut.

Geheugengebruik van integers in Java

De Java-engine van een van onze applicaties bevat een gefacetteerde zoekmachine die zijn data in-memory houdt om snel resultaten te kunnen leveren. De indexen van die zoekmachine bestaat uit een aantal Maps en Sets gevuld met Integers. Dat blijkt echter niet zo’n heel efficiënte manier te zijn als je kijkt naar het geheugengebruik.

Om te testen hoe veel geheugen een Set met Integers gebruikt heb ik deze testcode die een Set met 100.000 integers samenstelt. Door de hoeveelheid gebruikt geheugen daarvoor en daarna te vergelijken valt te meten hoe veel geheugen die Set kost:

package memtest;

import java.util.HashSet;
import java.util.Set;

public class MemTest {

    public static long getUsedMem() {

        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();

    }

    public static void main(String[] args) {

        long initialMem = getUsedMem();

        Set set = new HashSet();

        for (int i = 1; i <= 100000; i++) {
            set.add(i);
        }

        long finalMem = getUsedMem();

        System.out.println("Extra mem: " + (finalMem - initialMem));

    }

}

Dit leverde bij mij na een aantal keer draaien de volgende output:

Extra mem: 6655960
Extra mem: 6655960
Extra mem: 6655944
Extra mem: 6727368
Extra mem: 6742968
Extra mem: 6658232

Een Set met 100.000 Integers kost in dit geval dus ongeveer 6MB oftewel gemiddeld 66 bytes per stuk. Hoe komt dat? Een integer is toch maar 4 bytes?

Het korte antwoord is dat als je een Set vult met integers, deze automatisch worden ‘geboxed’ naar Integer objecten en dat een object meer geheugen kost dan een ‘primitive’ integer. Bij een geringe hoeveelheid items in zo’n Set zul je dit niet snel merken maar in ons geval hebben we tientallen sets met tienduizenden integers. Dan loopt het geheugengebruik al snel op.

Een oplossing hiervoor is geen gebruik te maken van een Set (die kan namelijk alleen met objecten overweg) maar bijvoorbeeld gebruik te maken van Trove. Die biedt een keur aan Collections klassen die onder water gebruik maken van primitieve variabelen. Dat is niet alleen sneller maar kost ook nog eens minder geheugen.

Om dit te testen passen we bovenstaande code aan naar het volgende:

package memtest;

import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;

public class MemTestTrove {

    public static long getUsedMem() {

        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();

    }

    public static void main(String[] args) {

        long initialMem = getUsedMem();

        TIntSet set = new TIntHashSet();

        for (int i = 1; i <= 100000; i++) {
            set.add(i);
        }

        long finalMem = getUsedMem();

        System.out.println("Extra mem: " + (finalMem - initialMem));

    }

}

Na deze code een aantal keer te draaien blijkt er nog minder dan een derde van de hoeveelheid geheugen nodig is en ook verrassend constant is:

Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208

Dat scheelt! Code die sneller draait en minder geheugen kost. Maar ja; dat levert wel een extra dependency op. En een ‘rare’ interface want een TSet is niet hetzelfde als een Set uit de standaard API. In ons geval konden we de publieke API ‘schoon’ houden door daar gebruik te blijven maken van de standaard Set interface maar onder water worden de Trove classes gebruikt.