MQTT Client connect with in-memory cert/key pair

Hi!
Kind of new to Java, been scouring the net for examples of what I need to do but cannot stitch everything together and would appreciate some help!

I have a cert/key pair I need to use when I connect to a third-party broker, but we need to fetch them at runtime. I tried out the AWS IoT MQTT client before moving on to evaluating the HiveMQ MQTT client, and with the AWS one I simply needed to pass in the cert and key as strings to one of the connection builder methods, which fits perfectly since we are fetching them as strings from AWS Secrets Manager.

I didn’t find any info on using cert/key pairs in the official docs for the HiveMQ MQTT client, but I found a short segment at the end of this blog post, where a KeyManagerFactory and a TrustManagerFactory seems to be required? Or do I just need one of them? I’ve tried reading up on them but it’s a whole new big area, and I’m uncertain if I’m supposed to need to understand it since it was so easy with the AWS client. And if they are needed, the examples I’m finding of instantiating them always refer to reading from disk, but as I mentioned we fetch strings at runtime which makes it a bit trickier to follow examples as well.

Thankful for help and pointers!

Hello @TheBroach ,

First off, welcome to the HiveMQ Community!

I believe that what you’re looking for is covered here in our How-To guide for configuring TLS. Let us know if this leaves you with any further questions, and in the meantime, welcome, once again!

Best,
Aaron from HiveMQ Team

Hi Aaron,
Thanks for your answer and the welcome! And the link to the how-to-guide, maybe that should be linked somewhere in the documentation site?

I made a try to adapt the code in the guide to my scenario, I’ll paste it at the end of this message (sorry for extra complexity from the Quarkus/Mutiny framework). But when I tried running it I ran into another problem which I have a hard time finding guidance for…

toDerInputStream rejects tag type 45

After researching it I guess it comes from the fact that the mqttClientConfig.clientKey() returns the whole contents of a .PEM file, while the code expects the .P12 format? But having a hard time finding solutions for converting that at runtime, do you have any pointers? All I can find are examples using CLI tools…

Also, is this amount of complexity what can be expected when trying to establish MQTT connections with cert/key pairs with a Java client? Naturally, when trying to get this working I’m thinking about the contrast to just passing in the cert/key pair as strings to the AWS MQTT client, but maybe the added complexity with your client comes from it being more flexible/less opinionated?

public void startMqttSubscribers(final MqttClientConfig mqttClientConfig) {
        var client = createClient(mqttClientConfig);
        Uni.createFrom().completionStage(() -> client.connectWith().keepAlive(KEEP_ALIVE_SECONDS).send())
                .chain(() -> subscribeToTopics(client))
                .subscribe().with(
                        unused -> LOG.info("HiveMQ MQTT Client connected and subscribed to topics."),
                        e -> LOG.error("Failed to connect or subscribe to topics.", e)
                );
    }

    private Mqtt5AsyncClient createClient(final MqttClientConfig mqttClientConfig) {
        var keyManagerFactory = createKeyManagerFactory(mqttClientConfig.clientKey());
        var trustManagerFactory = createTrustManagerFactory(mqttClientConfig.clientCertificate());
        return MqttClient.builder()
                .useMqttVersion5()
                .identifier(mqttClientConfig.clientId())
                .serverHost(mqttClientConfig.brokerUrl())
                .serverPort(PORT)
                .sslConfig()
                .keyManagerFactory(keyManagerFactory)
                .trustManagerFactory(trustManagerFactory)
                .applySslConfig()
                .buildAsync();
    }

    private KeyManagerFactory createKeyManagerFactory(final String privateKey) {
            try {
                var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                var keyInputStream = new ByteArrayInputStream(privateKey.getBytes());
                keyStore.load(keyInputStream, null);

                var keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                keyManagerFactory.init(keyStore, null);
                return keyManagerFactory;
            } catch (final Exception e) {
                throw new IllegalArgumentException("Failed to create KeyManagerFactory for MQTT Client connection.", e);
            }
    }

    private TrustManagerFactory createTrustManagerFactory(final String certificate) {
        try {
            var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            var certInputStream = new ByteArrayInputStream(certificate.getBytes());
            keyStore.load(certInputStream, null);

            var trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            return trustManagerFactory;
        } catch (final Exception e) {
            throw new IllegalArgumentException("Failed to create TrustManagerFactory for MQTT Client connection.", e);
        }
    }

    private Uni<Void> subscribeToTopic(final Mqtt5AsyncClient client) {
        return Uni.createFrom().completionStage(() -> client.subscribeWith().topicFilter(SOME_TOPIC).callback(SOME_HANDLER).send());
    }

Hello @TheBroach ,

Most definitely - there is intended to be an “enhanced auth” section within the documentation that looks to be not properly linking at this time - I’m coordinating with our team to investigate that presently.

As for the configuration here, it looks like we may have missed a step during the JKS import process, and instead of using the imported keystore are using the pem file directly. This document outlines the process for creating the client keystore from the pem file. Please bear in mind that some information, like the broker configuration steps, or the initial creation of the pem file, may not be accurate for your use-case, as this is specific to the HiveMQ Broker. That said, the details outlining the client process is accurate, and should walk you through the process. This can be found here.

Best,

Aaron from HiveMQ Team