Logic Machine Forum
https digest call to philips tv (jointSPACE) - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: https digest call to philips tv (jointSPACE) (/showthread.php?tid=2266)



https digest call to philips tv (jointSPACE) - ralwet - 05.10.2019

Hi

I own a Philips TV wich is supporting jointSPACE Rest-Calls. And I can do rest-calls to my Philips-TV with Curl without problems:
The folowing Curl-Example is successfully returning  the "Powerstate" of my TV ("tv on" or "tv off").
  • The Call is using digest authorization
  • The Call is using https
  • Insecure server connections has to be allowed ("-k" - because the TV is using a self signed certificate)
  • Beside SSL, it connects to Port 1926 where the jointSPACE API is answering the requests.

Code:
curl -XGET -u <user>:<password> https://192.168.3.123:1926/powerstate -k --digest -v

Now I try to do this curl-call on my SpaceLynk with Lua. So far it is not working. It returns code 404.
I also tried to use the Erwin's digest-example:  https://forum.logicmachine.net/showthread.php?tid=237&highlight=mpm_digest 3
But no luck so far Sad
Can somebody help me by providing a valid lua code which is doing this curl-call?

Thanks!


RE: https digest call to philips tv (jointSPACE) - Habib - 05.10.2019

Hi,

I've no experiance with your problem, but what I've seen is, that you need an HTTPS connection and Erwin Solution use only http?
You've seen that?


RE: https digest call to philips tv (jointSPACE) - ralwet - 05.10.2019

(05.10.2019, 12:21)Habib Wrote: Hi,

I've no experiance with your problem, but what I've seen is, that you need an HTTPS connection and Erwin Solution use only http?
You've seen that?

Hi Habib

Oh, yes. I forget to mention, that i adaptet Erwin's Solution to https (line 366). But this is not working Sad
Code:
local s_http = require 'ssl.https'
--local s_http = require "socket.http"



RE: https digest call to philips tv (jointSPACE) - admin - 08.10.2019

Try digest code from this post:
https://forum.logicmachine.net/showthread.php?tid=1061&pid=9364#pid9364

Replace
Code:
local skthttp = require('socket.http')
with
Code:
local skthttp = require('ssl.https')



RE: https digest call to philips tv (jointSPACE) - ralwet - 08.10.2019

Thanks for you help! Unfortunately using this script I get again a 404.

Code:
local skthttp = require('ssl.https')
--local skthttp = require('socket.http')
local skturl = require('socket.url')
local ltn12 = require('ltn12')
local md5sum = require('encdec').md5

local hash = function(...)
  return md5sum(table.concat({...}, ':'))
end

local parse_header = function(header)
  local result = {}
  for key, value in (header .. ','):gmatch('(%w+)=(.-),') do
    if value:sub(1, 1) == '"' then -- strip quotes
      result[ key:lower() ] = value:sub(2, -2)
    else
      result[ key:lower() ] = value
    end
  end
  return result
end

local make_digest_header = function(headers)
  local digest = {}
  for _, header in ipairs(headers) do
    if not header.unquote then
      header[ 2 ] = '"' .. header[ 2 ] .. '"'
    end

    digest[ #digest + 1 ] = header[ 1 ] .. '=' .. header[ 2 ]
  end
  return 'Digest ' .. table.concat(digest, ', ')
end

local _request = function(req)
  if not req.url then
    return nil, 'missing url'
  end

  local url = skturl.parse(req.url)
  local user, password = url.user, url.password
  if not user or not password then
    return nil, 'missing credentials in url'
  end

  url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil
  req.url = skturl.build(url)
  local source
  if req.source then
    local chunks = {}
    local capture = function(chunk)
      if chunk then
        chunks[ #chunks + 1 ] = chunk
      end
      return chunk
    end
    local chunk_id = 0
    source = function()
      chunk_id = chunk_id + 1
      return chunks[ chunk_id ]
    end
    req.source = ltn12.source.chain(req.source, capture)
  end
  local body, code, hdrs = skthttp.request(req)
  if code == 401 and hdrs['www-authenticate'] then
    local ht = parse_header(hdrs['www-authenticate'])
    if not ht.realm or not ht.nonce then
      return nil, 'missing realm/nonce from response'
    end
    if ht.qop ~= 'auth' then
      return nil, 'unsupported qop ' .. tostring(ht.qop)
    end
    if ht.algorithm and ht.algorithm:lower() ~= 'md5' then
      return nil, 'unsupported algo ' .. tostring(ht.algorithm)
    end
    local nc = '00000001'
    local cnonce = string.format('%08x', os.time())
    local uri = skturl.build({ path = url.path, query = url.query })
    local method = req.method or 'GET'
    local response = hash(
      hash(user, ht.realm, password),
      ht.nonce,
      nc,
      cnonce,
      'auth',
      hash(method, uri)
    )
    req.headers = req.headers or {}
    local auth = {
      { 'username', user },
      { 'realm', ht.realm },
      { 'nonce', ht.nonce },
      { 'uri', uri },
      { 'cnonce', cnonce },
      { 'nc', nc, unquote = true },
      { 'qop', 'auth' },
      { 'algorithm', 'MD5' },
      { 'response', response },
    }
    if ht.opaque then
      table.insert(auth, { 'opaque', ht.opaque })
    end
    req.headers.authorization = make_digest_header(auth)
    if not req.headers.cookie and hdrs['set-cookie'] then
      -- not really correct but enough for httpbin
      local cookie = (hdrs['set-cookie'] .. ';'):match('(.-=.-)[;,]')
      if cookie then
        req.headers.cookie = '$Version: 0; ' .. cookie .. ';'
      end
    end
    if req.source then
      req.source = source
    end
    body, code, hdrs = skthttp.request(req)
  end

  return body, code, hdrs
end

local request = function(url)
  local t = type(url)
  if t == 'table' then
    return _request(table.clone(url))
  elseif t == 'string' then
    local req = {}
    local _, code, headers = _request({ url = url, sink = ltn12.sink.table(req) })
    return table.concat(req), code, headers
  end
end

url = 'https://<USER>:<PASSWORD>@192.168.3.123:1926/powerstate'
image, err, hdrs = request(url)
log (image)
log (err)
log (hdrs)

The url works in a Web-Browser. But not in this Script Sad

This Curl-Call does the Job too (-k Param is necessery. This allows self signed certificate):
Code:
curl -XGET -u <user>:<password> https://192.168.3.123:1926/powerstate -k --digest -v

Any further Ideas?


RE: https digest call to philips tv (jointSPACE) - admin - 08.10.2019

Can you run curl in verbose mode (-v) and post the output?


RE: https digest call to philips tv (jointSPACE) - ralwet - 08.10.2019

Curl-Request with verbose:
Code:
curl -XGET -u <user>:<password> https://192.168.3.151:1926/powerstate -k --digest -v

Response:
Code:
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying 192.168.3.151...
* TCP_NODELAY set
* Connected to 192.168.3.151 (192.168.3.151) port 1926 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: C=IN; ST=Karnataka; L=Bengaluru; O=TP VISION India Pvt. Ltd.; OU=Smart TV; CN=restfultv.tpvision.com
*  start date: May 19 10:51:53 2015 GMT
*  expire date: Oct  4 10:51:53 2042 GMT
*  issuer: C=IN; ST=Karnataka; L=Bengaluru; O=TP VISION India Pvt. Ltd.; OU=Smart TV; CN=ca.tpvision.com
*  SSL certificate verify result: self signed certificate in certificate chain (19), continuing anyway.
* Server auth using Digest with user '<user>'
> GET /powerstate HTTP/1.1
> Host: 192.168.3.151:1926
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Tue, 08 Oct 2019 16:11:17 GMT+00:00
< Accept-Ranges: bytes
< Server: Restlet-Framework/2.3.12
< WWW-Authenticate: Digest realm="XTV", domain="/", nonce="MTU3MDU1MTA3NzcyNTplZGFiYzY4NWNmMDAzNTM2YjRiMzJjYmQxMGM1YzY0OA==", algorithm=MD5, qop="auth"
< Content-Length: 424
< Content-Type: text/html; charset=UTF-8
<
* Ignoring the response-body
* Connection #0 to host 192.168.3.151 left intact
* Issue another request to this URL: 'https://192.168.3.151:1926/powerstate'
* Found bundle for host 192.168.3.151: 0x55f0b0f70a50 [can pipeline]
* Re-using existing connection! (#0) with host 192.168.3.151
* Connected to 192.168.3.151 (192.168.3.151) port 1926 (#0)
* Server auth using Digest with user '<user>'
> GET /powerstate HTTP/1.1
> Host: 192.168.3.151:1926
> Authorization: Digest username="<user>", realm="XTV", nonce="MTU3MDU1MTA3NzcyNTplZGFiYzY4NWNmMDAzNTM2YjRiMzJjYmQxMGM1YzY0OA==", uri="/powerstate", cnonce="NzVmNDdjYzYwMDBiYjU0Y2FmMjFjNmM5ZjA4MWE1NDE=", nc=00000001, qop=auth, response="ad84deaff1b8f9cf07c651a689543991", algorithm="MD5"
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 08 Oct 2019 16:11:17 GMT+00:00
< Accept-Ranges: bytes
< Server: Restlet-Framework/2.3.12
< Access-Control-Allow-Origin: *
< Content-Length: 19
< Content-Type: application/json; charset=UTF-8
<
* Connection #0 to host 192.168.3.151 left intact
{"powerstate":"On"}



RE: https digest call to philips tv (jointSPACE) - admin - 09.10.2019

Looks correct. Do you get error 401 with your script if you specify incorrect password? Can you provide remote access to your device?


RE: https digest call to philips tv (jointSPACE) - ralwet - 09.10.2019

I get e 404

Script:
Code:
local skthttp = require('ssl.https')
local skturl = require('socket.url')
local ltn12 = require('ltn12')
local md5sum = require('encdec').md5

local hash = function(...)
  return md5sum(table.concat({...}, ':'))
end

local parse_header = function(header)
  local result = {}
  for key, value in (header .. ','):gmatch('(%w+)=(.-),') do
    if value:sub(1, 1) == '"' then -- strip quotes
      result[ key:lower() ] = value:sub(2, -2)
    else
      result[ key:lower() ] = value
    end
  end
  return result
end

local make_digest_header = function(headers)
  local digest = {}
  for _, header in ipairs(headers) do
    if not header.unquote then
      header[ 2 ] = '"' .. header[ 2 ] .. '"'
    end

    digest[ #digest + 1 ] = header[ 1 ] .. '=' .. header[ 2 ]
  end
  return 'Digest ' .. table.concat(digest, ', ')
end

local _request = function(req)
  if not req.url then
    return nil, 'missing url'
  end

  local url = skturl.parse(req.url)
  local user, password = url.user, url.password
  if not user or not password then
    return nil, 'missing credentials in url'
  end

  url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil
  req.url = skturl.build(url)
  local source
  if req.source then
    local chunks = {}
    local capture = function(chunk)
      if chunk then
        chunks[ #chunks + 1 ] = chunk
      end
      return chunk
    end
    local chunk_id = 0
    source = function()
      chunk_id = chunk_id + 1
      return chunks[ chunk_id ]
    end
    req.source = ltn12.source.chain(req.source, capture)
  end
  local body, code, hdrs = skthttp.request(req)
  if code == 401 and hdrs['www-authenticate'] then
    local ht = parse_header(hdrs['www-authenticate'])
    if not ht.realm or not ht.nonce then
      return nil, 'missing realm/nonce from response'
    end
    if ht.qop ~= 'auth' then
      return nil, 'unsupported qop ' .. tostring(ht.qop)
    end
    if ht.algorithm and ht.algorithm:lower() ~= 'md5' then
      return nil, 'unsupported algo ' .. tostring(ht.algorithm)
    end
    local nc = '00000001'
    local cnonce = string.format('%08x', os.time())
    local uri = skturl.build({ path = url.path, query = url.query })
    local method = req.method or 'GET'
    local response = hash(
      hash(user, ht.realm, password),
      ht.nonce,
      nc,
      cnonce,
      'auth',
      hash(method, uri)
    )
    req.headers = req.headers or {}
    local auth = {
      { 'username', user },
      { 'realm', ht.realm },
      { 'nonce', ht.nonce },
      { 'uri', uri },
      { 'cnonce', cnonce },
      { 'nc', nc, unquote = true },
      { 'qop', 'auth' },
      { 'algorithm', 'MD5' },
      { 'response', response },
    }
    if ht.opaque then
      table.insert(auth, { 'opaque', ht.opaque })
    end
    req.headers.authorization = make_digest_header(auth)
    if not req.headers.cookie and hdrs['set-cookie'] then
      -- not really correct but enough for httpbin
      local cookie = (hdrs['set-cookie'] .. ';'):match('(.-=.-)[;,]')
      if cookie then
        req.headers.cookie = '$Version: 0; ' .. cookie .. ';'
      end
    end
    if req.source then
      req.source = source
    end
    body, code, hdrs = skthttp.request(req)
  end

  return body, code, hdrs
end

local request = function(url)
  local t = type(url)
  if t == 'table' then
    return _request(table.clone(url))
  elseif t == 'string' then
    local req = {}
    local _, code, headers = _request({ url = url, sink = ltn12.sink.table(req) })
    return table.concat(req), code, headers
  end
end

url = 'https://<user>:<password>@192.168.3.151:1926/powerstate'
image, err, hdrs = request(url)
log (image)
log (err)
log (hdrs)



SpaceLynk Log:
Code:
GetStatePhilipsTV 09.10.2019 11:12:37
* string: <html>
<head>
   <title>Status page</title>
</head>
<body style="font-family: sans-serif;">
<p style="font-size: 1.2em;font-weight: bold;margin: 1em 0px;">Not Found</p>
<p>The server has not found anything matching the request URI</p>
<p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
</body>
</html>

GetStatePhilipsTV 09.10.2019 11:12:37
* number: 404
GetStatePhilipsTV 09.10.2019 11:12:37
* table:
["date"]
  * string: Wed, 09 Oct 2019 09:12:37 GMT+00:00
["content-length"]
  * string: 439
["content-type"]
  * string: text/html; charset=UTF-8
["server"]
  * string: Restlet-Framework/2.3.12

Thank you very much for your Support Smile But a Remote-Session is not necessary. If we don't find a quick solution on the SpaceLynk I will go for a workaraound (using a Linux-Device for calling the TV).


RE: https digest call to philips tv (jointSPACE) - admin - 09.10.2019

This issue might appear due to missing port part in HTTP host header.

Try replacing line 125:
Code:
local _, code, headers = _request({ url = url, sink = ltn12.sink.table(req), headers = { host = '192.168.3.151:1926' } })



RE: https digest call to philips tv (jointSPACE) - ralwet - 09.10.2019

Yes, now it works! Thank you very much!! Smile