From c79081d0cbedf95bcb6e8ec1a18e9b0d61d8e796 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 2 Sep 2023 14:25:37 +0200 Subject: [PATCH] Various tweaks to extended SecureConnectionException exceptions --- .../org/jellyfin/sdk/api/ktor/KtorClient.kt | 34 ++++++++----------- jellyfin-api/api/jellyfin-api.api | 2 +- .../exception/ssl/BadPeerSSLKeyException.kt | 5 +-- ...on.kt => HandshakeCertificateException.kt} | 2 +- ...validSSLProtocolImplementationException.kt | 5 +-- .../ssl/PeerNotAuthenticatedException.kt | 5 +-- jellyfin-core/api/android/jellyfin-core.api | 10 +++--- jellyfin-core/api/jvm/jellyfin-core.api | 10 +++--- .../discovery/RecommendedServerDiscovery.kt | 19 ++++------- .../sdk/discovery/RecommendedServerIssue.kt | 13 ++++--- .../org/jellyfin/sdk/api/SSLResolverTests.kt | 31 ++++++++++------- 11 files changed, 71 insertions(+), 65 deletions(-) rename jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/{HandShakeCertificateException.kt => HandshakeCertificateException.kt} (90%) diff --git a/jellyfin-api-ktor/src/jvmMain/kotlin/org/jellyfin/sdk/api/ktor/KtorClient.kt b/jellyfin-api-ktor/src/jvmMain/kotlin/org/jellyfin/sdk/api/ktor/KtorClient.kt index ecac8972..77f43abd 100644 --- a/jellyfin-api-ktor/src/jvmMain/kotlin/org/jellyfin/sdk/api/ktor/KtorClient.kt +++ b/jellyfin-api-ktor/src/jvmMain/kotlin/org/jellyfin/sdk/api/ktor/KtorClient.kt @@ -27,7 +27,7 @@ import org.jellyfin.sdk.api.client.exception.InvalidStatusException import org.jellyfin.sdk.api.client.exception.SecureConnectionException import org.jellyfin.sdk.api.client.exception.TimeoutException import org.jellyfin.sdk.api.client.exception.ssl.BadPeerSSLKeyException -import org.jellyfin.sdk.api.client.exception.ssl.HandShakeCertificateException +import org.jellyfin.sdk.api.client.exception.ssl.HandshakeCertificateException import org.jellyfin.sdk.api.client.exception.ssl.InvalidSSLProtocolImplementationException import org.jellyfin.sdk.api.client.exception.ssl.PeerNotAuthenticatedException import org.jellyfin.sdk.api.client.util.ApiSerializer @@ -142,25 +142,21 @@ public actual open class KtorClient actual constructor( } catch (err: SerializationException) { logger.error(err) { "Serialization failed" } throw InvalidContentException("Serialization failed", err) + } catch (err: SSLKeyException) { + logger.error(err) { "Invalid SSL peer key format" } + throw BadPeerSSLKeyException("Invalid SSL peer key format", err) + } catch (err: SSLPeerUnverifiedException) { + logger.error(err) { "Couldn't authenticate peer" } + throw PeerNotAuthenticatedException("Couldn't authenticate peer", err) + } catch (err: SSLHandshakeException) { + logger.error(err) { "SSL Invalid handshake" } + throw HandshakeCertificateException("Invalid handshake", err) + } catch (err: SSLProtocolException) { + logger.error(err) { "Invalid SSL protocol implementation" } + throw InvalidSSLProtocolImplementationException("Invalid SSL protocol implementation", err) } catch (err: SSLException) { - logger.error(err) { "SSL error occurred" } - when (err) { - is SSLKeyException -> { - throw BadPeerSSLKeyException("Invalid SSL peer key format", err) - } - is SSLPeerUnverifiedException -> { - throw PeerNotAuthenticatedException("Couldn't authenticate peer", err) - } - is SSLHandshakeException -> { - throw HandShakeCertificateException("Invalid hand shake certificate", err) - } - is SSLProtocolException -> { - throw InvalidSSLProtocolImplementationException("Invalid SSL protocol implementation", err) - } - else -> { - throw SecureConnectionException("Unknown SSL error occurred", err) - } - } + logger.error(err) { "Unknown SSL error occurred" } + throw SecureConnectionException("Unknown SSL error occurred", err) } catch (err: IOException) { logger.error(err) { "Unknown IO error occurred!" } throw ApiClientException("Unknown IO error occurred!", err) diff --git a/jellyfin-api/api/jellyfin-api.api b/jellyfin-api/api/jellyfin-api.api index c7bfedb4..ca2c3e80 100644 --- a/jellyfin-api/api/jellyfin-api.api +++ b/jellyfin-api/api/jellyfin-api.api @@ -129,7 +129,7 @@ public final class org/jellyfin/sdk/api/client/exception/ssl/BadPeerSSLKeyExcept public fun (Ljava/lang/String;Ljava/lang/Throwable;)V } -public final class org/jellyfin/sdk/api/client/exception/ssl/HandShakeCertificateException : org/jellyfin/sdk/api/client/exception/SecureConnectionException { +public final class org/jellyfin/sdk/api/client/exception/ssl/HandshakeCertificateException : org/jellyfin/sdk/api/client/exception/SecureConnectionException { public fun (Ljava/lang/String;Ljava/lang/Throwable;)V } diff --git a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/BadPeerSSLKeyException.kt b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/BadPeerSSLKeyException.kt index 3dc970ad..e2000f57 100644 --- a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/BadPeerSSLKeyException.kt +++ b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/BadPeerSSLKeyException.kt @@ -7,5 +7,6 @@ import org.jellyfin.sdk.api.client.exception.SecureConnectionException * This can happen when a bad key format is given. */ public class BadPeerSSLKeyException( - message: String, exception: Throwable -): SecureConnectionException(message, exception) + message: String, + exception: Throwable, +) : SecureConnectionException(message, exception) diff --git a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/HandShakeCertificateException.kt b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/HandshakeCertificateException.kt similarity index 90% rename from jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/HandShakeCertificateException.kt rename to jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/HandshakeCertificateException.kt index 8892b538..86bb7aaf 100644 --- a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/HandShakeCertificateException.kt +++ b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/HandshakeCertificateException.kt @@ -7,6 +7,6 @@ import org.jellyfin.sdk.api.client.exception.SecureConnectionException * Indicates that the client and server could not negotiate the desired level of security or the certificate was * revoked. */ -public class HandShakeCertificateException( +public class HandshakeCertificateException( message: String, exception: Throwable ): SecureConnectionException(message, exception) diff --git a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/InvalidSSLProtocolImplementationException.kt b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/InvalidSSLProtocolImplementationException.kt index 0caaf897..fd9246e9 100644 --- a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/InvalidSSLProtocolImplementationException.kt +++ b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/InvalidSSLProtocolImplementationException.kt @@ -7,5 +7,6 @@ import org.jellyfin.sdk.api.client.exception.SecureConnectionException * Normally this indicates a flaw in one of the protocol implementations. */ public class InvalidSSLProtocolImplementationException( - message: String, exception: Throwable -): SecureConnectionException(message, exception) + message: String, + exception: Throwable, +) : SecureConnectionException(message, exception) diff --git a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/PeerNotAuthenticatedException.kt b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/PeerNotAuthenticatedException.kt index 4d9d8fd4..49931d26 100644 --- a/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/PeerNotAuthenticatedException.kt +++ b/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/exception/ssl/PeerNotAuthenticatedException.kt @@ -10,5 +10,6 @@ import org.jellyfin.sdk.api.client.exception.SecureConnectionException * this exception is thrown. */ public class PeerNotAuthenticatedException( - message: String, exception: Throwable -): SecureConnectionException(message, exception) + message: String, + exception: Throwable, +) : SecureConnectionException(message, exception) diff --git a/jellyfin-core/api/android/jellyfin-core.api b/jellyfin-core/api/android/jellyfin-core.api index 61c1a3e4..5048fbb9 100644 --- a/jellyfin-core/api/android/jellyfin-core.api +++ b/jellyfin-core/api/android/jellyfin-core.api @@ -245,12 +245,12 @@ public final class org/jellyfin/sdk/discovery/RecommendedServerIssue$SecureConne } public final class org/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable : org/jellyfin/sdk/discovery/RecommendedServerIssue { - public fun (Ljava/lang/Throwable;)V - public final fun component1 ()Ljava/lang/Throwable; - public final fun copy (Ljava/lang/Throwable;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; - public static synthetic fun copy$default (Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable;Ljava/lang/Throwable;ILjava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; + public fun (Lorg/jellyfin/sdk/api/client/exception/TimeoutException;)V + public final fun component1 ()Lorg/jellyfin/sdk/api/client/exception/TimeoutException; + public final fun copy (Lorg/jellyfin/sdk/api/client/exception/TimeoutException;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; + public static synthetic fun copy$default (Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable;Lorg/jellyfin/sdk/api/client/exception/TimeoutException;ILjava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; public fun equals (Ljava/lang/Object;)Z - public final fun getThrowable ()Ljava/lang/Throwable; + public final fun getThrowable ()Lorg/jellyfin/sdk/api/client/exception/TimeoutException; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/jellyfin-core/api/jvm/jellyfin-core.api b/jellyfin-core/api/jvm/jellyfin-core.api index 819cd7c4..23ed3bd9 100644 --- a/jellyfin-core/api/jvm/jellyfin-core.api +++ b/jellyfin-core/api/jvm/jellyfin-core.api @@ -230,12 +230,12 @@ public final class org/jellyfin/sdk/discovery/RecommendedServerIssue$SecureConne } public final class org/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable : org/jellyfin/sdk/discovery/RecommendedServerIssue { - public fun (Ljava/lang/Throwable;)V - public final fun component1 ()Ljava/lang/Throwable; - public final fun copy (Ljava/lang/Throwable;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; - public static synthetic fun copy$default (Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable;Ljava/lang/Throwable;ILjava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; + public fun (Lorg/jellyfin/sdk/api/client/exception/TimeoutException;)V + public final fun component1 ()Lorg/jellyfin/sdk/api/client/exception/TimeoutException; + public final fun copy (Lorg/jellyfin/sdk/api/client/exception/TimeoutException;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; + public static synthetic fun copy$default (Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable;Lorg/jellyfin/sdk/api/client/exception/TimeoutException;ILjava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$ServerUnreachable; public fun equals (Ljava/lang/Object;)Z - public final fun getThrowable ()Ljava/lang/Throwable; + public final fun getThrowable ()Lorg/jellyfin/sdk/api/client/exception/TimeoutException; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerDiscovery.kt b/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerDiscovery.kt index 8857e0b2..85876271 100644 --- a/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerDiscovery.kt +++ b/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerDiscovery.kt @@ -50,25 +50,20 @@ public class RecommendedServerDiscovery constructor( // Failure checks result.systemInfo.exceptionOrNull()?.let { exception -> when (exception) { - is SecureConnectionException -> { - issues.add(RecommendedServerIssue.SecureConnectionFailed(exception)) - } - is TimeoutException -> { - issues.add(RecommendedServerIssue.ServerUnreachable(exception)) - } - else -> { - // Did not reply with a system information - issues.add(RecommendedServerIssue.MissingSystemInfo(result.systemInfo.exceptionOrNull())) - } + is SecureConnectionException -> issues.add(RecommendedServerIssue.SecureConnectionFailed(exception)) + is TimeoutException -> issues.add(RecommendedServerIssue.ServerUnreachable(exception)) + // Did not reply with a system information + else -> issues.add(RecommendedServerIssue.MissingSystemInfo(result.systemInfo.exceptionOrNull())) } + scores.add(RecommendedServerInfoScore.BAD) } // System Info data validation when { // Wrong product name - might be a different service on this connection - !systemInfo?.productName.equals(PRODUCT_NAME) -> { - issues.add(RecommendedServerIssue.InvalidProductName(systemInfo?.productName)) + systemInfo != null && !systemInfo.productName.equals(PRODUCT_NAME) -> { + issues.add(RecommendedServerIssue.InvalidProductName(systemInfo.productName)) scores.add(RecommendedServerInfoScore.BAD) } } diff --git a/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerIssue.kt b/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerIssue.kt index 0ef4bd2f..8f4a1b09 100644 --- a/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerIssue.kt +++ b/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerIssue.kt @@ -1,18 +1,23 @@ package org.jellyfin.sdk.discovery import org.jellyfin.sdk.api.client.exception.SecureConnectionException +import org.jellyfin.sdk.api.client.exception.TimeoutException import org.jellyfin.sdk.model.ServerVersion public sealed interface RecommendedServerIssue { - /** * Failed to acquire a secure connection. This happens due to incorrect SSL configurations. */ - public data class SecureConnectionFailed(public val sslException: SecureConnectionException) : RecommendedServerIssue + public data class SecureConnectionFailed( + public val sslException: SecureConnectionException, + ) : RecommendedServerIssue + /** - * Server is unreachable. This happens when the server is overloaded, unstable or the client is unable to establish a connection. + * Server is unreachable. This happens when the server is overloaded, unstable or the client is unable to establish + * a connection. */ - public data class ServerUnreachable(public val throwable: Throwable) : RecommendedServerIssue + public data class ServerUnreachable(public val throwable: TimeoutException) : RecommendedServerIssue + /** * No system information found from server. Server is returning invalid system info. */ diff --git a/jellyfin-core/src/jvmTest/kotlin/org/jellyfin/sdk/api/SSLResolverTests.kt b/jellyfin-core/src/jvmTest/kotlin/org/jellyfin/sdk/api/SSLResolverTests.kt index 3eb2f5c1..306dd506 100644 --- a/jellyfin-core/src/jvmTest/kotlin/org/jellyfin/sdk/api/SSLResolverTests.kt +++ b/jellyfin-core/src/jvmTest/kotlin/org/jellyfin/sdk/api/SSLResolverTests.kt @@ -1,48 +1,55 @@ package org.jellyfin.sdk.api; +import io.kotest.assertions.retry import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import org.jellyfin.sdk.api.client.exception.SecureConnectionException -import org.jellyfin.sdk.api.client.exception.ssl.HandShakeCertificateException +import org.jellyfin.sdk.api.client.exception.ssl.HandshakeCertificateException import org.jellyfin.sdk.api.client.exception.ssl.PeerNotAuthenticatedException import org.jellyfin.sdk.createJellyfin import org.jellyfin.sdk.model.ClientInfo import org.jellyfin.sdk.model.DeviceInfo +import kotlin.time.Duration.Companion.minutes class SSLResolverTests : FunSpec({ - - val jellyfin = createJellyfin { + fun getInstance() = createJellyfin { clientInfo = ClientInfo("Jellyfin Testing SSL Errors", "TEST") deviceInfo = DeviceInfo("test", "test") } test("should throw HandShakeCertificateException when calling an https endpoint with revoked certificate") { - val api = jellyfin.createApi( + val api = getInstance().createApi( baseUrl = "https://revoked.badssl.com" ) - shouldThrow { - api.request(pathTemplate = "/") + retry(3, 1.minutes) { + shouldThrow { + api.request(pathTemplate = "/") + } } } test("should throw PeerNotAuthenticatedException when wrong host is returned from https endpoint") { - val api = jellyfin.createApi( + val api = getInstance().createApi( baseUrl = "https://wrong.host.badssl.com" ) - shouldThrow { - api.request(pathTemplate = "/") + retry(3, 1.minutes) { + shouldThrow { + api.request(pathTemplate = "/") + } } } test("should throw SecureConnectionException when using wrong https port") { - val api = jellyfin.createApi( + val api = getInstance().createApi( baseUrl = "https://badssl.com:80" ) - shouldThrow { - api.request(pathTemplate = "/") + retry(3, 1.minutes) { + shouldThrow { + api.request(pathTemplate = "/") + } } } })