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

Panasonic TV TX-65FZW804 not working correctly #9

Open
noodles101 opened this issue Mar 23, 2019 · 17 comments
Open

Panasonic TV TX-65FZW804 not working correctly #9

noodles101 opened this issue Mar 23, 2019 · 17 comments

Comments

@noodles101
Copy link

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.

Log Details (ERROR)
Fri Mar 22 2019 08:38:03 GMT+0100 (CET)

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 122, 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 188, in handle_service
    self._platforms.values(), func, call, service_name
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py", line 314, in entity_service_call
    future.result()  # pop exception if have
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py", line 328, 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/media_player/panasonic_viera.py", line 150, in turn_off
    self._remote.turn_off()
  File "/usr/local/lib/python3.7/site-packages/panasonic_viera/__init__.py", line 238, in turn_off
    self.send_key(Keys.power)
  File "/usr/local/lib/python3.7/site-packages/panasonic_viera/__init__.py", line 234, in send_key
    'X_SendKey', params)
  File "/usr/local/lib/python3.7/site-packages/panasonic_viera/__init__.py", line 132, 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

Thanks for your help.

@ngws
Copy link
Contributor

ngws commented Mar 24, 2019

@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.

@ngws
Copy link
Contributor

ngws commented Mar 26, 2019

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 X_RequestAuth SOAP action, inside the X_AuthInfo tag. The plaintext content of X_AuthInfo is in this format: <X_PinCode>1234</X_PinCode>

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>

@ngws
Copy link
Contributor

ngws commented Mar 27, 2019

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.

@florianholzapfel
Copy link
Owner

hey nick, thank you for your awesome work so far.

@ngws
Copy link
Contributor

ngws commented Mar 28, 2019

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.

@noodles101
Copy link
Author

noodles101 commented May 3, 2019

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

@noodles101
Copy link
Author

@florianholzapfel & @ngws: Hi there. Any progress to release the fixes? Is there a way that I can help?

@ngws
Copy link
Contributor

ngws commented Jun 30, 2019

@noodles101 I've created a pull request to merge my updated branch into the official repo here.

@nodecentral
Copy link

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.

@Flyingfufu
Copy link

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....

@noodles101
Copy link
Author

The merge request #15 is still open and not merged.

@mabulaza
Copy link

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 <X_ApplicationId>....?

@basicmonkey
Copy link

basicmonkey commented Jan 1, 2020

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 <X_ApplicationId>....?

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 <X_ApplicationId> line has a factor of 16. I just added an extra block of 16 nulls for mods of 0.

@basicmonkey
Copy link

@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!

@dduck669
Copy link

dduck669 commented Jan 30, 2020

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

@joogps
Copy link

joogps commented Apr 26, 2020

@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

:)

@noodles101
Copy link
Author

@joogps thanks for the information. I will try to upgrade soon.

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

No branches or pull requests

9 participants