Search code examples
javasslapache-httpclient-4.x

How to create an SSLContext that will trust no server certificates?


I've seen plenty of examples of constructing an SSLContext that will accept all server certificates. For my test cases, I'm trying to do exactly the opposite and force the client to reject the server's certificate.

So I'm trying to create a KeyStore object that contains no root certificates, but when I try to use it I get an InvalidAlgorithmParameterException with the message 'the trustAnchors parameter must be non-empty'. I have tried this:

KeyStore emptyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyTrustStore.load(null, null);
sslcontext = SSLContexts.custom().loadTrustMaterial(emptyTrustStore, new TrustNoOneStrategy()).build();

and this:

KeyStore emptyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyTrustStore.setCertificateEntry("notreal", null);
sslcontext = SSLContexts.custom().loadTrustMaterial(emptyTrustStore, new TrustNoOneStrategy()).build();

and (from an idea in a comment), this:

KeyStore emptyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustNoOneStrategy()).build();

but none of these approaches solves the problem.

Obviously I could simply create a JKS file that contains a dummy root certificate and load that using the SSLContexts.loadTrustMaterial(File) method, but that seems really ugly: surely there is a way to do this just in code?


Solution

  • Thanks for the suggestions. What I ended up with as a solution was to create a dummy root CA using OpenSSL, and including the PEM-formatted certificate in my test as a String value, building an X509Certificate from that String, installing it in a newly-created KeyStore object and then using that KeyStore as the trust store.

    Since I created the certificate and then immediately deleted the private key once I had the certificate, I know that there can never be a server certificate actually signed by this dummy CA.

    KeyStore dummyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        try {
            dummyTrustStore.load(null, null);
            dummyTrustStore.setCertificateEntry("dummyroot", buildDummyCertificate());
        } catch (CertificateException | IOException e) {
            // handle exception
        }
    sslcontext = SSLContexts.custom().loadTrustMaterial(dummyTrustStore, new TrustNoOneStrategy()).build();
    

    where the buildDummyCertificate() method is:

    private X509Certificate buildDummyCertificate() throws CertificateException {
        CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
        ByteArrayInputStream is = new ByteArrayInputStream(PEM_CERTIFICATE.getBytes(StandardCharsets.US_ASCII));
        return (X509Certificate) cFactory.generateCertificate(is);
    }
    
    private static final String PEM_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n"
                                                      + "MIIEMDCCAxigAwIBAgI..."
                                                      ....
                                                      + "...n1xJLO3k=-----END CERTIFICATE-----";
    

    One interesting wrinkle is that the newline character after -----BEGIN CERTIFICATE----- is required: without this the certificate creation fails with this error:

    java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Incomplete data
    at sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:104)
    at java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:339)
    ...