Access X.509 Certificates in Authentication Extensions

I am building a prototype using the HiveMQ extension SDK on 4.7.5.
I have a working extension with one exception: I want to verify the thumbprint of the device certificate. The input parameters SimpleAuthInput and EnhancedAuthConnectInput in the onConnect and onAuth callbacks do not have the client certificate set.

Is this a supported use case? Is there an example out there that I can check out?

And yes, I have verified the client implementation that it passed the certificate on to the broker. Using the same client with Azure IoT Hub and thumbprint certification just works fine with the same certificate.

Any help is appreciated.

Hi @mattneug,

you should be able to get the certificate of the client via the connection information, simple example:

final ClientTlsInformation tlsInformation = simpleAuthInput.getConnectionInformation().getClientTlsInformation()
                    .get();
final X509Certificate certificate = tlsInformation.getClientCertificate().get();

Or is your problem that the Optional’s are empty?

Greetings,
Michael

1 Like

Hey Michael, this is exactly the problem. The Client certificate is not present and throws an exception when I try to access it. Same for client certificate chain:
log.info("client cert signature algorithm name: " + enhancedAuthConnectInput.getConnectionInformation().getClientTlsInformation().orElseThrow().getClientCertificate().orElseThrow().getSigAlgName());

BTW. The TLS information is fine and returns the expected values, e.g. protocol version TLS 1.2

Cheers
Matthias

Hi @mattneug,

can you post your <tls> configuration of your listener? My guess is that you have not set <client-authentication-mode> which then is by default NONE which means that the HiveMQ broker will not process the client certificate.

Greetings,
Michael

Hey Michael,
good point, but I have configured the client-authentication-mode as REQUIRED.


<?xml version="1.0"?>
<hivemq>
    <listeners>
        <tcp-listener>
            <port>1883</port>
            <bind-address>0.0.0.0</bind-address>
        </tcp-listener>
        <tls-tcp-listener>
            <port>8883</port>
            <bind-address>0.0.0.0</bind-address>
            <client-authentication-mode>REQUIRED</client-authentication-mode>
            <tls>
                <!-- Enable specific TLS versions manually -->
                <protocols>
                    <protocol>TLSv1.2</protocol>
                </protocols>
                <keystore>
                    <path>conf/hivemq.jks</path>
                    <password>...</password>
                    <private-key-password>...</private-key-password>
                </keystore>
            </tls>
        </tls-tcp-listener>
    </listeners>
    <anonymous-usage-statistics>
        <enabled>true</enabled>
    </anonymous-usage-statistics>
</hivemq>

Ah now I was confused for a second how you were able to connect a client with mode = REQUIRED but not having a trust-store to verify the client certificate.

So I see two problems:

  • the client-authentication-mode is part of the <tls> tag and not the <tls-tcp-listener> tag, so basically you still have the default config of NONE
  • just moving the line is not enough, this should lead to clients not being able to connect anymore with event.log entry of Client ID: UNKNOWN, IP: UNKNOWN was disconnected. reason: SSL handshake failed.. This is because you are missing a trust-store where the broker then could verify the client certificate of the client. So you need to create a trust-store that contains the information of the client certifcate, this HowTo should help you.

Your config should look like this:

  <tls-tcp-listener>
            <port>1883</port>
            <bind-address>0.0.0.0</bind-address>
            <tls>
                <keystore>
                    <path>testSslStore.jks</path>
                    <password>password1</password>
                    <private-key-password>password2</private-key-password>
                </keystore>
                <truststore>
                    <path>testCertStore.jks</path>
                    <password>password1</password>
                </truststore>
                <client-authentication-mode>REQUIRED</client-authentication-mode>
            </tls>
        </tls-tcp-listener>

Greetings,
Michael

1 Like

Good catch Michael, thanks a lot.
Unfortunately, I have a different issue now. HiveMQ now complains with

2022-04-04 19:03:32,859 DEBUG - SSL Handshake failed for client with ID UNKNOWN and IP 127.0.0.1: Empty server certificate chain.

Not sure what is wrong here. Is the server certificate from the keystore an issue or the client certificate? The server certificate is created via keytool as described in ‘Generate a server side certificate for HiveMQ

I also tried to add the client certificate to a truststore that I have configured in the config. This was also not working and creates the same error message as mentioned above. Registering client certificates would also not be the intended solution for me, because I want to check the client certificate thumbprint with another service from our backend.

My config looks like this now

<?xml version="1.0"?>
<hivemq>
    <listeners>
        <tcp-listener>
            <port>1883</port>
            <bind-address>0.0.0.0</bind-address>
        </tcp-listener>
        <tls-tcp-listener>
            <port>8883</port>
            <bind-address>0.0.0.0</bind-address>
            <tls>
                <!-- Enable specific TLS versions manually -->
                <protocols>
                    <protocol>TLSv1.2</protocol>
                </protocols>
                <keystore>
                    <path>conf/hivemq.jks</path>
                    <password>...</password>
                    <private-key-password>...</private-key-password>
                </keystore>
                <client-authentication-mode>REQUIRED</client-authentication-mode>
            </tls>
        </tls-tcp-listener>
    </listeners>
    <anonymous-usage-statistics>
        <enabled>true</enabled>
    </anonymous-usage-statistics>
</hivemq>

Hi @mattneug,

I see you still didn’t configure the truststore in the <tls> tag (see my config example in previous comment), now you got the issue I mentioned above. Check also the HowTo link above to see how you can build the trust-store.

Greetings,
Michael

Thanks for the patience Michael.
I tried both with and without trust store and I get the same error message.
I retried again using exactly the How To documentation you mentioned. No success, same message.

Thinking again about the error message Empty server certificate chain, I suspect that HiveMQ requires the client certificate to be signed by a CA (certificate chain). My client certificate is not signed.

Matthias

In the meantime I rebuild my certificates and signed the device certificate with a sub CA / CA certificate. I imported the CA certs into the keystore and the new device certificates into the truststore.

I have updated the client code with the new certificate. Now, I get a new error message

2022-04-05 18:22:45,985 DEBUG - SSL Handshake failed for client with ID UNKNOWN and IP 127.0.0.1: no cipher suites in common

HiveMQ runs on

java version "18" 2022-03-22
Java(TM) SE Runtime Environment (build 18+36-2087)
Java HotSpot(TM) 64-Bit Server VM (build 18+36-2087, mixed mode, sharing)

The client is a Dotnet 6.0 application using the latest MQTTnet client SDK.
Client and and broker run on the same machine installed with Windows 21H2.

For completeness: HiveMQ config file is now

<?xml version="1.0"?>
<hivemq>
    <listeners>
        <tcp-listener>
            <port>1883</port>
            <bind-address>0.0.0.0</bind-address>
        </tcp-listener>
        <tls-tcp-listener>
            <port>8883</port>
            <bind-address>0.0.0.0</bind-address>
            <tls>
                <!-- Enable specific TLS versions manually -->
                <protocols>
                    <protocol>TLSv1.2</protocol>
                </protocols>
                <keystore>
                    <path>conf/keystore.jks</path>
                    <password>...</password>
                    <private-key-password>...</private-key-password>
                </keystore>
                <truststore>
                    <path>conf/truststore.jks</path>
                    <password>...</password>
                </truststore>
                <client-authentication-mode>REQUIRED</client-authentication-mode>
            </tls>
        </tls-tcp-listener>
    </listeners>
    <anonymous-usage-statistics>
        <enabled>true</enabled>
    </anonymous-usage-statistics>
</hivemq>

This is quite simple, the client and the server both need to support one cipher suite they share in common.

So for example the client supports these cipher suites:

TLS_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA

You have to configure the server that at least one of these cipher suites is also enabled, so in your tls tag you need to set one/all of the above cipher suites, so something like this:

<tls>
    <protocols>
           <protocol>TLSv1.2</protocol>
    </protocols>
    <cipher-suites>
        <cipher-suite>TLS_RSA_WITH_AES_256_CBC_SHA</cipher-suite>
        <cipher-suite>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</cipher-suite>
        <cipher-suite>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</cipher-suite>
        <cipher-suite>TLS_RSA_WITH_AES_128_CBC_SHA</cipher-suite>
    </cipher-suites>
                ....
</tls>

So now you need to find out which cipher suites the client has enabled by default and pick one (or more) and add them like in the example above. If you are familiar with Wireshark you can find the client support cipher suites in the CLIENT HELLO or maybe they are logged or documented.

Michael,
thanks for the tip with Wireshark.

I have updated the cipher-suites section by adding all cipher-suites from the hello message, but without success. I still get the same error message

2022-04-06 09:54:58,062 DEBUG - SSL Handshake failed for client with ID UNKNOWN and IP 127.0.0.1: no cipher suites in common

I have specified all cipher suites from the hello message in the HiveMQ config. The console log shows the updated list of cipher suites. I am ignoring the complain about 1 unknown cipher suite.

Unknown cipher suites for TCP Listener with TLS at address 0.0.0.0 and port 8883: [TLS_RSA_WITH_3DES_EDE_CBC_SHA]

I have now 20 cipher suites enabled in HiveMQ that were send out by the client’s Client Hello message. The console log acknowledges this:

2022-04-06 09:54:02,454 INFO  - Enabled cipher suites for TCP Listener with TLS at address 0.0.0.0 and port 8883: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA]

HiveMQ still complains about having no common ground regarding cipher suites with the client.
Is there a way to get more detailed debugging output from HiveMQ to get to the ground of this?

Cheers
Matthias

p.s. for completeness, here is the config file

<?xml version="1.0"?>
<hivemq>
    <listeners>
        <tcp-listener>
            <port>1883</port>
            <bind-address>0.0.0.0</bind-address>
        </tcp-listener>
        <tls-tcp-listener>
            <port>8883</port>
            <bind-address>0.0.0.0</bind-address>
            <tls>
                <!-- Enable specific TLS versions manually -->
                <protocols>
                    <protocol>TLSv1.2</protocol>
                </protocols>
                <cipher-suites>
                    <cipher-suite>TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384</cipher-suite>
                    <cipher-suite>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</cipher-suite>
                    <cipher-suite>TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</cipher-suite>
                    <cipher-suite>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</cipher-suite>
                    <cipher-suite>TLS_DHE_RSA_WITH_AES_256_GCM_SHA384</cipher-suite>
                    <cipher-suite>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256</cipher-suite>
                    <cipher-suite>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384</cipher-suite>
                    <cipher-suite>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256</cipher-suite>
                    <cipher-suite>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384</cipher-suite>
                    <cipher-suite>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256</cipher-suite>
                    <cipher-suite>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA</cipher-suite>
                    <cipher-suite>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA</cipher-suite>
                    <cipher-suite>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</cipher-suite>
                    <cipher-suite>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</cipher-suite>
                    <cipher-suite>TLS_RSA_WITH_AES_256_GCM_SHA384</cipher-suite>
                    <cipher-suite>TLS_RSA_WITH_AES_128_GCM_SHA256</cipher-suite>
                    <cipher-suite>TLS_RSA_WITH_AES_256_CBC_SHA256</cipher-suite>
                    <cipher-suite>TLS_RSA_WITH_AES_128_CBC_SHA256</cipher-suite>
                    <cipher-suite>TLS_RSA_WITH_AES_256_CBC_SHA</cipher-suite>
                    <cipher-suite>TLS_RSA_WITH_AES_128_CBC_SHA</cipher-suite>
                    <cipher-suite>TLS_RSA_WITH_3DES_EDE_CBC_SHA</cipher-suite>
                </cipher-suites>
                <keystore>
                    <path>conf/keystore.jks</path>
                    <password>...</password>
                    <private-key-password>...</private-key-password>
                </keystore>
                <truststore>
                    <path>conf/truststore.jks</path>
                    <password>...</password>
                </truststore>
                <client-authentication-mode>REQUIRED</client-authentication-mode>
            </tls>
        </tls-tcp-listener>
    </listeners>
    <anonymous-usage-statistics>
        <enabled>true</enabled>
    </anonymous-usage-statistics>
</hivemq>
Unknown cipher suites for TCP Listener with TLS at address 0.0.0.0 and port 8883: [TLS_RSA_WITH_3DES_EDE_CBC_SHA]

The complain comes from the fact that this is a TLSv1.0/1.1 cipher suite and because you only enabled TLSv1.2.

As for why your client still can’t connect, can you please check if the client sends always the same cipher suites? And just to make sure this CLIENT_HELLO is the one from your MQTT client?

What you also could do is to configure your client to also use just TLSv1.2 and the cipher suites you set for the broker. If this still doesn’t work I eat a broom.

Sadly not because the SSL handshake is handled by the JDK SSL implementation. The only thing you can do is to set the log level to TRACE and find out the exact exception that is thrown.

How to set log level to TRACE:
In conf/logback.xml change in line <root level="${HIVEMQ_LOG_LEVEL:-INFO}"> the INFO to TRACE.

Michael, enjoy the broom :rofl:

  1. I can acknowledge that the wireshark CLIENT HELLO is from the client/broker combination I am testing.
  2. I am using MQTTnet for C# 6.0 and have set the protocol to TLS1.2
  3. The client sends always the same list of cipher suites
  4. I have enabled TRACE level logging. No additional insights. Exception is javax.net.ssl.SSLHandshakeException: no cipher suites in common thrown at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
  5. The C# client can successfully connect plain SSL to the broker without error. I see the disconnect message after closing the client in the broker log: 2022-04-07 18:22:57,556 TRACE - Client UNKNOWN disconnected ungracefully.
  6. I have written a Java client using the HiveMQ client library 1.3.0. Connect without TLS works fine. Using TLS renders exactly the same error message. See the java code below.
  7. I have tried the Java client and broker on my personal Windows 11 machine to avoid machine configuration issues, e.g. firewall settings. Same result.
  8. I have also disabled my authentication extension. Same result.
  9. I have tried the HiveMQ MQTT CLI 4.5.1 using the following command mqtt> con -v -d --port 8883 --secure. Same result.
  10. tried 127.0.0.1 and localhost and . Same result.
  11. tried running the broker in docker. Same result.

I have no more ideas. Call it a day now.

Thanks a lot for your hand-holding
Matthias

This is my Java client code mentioned above.

package de.mattneug;

import com.hivemq.client.mqtt.MqttClient;
import com.hivemq.client.mqtt.MqttClientSslConfig;
import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient;

import java.util.UUID;

public class Main {

    public static void main(String[] args) {
        System.out.println("starting...");

        Mqtt3AsyncClient client = MqttClient.builder()
                .useMqttVersion3()
                .identifier(UUID.randomUUID().toString())
                .serverHost("...")
                .serverPort(8883)
                .sslWithDefaultConfig()
                .buildAsync();

        client.connect()
                .whenComplete((connAck, throwable) -> {
                    if(throwable != null) {
                        System.out.println("failed: ");
                        System.out.println(throwable);
                    } else {
                        System.out.println("connected");
                    }
                });
    }
}

Michael, this is solved now :slight_smile:
The problem was that the server certificate was configured wrong. The error message no cipher suites in common is misleading here.

Thanks again for your support.
Matthias

1 Like

Glad to hear, eating a broom avoided :fireworks:

Yes that message is misleading, as are those TLS messages most of the time (sadly).