HTTPS in Java with a self-signed certificate

par le
Lecture: 90 minutes

Your application should connect to an HTTPS server, usually that’s a no brainer, however as good software craft-man, you may think how do I test this code to make nothing has been forgotten.

The following code is using wiremock, the test assert that the client can connect to the HTTPS port of the wiremock server.

public class WireMockSSLTest {
    @Rule
    public WireMockRule wireMock = new WireMockRule(wireMockConfig().dynamicPort()
                                                                    .dynamicHttpsPort());

    @Test
    public void ssl_poke() throws IOException {
        new OkHttpClient.Builder().build()
                                  .newCall(new Request.Builder().get()
                                                                .url("https://localhost:" 
                                                                     + wireMock.httpsPort())
                                                                .build())
                                  .execute();
    }
}

When executed the test will raise an exception with the following stacktrace :

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at ...
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at ...
	... 10 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at ...
	... 16 more

Wow that might look overwhelming at the first sight, there’s scary acronyms (like PKIX), sun packages. So what does say this stack trace :

  • SSLHandshakeException : An error happened while establishing the SSL layer
  • ValidatorException: PKIX path building failed : the client can’t build the certificate chain by following the PKIX standard

In plain english, this server (wiremock) exposes a certificate, but the client can’t trust this certificate because it cannot rebuild a path to a known authority that signed the wiremock certificate. Why does this exception happen in this case, the very same code connecting to https://google.com will succeed ; the issue in this case is that wiremock is using a self-signed certificate, this certificate is not trusted because it hasn’t been signed by any of the CAs or public certificates available in the JVM standard installation.

In this article, we will look at various ways to generate a self-signed certificate and explore how to control the certificate used in test environment.

First a brief reminder on SSL, I mean TLS ?

Nowadays when people speak about SSL they conflate two protocols : TLS and his predecessor SSL.

  • SSL (Secure Sockets Layer) protocol was engineered and developed by Netscape (Thought for those who remember this pioneer internet browser then Internet Explorer competitor). The last version SSLv3 is considered as deprecated since 2015.
  • TLS (Transport Layer Security) protocol is the direct successor of the SSLv3 protocol, however they are not compatible.

[source]

The role of TLS is to ensure that communication between two peers is secured. In order to do so, TLS rely among other things on PKIX (Public Key Infrastructure X.509), equipped with these certificate mechanism a client can verify the server identity is actually the one it pretends to be with the help of a trusted third party.

For example :

Upon connecting to https://github.com, the client will first verify the GitHub certificate, then inspect who signed this certificate, then verify the signer’s certificate and so on, until a trusted third party certificate is reached. Usually this root certificate comes from a certificate authority.

The certificates are called X.509 they carry informations such as the server name, the signer name, the signature, etc. This certificate mechanism is one approach to implement a PKI (Public Key Infrastructure), hence the acronym PKIX.

A certificate can be stored in a file, either encoded in its binary form known as DER (Distinguished Encoding Rules), or in a US-ASCII text form which the Base 64 encoding of the binary form known as PEM (Privacy-enhanced Electronic Mail).

Note that a file with the CRT extension is a certificate but allow the content to be either binary or plain text (i.e. either DER or PEM). This filename extension is an alternative naming form used by Microsoft.

Fixing the test code

Trusting every peer

So to accept the self-signed certificate, it is possible to configure the SSL socket to accept all certificates.

@Rule
public WireMockRule wireMock = new WireMockRule(wireMockConfig().dynamicPort()
                                                                .dynamicHttpsPort());

@Test
public void ssl_poke() throws IOException {
    X509TrustManager trustManager = TrustAllX509TrustManager.INSTANCE;
    OkHttpClient client = new OkHttpClient.Builder()
            .sslSocketFactory(
                    sslContext(null,
                                new TrustManager[]{trustManager}).getSocketFactory(),
                    trustManager)
            .build();
    try (Response r = client.newCall(new Request.Builder().get()
                                                          .url("https://localhost:"
                                                               + wireMock.httpsPort())
                                                          .build())
                            .execute()) {
        // noop / success
    }
}

OkHttp client can be configured with a SSL socket factory that is itself configured with a custom trust manager. This is the job of the trust manager to validate or not a certificate. This one is coed to accept all certificate.

Since the security API is a bit tedious to read and use the code has been abstracted away in methods that are easier to read and comment.

public static SSLContext sslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) {
    try {
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagers,
                        trustManagers,
                        null);
        return sslContext;
    } catch (NoSuchAlgorithmException | KeyManagementException e) {
        throw new IllegalStateException("Couldn't init TLS context", e);
    }
}

The cryptography code architecture is very very generic and some design choices raise questions on its usage.

For example why the method SSLContext#init require arrays ? While the javadoc of this method SSLContext#init indicate that only the first element of the array will be used. This also explains why the code above initialize the context with a single trust manager.

Emphasizes in the javadoc are mine.

javax.net.ssl.SSLContext
public final void init(KeyManager[] km,
                      TrustManager[] tm,
                      SecureRandom random)
               throws KeyManagementException

Initializes this context. Either of the first two parameters may be null in which 
case the installed security providers will be searched for the highest priority 
implementation of the appropriate factory. Likewise, the secure random parameter 
may be null in which case the default implementation will be used.

**Only the first instance** of a particular key and/or trust manager implementation 
type in the array is used. (For example, only the first javax.net.ssl.X509KeyManager 
in the array will be used.)

Parameters:
    km - the sources of authentication keys or null
    tm - the sources of peer authentication trust decisions or null
    random - the source of randomness for this generator or null
Throws:
   KeyManagementException - if this operation fails

Another thing is that the TrustManager interface is empty, that’s right there is no methods, it’s a marker interface. Given that it is about TLS and the only active specification is based on PKIX (X.509), the code will have to implement X509TrustManager interface (itself a subtype of TrsutManager). This interface declares the required method to implement certificate validation.

In this section this trust manager will accept all certificates, in fact it will just do nothing.

public static class TrustAllX509TrustManager implements X509TrustManager {
    public static final X509TrustManager INSTANCE = new TrustAllX509TrustManager();

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) 
    throws CertificateException { }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) 
    throws CertificateException { }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}

So this code is supposedly working, however on first try there a new exception showing up upon connecting to wiremock.

javax.net.ssl.SSLPeerUnverifiedException: Hostname localhost not verified:
    certificate: sha256//W3v5TDAEE3dl4peiEwpwDKa1OZAna1ITgokQDz0rkQ=
    DN: CN=Tom Akehurst, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
    subjectAltNames: []

	at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:308)
	...

What is this SSLPeerUnverifiedException ? After inspection, the above code tackle the trust layer of the TLS, but according to the HTTPS RFC 2818, the client is required to check the identity of the hostname against the certificate, this help prevent man-in-the-middle attacks. That means that the TLS layer is established, but this failure happens at the upper level.

Indeed in the current setup, wiremock self-signed certificate doesn’t provide enough data to perform this verification. The subjectAltNames field should contain server alternative names like localhost or the (local) IP address(es).

So let’s tell OkHttp to accept all hostnames :

public static HostnameVerifier allowAllHostNames() {
    return (hostname, sslSession) -> true;
}
new OkHttpClient.Builder()
            .sslSocketFactory(
                    sslContext(null, 
                               new TrustManager[] {TrustAllX509TrustManager.INSTANCE}).getSocketFactory(),
                    TrustAllX509TrustManager.INSTANCE)
            .hostnameVerifier(allowAllHostNames())
            .build()
            .newCall(new Request.Builder().get()
                                          .url("https://localhost:" + wireMock.httpsPort())
                                          .build())
            .execute();

Finally this code works, we have an HTTPS connection to a wiremock server.

However this way of setting up the code is not right and especially for production code, because this code just deactivated security for every HTTPS connection passing by this http client.

With this approach one should think of a configuration mechanism that will enforce sane settings in production code and relax settings for test code.

On a side note this example has been written with backend application in mind, however this topic is concerning mobile application or IoT as well. The Android documentation explains very well why hostname verification is important to consider.

Vanilla Java

For those interested the same issues happen with the JDK way to connect to HTTPS via URL, this can be configured via the HttpsUrlConnection class, here’s how to configure it, before establishing the connection :

// Allow all certificates
HttpsURLConnection.setDefaultSSLSocketFactory(trustAllSslContext().getSocketFactory());
new URL("https://localhost:" + wireMock.httpsPort()).openConnection().connect();

And just set a HostnameVerifier that does nothing.

// Allow all hostnames
HostnameVerifier allHostsValid = (hostname, session) -> true;
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
HttpsURLConnection.setDefaultSSLSocketFactory(trustAllSslContext().getSocketFactory());
new URL("https://localhost:" + wireMock.httpsPort()).openConnection().connect();

Notice that setDefaultHostnameVerifier and setDefaultSSLSocketFactory are static methods, that means these changes affect all connections using the JDK.

Extending trust to self-signed certificate

So rather than turning off the whole security of HTTPS connections, a big improvement would be to ignore verification of self-signed certificate only. The first question is how to identify a self-signed certificate ?

To answer that question, let’s inspect certificates with a reference tool. OpenSSL has an SSL/TLS client available with the s_client sub-command, we can then connect to the wiremock HTTPS server.

echo -n | openssl s_client -connect localhost:8443 2>&1

Upon connection, the terminal shows a bunch of interesting data…

CONNECTED(00000003)
depth=0 C = Unknown, ST = Unknown, L = Unknown, O = Unknown, OU = Unknown, CN = Tom Akehurst
verify error:num=18:self signed certificate
verify return:1
depth=0 C = Unknown, ST = Unknown, L = Unknown, O = Unknown, OU = Unknown, CN = Tom Akehurst
verify return:1
---
Certificate chain
 0 s:/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Tom Akehurst
   i:/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Tom Akehurst
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDgzCCAmugAwIBAgIEHYkuTzANBgkqhkiG9w0BAQsFADBxMRAwDgYDVQQGEwdV
bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRUwEwYDVQQDEwxUb20gQWtl
aHVyc3QwIBcNMTUwMjI0MTM1ODUwWhgPMjExNTAxMzExMzU4NTBaMHExEDAOBgNV
BAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vua25vd24x
EDAOBgNVBAoTB1Vua25vd24xEDAOBgNVBAsTB1Vua25vd24xFTATBgNVBAMTDFRv
bSBBa2VodXJzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIAIvMUo
vy4ufnWKxMU0tBdXqtX6RzKYgQvj/82qPAmRiNki8PpPGrF70Lb3WzsUDYB9CsXw
m5VWc9l1XBdGh6zZVFkkSzBtRjyHy8Z8azsIv/YzQF5bRxE2Cvruh7o01Sq1qz5B
kxt0u/NbUUErxKZeA0li1W/op7RC94h0dzob7auruHUvb56NXAJZcu8r2G9jxh9w
WBPC6lSozuCzwfdS4v2ZOQBYpmMz9oJm3ElQUbOrhnVQtgxQicU2oDETwz37IIEw
FV12la+qNIMSOTe6uJj1jEZP22NL2IYq06BT/ZnK6HYIOXAtwURSsf0MN0b8NKBB
NOLQN2juRj+vn6UCAwEAAaMhMB8wHQYDVR0OBBYEFDZ6soXRxD/N2n5b++CVrWbr
XLKWMA0GCSqGSIb3DQEBCwUAA4IBAQBiPfCUg7EHz8poRgZL60PzMdyaKLwafGtF
dshmY1y9vzpPJIoFcIH7crSsmUcRk+XSj5WhSr4RT3y15JsfZy935057f0knEXEf
or+Gi8BlDaC33qX+6twiAaub1inEDc028ZFtEwbzJQYgJo1GvLG2o2BMZB1C5F+k
Nm9jawu4rTNtXktXloNhoxrSWtyEUoDAvGgBVnAJwQXcfayWq3AsCr9kpHI3bBwL
J9NAGC4M8j7z9Aw71JGmwBDk1ooAO6L82W7DWBYPzpLXXeXmHRCxpujKWaveAV2T
cgsQaCmzy29i+F03pLl7Vio4Ei+z9XQgZiN4Awiwz9D+lshnKuII
-----END CERTIFICATE-----
subject=/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Tom Akehurst
issuer=/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Tom Akehurst
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 1387 bytes and written 434 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 59D642ED8CCB7F4219617B3739CB93E1C294F873854C2A284D28A77D674AC050
    Session-ID-ctx: 
    Master-Key: A43DF026E3FA620BC7CC5207D5BCB87828B7E3D673CFEEF12CAB425619B63610F443FB96FEC33CC50FBBAC73C152572B
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1507214061
    Timeout   : 300 (sec)
    Verify return code: 18 (self signed certificate)
---
DONE

…typically the first lines :

depth=0 C = Unknown, ST = Unknown, L = Unknown, O = Unknown, OU = Unknown, CN = Tom Akehurst
verify error:num=18:self signed certificate

The client reports a verification error number 18, and display the associated reason that it is a self-signed certificate. More specifically the openssl wiki defines the error 18 as :

18 X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: self signed certificate

So openssl understand that a certificate chain with a depth of 0 is in fact a self-signed-certificate.

Continuing on the output, there’s the certificate chain section itself, it declares each certificate that are presented by the server.

For the certificate with a 0 depth, there’s two lines :

  • the first prefixed by s that print the subject of the certificate
  • the second prefixed by i that print the issuer of the certificate
Certificate chain
 0 s:/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Tom Akehurst
   i:/C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Tom Akehurst

There is additional information, but the above information leads to think it is possible to write a trust manager that will skip verification for self-signed certificates only. The code below checks the number of certificate, if there’s only one element in the array, it means the depth is zero.

public static class TrustSelfSignedX509TrustManager implements X509TrustManager {
    private X509TrustManager delegate;

    private TrustSelfSignedX509TrustManager(X509TrustManager delegate) {
        this.delegate = delegate;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        delegate.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        if (isSelfSigned(chain)) {
            return;
        }
        delegate.checkServerTrusted(chain, authType);
    }

    private boolean isSelfSigned(X509Certificate[] chain) {
        return chain.length == 1;
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return delegate.getAcceptedIssuers();
    }

    public static X509TrustManager[] wrap(X509TrustManager delegate) {
        return new X509TrustManager[]{new TrustSelfSignedX509TrustManager(delegate)};
    }
}

Notice how this code don’t actually perform certificate verification but delegates this task to an existing TrustManager.

This decorator can wrap the JVM trust manager, which happens to be able to verify normal certificates.

X509TrustManager trustManager = TrustSelfSignedX509TrustManager.wrap(systemTrustManager());
new OkHttpClient.Builder()
            .sslSocketFactory(
                    sslContext(null, 
                               new TrustManager[] { trustManager }).getSocketFactory(),
                    trustManager)
            .hostnameVerifier(allowAllHostname())
            .build()
            .newCall(new Request.Builder().get()
                                          .url("https://localhost:" + wireMock.httpsPort())
                                          .build())
            .execute();

Here’s how to gain access to the default manager :

public static X509TrustManager systemTrustManager() {
    TrustManager[] trustManagers = systemTrustManagerFactory().getTrustManagers();
    if (trustManagers.length != 1) {
        throw new IllegalStateException("Unexpected default trust managers:"
                                        + Arrays.toString(trustManagers));
    }
    TrustManager trustManager = trustManagers[0];
    if (trustManager instanceof X509TrustManager) {
        return (X509TrustManager) trustManager;
    }
    throw new IllegalStateException("'" + trustManager + "' is not a X509TrustManager");
}


private static TrustManagerFactory systemTrustManagerFactory() {
    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore) null);
        return tmf;
    } catch (NoSuchAlgorithmException | KeyStoreException e) {
        throw new IllegalStateException("Can't load default trust manager", e);
    }
}

TrustManagerFactory.getInstance(String) is asking for the name of trust implementation algorithm. And at this time TrustManagerFactory.getDefaultAlgorithm() should only return PKIX because that’s the only trust implementation of TLS at this time.

However one should note that JVM implementers load their own algorithm implementation of PKIX, this implementation is available under the implementation name like Sun did with SunX509, of course this implementation is not available when using IBM J9 or when using Android, they have their own. All should implement X509TrustManager. Avoid using those names those implementation may change for various reason, could renamed to OracleX509 one day, or be replace by some other thing. So when looking at code on the internet that rely on implementation specific one should take this opportunity to use the most generic name unless something specific is needed, in this case we want the default PKIX algorithm.

Notice again how this JDK security API is very generic, the trust interface just does not make any assumption about the trust implementation. It could be public key infrastructure based (i.e X.509 or something else) or something else entirely.

Also TrustManagerFactory.getTrustManagers() returns an array for supposedly for each type of trust management. Here’s the javadoc:

public final TrustManager[] getTrustManagers()

Returns one trust manager for each type of trust material.

Returns:
    the trust managers
Throws:
    IllegalStateException - if the factory is not initialized.

Give the javadoc of SSLContext#init and that in practice today the only available kind of way to connect over TLS is PKIX / X.509. So this factory only creates a single trust manager instance, of type X509Trustmanager.

So the above described approach at least doesn’t degrade the security for the HTTPS calls to website properly relying on root certificate authorities. Yet the security for self-signed signed certificates is still not handled, just dismissed. In my opinion this is acceptable for test code, but barely, security is a major concern, if we cannot get it right in tests what about production code that must speak to self signed certificate.

Just because it is self-signed, doesn’t mean there’s no way to verify this certificate.

How to properly validate a self-signed certificate ?

What to do if the production code has to establish to a server whose certificate is self-signed ?

We saw that the JVM doesn’t grant trust to certificates whose no known certification authority (CA) signed the peer certificate. A simple workaround would be install the unknown certificate in question.

But before installing anything, let’s get a hold of it.

Get the self-signed certificate

Upon the TLS connection handshake, the server sends his certificate chain, as we saw earlier when using the s_client of openssl.

It his possible to get the certificates in a PEM format using another command of openssl.

echo -n | \
  openssl s_client -prexit -connect host:port 2>&1 | \
  openssl x509 -outform pem \
  > certificate-host.pem

The above command indicate to openssl to establish a connection to host:port then to pass the results to the x509 command in order to decode the certificate and to encode it in a PEM format.

It is also possible to print on the console a human readable representation (not PEM):

echo -n | \
  openssl s_client -connect host:port 2>&1 | \
  openssl x509 -text

To read the certificate file in the PEM format there either the standard tool openssl or with keytool coming with the base install of the JDK :

keytool -printcert -file certificate-host.pem
openssl x509 -inform pem -in certificate-host.pem -text

Using the self-signed certificate on the JVM

Importing the certificate in the JVM’s own cacerts

The default JVM install comes with cacerts file that stores the certificates of certificate authorities. A self-signed certificate wasn’t signed (issued) by any of these authority, but since this certificate is signing itself, it is possible to inject it in the cacerts key store. It is possible to achieve this with keytool ; prepended with sudo if the actual file system permission requires it. At JVM installation time this key store is configured with password changeit, but a good practice in production is to change this password to prevent any tampering with the cacerts file.

keytool -import \
        -alias wiremock \
        -file wiremock.pem \
        -keystore $JAVA_HOME/jre/lib/security/cacerts

The wiremock certificate has been extracted to file wiremock.pem in the PEM format. The following command will import this certificate with the alias wiremock in the cacerts key store.

Eventually check the import went well by querying the content of the cacerts key store :

keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts

After the injection of the certificate, a wiremock entry should appear next to Verisign, Digital Certs, Geo Trust, etc.

Once done, when trying a simpler version of the HTTPS calling code without the whole SSL setup, there’s no error, the standard security configuration can match the wiremock certificate because it now knows about it.

new OkHttpClient.Builder()
            .hostnameVerifier(allowAllHostNames())
            .build()
            .newCall(new Request.Builder().get()
                                          .url("https://localhost:8443")
                                          .build())
            .execute();

Note however there’s still a need of a dedicated hostname verifier that allows every host, because the TLS protocol handshake is different than host or IP verification that is part of HTTPS (RFC 2818).

Regardless injecting certificates in the JDK installation requires a deployment, that is a pain for developers, that is a pain for CI, and that is a pain for OPS. We can do better.

Using an alternate key store

Sometime this specific key store is referred to as a trust store.

In this part let’s craft a key store from the ground using the same tool as above keytool.

THe first contact with the key store was when we were meddling with the cacerts file. To create our own the usual extension is jks, that acronym stands for Java key - store is a file, that’s where the PEM certificate will be imported.

keytool -import \
        -alias wiremock \
        -file wiremock.pem \
        -keystore ./wiremock-truststore.jks

This commands takes a certificate file stored in the PEM format and stores it in a newly created Java Key Store file named wiremock-truststore.jks.

The above commands requires a user interaction, this can be avoided by passing a few options, especially -noprompt and -storepass <password>.

keytool -import \
        -noprompt \
        -storepass changeit \
        -alias wiremock \
        -file wiremock.pem \
        -keystore ./wiremock-truststore.jks

Then the java commands allow to change the runtime trust store, which is the by default the cacerts file, with the following options :

  • -Djavax.net.ssl.trustStore=./wiremock-truststore.jks
  • -Djavax.net.ssl.trustStorePassword=changeit

The full command :

java -Djavax.net.ssl.trustStore=./wiremock-truststore.jks \
     -Djavax.net.ssl.trustStorePassword=changeit \
     OkSSLConnect localhost 8443

In the above command OkSSLConnect is the program that will establish the TLS connection to the wiremock HTTPS server with the given host and port. The main function consists only of the simplest code above (no special TLS configuration, just the hostname verifier). If the program succeeds then the alternate trust store has been used.

However using this alternate trust store won’t work if the same program must connect to some well known website (i.e. like github.com) whose certificates are issued by well known certificate authorities. In this case the same SSLHandshakeException will be raised.

java -Djavax.net.ssl.trustStore=./wiremock-truststore.jks \
     -Djavax.net.ssl.trustStorePassword=changeit \
     OkSSLConnect google.com 443

Without setting the alternate trust store, this command java OkSSLConnect google.com 443 succeeds.

So this approach is also lacking in some areas, the simple fact these options change the JVM’s trust store is wrong, unless there’s specific use cases. Also this can become a deployment issue in local development environment or on servers. Moreover it restrict the servers the JVM can connect to servers that have the very specific certificates. Not an acceptable solution in my opinion.

Importing programmatically the self-signed certificate

From JKS key store

Lets begin with the JKS file created in the section above with keytool. The code below is split in three different method with each their own concerns :

  • one to load TrustManager factory, using the default algorithm PKIX
  • one to load the trust store itself, using the default type KeyStore.getDefauktType() returns jks [1] [2] .
  • one to build the trust manager instance from the trust store
public static X509TrustManager trustManagerFor(KeyStore keyStore) {
    TrustManagerFactory tmf = trustManagerFactoryFor(keyStore);

    TrustManager[] trustManagers = tmf.getTrustManagers();
    if (trustManagers.length != 1) {
        throw new IllegalStateException("Unexpected number of trust managers:"
                                                + Arrays.toString(trustManagers));
    }
    TrustManager trustManager = trustManagers[0];
    if (trustManager instanceof X509TrustManager) {
        return (X509TrustManager) trustManager;
    }
    throw new IllegalStateException("'" + trustManager + "' is not a X509TrustManager");
}


public static TrustManagerFactory trustManagerFactoryFor(KeyStore keyStore) {
    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);
        return tmf;
    } catch (KeyStoreException | NoSuchAlgorithmException e) {
        throw new IllegalStateException("Can't load trust manager for keystore : " + keyStore, e);
    }
}


public static KeyStore readJavaKeyStore(Path javaKeyStorePath, String password) {
    try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(javaKeyStorePath))) {
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(inputStream, password.toCharArray());
        return ks;
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    } catch (CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
        throw new IllegalStateException(e);
    }
}

Note in the code above the password parameter given to read the trust store. Usually it is advised to array for password because a program have a better control on their lifecycle (String are immutable and on the heap, while array are mutable thus allowing to erase the password, if not the array object).

Then the OkHttp client builder can be configured with the custom SSL socket factory :

X509TrustManager trustManager = trustManagerFor(readJavaKeyStore(Paths.get("./wiremock-truststore.jks"), "changeit"));
new OkHttpClient.Builder()
        .sslSocketFactory(sslContext(null, new TrustManager[]{trustManager}).getSocketFactory(),
                          trustManager)
        .hostnameVerifier(allowAllHostNames())
        .build()
        .newCall(new Request.Builder().get()
                                        .url("https://localhost:8443")
                                        .build())
        .execute();

This code is quite nice because it can server for the majority of HTTP usage in the application. The code allows a approach that is a bit more configurable and under control.

From a PEM certificate

Since the trust manager factory can only be built with a key store, this approach will build a key store in memory. This key store will be injected with the X.509 certificate that was extracted previously with the command openssl x509 -outform pem.

Since the only type of key store available in the JVM is jks, and the scope of this article is limited to SSL/TLS mechanism of the JVM only, the following code will then build a JKS in memory. This key store will inject the X.509 certificate that was extracted previously with the command openssl x509 -outform pem.

public static KeyStore makeJavaKeyStore(Path certificatePath) {
    try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(certificatePath))) {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(null, null);
        int certificate_counter = 0;
        for (X509Certificate certificate : (Collection<X509Certificate>) cf.generateCertificates(bis)) {
            ks.setCertificateEntry("cert_" + certificate_counter++, certificate);
        }

        return ks;
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    } catch (CertificateException e) {
        throw new IllegalStateException("Can't load certificate : " + certificatePath, e);
    } catch (KeyStoreException | NoSuchAlgorithmException e) {
        throw new IllegalStateException("Can't create the internal keystore for certificate : " + certificatePath, e);
    }
}

As a reminder notice this code use the key store default type, i.e. jks, yet the JVM also supports other types like PKCS12 (see more)

Also notice how the call (Collection<X509Certificate>) cf.generateCertificates(bis) is generic and that depending on the instance type the generated certificate object could have different type, here the type is "X.509" so according to the javadoc objects can be cast to X509Certificate type.

One other thing to note is that (Collection<X509Certificate>) cf.generateCertificates(bis) returns actually a collection of certificates. That means the file represented by this input stream may contain multiple certificates. Indeed the CertificateFactory.generateCertificate(InputStream) javadoc specifies that for X.509 type the input stream content must be

  • PEM formatted, which is means a payload encoded to ASCII base64 within the following boundaries or markers -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----

  • DER formatted which is the binary representation of the certificate (It is possible to get one using this command openssl x509 -in wiremock.pem -outform der > wiremock.der).

In the case of a certificate factory for X.509 certificates, the certificate provided in inStream must be DER-encoded and may be supplied in binary or printable (Base64) encoding. If the certificate is provided in Base64 encoding, it must be bounded at the beginning by -----BEGIN CERTIFICATE-----, and must be bounded at the end by -----END CERTIFICATE-----.

The certificate entry name is not important in the context of this blog.

Finally this code can be integrated by instructing the code to create a trust manager using this key store :

X509TrustManager trustManager = trustManagerFor(makeJavaKeyStore(Paths.get("./wiremock.pem")));
new OkHttpClient.Builder()
        .sslSocketFactory(sslContext(null, new TrustManager[]{trustManager}).getSocketFactory(),
                          trustManager)
        .hostnameVerifier(allowAllHostNames())
        .build()
        .newCall(new Request.Builder().get()
                                        .url("https://localhost:8443")
                                        .build())
        .execute();

That’s even nicer because there’s once less step, no keytool action required.

Yet there again once this client is configured it still cannot connect to HTTPS servers using other certificates, like github.com.

Programmatically use both the system trust chain, and the self signed certificate

If the HTTPS client must connect to various third parties having either a certificate signed from a known certificate authority or a self signed certificate, then it is needed to tweak the custom trust manager.

This approach simply craft a composite trust manager delegating certificate verification to the configured delegate trust managers :

public class CompositeX509TrustManager implements X509TrustManager {
    private final List<X509TrustManager> trustManagers;

    public CompositeX509TrustManager(X509TrustManager... trustManagers) {
        this.trustManagers = Arrays.asList(trustManagers);
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        new MultiException<>(new CertificateException("This certification chain couldn't be trusted"))
                .collectFrom(trustManagers.stream(),
                             trustManager -> trustManager.checkClientTrusted(chain, authType))
                .scream(UNLESS_ANY_SUCCESS);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        new MultiException<>(new CertificateException("This certification chain couldn't be trusted"))
                .collectFrom(trustManagers.stream(),
                             trustManager -> trustManager.checkServerTrusted(chain, authType))
                .scream(UNLESS_ANY_SUCCESS);
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return trustManagers.stream()
                            .map(X509TrustManager::getAcceptedIssuers)
                            .flatMap(Arrays::stream)
                            .toArray(X509Certificate[]::new);
    }
}

MultiException is small helper that allows to collect several exceptions that are raised by a collection of calls, while still raising a single parent exception, it relies on Throwable.addSuppressed that was added in Java 1.7 for try-with-resources statements.

public class MultiException<E extends Exception> {
    private final E parent;
    private boolean successMarker = false;

    public MultiException(E parent, Exception... exceptions) {
        this.parent = parent;
        Arrays.stream(exceptions).forEach(parent::addSuppressed);
    }

    public <T> MultiException<E> collectFrom(Stream<T> stream, ThrowingConsumer<T> invocation) {
        stream.forEach(t -> collect(t, invocation).ifPresent(parent::addSuppressed));
        return this;
    }

    private <T> Optional<Exception> collect(T type, ThrowingConsumer<T> throwing) {
        try {
            throwing.accept(type);

            successMarker = true;
            return Optional.empty();
        } catch (Exception e) {
            return Optional.of(e);
        }
    }

    public void scream(Mode mode) throws E {
        if (Mode.UNLESS_ANY_SUCCESS == mode && successMarker) {
            return;
        }
        if (parent.getSuppressed().length > 0) {
            throw parent;
        }
    }

    @FunctionalInterface
    public interface ThrowingConsumer<T> {
        void accept(T type) throws Exception;
    }

    public enum Mode {
        UNLESS_ANY_SUCCESS,
        ANY_FAILURE
    }
}

The composite manager can be used this way :

X509TrustManager compositeTrustManager = new CompositeX509TrustManager(
        trustManagerFor(makeJavaKeyStore(Paths.get("./wiremock.pem"))),
        systemTrustManager());
OkHttpClient okHttpClient = httpClient(sslContext(null,
                                                  new TrustManager[]{compositeTrustManager}),
                                        compositeTrustManager)
        .newBuilder()
        .hostnameVerifier(allowAllHostname())
        .build();

This client can now access both kind of HTTPS servers.

Server side

This blog post explored a bit the client side, but form the beginning the code dealt with wiremock’s certificate. There’s something to do on this side to allow a complete control in the tests. The first thing would be to generate one.

Generate a self-signed certificate with keytool

The JDK’s keytool offers various commands and one, especially useful in the current case, that allows to generate a certificate.

keytool -genkey \
        -keyalg RSA \
        -alias bric3 \
        -keystore bric3.jks \
        -storepass the_password \
        -validity 360 \
        -keysize 2048

With the above command keytool will generate a Java keystore file with the brand new certificate. It will interact with the user, asking for the following certificate’s attributes :

  • CN (Common Name)
  • OU (Organizational Unit)
  • O (Organization)
  • L (Locality)
  • ST (STate)
  • C (Country)

then it will ask for the certificate password, not to be mixed with the keystore password (which is given in the above command with -storepass the_password). This can work in batch mode (non-interactive) if the command has the following options :

  • -keypass password
  • -dname 'CN=Brice Duhteil, OU=Arkey, O=Arkey, L=Paris, ST=France, C=FR'

dname means Distinguished Names

However this new generated certificate and the one of wiremock have the same problems leading to the same obstacle that had to be dealt with in the first half of this blog :

  • it is self-signed
  • it will require a custom host name verifier (RFC 2818)

Let’s first fix this last point first, what this certificate need is a SAN (Subject Alternative Names) section that can contain DNS names or IP addresses. keytool can be tell to add this information with the -ext option along with the SAN extension that needs to be formatted with almost any number of dns or ip elements.

-ext SAN=dns:domain.com,dns:localhost,ip:127.0.0.1

For example if the certificate needs to be valid for the following hostnames

  • blog.arkey.fr
  • blog
  • 127.0.0.1
  • ::1

keytool needs to be told to generate the certificate with the following options :

keytool -genkey \
        -keyalg RSA \
        -alias bric3 \
        -keystore bric3.jks \
        -storepass the_password \
        -validity 360 \
        -keysize 2048 \
        -keypass the_password \
        -dname 'CN=Brice Duhteil, OU=Arkey, O=Arkey, L=Paris, ST=France, C=FR' \
        -ext 'SAN=dns:blog.arkey.fr,dns:blog,dns:localhost,ip:127.0.0.1,ip:::1'

Note #1:

  1. keytool generate a certificate and stores it immediately in the Java Key Store
  2. wiremock in this version only allow to configure a single password that will be used for both the certificate and the Java Key Store ; that means for our tests the command should specify the same password for both JKS and the certificate e.g. : -storepass the_password and -keypass the_password.

Note #2:

keytool do validate the DNS entry, but it doesn’t understand all possible allowed characters of a domain name for this reason it would be preferable to use openssl or similar tools to generate the certificates. An interesting part of the PKIX / X509 specification in version 3 is the Subject Alt Names ; this extension allows to list the names of the servers for for which this certificate has been issued, this completes the limited Common Name. One of the advantages of this extension is the ability to enter wildcard names, although the RFC does cover how a client should interpret and act on those wildcards :

Finally, the semantics of subject alternative names that include wildcard characters (e.g., as a placeholder for a set of names) are not addressed by this specification. Applications with specific requirements MAY use such names, but they must define the semantics.

E.g. google.com certificate is configured with domain names with wildcard:

echo -n | openssl s_client -showcerts -connect google.com:443 2>&1 | openssl x509 -text
X509v3 Subject Alternative Name: 
    DNS:*.google.com, DNS:*.android.com, DNS:*.appengine.google.com, DNS:*.cloud.google.com, DNS:*.db833953.google.cn, DNS:*.g.co, DNS:*.gcp.gvt2.com, DNS:*.google-analytics.com, DNS:*.google.ca, DNS:*.google.cl, DNS:*.google.co.in, DNS:*.google.co.jp, DNS:*.google.co.uk, DNS:*.google.com.ar, DNS:*.google.com.au, DNS:*.google.com.br, DNS:*.google.com.co, DNS:*.google.com.mx, DNS:*.google.com.tr, DNS:*.google.com.vn, DNS:*.google.de, DNS:*.google.es, DNS:*.google.fr, DNS:*.google.hu, DNS:*.google.it, DNS:*.google.nl, DNS:*.google.pl, DNS:*.google.pt, DNS:*.googleadapis.com, DNS:*.googleapis.cn, DNS:*.googlecommerce.com, DNS:*.googlevideo.com, DNS:*.gstatic.cn, DNS:*.gstatic.com, DNS:*.gvt1.com, DNS:*.gvt2.com, DNS:*.metric.gstatic.com, DNS:*.urchin.com, DNS:*.url.google.com, DNS:*.youtube-nocookie.com, DNS:*.youtube.com, DNS:*.youtubeeducation.com, DNS:*.yt.be, DNS:*.ytimg.com, DNS:android.clients.google.com, DNS:android.com, DNS:developer.android.google.cn, DNS:developers.android.google.cn, DNS:g.co, DNS:goo.gl, DNS:google-analytics.com, DNS:google.com, DNS:googlecommerce.com, DNS:source.android.google.cn, DNS:urchin.com, DNS:www.goo.gl, DNS:youtu.be, DNS:youtube.com, DNS:youtubeeducation.com, DNS:yt.be

Generate a self-signed certificate with openssl

keytool is nice, but it requires the JVM, and let’s use the standard tool, the Swiss army knife openssl (or the new forks BoringSSL or LibreSSL).

The keytool command that was used was generating a self-signed certificate in one shot, it is also possible with openssl but let’s understand the different steps that are needed under the hood to generate a self-signed certificate.

  1. Generate a private key

     openssl genrsa \
         -out bric3-private.key \
         2048
    

    The above command creates a private key of 2048 bits using the RSA algorithm. This key is not protected by a password, so keep it safe (to do that use the -des3 option).

  2. Request a new certificate for the domain(s).

     openssl req \
         -new \
         -outform pem \
         -out bric3-self.csr \
         -keyform pem \
         -key bric3-private.key \
         -sha256 \
         -config <(cat <<-EOF
    
     [req]
     prompt = no
     req_extensions = bric3_req_ext
     distinguished_name = dn
        
     [dn]
     CN=Brice Dutheil
     O=Arkey
     OU=Arkey
     L=Paris
     ST=France
     C=FR
    
     [bric3_req_ext]
     subjectAltName = @alt_names
    
     [alt_names]
     DNS.1 = localhost
     DNS.2 = arkey.fr
     DNS.3 = *.arkey.fr
     DNS.4 = arkey.pro
     DNS.5 = *.arkey.pro
     DNS.6 = blog
     IP.1 = 127.0.0.1
     IP.2 = ::1
    
     EOF
     )
    

    The above req command works in batch mode (i.e. non-interractive). It will generate a CSR file (Certificate Sign Request) stored in the PEM format. This command needs the private key of the server’s owner. In order to pass additional parameters to generate the CSR are passed via a shell stream and here document <(cat <<-MARKER ... MARKER).

    In the document there’s a [req] section with

    • an option that instruct the program to run in non-interactive mode : prompt = no,
    • a link to the Distinguished Name section named dn : distinguished_name = dn,
    • a link to the Subject Alt Name extension section : req_extensions = bric3_req_ext, without this field it would be necessary to pass the the following option to the command -reqexts bric3_req_ext

    The [dn] section defines fields that compose what is a DN, e.g. the Common Name, the Organization, etc.

    The extension section (bric3_req_ext) defines where is located the alternative name section : subjectAltName = @alt_names, in this case only the @ prefix is necessary.

    The alternative name section (alt_names) will define the wanted entries of DNS type, IP type, etc.

    For more on this req command and how it can be configured, just deep dive in the man (master branch) page.

  3. Sign the certificate sign request to generate the actual certificate

     openssl x509 \
         -req \
         -days 3650 \
         -inform pem \
         -in bric3-self.csr \
         -signkey bric3-private.key \
         -outform pem \
         -out bric3-self.pem \
         -extensions bric3_ext \
         -extfile <(cat <<-EOF
     [bric3_ext]
     subjectAltName = @alt_names
    
     [alt_names]
     DNS.1 = localhost
     DNS.2 = arkey.fr
     DNS.3 = *.arkey.fr
     DNS.4 = arkey.pro
     DNS.5 = *.arkey.pro
     DNS.6 = blog
     IP.1 = 127.0.0.1
     IP.2 = ::1
     EOF
     )
    

    The command x509 gnerate a new X.509 certificate from a CSR (-req). As an intput it obvisouly need the request file -in bric3-self.csr, and since it is a self-signed certificate the command uses the same private key that was used to generate the request -signkey bric3-private.key, otherwise it would be the private key of the certificate authority. The new certificate will be valid for ten years (-days 3650).

    Unfortunately this openssl command don’t use the extensions of the request, consequently it his needed to provide them as well via configuration file or via a stream (and here docs) -extfile <(cat <<-EOF ... EOF), however in this case the extension section name has to be passed via an option -extensions bric3_ext.

    If everything is correct a new bric3-self.pem certificate will be issued.

    For more on this x509 command and how it can be configured, just deep dive in the man (master branch) page.

The configuration shown above is quite simple for this blog, in practice it will be more complicated with a certificate authority and a more elaborate certificate chain.

As said earlier the 3 above steps that can be reduced to the the following command to generate a self-signed certificate.

openssl req \
    -new \
    -nodes \
    -sha256 \
    -newkey rsa:2048 \
    -keyform pem \
    -keyout bric3-openssl.key \
    -x509 \
    -days 3650 \
    -outform pem \
    -out bric3-openssl.crt \
    -config <(cat <<-EOF
[req]
prompt = no
distinguished_name = dn
x509_extensions = bric3_ext

[dn]
CN=Brice Dutheil
O=Arkey
OU=Arkey
L=Paris
ST=France
C=FR

[bric3_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = arkey.fr
DNS.3 = *.arkey.fr
DNS.4 = arkey.pro
DNS.5 = *.arkey.pro
DNS.6 = blog
IP.1 = 127.0.0.1
IP.2 = ::1
EOF
)

The command is req but tweaked with these options :

  • -newkey rsa:2048 option that will generate the RSA 2048 private key. The key will be stored with the PEM format in the bric3-openssl.key file.
  • -x509 that tell the command the output won’t be a certificate sign request but a X.509 signed certificate. The certificate will be valid for ten years -days 3650 and will be stored with the PEM format in the bric3-openssl.crt file. Since -x509 option is used the extension section location field changes from req_extensions to x509_extensions = bric3_ext.

Note the subjectAltName = @alt_names line again, the @ allows to reference a vertical list declared within the alt_names section. For example

[bric3_ext]
subjectAltName = DNS.1 : localhost, DNS.3 : arkey.fr, DNS.3 : *.arkey.fr, DNS.4 : arkey.pro, DNS.5 : *.arkey.pro, DNS.6 : blog, IP.1 : 127.0.0.1, IP.2 : ::1

is equivalent to

[bric3_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = arkey.fr
DNS.3 = *.arkey.fr
DNS.4 = arkey.pro
DNS.5 = *.arkey.pro
DNS.6 = blog
IP.1 = 127.0.0.1
IP.2 = ::1

There’s however a few syntax changes

  • the use of a colon : next to the the alternative name (IP, DNS, etc.) for the horizontal list, an equal sign = for the vertical one.

  • the post-fix index notation is optional for the horizontal list, but mandatory in the vertical list.


Finally they need to be packaged in order to use the certificate and the key with the JVM. Standard libraries of the JDK can load a Java Key Store or P12 (PKCS12) file.

openssl pkcs12 \
    -export \
    -in bric3-openssl.crt \
    -inkey bric3-openssl.key \
    -passout pass:cadeau \
    -out bric3.p12

As a side note here’s how to create a Java Keystore from a PKCS12 store :

keytool -importkeystore \
        -srckeystore bric3.p12 \
        -srcstoretype PKCS12 \
        -srcstorepass cadeau \
        -deststorepass the_password \
        -destkeypass the_password \
        -destkeystore bric3-openssl.jks

Loading the generated certificate

In order to use the certificate wiremock need to be configured with the location of the key store (and of course in the client code).

@Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()
                                                                    .keystorePath("./bric3.jks")
                                                                    .keystorePassword("the_password")
                                                                    .dynamicHttpsPort());

@Test
public void my_precious_self_signed_certificate() throws IOException {
    X509TrustManager compositeTrustManager = new CompositeX509TrustManager(
            trustManagerFor(readJavaKeyStore(Paths.get("./bric3.jks"), "the_password")),
            systemTrustManager());
    OkHttpClient okHttpClient = httpClient(sslContext(null,
                                                      new TrustManager[]{compositeTrustManager}),
                                           compositeTrustManager)
            .newBuilder()
            .build();
    try (Response response = okHttpClient.newCall(new Request.Builder().get()
                                                                       .url(format("https://%s:%d",
                                                                                   "localhost",
                                                                                   wireMockRule.httpsPort()))
                                                                       .build())
                                         .execute()) {
        // successfully established connection
    }
}

Notice the use of the JKS key store instead of the P12 file, while I just wrote that all JVM implementations are supposed to support PKCS12 type key store, what happened ? Unfortunately wiremock uses Jetty under the hood and Jetty doesn’t allow to be configured with a PKCS12 file. Meaning that our P12 file have to be converted in a Java Key Store.

Wrap up

This was the last piece of code of this blog entry. In this article I hope you discovered how to manage simple self-signed certificates with Java, and how to generate self-signed certificates with Java tools and a almost standard tool openssl. I also you better understand how Java architectured their security infrastructure classes.

There’s way more thing that needs to dive in with TLS, for example

  • how to build his own certificate authority
  • how to handle a real certificate chain
  • how to handle mutual authentication (clients is also sending his own certificate)
  • how to use third party providers like BouncyCastle

Versions

This article has been elaborated with the following versions

  • Java 1.8.0u144
  • okhttp 3.9.0
  • wiremock 2.8.0
  • High Sierra / OSX 10.13 / Darwin 17.0.0 / 17A405
  • openssl => LibreSSL 2.2.7 (High Sierra comes with LibreSSL)

Some references

  • https://www.openssl.org
  • https://www.libressl.org
  • http://wiki.cacert.org/FAQ/subjectAltName
  • https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04
  • https://tools.ietf.org/html/rfc5280
  • https://tools.ietf.org/html/rfc2818
  • https://tools.ietf.org/html/rfc5246
  • https://docs.oracle.com/javase/8/docs/technotes/guides/security/index.html