javasslsslsocketfactory

Creating SSL socket using imported certificate without private key


Im very new to SSL and to be frank i've found implementation to be pretty confusing so sorry if my question seems clueless or off base.

So i have a SSL socket server using a self signed certificate in the form of a p12 file. I want to have a client socket be able to connect to this server WITHOUT having access to the private key. I do however want the client to be able to verify that the server is not an imposter and does have the private key.

my problem is that i cannot seem to create a SSLContext and thus a SSLSocketFactory using anything other than a p12 file. Ideally i would create my SSLSocketFactory using the certificate root file.

heres the way i have my client setup:

public class SSLClient {
    
    private static final String TLS_VERSION = "TLSv1.2";

    public static void run(int serverPort, String certName, char[] certPass) throws Exception {

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream certInputStream = TLSClient.class.getResourceAsStream("/" + certName);
        trustStore.load(certInputStream, certPass);
        certInputStream.close();
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream keyStoreInputStream = TLSClient.class.getResourceAsStream("/" + certName);
        keyStore.load(keyStoreInputStream, certPass);
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, certPass);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), SecureRandom.getInstanceStrong());

        SocketFactory factory = sslContext.getSocketFactory();

        try (Socket connection = factory.createSocket("localhost", serverPort)) {
            ((SSLSocket) connection).setEnabledProtocols(new String[] {TLS_VERSION});
            SSLParameters sslParams = new SSLParameters();
            sslParams.setEndpointIdentificationAlgorithm("HTTPS");
            ((SSLSocket) connection).setSSLParameters(sslParams);


            BufferedReader input = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            System.out.println(input.readLine());
            new PrintWriter(connection.getOutputStream(),true).println("client reply");
        }
    }
}

Ive tried using the root certificate (.crt) and a PEM file but both of them give me an input stream error:

Exception in thread "main" java.io.IOException: toDerInputStream rejects tag type 45
    at java.base/sun.security.util.DerValue.toDerInputStream(DerValue.java:1155)
    at java.base/sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:2013)
    at java.base/sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:221)
    at java.base/java.security.KeyStore.load(KeyStore.java:1473)
    at TLSClient.run(TLSClient.java:16)
    at Main.main(Main.java:12)

My current approach/solution to this: i created an "empty" p12 file for the client using the servers root certificate. This appears to be working as intended... is there a problem doing it this way?

made using:

openssl pkcs12 -export -in myCert.crt -out myCertPks.p12 -name alias


Solution

  • An easy way would be to import the trusted CAs public certificate in a Java keystore (you manipulate them with keytool, a CLI tool that's delivered with Java).

    If you use the file cacerts from a subfolder of your JAVA_HOME, it will be recognized by your JVM automatically.

    You find documentation about keytool in the tool docs of JDK (linked it for Java 21, but version is probably not that relevant). Also a better description of the exact location of the cacerts file, it's default password etc. are documented there.

    In this way you don't have to mess around with KeyStore, TrustManager, etc., just create the SSLSocket with a default SSLSocketFactory and you're good.