From 1130222823662a16b2ae78328f935ed62ff0a90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Mikul=C3=A1=C5=A1ek?= Date: Fri, 6 Dec 2024 13:45:56 +0100 Subject: [PATCH] fix: try get hashing key everytime for http --- src/commonMain/kotlin/LoxoneCrypto.kt | 9 ++----- .../kotlin/LoxoneTokenAuthenticator.kt | 25 +++++++++---------- .../kotlin/ktor/KtorHttpLoxoneClient.kt | 16 ++++++++---- src/commonMain/kotlin/message/Token.kt | 5 ---- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/commonMain/kotlin/LoxoneCrypto.kt b/src/commonMain/kotlin/LoxoneCrypto.kt index 3b07854..f0acd80 100644 --- a/src/commonMain/kotlin/LoxoneCrypto.kt +++ b/src/commonMain/kotlin/LoxoneCrypto.kt @@ -60,13 +60,8 @@ internal object LoxoneCrypto { } } - @OptIn(ExperimentalStdlibApi::class) - fun loxoneHmac(token: Token, operation: String): String = - token.withTokenAndKey { tokenVal, key -> - bytesToHex(HmacSHA256(key).doFinal(tokenVal.encodeToByteArray())).also { finalHash -> - logger.trace { "$operation final hash: $finalHash" } - } - } + fun loxoneHmac(token: Token, hashing: Hashing, operation: String): String = + loxoneHmac(checkNotNull(token.token) { "Can't hash non-filled token" }, hashing, operation) @OptIn(ExperimentalStdlibApi::class) private fun loxoneDigest(secret: String, hashing: Hashing): String { diff --git a/src/commonMain/kotlin/LoxoneTokenAuthenticator.kt b/src/commonMain/kotlin/LoxoneTokenAuthenticator.kt index 1c8ff19..8c78c5d 100644 --- a/src/commonMain/kotlin/LoxoneTokenAuthenticator.kt +++ b/src/commonMain/kotlin/LoxoneTokenAuthenticator.kt @@ -26,8 +26,6 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor( private val mutex = Mutex() - private var hashing: Hashing? = null - private var token: Token? by Delegates.observable(repository.getToken(profile)) { _, _, newValue -> if (newValue != null) { logger.info { @@ -45,21 +43,17 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor( suspend fun ensureAuthenticated(client: LoxoneClient) = execConditionalWithMutex({ - // Token is not usable or websocket client is not authenticated - !TokenState(token).isUsable || (client is WebsocketLoxoneClient && !authWebsockets.contains(client)) + !TokenState(token).isUsable + || (client is WebsocketLoxoneClient && !authWebsockets.contains(client)) }) { - if (hashing == null) { - hashing = client.callForMsg(commandForUser(user)) - } - + val hashing = client.callForMsg(commandForUser(user)) val state = TokenState(token) - when { state.isExpired -> { logger.debug { "Token expired, requesting new one" } token = client.callForMsg( Tokens.get( - loxoneHashing(profile.credentials!!.password, checkNotNull(hashing), "getttoken", user), + loxoneHashing(profile.credentials!!.password, hashing, "getttoken", user), user, settings.tokenPermission, settings.clientId, @@ -79,7 +73,7 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor( else -> { if (client is WebsocketLoxoneClient) { logger.debug { "Authenticating websocket with token $token" } - val authResponse = client.callForMsg(Tokens.auth(tokenHash("authenticate"), user)) + val authResponse = client.callForMsg(Tokens.auth(tokenHash(client, "authenticate"), user)) token = token!!.merge(authResponse) authWebsockets.add(client) } @@ -89,7 +83,7 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor( suspend fun killToken(client: LoxoneClient) = execConditionalWithMutex({ TokenState(token).isUsable }) { logger.debug { "Going to kill token $token" } - client.callForMsg(Tokens.kill(tokenHash("killtoken"), user)) + client.callForMsg(Tokens.kill(tokenHash(client, "killtoken"), user)) logger.info { "Token killed" } token = null authWebsockets.remove(client) @@ -101,7 +95,12 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor( } } - fun tokenHash(operation: String) = LoxoneCrypto.loxoneHmac(checkNotNull(token), operation) + suspend fun tokenHash(client: LoxoneClient, operation: String) = + LoxoneCrypto.loxoneHmac( + checkNotNull(token) { "Token must be present for hashing" }, + client.callForMsg(commandForUser(user)), + operation + ) private suspend fun execConditionalWithMutex(condition: () -> Boolean, block: suspend () -> Unit) { if (condition()) { diff --git a/src/commonMain/kotlin/ktor/KtorHttpLoxoneClient.kt b/src/commonMain/kotlin/ktor/KtorHttpLoxoneClient.kt index 448476f..fb71d3b 100644 --- a/src/commonMain/kotlin/ktor/KtorHttpLoxoneClient.kt +++ b/src/commonMain/kotlin/ktor/KtorHttpLoxoneClient.kt @@ -108,17 +108,23 @@ class KtorHttpLoxoneClient internal constructor( httpClient.close() } - private fun HttpRequestBuilder.commandRequest(addAuth: Boolean = true, pathBuilder: URLBuilder.() -> Unit) { + private suspend fun HttpRequestBuilder.commandRequest(addAuth: Boolean = true, pathBuilder: URLBuilder.() -> Unit) { + val userAndHash = if (addAuth && authentication is LoxoneAuth.Token) { + val authenticator = authentication.authenticator + authenticator.user to authenticator.tokenHash(this@KtorHttpLoxoneClient, "http-autht") + } else { + null + } + url { protocol = if (endpoint.useSsl) URLProtocol.HTTPS else URLProtocol.HTTP host = endpoint.host port = endpoint.port appendEncodedPathSegments(endpoint.path) pathBuilder() - if (addAuth && authentication is LoxoneAuth.Token) { - val authenticator = authentication.authenticator - parameters.append("autht", authenticator.tokenHash("http-autht")) - parameters.append("user", authenticator.user) + if (userAndHash != null) { + parameters.append("user", userAndHash.first) + parameters.append("autht", userAndHash.second) } } } diff --git a/src/commonMain/kotlin/message/Token.kt b/src/commonMain/kotlin/message/Token.kt index 790d60a..4a78f92 100644 --- a/src/commonMain/kotlin/message/Token.kt +++ b/src/commonMain/kotlin/message/Token.kt @@ -31,11 +31,6 @@ data class Token( */ fun secondsToExpireFromNow() = LoxoneTime.getUnixEpochSeconds(validUntil) - Clock.System.now().epochSeconds - fun withTokenAndKey(block: (String, ByteArray) -> T): T { - check(filled) { "Can't invoke block(token, key) on nonfilled token" } - return block(token!!, key!!) - } - /** * Merges the given token to this one and returns the merged token. The [Token.token] and [Token.key] are taken * from given token only if they are not null, otherwise the values from this token are used. Other properties are