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?
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)
...