From 10d50815b8c2722db4b74058a51ec8d6a14111c6 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:57:01 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20HTTP=20Signature=E3=81=AE=E7=BD=B2?= =?UTF-8?q?=E5=90=8D=E5=99=A8=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/signature/HttpSignatureSigner.kt | 16 ++++ .../signature/HttpSignatureSignerImpl.kt | 78 +++++++++++++++++++ .../usbharu/hideout/service/signature/Key.kt | 10 +++ .../usbharu/hideout/service/signature/Sign.kt | 6 ++ .../service/signature/SignedRequest.kt | 23 ++++++ 5 files changed, 133 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSigner.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSignerImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/signature/Key.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/signature/Sign.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/signature/SignedRequest.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSigner.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSigner.kt new file mode 100644 index 00000000..4f0eb495 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSigner.kt @@ -0,0 +1,16 @@ +package dev.usbharu.hideout.service.signature + +import io.ktor.http.* + +interface HttpSignatureSigner { + suspend fun sign( + url: String, + method: HttpMethod, + headers: Headers, + requestBody: String, + keyPair: Key, + signHeaders: List + ): SignedRequest + + suspend fun signRaw(signString: String, keyPair: Key, signHeaders: List): Sign +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSignerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSignerImpl.kt new file mode 100644 index 00000000..f26a820d --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureSignerImpl.kt @@ -0,0 +1,78 @@ +package dev.usbharu.hideout.service.signature + +import dev.usbharu.hideout.util.Base64Util +import io.ktor.http.* +import io.ktor.util.* +import org.springframework.stereotype.Component +import java.net.URL +import java.security.Signature + +@Component +class HttpSignatureSignerImpl : HttpSignatureSigner { + override suspend fun sign( + url: String, + method: HttpMethod, + headers: Headers, + requestBody: String, + keyPair: Key, + signHeaders: List + ): SignedRequest { + val sign = signRaw( + signString = buildSignString( + url = URL(url), + method = method, + headers = headers, + signHeaders = signHeaders + ), + keyPair = keyPair, + signHeaders = signHeaders + ) + return SignedRequest( + url = url, + method = method, + headers = headers, + requestBody = requestBody, + sign = sign + ) + } + + override suspend fun signRaw(signString: String, keyPair: Key, signHeaders: List): Sign { + val signer = Signature.getInstance("SHA256withRSA") + signer.initSign(keyPair.privateKey) + signer.update(signString.toByteArray()) + val sign = signer.sign() + val signature = Base64Util.encode(sign) + return Sign( + signature, + """keyId="${keyPair.keyId}",algorithm="${signHeaders.joinToString(" ")}",signature="$signature"""" + ) + } + + private fun buildSignString( + url: URL, + method: HttpMethod, + headers: Headers, + signHeaders: List + ): String { + headers.toMap().map { it.key.lowercase() to it.value }.toMap() + val result = signHeaders.map { + if (it.startsWith("(")) { + specialHeader(it, url, method) + } else { + generalHeader(it, headers.get(it)!!) + } + }.joinToString("\n") + return result + } + + private fun specialHeader(fieldName: String, url: URL, method: HttpMethod): String { + if (fieldName != "(request-target)") { + throw IllegalArgumentException(fieldName + "is unsupported type") + } + return "(request-target): ${method.value.lowercase()} ${url.path}" + } + + private fun generalHeader(fieldName: String, value: String): String { + return "$fieldName: $value" + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/Key.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/Key.kt new file mode 100644 index 00000000..0eb5171f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/Key.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.service.signature + +import java.security.PrivateKey +import java.security.PublicKey + +data class Key( + val keyId: String, + val privateKey: PrivateKey, + val publicKey: PublicKey +) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/Sign.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/Sign.kt new file mode 100644 index 00000000..75711759 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/Sign.kt @@ -0,0 +1,6 @@ +package dev.usbharu.hideout.service.signature + +data class Sign( + val signature: String, + val signatureHeader: String +) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/SignedRequest.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/SignedRequest.kt new file mode 100644 index 00000000..346dca87 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/SignedRequest.kt @@ -0,0 +1,23 @@ +package dev.usbharu.hideout.service.signature + +import io.ktor.client.request.* +import io.ktor.http.* + +data class SignedRequest( + val url: String, + val method: HttpMethod, + val headers: Headers, + val requestBody: String, + val sign: Sign +) { + fun toRequestBuilder(): HttpRequestBuilder { + val httpRequestBuilder = HttpRequestBuilder() + httpRequestBuilder.url(this.url) + httpRequestBuilder.method = this.method + httpRequestBuilder.headers { + this.appendAll(headers) + } + httpRequestBuilder.setBody(requestBody) + return httpRequestBuilder + } +}