android-client/app/src/main/java/com/projects/httpsserverapp/HttpsServerApp.kt
2025-12-23 16:33:10 +03:00

146 lines
4.6 KiB
Kotlin

package com.projects.httpsserverapp
import android.app.Application
import android.content.Intent
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import okhttp3.tls.HeldCertificate
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLServerSocket
import javax.net.ssl.SSLSocket
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.ExtendedSSLSession
import javax.net.ssl.SNIHostName
import javax.net.ssl.SNIServerName
class HttpsServerApp : Application() {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
val connectionState: StateFlow<ConnectionState> get() = _connectionState
private val _domains = MutableStateFlow(listOf("api.phone.local", "photo.phone.local"))
val domains: StateFlow<List<String>> get() = _domains
private val _domainStates =
MutableStateFlow<Map<String, DomainState>>(emptyMap())
val domainStates: StateFlow<Map<String, DomainState>> get() = _domainStates
fun setDomainPending(domain: String) {
_domainStates.value += (domain to DomainState.PENDING)
}
fun setDomainActive(domain: String) {
_domainStates.value += (domain to DomainState.ACTIVE)
}
fun setDomainRejected(domain: String) {
_domainStates.value += (domain to DomainState.REJECTED)
}
fun clearDomainStates() {
_domainStates.value = emptyMap()
}
fun updateConnectionState(state: ConnectionState) {
_connectionState.value = state
}
fun updateDomains(domains: List<String>) {
_domains.value = domains
}
override fun onCreate() {
super.onCreate()
scope.launch { startHttpsServer() }
startService(Intent(this, TunnelService::class.java))
}
private fun startHttpsServer() {
val heldCert = HeldCertificate.Builder()
.commonName("phone.local")
.addSubjectAlternativeName("api.phone.local")
.addSubjectAlternativeName("photo.phone.local")
.build()
val keyStore = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType())
keyStore.load(null, null)
keyStore.setKeyEntry(
"server",
heldCert.keyPair.private,
null,
arrayOf(heldCert.certificate)
)
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(keyStore, null)
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(keyStore)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(kmf.keyManagers, tmf.trustManagers, null)
val factory = sslContext.serverSocketFactory
val server = factory.createServerSocket(8443) as SSLServerSocket
Log.i("HTTPS", "Listening on 127.0.0.1:8443")
while (true) {
val socket = server.accept() as SSLSocket
scope.launch { handleClient(socket) }
}
}
private fun handleClient(socket: SSLSocket) {
try {
socket.startHandshake()
val session = socket.session
var sni: String? = null
if (session is ExtendedSSLSession) {
for (serverName in session.requestedServerNames) {
if (serverName is SNIHostName) {
sni = serverName.asciiName
break
}
}
}
Log.i("HTTPS", "Incoming request for SNI: $sni")
val body = when (sni) {
"api.phone.local" -> "API: Hello from phone"
"photo.phone.local" -> "PHOTO: Hello from phone"
else -> "DEFAULT: Hello"
}
val bodyBytes = body.toByteArray(Charsets.UTF_8)
val headers =
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Length: ${bodyBytes.size}\r\n" +
"Connection: close\r\n" +
"\r\n"
val out = socket.outputStream
out.write(headers.toByteArray(Charsets.UTF_8))
out.write(bodyBytes)
out.flush()
socket.close()
} catch (e: Exception) {
Log.e("HTTPS", "Error handling client: ${e.message}", e)
}
}
}