sampsyo / wideq

reverse-engineered client for the LG SmartThinQ API
https://pypi.org/project/wideq/
MIT License
332 stars 158 forks source link

wrong language code led to SSL error #55

Closed hmoffatt closed 4 years ago

hmoffatt commented 5 years ago

I'm in Australia and my LG account is set to Australia.

I was trying to login with country set to AU, but no language specified. I got a token from LG successfully, but then an SSL protocol error when it tried to access the API.

Eventually I tried adding the country code en-AU, didn't even login again but used the same token URL, and then it worked. Is it possible to handle this condition better than pages of SSL errors?

$ python3 example.py -c US
Log in here:
https://us.m.lgaccount.com/login/sign_in?country=US&language=en-US&svcCode=SVC202&authSvr=oauth2&client_id=LGAO221A02&division=ha&grant_type=password
Then paste the URL where the browser is redirected:
https://us.m.lgaccount.com/member/iabClose?access_token=<>&refresh_token=<>&oauth2_backend_url=https://kr.lgeapi.com/
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/urllib3/contrib/pyopenssl.py", line 453, in wrap_socket
    cnx.do_handshake()
  File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1915, in do_handshake
    self._raise_ssl_error(self._ssl, result)
  File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1647, in _raise_ssl_error
    _raise_current_error()
  File "/usr/lib/python3/dist-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
    raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'ssl_choose_client_version', 'unsupported protocol')]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 600, in urlopen
    chunked=chunked)
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 343, in _make_request
    self._validate_conn(conn)
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 841, in _validate_conn
    conn.connect()
  File "/usr/lib/python3/dist-packages/urllib3/connection.py", line 344, in connect
    ssl_context=context)
  File "/usr/lib/python3/dist-packages/urllib3/util/ssl_.py", line 344, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3/dist-packages/urllib3/contrib/pyopenssl.py", line 459, in wrap_socket
    raise ssl.SSLError('bad handshake: %r' % e)
ssl.SSLError: ("bad handshake: Error([('SSL routines', 'ssl_choose_client_version', 'unsupported protocol')])",)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/requests/adapters.py", line 449, in send
    timeout=timeout
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 638, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/usr/lib/python3/dist-packages/urllib3/util/retry.py", line 398, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='aic.lgthinq.com', port=46030): Max retries exceeded with url: /api/member/login (Caused by SSLError(SSLError("bad handshake:     Error([('SSL routines', 'ssl_choose_client_version', 'unsupported protocol')])")))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "example.py", line 225, in <module>
    main()
  File "example.py", line 221, in main
    example(args.country, args.language, args.cmd, args.args)
  File "example.py", line 181, in example
    example_command(client, cmd, args)
  File "example.py", line 157, in example_command
    func(client, *args)
  File "example.py", line 26, in ls
    for device in client.devices:
  File "/home/hamish/src/wideq/wideq/client.py", line 123, in devices
    self._devices = self.session.get_devices()
  File "/home/hamish/src/wideq/wideq/client.py", line 114, in session
    self._session, self._devices = self.auth.start_session()
  File "/home/hamish/src/wideq/wideq/core.py", line 273, in start_session
    self.gateway.country, self.gateway.language)
  File "/home/hamish/src/wideq/wideq/core.py", line 191, in login
    return lgedm_post(url, data)
  File "/home/hamish/src/wideq/wideq/core.py", line 120, in lgedm_post
    res = requests.post(url, json={DATA_ROOT: data}, headers=headers)
  File "/usr/lib/python3/dist-packages/requests/api.py", line 116, in post
    return request('post', url, data=data, json=json, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/api.py", line 60, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/lib/python3/dist-packages/requests/sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/adapters.py", line 514, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='aic.lgthinq.com', port=46030): Max retries exceeded with url: /api/member/login (Caused by SSLError(SSLError("bad handshake:     Error([('SSL routines', 'ssl_choose_client_version', 'unsupported protocol')])")))
sampsyo commented 5 years ago

Huh; that's very interesting! It does seem like a good idea to give an informative error message, but there is some more detective work to do here. Any idea why this leads to an SSL error? Does the client get redirected to a bogus host when the language is wrong, and that host has a bad SSL configuration?

If we can figure out what exactly is going on, then we could perhaps distinguish this error earlier and tell it apart from other certificate errors.

hmoffatt commented 5 years ago

I turned on logging in urllib3 and here's what I got.

Firstly with the wrong language code:

DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kic.lgthinq.com:46030
DEBUG:urllib3.connectionpool:https://kic.lgthinq.com:46030 "POST /api/common/gatewayUriList HTTP/1.1" 200 1255
Log in here:
https://us.m.lgaccount.com/login/sign_in?country=AU&language=en-US&svcCode=SVC202&authSvr=oauth2&client_id=LGAO221A02&division=ha&grant_type=password
Then paste the URL where the browser is redirected:
https://us.m.lgaccount.com/login/iabClose?access_token=<>&refresh_token=<>&oauth2_backend_url=https://kr.lgeapi.com/
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): aic.lgthinq.com:46030
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/urllib3/contrib/pyopenssl.py", line 453, in wrap_socket
    cnx.do_handshake()
  File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1915, in do_handshake
    self._raise_ssl_error(self._ssl, result)
  File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1647, in _raise_ssl_error
    _raise_current_error()
  File "/usr/lib/python3/dist-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
    raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'ssl_choose_client_version', 'unsupported protocol')]

then with the correct country code:

DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kic.lgthinq.com:46030
DEBUG:urllib3.connectionpool:https://kic.lgthinq.com:46030 "POST /api/common/gatewayUriList HTTP/1.1" 200 1232
Log in here:
https://au.m.lgaccount.com/login/sign_in?country=AU&language=en-AU&svcCode=SVC202&authSvr=oauth2&client_id=LGAO221A02&division=ha&grant_type=password
Then paste the URL where the browser is redirected:
https://us.m.lgaccount.com/login/iabClose?access_token=<>&refresh_token=<>&oauth2_backend_url=https://kr.lgeapi.com/
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kic.lgthinq.com:46030
DEBUG:urllib3.connectionpool:https://kic.lgthinq.com:46030 "POST /api/member/login HTTP/1.1" 200 2142
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kic.lgthinq.com:46030
DEBUG:urllib3.connectionpool:https://kic.lgthinq.com:46030 "POST /api/device/deviceList HTTP/1.1" 200 1838
88ce8430-111a-11e8-b9bb-044eaf87a0e4: Dishwasher (DISHWASHER D2201)

However I think I'm missing something because I don't see how it decided on a different URL, when the login link was the same (literally, I just pasted it in again, didn't login again).

sampsyo commented 5 years ago

Hmm; Indeed—it looks like the wrong language code led to a connection to aic.lgthinq.com:46030, whereas the right one led to contacting kic.lgthinq.com:46030.

The underlying reason is that the LG API uses this funky gateway system where you have to ask a central service which server to connect to. You give it a country and a language code and it tells you where to log in. I wrote a function called gateway_info in wideq.core that wraps this lookup. You can see how the service responds for various combinations of codes:

>>> import wideq
>>> wideq.core.gateway_info('AU', 'en-US')['thinqUri']
'https://aic.lgthinq.com:46030/api'
>>> wideq.core.gateway_info('AU', 'en-AU')['thinqUri']
'https://kic.lgthinq.com:46030/api'
>>> wideq.core.gateway_info('US', 'en-US')['thinqUri']
'https://aic.lgthinq.com:46030/api'
>>> wideq.core.gateway_info('US', 'en-AU')['thinqUri']
'https://aic.lgthinq.com:46030/api'

So I guess aic might be the US service, and specifying en-US erroneously directs you there? Still not sure why that eventually turns into an SSL error, though.

hmoffatt commented 5 years ago

I can't connect to the aic.lgthinq.com:46030 URL at all from Python. gnutls-cli connects OK, but reports it is using TLS 1.0. When I connect to the kic.lgthinq.com:46030 URL I get TLS 1.2.

I'm using Python on Debian buster. If I try it it from python-requests on Debian stretch, it works. I think Python or OpenSSL must be enforcing a minimum TLS version > 1.0 which is causing this failure.

$ python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> requests.get('https://aic.lgthinq.com:46030')
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/urllib3/contrib/pyopenssl.py", line 453, in wrap_socket
    cnx.do_handshake()
  File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1915, in do_handshake
    self._raise_ssl_error(self._ssl, result)
  File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1647, in _raise_ssl_error
    _raise_current_error()
  File "/usr/lib/python3/dist-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
    raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'ssl_choose_client_version', 'unsupported protocol')]
hmoffatt commented 5 years ago

It looks like this might be a Debian-specific change to disallow the old TLS versions by default. You might be able to re-enable them. Here's some links I found.

https://bugs.python.org/issue31453 https://stackoverflow.com/questions/50707974/force-tls-1-0-connection-with-requests?noredirect=1&lq=1

So it looks like it's nothing to do with the wrong language, just luck for me that en-AU is on a TLSv1.2-capable server and en-US is not.

Cortex33 commented 4 years ago

Easy fix to that on debian (work on Ubuntu too, perhaps on all other linux distro). sudo nano /etc/ssl/openssl.cnf Search and replace : MinProtocol = TLSv1.X by MinProtocol = TLSv1.0

Restart your server, follow the process to install wideq, everything will work fine.

hmoffatt commented 4 years ago

You don't need to restart, that file will be read by each program that uses OpenSSL when it starts.

I was hoping there would be a way to override this from Python so that it could be added to this library rather than the users having to reconfigure their systems. But really it must be fixed by LG.

I think this issue can be closed.

Sent from Nine


From: Cortex33 notifications@github.com Sent: Sunday, 1 December 2019 16:41 To: sampsyo/wideq Cc: Hamish Moffatt; Author Subject: Re: [sampsyo/wideq] wrong language code led to SSL error (#55)

Easy fix to that on debian (work on Ubuntu too, perhaps on all other linux distro). sudo nano /etc/ssl/openssl.cnf Search and replace : MinProtocol = TLSv1.X by MinProtocol = TLSv1.0

Restart your server, follow the process to install wideq, everything will work fine.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.