146 lines
4.6 KiB
Kotlin
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)
|
|
}
|
|
}
|
|
}
|