mirror of https://github.com/usbharu/Hideout.git
feat: HTTP Signatureを追加
This commit is contained in:
parent
ad17456754
commit
0d21be805b
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()) }
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue