feat: HTTP Signatureを追加

This commit is contained in:
usbharu 2023-03-31 12:23:58 +09:00
parent ad17456754
commit 0d21be805b
6 changed files with 163 additions and 3 deletions

View File

@ -58,6 +58,9 @@ dependencies {
implementation("io.ktor:ktor-client-core:$ktor_version") implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("io.ktor:ktor-client-content-negotiation:$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") testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
} }

View File

@ -12,17 +12,14 @@ import dev.usbharu.hideout.repository.UserAuthRepository
import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.repository.UserRepository
import dev.usbharu.hideout.routing.* import dev.usbharu.hideout.routing.*
import dev.usbharu.hideout.service.* import dev.usbharu.hideout.service.*
import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.cio.* import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.* import io.ktor.client.plugins.logging.*
import io.ktor.http.*
import io.ktor.serialization.jackson.* import io.ktor.serialization.jackson.*
import io.ktor.server.application.* import io.ktor.server.application.*
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.ktor.ext.inject import org.koin.ktor.ext.inject
import java.lang.Compiler.enable
import java.util.* import java.util.*
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args) fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@ -60,6 +57,9 @@ fun Application.module() {
logger = Logger.DEFAULT logger = Logger.DEFAULT
level = LogLevel.ALL level = LogLevel.ALL
} }
install(httpSignaturePlugin){
keyMap = KtorKeyMap(get())
}
} }
} }
single<IUserRepository> { UserRepository(get()) } single<IUserRepository> { UserRepository(get()) }

View File

@ -2,13 +2,160 @@ package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.ap.JsonLd import dev.usbharu.hideout.ap.JsonLd
import dev.usbharu.hideout.config.Config 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 dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.plugins.api.*
import io.ktor.client.request.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.response.* 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 <T : JsonLd> ApplicationCall.respondAp(message: T, status: HttpStatusCode = HttpStatusCode.OK) { suspend fun <T : JsonLd> ApplicationCall.respondAp(message: T, status: HttpStatusCode = HttpStatusCode.OK) {
message.context += "https://www.w3.org/ns/activitystreams" message.context += "https://www.w3.org/ns/activitystreams"
val activityJson = Config.configData.objectMapper.writeValueAsString(message) val activityJson = Config.configData.objectMapper.writeValueAsString(message)
respondText(activityJson, ContentType.Application.Activity, status) 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<String>()
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<String> {
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")
}
}

View File

@ -1,5 +1,7 @@
package dev.usbharu.hideout.service package dev.usbharu.hideout.service
import java.security.PrivateKey
class HttpSignService { class HttpSignService {
suspend fun sign(){ suspend fun sign(){

View File

@ -11,4 +11,6 @@ interface IUserAuthService {
suspend fun verifyAccount(username: String,password: String): Boolean suspend fun verifyAccount(username: String,password: String): Boolean
suspend fun findByUserId(userId: Long):UserAuthenticationEntity suspend fun findByUserId(userId: Long):UserAuthenticationEntity
suspend fun findByUsername(username: String):UserAuthenticationEntity
} }

View File

@ -68,6 +68,12 @@ class UserAuthService(
return userAuthRepository.findByUserId(userId) ?: throw UserNotFoundException("$userId was not found") 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 { private fun generateKeyPair(): KeyPair {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA") val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024) keyPairGenerator.initialize(1024)