shamblett / mqtt_client

A server and browser based MQTT client for dart
Other
544 stars 176 forks source link

CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate #550

Closed nouhouari closed 1 month ago

nouhouari commented 1 month ago

I'm trying to connect to a secure websocket over wss to Mosquitto server and publish messages to a MQTT topic.

mqtt_client: ^10.3.1

My server is working fine and I've checked with the mqttx command line.

mqttx pub -t 'hello' -l wss -h 'XXXXXXXXXX' -p 443 -m 'Hello' --ca ca.crt
✔ Connected
✔ Message published

In the flutter application, I'm using the same ca.crt to secure the connection. Here is my Dart code:

 const certificatePath = 'assets/certs/ca.crt';

    final client =
        MqttServerClient('wss://XXXXXXXXXX', 'flutter_app');
    client.port = 443;
    client.useWebSocket = true;
    client.logging(on: true);

    ByteData crtData = await rootBundle.load(certificatePath);
    SecurityContext securityContext = SecurityContext(withTrustedRoots: true);
    securityContext.setTrustedCertificatesBytes(crtData.buffer.asInt8List());
    client.onBadCertificate = onBadCertificate;
    client.securityContext = securityContext;
    client.setProtocolV31();

    try {
      await client.connect();
    } catch (e) {
      if (kDebugMode) {
        print('Error while connecting: $e');
      }
    }

And the workaround to validate the self-signed certificate

  bool onBadCertificate(X509Certificate certificate){
    print('***** Bad certicicate' + certificate.toString());
    return true;
  }

Here are the logs of the flutter application running on Android 14 (API 34):

[  +18 ms] I/flutter ( 3136): 1-2024-07-15 16:26:43.947687 -- MqttConnectionHandlerBase::connect - server wss://XXXXXXXXXX, port 443
[  +14 ms] I/flutter ( 3136): 1-2024-07-15 16:26:43.960715 -- SynchronousMqttServerConnectionHandler::internalConnect entered
[        ] I/flutter ( 3136): 1-2024-07-15 16:26:43.962824 -- SynchronousMqttServerConnectionHandler::internalConnect - initiating connection try 0, auto reconnect in progress false
[   +3 ms] I/flutter ( 3136): 1-2024-07-15 16:26:43.965459 -- SynchronousMqttServerConnectionHandler::internalConnect - websocket selected
[  +13 ms] I/flutter ( 3136): 1-2024-07-15 16:26:43.978912 -- SynchronousMqttServerConnectionHandler::internalConnect - calling connect
[   +3 ms] I/flutter ( 3136): 1-2024-07-15 16:26:43.983443 -- MqttWsConnection::connect - entered
[  +25 ms] I/flutter ( 3136): 1-2024-07-15 16:26:44.007883 -- MqttWsConnection::connect - WS URL is wss://XXXXXXXXXX:443, protocols are [mqtt, mqttv3.1, mqttv3.11]
[ +612 ms] I/flutter ( 3136): 1-2024-07-15 16:26:44.619522 -- MqttConnectionBase::_onError - calling disconnected callback
[  +15 ms] I/flutter ( 3136): Error while connecting: HandshakeException: Handshake error in client (OS Error: 
[        ] I/flutter ( 3136):   CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:393))

Note that the onBadCertificate is not called.

What am I doing wrong?

Thanks

shamblett commented 1 month ago

You can't use secure working with websockets, the 'secure' setting is for server side sockets only, please read the clients API documentation.

There is no way of using a cert if you are using websockets, see the Dart API here. You can use the protocol and headers parameters if you need to, again read the clients API documentation.

nouhouari commented 1 month ago

You can't use secure working with websockets, the 'secure' setting is for server side sockets only, please read the clients API documentation.

There is no way of using a cert if you are using websockets, see the Dart API here. You can use the protocol and headers parameters if you need to, again read the clients API documentation.

I'm not using 'secure'.

shamblett commented 1 month ago

From your code above -

 SecurityContext securityContext = SecurityContext(withTrustedRoots: true);
   securityContext.setTrustedCertificatesBytes(crtData.buffer.asInt8List());

client.securityContext = securityContext;

you're setting a security context whether or not you are setting the secure field, this will not work with websockets, see the Dart API for the SecurityContext class.

nouhouari commented 1 month ago

It's working now by applying these changes.

    client = MqttServerClient(mqttURL, mqttClientId);
    client.port = mqttPort;
    client.useWebSocket = true;
    client.autoReconnect = true;
    client.logging(on: true);

    SecurityContext securityContext = SecurityContext.defaultContext;
    securityContext.setTrustedCertificatesBytes(mqttCertBytes);
    client.onBadCertificate = onBadCertificate;
    client.securityContext = securityContext;

    // Connect to server
    await connect(client);

I think the main change is:

SecurityContext securityContext = SecurityContext(withTrustedRoots: true);