Open noodles101 opened 5 years ago
@florianholzapfel
The new TVs use encryption. You need to ask the TV to display a pin code, the TV then gives you back a challenge key, which (I believe) you then use to encrypt the pin code displayed on the TV and send it back.
The first request uses "X_DisplayPinCode", like this:
POST http://com-mid1:55000/nrc/control_0 HTTP/1.1
Accept: text/xml
Cache-Control: no-cache
Pragma: no-cache
SOAPACTION: "urn:panasonic-com:service:p00NetworkControl:1#X_DisplayPinCode"
Content-Length: 347
Content-Type: text/xml;charset="utf-8"
Host: com-mid1:55000
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:X_DisplayPinCode xmlns:u="urn:panasonic-com:service:p00NetworkControl:1">
<X_DeviceName>My Test Remote Control</X_DeviceName>
</u:X_DisplayPinCode>
</s:Body>
</s:Envelope>
You'll get a response like this:
HTTP/1.1 200 OK
CONTENT-LENGTH: 379
Content-Type: text/xml; charset="utf-8"
EXT:
SERVER: Panasonic-VIErA/1, UPnP/1.0, Panasonic MIL DLNA SERVER
CONNECTION: close
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:X_DisplayPinCodeResponse xmlns:u="urn:panasonic-com:service:p00NetworkControl:1">
<X_ChallengeKey>A2ZrfRk5oJarHc6IHL5BpQ==</X_ChallengeKey>
</u:X_DisplayPinCodeResponse>
</s:Body>
</s:Envelope>
I'm not sure what you do with the challenge key. I had a quick look at the Panasonic TV Remote 2 APK and it looks like they use AES-CBC, but haven't had time to see if they use that for the pin code challenge auth. I'd suggest looking more at the APK to see exactly how they do it.
Anyway, the next request would like something like this using "X_RequestAuth" with "X_AuthInfo" containing the encrypted pin code using the challenge key:
POST http://com-mid1:55000/nrc/control_0 HTTP/1.1
Accept: text/xml
Cache-Control: no-cache
Pragma: no-cache
SOAPACTION: "urn:panasonic-com:service:p00NetworkControl:1#X_RequestAuth"
Content-Length: 346
Content-Type: text/xml;charset="utf-8"
Host: com-mid1:55000
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:X_RequestAuth xmlns:u="urn:panasonic-com:service:p00NetworkControl:1">
<X_AuthInfo>qOSdwW7kfBnTDjcVbfjNVw==</X_AuthInfo>
</u:X_RequestAuth>
</s:Body>
</s:Envelope>
That's about as far as I've looked, and I probably won't get time to look at it much more. Hopefully this is helpful though.
Following up...
After looking at the ELF binary that's responsible for the encryption, it looks like they use AES-CBC where the X_ChallengeKey is used for the IV (after being base64 decoded of course). The AES encryption key is derived from the IV like this:
for (int i=0; i < 16; i+=4) {
key[i] = ~iv[i + 3];
key[i + 1] = ~iv[i + 2];
key[i + 2] = ~iv[i + 1];
key[i + 3] = ~iv[i];
}
As I mentioned before, the pin code must be encrypted (with the AES-CBC key algo above, and then base64-encoded) and sent in an _XRequestAuth SOAP action, inside the _XAuthInfo tag. The plaintext content of _XAuthInfo is in this format: _
So, the plaintext response to send the pin code would look like this:
POST http://com-mid1:55000/nrc/control_0 HTTP/1.1
Accept: text/xml
Cache-Control: no-cache
Pragma: no-cache
SOAPACTION: "urn:panasonic-com:service:p00NetworkControl:1#X_RequestAuth"
Content-Length: 346
Content-Type: text/xml;charset="utf-8"
Host: com-mid1:55000
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:X_RequestAuth xmlns:u="urn:panasonic-com:service:p00NetworkControl:1">
<X_AuthInfo><X_PinCode>1234</X_PinCode></X_AuthInfo>
</u:X_RequestAuth>
</s:Body>
</s:Envelope>
And the encrypted version would be something like this:
POST http://com-mid1:55000/nrc/control_0 HTTP/1.1
Accept: text/xml
Cache-Control: no-cache
Pragma: no-cache
SOAPACTION: "urn:panasonic-com:service:p00NetworkControl:1#X_RequestAuth"
Content-Length: 346
Content-Type: text/xml;charset="utf-8"
Host: com-mid1:55000
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:X_RequestAuth xmlns:u="urn:panasonic-com:service:p00NetworkControl:1">
<X_AuthInfo>MSakYsXqaZCVRRHRXm6RP9A7O4SB4/XIkB1mcpxWRYo=</X_AuthInfo>
</u:X_RequestAuth>
</s:Body>
</s:Envelope>
OK I got it working, it was a little more complicated. Not only is the encryption key derived from the IV, but also there is a 16 byte header at the start of the payload (actually 12 random bytes and 4 bytes for the payload length in big endian). And there is also an HMAC signature to calculate, where the HMAC key is also derived from the IV.
Anyway, working example code below.
import binascii
import base64
import hmac, hashlib
from Crypto.Cipher import AES
# Example challenge (which is our IV)
iv = base64.b64decode("mUQdS7/RyJTMsiojPz9i1Q==")
# Get character codes from IV bytes
iv_vals = [ord(c) for c in iv]
# Initialise key character codes array
key_vals = [0] * 16
# Derive key from IV
i = 0
while i < 16:
key_vals[i] = ~iv_vals[i + 3] & 0xFF
key_vals[i + 1] = ~iv_vals[i + 2] & 0xFF
key_vals[i + 2] = ~iv_vals[i + 1] & 0xFF
key_vals[i + 3] = ~iv_vals[i] & 0xFF
i += 4
# Convert our key character codes to bytes
key = ''.join(chr(c) for c in key_vals)
# Initialise HMAC key mask (taken from libtvconnect.so)
hmac_key_mask_vals = [ord(c) for c in binascii.unhexlify("15C95AC2B08AA7EB4E228F811E34D04FA54BA7DCAC9879FA8ACDA3FC244F3854")]
# Initialise HMAC key character codes array
hmac_vals = [0] * 32
# Calculate HMAC key using HMAC key mask and IV
i = 0
while i < 32:
hmac_vals[i] = hmac_key_mask_vals[i] ^ iv_vals[(i + 2) & 0xF]
hmac_vals[i + 1] = hmac_key_mask_vals[i + 1] ^ iv_vals[(i + 3) & 0xF]
hmac_vals[i + 2] = hmac_key_mask_vals[i + 2] ^ iv_vals[i & 0xF]
hmac_vals[i + 3] = hmac_key_mask_vals[i + 3] ^ iv_vals[(i + 1) & 0xF]
i += 4
# Convert our HMAC key character codes to bytes
hmac_key = ''.join(chr(c) for c in hmac_vals)
# This is our plaintext SOAP argument for the pin code shown on the TV
authinfo = "<X_PinCode>4410</X_PinCode>"
# First 12 bytes are randomised, let's just set them to 0 because it doesn't matter
payload = "000000000000"
# The next 4 bytes contain the plaintext (SOAP arg) length in big endian
n = len(authinfo)
payload += chr(n >> 24)
payload += chr((n >> 16) & 0xFF)
payload += chr((n >> 8) & 0xFF)
payload += chr(n & 0xFF)
# Now we concatenate our payload, which is starting at byte 17 of the payload
payload += authinfo
# Let's encrypt it with AES-CBC! We need to make sure we pad it to a multiple of 16 bytes beforehand
aes = AES.new(key, AES.MODE_CBC, iv)
ciphertext = aes.encrypt(pad(payload))
# Calculate the HMAC-SHA-256 signature of our encrypted payload
sig = hmac.new(hmac_key, ciphertext, hashlib.sha256).digest()
# Concatenate the HMAC signature to the encrypted payload and base64 encode it, and we're done!
encrypted_payload = base64.b64encode(ciphertext + sig)
I successfully authenticated with my TV using this, and got back the response below. I've redacted the auth result content for privacy reasons:
HTTP/1.1 200 OK
CONTENT-LENGTH: 561
Content-Type: text/xml; charset="utf-8"
EXT:
SERVER: Panasonic-VIErA/1, UPnP/1.0, Panasonic MIL DLNA SERVER
CONNECTION: close
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:X_RequestAuthResponse xmlns:u="urn:panasonic-com:service:p00NetworkControl:1">
<X_AuthResult>4zOR0j5c...snip...</X_AuthResult>
</u:X_RequestAuthResponse>
</s:Body>
</s:Envelope>
I will carry on researching this soon, hopefully this week. I believe the rest should be quite easy, but I'm not sure exactly how sending encrypted commands works yet.
hey nick, thank you for your awesome work so far.
All done on my fork :), all changes here: https://github.com/ngws/panasonic-viera/blob/master/panasonic_viera/__init__.py
Tested with Python 2.7 and against a Panasonic TX-55FZ952B. I recommend testing with Python 3.x and ideally confirm that I didn't break anything for older TV models.
since HA 0.91 I'm unable to turn the TV on or off.
Today with HA 0.92.2 I also added the new parameter to my config and nothing happened except this exception:
Log Details (ERROR)
Fri May 03 2019 09:27:02 GMT+0200 (CEST)
HTTP Error 500: Internal Server Error
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/homeassistant/components/websocket_api/commands.py", line 121, in handle_call_service
connection.context(msg))
File "/usr/local/lib/python3.7/site-packages/homeassistant/core.py", line 1138, in async_call
self._execute_service(handler, service_call))
File "/usr/local/lib/python3.7/site-packages/homeassistant/core.py", line 1160, in _execute_service
await handler.func(service_call)
File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity_component.py", line 194, in handle_service
required_features
File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py", line 316, in entity_service_call
future.result() # pop exception if have
File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py", line 337, in _handle_service_platform_call
await getattr(entity, func)(**data)
File "/usr/local/lib/python3.7/concurrent/futures/thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "/usr/local/lib/python3.7/site-packages/homeassistant/components/panasonic_viera/media_player.py", line 153, in turn_off
self._remote.turn_off()
File "/usr/local/lib/python3.7/site-packages/panasonic_viera/__init__.py", line 239, in turn_off
self.send_key(Keys.power)
File "/usr/local/lib/python3.7/site-packages/panasonic_viera/__init__.py", line 235, in send_key
'X_SendKey', params)
File "/usr/local/lib/python3.7/site-packages/panasonic_viera/__init__.py", line 133, in soap_request
res = urlopen(req, timeout=5).read()
File "/usr/local/lib/python3.7/urllib/request.py", line 222, in urlopen
return opener.open(url, data, timeout)
File "/usr/local/lib/python3.7/urllib/request.py", line 531, in open
response = meth(req, response)
File "/usr/local/lib/python3.7/urllib/request.py", line 641, in http_response
'http', request, response, code, msg, hdrs)
File "/usr/local/lib/python3.7/urllib/request.py", line 569, in error
return self._call_chain(*args)
File "/usr/local/lib/python3.7/urllib/request.py", line 503, in _call_chain
result = func(*args)
File "/usr/local/lib/python3.7/urllib/request.py", line 649, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 500: Internal Server Error
Fehler beim Aufrufen des Service media_player/turn_off. HTTP Error 500: Internal Server Error
@florianholzapfel & @ngws: Hi there. Any progress to release the fixes? Is there a way that I can help?
@noodles101 I've created a pull request to merge my updated branch into the official repo here.
Hi @noodles101 and @florianholzapfel - i’ve been following this github and this issue/thread with interest. Is it all working now as expected ? I’d love to see this capability within Node Red.
Hello Guys,
I'm might be not fully in line, but I'm sure you can help.
I try to achieve the same, remote control a 2019 TV, but over HTTP SOAP commands. Thanks to all the above I'm able to get the Pin code appearing on the TV screen and the xml feedback with the Challenge Key. My issue is now to be able to get the AuthKey to be reinjected to finalize the connection. My main idea was first to get your python file, but I don't get the pin code appearing. Would there be any code available to get it by manually entering the pin and ChallengeKey ?
I'm starting in "coding", so any help will be highly appreciated....
The merge request #15 is still open and not merged.
Hello
I'm just trying to convert this VIERA communication to PERL (home automation).
I succeeded to receive the app_Id and the encryption key. I looks like the one mentioned here with padding at the end.
However I fail to receive the session ID. Is it correct to leave the app_id padded in the command
Hello I'm just trying to convert this VIERA communication to PERL (home automation). I succeeded to receive the app_Id and the encryption key. I looks like the one mentioned here with padding at the end. However I fail to receive the session ID. Is it correct to leave the app_id padded in the command
....?
I've adapted this code to Xojo and found the same issue. It was my padding function that wasn't adding a block of 16 nulls because the
@ngws Thank you so much for your work on this. I've used your workings and code to create this in Xojo (language of choice). Works great!
import binascii import base64 import hmac, hashlib from Crypto.Cipher import AES # Example challenge (which is our IV) iv = base64.b64decode("mUQdS7/RyJTMsiojPz9i1Q==") # Get character codes from IV bytes iv_vals = [ord(c) for c in iv] # Initialise key character codes array key_vals = [0] * 16 # Derive key from IV i = 0 while i < 16: key_vals[i] = ~iv_vals[i + 3] & 0xFF key_vals[i + 1] = ~iv_vals[i + 2] & 0xFF key_vals[i + 2] = ~iv_vals[i + 1] & 0xFF key_vals[i + 3] = ~iv_vals[i] & 0xFF i += 4 # Convert our key character codes to bytes key = ''.join(chr(c) for c in key_vals) # Initialise HMAC key mask (taken from libtvconnect.so) hmac_key_mask_vals = [ord(c) for c in binascii.unhexlify("15C95AC2B08AA7EB4E228F811E34D04FA54BA7DCAC9879FA8ACDA3FC244F3854")] # Initialise HMAC key character codes array hmac_vals = [0] * 32 # Calculate HMAC key using HMAC key mask and IV i = 0 while i < 32: hmac_vals[i] = hmac_key_mask_vals[i] ^ iv_vals[(i + 2) & 0xF] hmac_vals[i + 1] = hmac_key_mask_vals[i + 1] ^ iv_vals[(i + 3) & 0xF] hmac_vals[i + 2] = hmac_key_mask_vals[i + 2] ^ iv_vals[i & 0xF] hmac_vals[i + 3] = hmac_key_mask_vals[i + 3] ^ iv_vals[(i + 1) & 0xF] i += 4 # Convert our HMAC key character codes to bytes hmac_key = ''.join(chr(c) for c in hmac_vals) # This is our plaintext SOAP argument for the pin code shown on the TV authinfo = "<X_PinCode>4410</X_PinCode>" # First 12 bytes are randomised, let's just set them to 0 because it doesn't matter payload = "000000000000" # The next 4 bytes contain the plaintext (SOAP arg) length in big endian n = len(authinfo) payload += chr(n >> 24) payload += chr((n >> 16) & 0xFF) payload += chr((n >> 8) & 0xFF) payload += chr(n & 0xFF) # Now we concatenate our payload, which is starting at byte 17 of the payload payload += authinfo # Let's encrypt it with AES-CBC! We need to make sure we pad it to a multiple of 16 bytes beforehand aes = AES.new(key, AES.MODE_CBC, iv) ciphertext = aes.encrypt(pad(payload)) # Calculate the HMAC-SHA-256 signature of our encrypted payload sig = hmac.new(hmac_key, ciphertext, hashlib.sha256).digest() # Concatenate the HMAC signature to the encrypted payload and base64 encode it, and we're done! encrypted_payload = base64.b64encode(ciphertext + sig)
Is anybody able to convert it to a bash script? Thank you
@noodles101 Are you still having this issue? If you are, it will very likely be fixed with version 0.109 of Home Assistant, since it features an updated panasonic_viera
component with encryption support and more stability. More info here: home-assistant/core#33829 and here #26
:)
@joogps thanks for the information. I will try to upgrade soon.
Hi,
i’m trying to integrate my new tv Panasonic TX-65FZW804 on Home Assistant v0.90.1 on an Raspberry Pi 3 model B+.
The Home Assistant Repo ticket was closed because it must solved here in the library.
I also configured the MAC address like in the documentation. So I can turn the TV on and use volume up or down. I’m not able to turn it off or select a source. When I try to turn it off I get the http error code 500.
Thanks for your help.