Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLS issue #448

Open
philipparndt opened this issue Mar 27, 2022 · 21 comments · May be fixed by philipparndt/CocoaMQTT#1
Open

TLS issue #448

philipparndt opened this issue Mar 27, 2022 · 21 comments · May be fixed by philipparndt/CocoaMQTT#1
Assignees
Labels

Comments

@philipparndt
Copy link

Hi,

when using the MQTT protocol with TLS, there are some issues (with versions 2.x and 1.x.
LetsEncrypt certificates generated with Traefik are denied with
errSSLXCertChainInvalid = -9807, /* invalid certificate chain */

However, the same certificate works when connecting with WSS.
I did some debugging, and think the problem is somewhere in the CocoaAsyncSocket which comes with a lot of deprecated API calls. Did you already do some experiments with Apples network framework to get rid of the deprecated API and maybe of this issue?

This issue was originally reported here:
philipparndt/mqtt-analyzer#69
see philipparndt/mqtt-analyzer#69 (comment) for a comparison between MQTTS and WSS (MQTTAnalyzer is using CocoaMQTT)

@leeway1208
Copy link
Collaborator

Hi. How about trying these codes below. 😄

 mqtt!.enableSSL = true
 mqtt!.allowUntrustCACertificate = true

@philipparndt
Copy link
Author

Hi @leeway1208

yes, I did this ;)

I tried to connect with the following settings (this is working with MQTT.js for example)

The connection fails in this test (with errSSLXCertChainInvalid = -9807, /* invalid certificate chain */)

func settings(mqtt: CocoaMQTT) {
	mqtt.enableSSL = true
	mqtt.allowUntrustCACertificate = true
	mqtt.username = "admin"
	mqtt.password = "password"

	mqtt.keepAlive = 60
	mqtt.autoReconnect = false
}

func testConnect() {
	let mqtt = CocoaMQTT(clientID: "some-id",
						 host: host,
						 port: 8883)
	
	settings(mqtt: mqtt)

	let caller = Caller()
	mqtt.delegate = caller
	
	if !mqtt.connect() {
		XCTAssertTrue(false)
	}

	wait_for {
		caller.isConnected
	}
}

However, the same test with WebSockets is working:

func testConnectWSS() {
	let websocket = CocoaMQTTWebSocket(uri: "/")
	let mqtt = CocoaMQTT(clientID: "some-id",
						 host: host,
						 port: 443,
						 socket: websocket)
	
	settings(mqtt: mqtt)
	
	let caller = Caller()
	mqtt.delegate = caller

	if !mqtt.connect() {
		XCTAssertTrue(false)
	}

	wait_for {
		caller.isConnected
	}
}

SSL is handled completely different, the MQTT connection is using CocoaAsyncSocket and the WebSocket connection is using StarScream.

@leeway1208
Copy link
Collaborator

leeway1208 commented Mar 27, 2022

😄 hi @philipparndt Can you give me your host? I will test the connection. I just tried the host (https://test.mosquitto.org/) which is ok. Thanks~

@philipparndt
Copy link
Author

😄 the host is on my local network with docker.
The DNS name resolves to a Traefik container which uses LetsEncrypt to create a certificate.

I'll look how I can host this temporary in the internet and come back to you.

@philipparndt
Copy link
Author

Hi @leeway1208 I have a server for you 😄

Try to connect to (no auth):

mqtts://cocoamqtt.rnd7.de:8883
wss://cocoamqtt.rnd7.de:443

The websocket connection will work wit CocoaMQTT, the MQTT connection fails with errSSLXCertChainInvalid = -9807, /* invalid certificate chain */

When I do the same with another MQTT Client (MQTT.js) both connections will work.
Example:

import * as mqtt from "mqtt"

const client = mqtt.connect("mqtts://cocoamqtt.rnd7.de:8883")

client.on("connect", () => {
    console.log("connected")
    client.subscribe("#", (err) => { console.log(err, "subscribed") })
    client.subscribe("$SYS/#", (err) => { console.log(err, "subscribed") })
})

client.on("message", (topic, message) => { console.log(topic, message.toString()) })

@leeway1208
Copy link
Collaborator

@philipparndt Thanks~ I will test it some days. If I have some new ideas, I will share with you.

@leeway1208 leeway1208 self-assigned this Apr 1, 2022
@leeway1208
Copy link
Collaborator

leeway1208 commented Apr 1, 2022

@philipparndt I found that apple no longer supports TLS1.0 and TLS1.1. These versions have been deprecated on Apple platforms as of iOS 15, iPadOS 15, macOS 12, watchOS 8, and tvOS 15, and support will be removed in future releases. Do you use these versions?
https://developer.apple.com/news/?id=bv8ur34d

@philipparndt
Copy link
Author

Hi @leeway1208 thank your ideas. I did some changes in the configuration and checked the connection with Wireshark.

When the connection is done with the MQTT protocol, it uses the TLS1.2 Protocol with TLS version 1.2
from Server Hello message:

Transport Layer Security
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 55
        Handshake Protocol: Server Hello
    TLSv1.2 Record Layer: Handshake Protocol: Certificate
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 875
        Handshake Protocol: Certificate
    TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 333
        Handshake Protocol: Server Key Exchange
            Handshake Type: Server Key Exchange (12)
            Length: 329
            EC Diffie-Hellman Server Params
                Curve Type: named_curve (0x03)
                Named Curve: secp256r1 (0x0017)
                Pubkey Length: 65
                Pubkey: …
                Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
                    Signature Hash Algorithm Hash: SHA256 (4)
                    Signature Hash Algorithm Signature: RSA (1)
                Signature Length: 256
                Signature: …
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello Done
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 4
        Handshake Protocol: Server Hello Done

after this, the client responds with Certificate unknown

Transport Layer Security
    TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Certificate Unknown)
        Content Type: Alert (21)
        Version: TLS 1.2 (0x0303)
        Length: 2
        Alert Message
            Level: Fatal (2)
            Description: Certificate Unknown (46)

@philipparndt
Copy link
Author

Another breadcrumb:

When I try to connect from the iPhone simulator using Apples Network Framework, the TLS handshake is successful.
It seems that figuring out how to write a Network Framework Layer for CocoaMQTT is worth some time.

Example code

// Create an outbound connection

let connection = NWConnection(host: "cocoamqtt.rnd7.de", port: 8883, using: .tls)

connection.stateUpdateHandler = { (newState) in
	print(newState)
}
connection.start(queue: DispatchQueue.global(qos: .userInitiated))
sleep(10)

image

image

@philipparndt
Copy link
Author

Hi @leeway1208

okay I have a working demonstrator with Apple Network, that can connect to the server with the MQTT protocol 😄
There is still some work to do and as this protocol was introduced with iOS 12, I had to update the version number.
See:
https://github.com/philipparndt/CocoaMQTT/tree/apple-network

@leeway1208
Copy link
Collaborator

@philipparndt I try to solve the connection problems with GCDAsyncSocket. But I haven't found a solution yet. The function - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag is not called. When I connect to host (cocoamqtt.rnd7.de).
I will keep trying

@leeway1208
Copy link
Collaborator

@philipparndt I downloaded four mqtt apps in the App Store, and they can't connect to the host(mqtts://cocoamqtt.rnd7.de) either. So I discussed this with my colleague and he provided some reference documents for us to see if there are any configuration issues.

Enable SSL/TLS for EMQX MQTT broker (https://www.emqx.com/en/blog/emqx-server-ssl-tls-secure-connection-configuration-guide)
Hands-on Demo: Set up TLS/SSL to Ensure MQTT Security (https://youtu.be/W7SedpZzQdo)
https://www.youtube.com/watch?v=aFDDq9dLnEI (https://youtu.be/aFDDq9dLnEI)

Thanks

@philipparndt
Copy link
Author

Hi @leeway1208,
Thanks for your investigation!

What apps did you test?

I don't think that it is a configuration issue. Maybe the configuration is not supported by CocoaAsyncSocket.
The configuration works like this:

  • Traefik with LetsEncrypt certificates as entry point. TLS is terminated here.
  • Mosquito without TLS behind this proxy.

This setup is pretty common when using Kubernetes (ingress controller). I think this will get used more and more and should be fixed/improved.

I have now exchanged CocoaAsyncSocket with the Apple network framework on my branch, and it works perfectly. I still have to do more tests, but the following will work:

  • TLS connections with Traefik
  • Connections without TLS
  • Self-singed certificated

I will continue developing this branch and remove CocoaAsyncSocket totally, as it has a lot of deprecated code (since iOS13). It should even be possible to handle WebSocket connections with this framework.

For your main branch I see the following options:

  • stick with CocoaAsyncSocket to support iOS < 12 but do not support newer TLS connections
  • find the issue in CocoaAsyncSocket (I don't think it is worse it, as the Apple framework provides everything necessary)
  • merge my branch or use it as inspiration when it is complete (I can provide a PR when you like to)
  • implement dynamic connection providers that can be installed as separate dependencies (one for CocoaAsyncSocket, one for Starscream, one for Apple network framework)

@leeway1208
Copy link
Collaborator

leeway1208 commented Apr 3, 2022

@philipparndt
Thanks for your help.
I think you are right. It is more likely that CocoaAsyncSocket or some old libraries do not support this TLS configuration. I hope we can find some documentation to explain this issue and I am looking forward to receiving your pr. Then I will try to implement dynamic connections.

the testing app which I used below:
EasyMQTT
MQTTAnalyzer
MQTTTool
MQTT Spy

If you have some new ideas. We can discuss more.
😄😄😄😄😄😄

@philipparndt
Copy link
Author

the testing app which I used below: EasyMQTT MQTTAnalyzer MQTTTool MQTT Spy

MQTTAnalyzer is my App and is using CocoaMQTT 😉
EasyMQTT is using CocoaMQTT as well 😉
MQTTTool is using Moscapsule (but was not updated for a long time)

I will come back to you when I have more. Currently, I implement connecting with client certificates.

@leeway1208
Copy link
Collaborator

@philipparndt haha. I find a way to connect the host. I write the sslSettings like this:

mqtt5!.allowUntrustCACertificate = true 
mqtt5!.sslSettings = [kCFStreamSSLPeerName as String: "cocoamqtt.rnd7.de" as NSObject] 

🎆🎆🎆

@philipparndt
Copy link
Author

philipparndt commented Apr 4, 2022

@philipparndt haha. I find a way to connect the host. I write the sslSettings like this:

mqtt5!.allowUntrustCACertificate = true 
mqtt5!.sslSettings = [kCFStreamSSLPeerName as String: "cocoamqtt.rnd7.de" as NSObject] 

🎆🎆🎆

I can confirm this is working 👍
Nevertheless, I will continue the branch so that there is way less deprecated code 😉 I think we might need this for the next major iOS version anyway.

@philipparndt
Copy link
Author

Hi @leeway1208,

I've implemented the changes on this branch: philipparndt#1 do you like to have a look at it?

While testing it I good some luck and seen some data races that are already reported by other users.
I fixed them as well.
Fixed:

@philipparndt philipparndt linked a pull request Apr 16, 2022 that will close this issue
7 tasks
@leeway1208
Copy link
Collaborator

@philipparndt
Well done! Thank you for sharing this which I can practice in my code as well. I will study your code and start preparing for dynamic connections.
Hope you can provide feedback when I'm done.
Thanks.

@philipparndt
Copy link
Author

@leeway1208
let me know when can help you or you have something we should discuss. Maybe it would be a good idea to specify some small API for the connection provider so that we can split-up the work a little bit.

@leeway1208
Copy link
Collaborator

@philipparndt Hi~~ Sorry, I can only start to work on this until May. But I can create a AppleNetwork branch for us first. haha

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants