dlarrick / pykumo

Python library to interact with Mitsubishi KumoCloud devices via their local API
MIT License
35 stars 12 forks source link

Kumo cloud encrypted communication #10

Open omriasta opened 4 years ago

omriasta commented 4 years ago

@dlarrick I was able to follow some online tutorials to listen to the encrypted traffic to kumo cloud servers. You can use genymotion emulator (free for personal) to bring up a Nexus 6 device with Android 6. Then use fiddler to proxy the traffic and install the certificate from the proxy. (Android 6 will trust the user certificate and allow the app to communicate through the proxy). Finally, you would have to push the apk file to the device and install it. It appears that the app is posting to the url ending in /saveUserData and sends over a json of all the info, attached sample..hope I got all the personal stuff out. THere also appear to be a few posts to /errorHistoryUpdates but those only appear to include the token and the serial of the device. I'll try to see what the communication looks like when an IP changes.

post_json.txt

omriasta commented 4 years ago

OK, First impression. When the device changes IP the app DOES NOT get the updated IP however the app will continue to work by sending commands directly to the cloud service. The app will post like this: POST /sendDeviceCommands/v2 HTTP/1.1 And will include the following json ["TOKEN_HERE",{"Serial_Here":{"power":1,"operationMode":3}}] This will turn on the unit to cool. And the following will turn the unit off: ["Token_here",{"Serial_here":{"power":0}}] The app retrieves the configuration json every 2 minutes or so from the cloud servers. I assume that something eventually triggers the ip address update (maybe a reboot of the device, maybe a longer interval) and at that point the device will update the servers with the new address. After that the app will receive the new address via the configuration json and resume working locally. Since I am monitoring the app and not the device I can't see what the device is sending to the cloud servers but I guess for a larger project we could write a whole failback to post to the cloud server instead of the local interface and then also poll the cloud servers for status if the local fails.... Not sure how closely the commands follow what we already have for the local interface but if they are similar maybe there would be an easy way to try the local first and failover to the cloud server if local not available. This makes sense if you read all the complaints online from people saying that the device takes forever to react to the app commands. I assume that is the experience if the app fails to communicate with the local interface.

dlarrick commented 4 years ago

Falling back to cloud control is a bad approach, in my opinion. It's papering over the real issue, and is not nearly as nice an experience.

I suspect the traffic from the unit to the KumoCloud servers is over a VPN. At least that's how I'd design it, and how systems like Ecobee work. The other possibility is that the unit identifies itself using its MAC address for identification and some sort of authentication. I think the chance of capturing and decrypting this communication is slim.

One thing you can try, though: establish local comm with the KumoCloud app on your simulator, then quit the app and give the indoor unit a new address. Then restart the KumoCloud app. I think you're saying you tried (essentially) this and it takes a while to notice the IP change? What I'm hoping is that the app sends a "Hey, that's not working" type of message to the service that causes the service to poke the indoor unit to report its new IP. That, we could simulate.

It's also possible that any old command serves this function. If you're motivated you could perform some more experiments.

omriasta commented 4 years ago

Yep, that's exactly what I did. I waited just about 5 minutes and neither the app or the cloud service got the new address. I'll try again soon and leave it a bit longer to see if it does eventually resolve. I did notice that the app appears to just keep trying the local interface and fails. I restarted the app several times but it didn't pick up the new IP address and just kept trying the old local IP.

omriasta commented 4 years ago

OK, not 100% sure that this is the trigger but I noticed every few minutes the app is posting to the URL /saveUserData The JSON looks similar to what we pull but I saw 2 fields that do change when I expect them to and once the ip updates they go back to what I would expect: _isRespondingLocally=False _requestRescan=1 Since the app does not know what the local ip is, it can't trigger the local device to do anything. My guess is the app detects no local connection -> Sets these 2 flags and posts the data in the json to the cloud server -> the kumo device is notified about this request when it checks in and sends the new info to kumocloud -> the kumo app downloads the configuration again with the new ip(it does this every few minutes anyway). It's pretty time consuming to strip all the personal info, let me know if there is any way I can send you the data privately so you can see it. I can send you a full backup from fiddler so you can see the urls as well as the json etc.

dlarrick commented 4 years ago

That sounds like a good lead.

I've made a private repo here on GitHub and invited you as a collaborator. You should get an invite. Anything you push to that repo only the two of us can see.

As per usual, I will not have much time to look at this until the weekend.

omriasta commented 4 years ago

OK, I tried to package it as well as I could so you could see the changes. I posted it all in a zip file as an issue in the new repo.

cjkrolak commented 2 years ago

@omriasta are you still working on this issue? Maybe a related topic, I've noticed some latency between the time you make a setting change in the Kumocloud mobile app and that setting showing up in the kumocloud json. It seems like exiting the zone menu in the app and re-entering the zone menu does force a refresh, do you know how to force this refresh, instead of waiting for the polling timer?

I haven't setup a sniffer yet to look for myself, I'm hoping you've already know the answer.