diff --git a/build.gradle.kts b/build.gradle.kts index 7f212a07..59577343 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,6 +58,9 @@ dependencies { implementation("io.ktor:ktor-client-core:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version") implementation("io.ktor:ktor-client-content-negotiation:$ktor_version") + + implementation("tech.barbero.http-messages-signing:http-messages-signing-core:1.0.0") + testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") } diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 6952fe31..56f3f1d8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -12,17 +12,14 @@ import dev.usbharu.hideout.repository.UserAuthRepository import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.routing.* import dev.usbharu.hideout.service.* -import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.logging.* -import io.ktor.http.* import io.ktor.serialization.jackson.* import io.ktor.server.application.* import org.jetbrains.exposed.sql.Database import org.koin.ktor.ext.inject -import java.lang.Compiler.enable import java.util.* fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) @@ -60,6 +57,9 @@ fun Application.module() { logger = Logger.DEFAULT level = LogLevel.ALL } + install(httpSignaturePlugin){ + keyMap = KtorKeyMap(get()) + } } } single { UserRepository(get()) } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt index 2a4ca8db..c0910735 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt @@ -2,13 +2,160 @@ package dev.usbharu.hideout.plugins import dev.usbharu.hideout.ap.JsonLd import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.repository.IUserAuthRepository +import dev.usbharu.hideout.service.IUserAuthService +import dev.usbharu.hideout.service.UserAuthService import dev.usbharu.hideout.util.HttpUtil.Activity +import io.ktor.client.plugins.api.* +import io.ktor.client.request.* import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* +import io.ktor.util.* +import io.netty.handler.codec.base64.Base64 +import kotlinx.coroutines.runBlocking +import org.koin.java.KoinJavaComponent.inject + +import org.koin.ktor.ext.inject +import tech.barbero.http.message.signing.HttpMessage +import tech.barbero.http.message.signing.HttpMessageSigner +import tech.barbero.http.message.signing.HttpRequest +import tech.barbero.http.message.signing.KeyMap +import java.net.URI +import java.security.KeyFactory +import java.security.KeyPairGenerator +import java.security.MessageDigest +import java.security.PrivateKey +import java.security.PublicKey +import java.security.spec.X509EncodedKeySpec +import java.text.SimpleDateFormat +import java.util.* +import javax.crypto.SecretKey suspend fun ApplicationCall.respondAp(message: T, status: HttpStatusCode = HttpStatusCode.OK) { message.context += "https://www.w3.org/ns/activitystreams" val activityJson = Config.configData.objectMapper.writeValueAsString(message) respondText(activityJson, ContentType.Application.Activity, status) } + +class HttpSignaturePluginConfig { + lateinit var keyMap: KeyMap +} + +val httpSignaturePlugin = createClientPlugin("HttpSign",::HttpSignaturePluginConfig) { + val keyMap = pluginConfig.keyMap + val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) + onRequest { request, body -> + + + request.header("Date", format.format(Date())) + + if (request.bodyType?.type == String::class) { + println("Digest !!") + val digest = + hex(UserAuthService.sha256.digest((body as String).toByteArray(Charsets.UTF_8))) + request.headers.append("Digest", digest) + } + + if (request.headers.contains("Signature")) { + val all = request.headers.getAll("Signature")!! + val parameters = mutableListOf() + for (s in all) { + s.split(",").forEach { parameters.add(it) } + } + + val keyId = parameters.find { it.startsWith("keyId") }?.split("=")?.get(1)?.replace("\"", "") + val algorithm = + parameters.find { it.startsWith("algorithm") }?.split("=")?.get(1)?.replace("\"", "") + val headers = parameters.find { it.startsWith("headers") }?.split("=")?.get(1)?.replace("\"", "") + ?.split(" ")?.toMutableList().orEmpty() + + val algorithmType = when (algorithm) { + "rsa-sha256" -> { + HttpMessageSigner.Algorithm.RSA_SHA256 + } + + else -> { + TODO() + } + } + + headers.map { + when (it) { + "(request-target)" -> { + HttpMessageSigner.REQUEST_TARGET + } + + "digest" -> { + "Digest" + } + + "date" -> { + "Date" + } + + else -> { + it + } + } + } + + val builder = HttpMessageSigner.builder().algorithm(algorithmType).keyId(keyId).keyMap(keyMap) + var tmp = builder + headers.forEach { + tmp = tmp.addHeaderToSign(it) + } + val signer = tmp.build() + + request.headers.remove("Signature") + + signer!!.sign(object : HttpMessage,HttpRequest { + override fun headerValues(name: String?): MutableList { + return name?.let { request.headers.getAll(it) }?.toMutableList() ?: mutableListOf() + } + + override fun addHeader(name: String?, value: String?) { + name?.let { request.header(it,value) } + } + + override fun method(): String { + return request.method.value + } + + override fun uri(): URI { + return request.url.build().toURI() + } + + + }) + + } + + } +} + +class KtorKeyMap(private val userAuthRepository: IUserAuthService) : KeyMap { + override fun getPublicKey(keyId: String?): PublicKey = runBlocking { + val publicBytes = java.util.Base64.getDecoder().decode( + userAuthRepository.findByUsername( + (keyId ?: throw IllegalArgumentException("keyId is null")) + ).publicKey + ) + val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes) + return@runBlocking KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec) + } + + override fun getPrivateKey(keyId: String?): PrivateKey = runBlocking { + val publicBytes = java.util.Base64.getDecoder().decode( + userAuthRepository.findByUsername( + (keyId ?: throw IllegalArgumentException("keyId is null")) + ).privateKey + ) + val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes) + return@runBlocking KeyFactory.getInstance("RSA").generatePrivate(x509EncodedKeySpec) + } + + override fun getSecretKey(keyId: String?): SecretKey { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/HttpSignService.kt b/src/main/kotlin/dev/usbharu/hideout/service/HttpSignService.kt index aacd9b6a..440ee426 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/HttpSignService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/HttpSignService.kt @@ -1,5 +1,7 @@ package dev.usbharu.hideout.service +import java.security.PrivateKey + class HttpSignService { suspend fun sign(){ diff --git a/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt index c53082de..e949d4d4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt @@ -11,4 +11,6 @@ interface IUserAuthService { suspend fun verifyAccount(username: String,password: String): Boolean suspend fun findByUserId(userId: Long):UserAuthenticationEntity + + suspend fun findByUsername(username: String):UserAuthenticationEntity } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/UserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/UserAuthService.kt index 830e6112..5f740315 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/UserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/UserAuthService.kt @@ -68,6 +68,12 @@ class UserAuthService( return userAuthRepository.findByUserId(userId) ?: throw UserNotFoundException("$userId was not found") } + override suspend fun findByUsername(username: String): UserAuthenticationEntity { + val userEntity = userRepository.findByName(username) ?: throw UserNotFoundException("$username was not found") + return userAuthRepository.findByUserId(userEntity.id) + ?: throw UserNotFoundException("$username auth data was not found") + } + private fun generateKeyPair(): KeyPair { val keyPairGenerator = KeyPairGenerator.getInstance("RSA") keyPairGenerator.initialize(1024)