Renew the handshake session 20 minutes before we think it will expire (#697)

* Renew the KLAP handshake session 20 minutes before we think it will expire

Currently we assumed the clocks were perfectly aligned and the handshake
session lasted 20 hours.  We now add a 20 minute buffer

* use timeout cookie when available
This commit is contained in:
J. Nick Koston 2024-01-23 23:11:27 -10:00 committed by GitHub
parent 24c645746e
commit bab40d43e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 31 additions and 11 deletions

View File

@ -36,6 +36,10 @@ from .protocol import DEFAULT_CREDENTIALS, BaseTransport, get_default_credential
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ONE_DAY_SECONDS = 86400
SESSION_EXPIRE_BUFFER_SECONDS = 60 * 20
def _sha1(payload: bytes) -> str: def _sha1(payload: bytes) -> str:
sha1_algo = hashlib.sha1() # noqa: S324 sha1_algo = hashlib.sha1() # noqa: S324
sha1_algo.update(payload) sha1_algo.update(payload)
@ -59,6 +63,7 @@ class AesTransport(BaseTransport):
DEFAULT_PORT: int = 80 DEFAULT_PORT: int = 80
SESSION_COOKIE_NAME = "TP_SESSIONID" SESSION_COOKIE_NAME = "TP_SESSIONID"
TIMEOUT_COOKIE_NAME = "TIMEOUT"
COMMON_HEADERS = { COMMON_HEADERS = {
"Content-Type": "application/json", "Content-Type": "application/json",
"requestByApp": "true", "requestByApp": "true",
@ -254,7 +259,9 @@ class AesTransport(BaseTransport):
**self.COMMON_HEADERS, **self.COMMON_HEADERS,
self.CONTENT_LENGTH: str(self.KEY_PAIR_CONTENT_LENGTH), self.CONTENT_LENGTH: str(self.KEY_PAIR_CONTENT_LENGTH),
} }
status_code, resp_dict = await self._http_client.post( http_client = self._http_client
status_code, resp_dict = await http_client.post(
url, url,
json=self._generate_key_pair_payload(), json=self._generate_key_pair_payload(),
headers=headers, headers=headers,
@ -277,17 +284,21 @@ class AesTransport(BaseTransport):
handshake_key = resp_dict["result"]["key"] handshake_key = resp_dict["result"]["key"]
if ( if (
cookie := self._http_client.get_cookie( # type: ignore cookie := http_client.get_cookie( # type: ignore
self.SESSION_COOKIE_NAME self.SESSION_COOKIE_NAME
) )
) or ( ) or (
cookie := self._http_client.get_cookie( # type: ignore cookie := http_client.get_cookie("SESSIONID") # type: ignore
"SESSIONID"
)
): ):
self._session_cookie = {self.SESSION_COOKIE_NAME: cookie} self._session_cookie = {self.SESSION_COOKIE_NAME: cookie}
self._session_expire_at = time.time() + 86400 timeout = int(
http_client.get_cookie(self.TIMEOUT_COOKIE_NAME) or ONE_DAY_SECONDS
)
# There is a 24 hour timeout on the session cookie
# but the clock on the device is not always accurate
# so we set the expiry to 24 hours from now minus a buffer
self._session_expire_at = time.time() + timeout - SESSION_EXPIRE_BUFFER_SECONDS
if TYPE_CHECKING: if TYPE_CHECKING:
assert self._key_pair is not None assert self._key_pair is not None
self._encryption_session = AesEncyptionSession.create_from_keypair( self._encryption_session = AesEncyptionSession.create_from_keypair(

View File

@ -63,6 +63,10 @@ from .protocol import DEFAULT_CREDENTIALS, BaseTransport, get_default_credential
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ONE_DAY_SECONDS = 86400
SESSION_EXPIRE_BUFFER_SECONDS = 60 * 20
def _sha256(payload: bytes) -> bytes: def _sha256(payload: bytes) -> bytes:
digest = hashes.Hash(hashes.SHA256()) # noqa: S303 digest = hashes.Hash(hashes.SHA256()) # noqa: S303
digest.update(payload) digest.update(payload)
@ -86,6 +90,7 @@ class KlapTransport(BaseTransport):
DEFAULT_PORT: int = 80 DEFAULT_PORT: int = 80
DISCOVERY_QUERY = {"system": {"get_sysinfo": None}} DISCOVERY_QUERY = {"system": {"get_sysinfo": None}}
SESSION_COOKIE_NAME = "TP_SESSIONID" SESSION_COOKIE_NAME = "TP_SESSIONID"
TIMEOUT_COOKIE_NAME = "TIMEOUT"
def __init__( def __init__(
self, self,
@ -271,14 +276,18 @@ class KlapTransport(BaseTransport):
self._session_cookie = None self._session_cookie = None
local_seed, remote_seed, auth_hash = await self.perform_handshake1() local_seed, remote_seed, auth_hash = await self.perform_handshake1()
if cookie := self._http_client.get_cookie( # type: ignore http_client = self._http_client
self.SESSION_COOKIE_NAME if cookie := http_client.get_cookie(self.SESSION_COOKIE_NAME): # type: ignore
):
self._session_cookie = {self.SESSION_COOKIE_NAME: cookie} self._session_cookie = {self.SESSION_COOKIE_NAME: cookie}
# The device returns a TIMEOUT cookie on handshake1 which # The device returns a TIMEOUT cookie on handshake1 which
# it doesn't like to get back so we store the one we want # it doesn't like to get back so we store the one we want
timeout = int(
self._session_expire_at = time.time() + 86400 http_client.get_cookie(self.TIMEOUT_COOKIE_NAME) or ONE_DAY_SECONDS
)
# There is a 24 hour timeout on the session cookie
# but the clock on the device is not always accurate
# so we set the expiry to 24 hours from now minus a buffer
self._session_expire_at = time.time() + timeout - SESSION_EXPIRE_BUFFER_SECONDS
self._encryption_session = await self.perform_handshake2( self._encryption_session = await self.perform_handshake2(
local_seed, remote_seed, auth_hash local_seed, remote_seed, auth_hash
) )