[Android] Remote Access: Enforce HTTPS for API and authentication endpoints
Nicolas Pomepuy
git at videolan.org
Fri Oct 2 07:29:56 UTC 2026
vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Tue Sep 29 13:06:35 2026 +0200| [14269cd03fc41d8e5b3d7f5f92ec6599916b3b6f] | committer: Nicolas Pomepuy
Remote Access: Enforce HTTPS for API and authentication endpoints
Restrict API routes and OTP authentication (/code, /verify-code) to the
HTTPS connector only, returning 403 Forbidden for cleartext HTTP requests.
The unencrypted HTTP port remains open solely to serve static UI assets
and redirect GET / requests to /index.html#/ssl, guiding the user to
accept the self-signed HTTPS certificate before proceeding.
> https://code.videolan.org/videolan/vlc-android/commit/14269cd03fc41d8e5b3d7f5f92ec6599916b3b6f
---
.../vlc/remoteaccessserver/RemoteAccessServer.kt | 13 +++++++++++++
.../remoteaccessserver/routing/RemoteAccessRouting.kt | 16 +++++++++++++++-
.../routing/RemoteAccessRoutingAuth.kt | 9 +++++++++
.../routing/RemoteAccessRoutingCommon.kt | 7 ++++++-
4 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt
index 99046b801b..802a979680 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessServer.kt
@@ -829,6 +829,19 @@ class RemoteAccessServer(private val context: Context) : PlaybackService.Callbac
}
}
+ /**
+ * Checks if a given port is the HTTPS port
+ *
+ * @param port the port to test
+ * @return true if the port is the HTTPS port
+ */
+ fun isHttpsPort(port: Int): Boolean {
+ if (::engine.isInitialized) {
+ return engine.environment.connectors.firstOrNull { it.type.name == "HTTPS" }?.port == port
+ }
+ return false
+ }
+
/**
* Returns the server addresses as a list
*
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRouting.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRouting.kt
index 5e80cad35d..15ca0e3449 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRouting.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRouting.kt
@@ -32,6 +32,10 @@ import kotlinx.coroutines.CoroutineScope
import org.videolan.tools.Settings
import org.videolan.vlc.remoteaccessserver.RemoteAccessServer
import org.videolan.vlc.remoteaccessserver.RemoteAccessServer.Companion.getServerFiles
+import io.ktor.http.HttpStatusCode
+import io.ktor.server.application.ApplicationCallPipeline
+import io.ktor.server.application.call
+import io.ktor.server.response.respond
import java.io.File
/**
@@ -40,12 +44,22 @@ import java.io.File
*/
fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
val settings = Settings.getInstance(appContext)
+
+ // Unprotected static files and common public routes (served on both HTTP and HTTPS)
staticFiles("", File(getServerFiles(appContext)))
+ publicCommonRouting(appContext)
publicAuthRouting(appContext, scope, settings)
- publicCommonRouting(appContext)
authenticate("user_session", optional = RemoteAccessServer.byPassAuth) {
+ intercept(ApplicationCallPipeline.Call) {
+ val server = RemoteAccessServer.getInstance(appContext)
+ if (!server.isHttpsPort(call.request.local.serverPort)) {
+ call.respond(HttpStatusCode.Forbidden)
+ finish()
+ }
+ }
+
authenticatedAuthRouting()
mediaRouting(appContext, scope, settings)
authenticatedFileRouting(appContext, scope, settings)
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingAuth.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingAuth.kt
index ef473cf655..a5f77b6267 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingAuth.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingAuth.kt
@@ -40,6 +40,7 @@ import io.ktor.server.routing.post
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.videolan.vlc.remoteaccessserver.RemoteAccessOTP
+import org.videolan.vlc.remoteaccessserver.RemoteAccessServer
import org.videolan.vlc.remoteaccessserver.RemoteAccessSession
import org.videolan.vlc.remoteaccessserver.websockets.RemoteAccessWebSockets
import org.videolan.vlc.util.RemoteAccessUtils
@@ -50,6 +51,10 @@ fun Route.publicAuthRouting(appContext: Context, scope: CoroutineScope, settings
//the client is requesting a new code.
// if the formparameters "challenge" is sent. Remove the corresponding code
post("/code") {
+ if (!RemoteAccessServer.getInstance(appContext).isHttpsPort(call.request.local.serverPort)) {
+ call.respond(HttpStatusCode.Forbidden)
+ return at post
+ }
val formParameters = try {
call.receiveParameters()
} catch (_: Exception) {
@@ -67,6 +72,10 @@ fun Route.publicAuthRouting(appContext: Context, scope: CoroutineScope, settings
}
//Verify the code and inject the cookie if valid
post("/verify-code") {
+ if (!RemoteAccessServer.getInstance(appContext).isHttpsPort(call.request.local.serverPort)) {
+ call.respond(HttpStatusCode.Forbidden)
+ return at post
+ }
val formParameters = try {
call.receiveParameters()
} catch (e: Exception) {
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingCommon.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingCommon.kt
index e959837a2e..278fc9f02f 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingCommon.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingCommon.kt
@@ -64,7 +64,12 @@ import java.io.File
fun Route.publicCommonRouting(appContext: Context) {
// Main end point redirect to index.html
get("/") {
- call.respondRedirect("index.html", permanent = true)
+ val isHttps = RemoteAccessServer.getInstance(appContext).isHttpsPort(call.request.local.serverPort)
+ if (isHttps) {
+ call.respondRedirect("index.html", permanent = true)
+ } else {
+ call.respondRedirect("/index.html#/ssl", permanent = false)
+ }
}
get("/index.html") {
try {
More information about the Android
mailing list