This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

https digest call to philips tv (jointSPACE)
#1
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/showthrea...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!
Reply
#2
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?
Reply
#3
(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"
Reply
#4
Try digest code from this post:
https://forum.logicmachine.net/showthrea...64#pid9364

Replace
Code:
local skthttp = require('socket.http')
with
Code:
local skthttp = require('ssl.https')
Reply
#5
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?
Reply
#6
Can you run curl in verbose mode (-v) and post the output?
Reply
#7
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"}
Reply
#8
Looks correct. Do you get error 401 with your script if you specify incorrect password? Can you provide remote access to your device?
Reply
#9
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).
Reply
#10
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' } })
Reply
#11
Yes, now it works! Thank you very much!! Smile
Reply


Forum Jump: