From bf3fa2c0141cda10c589a17e4daa0fbe9eb4ada2 Mon Sep 17 00:00:00 2001
From: usbharu <i@usbharu.dev>
Date: Mon, 23 Sep 2024 23:17:06 +0900
Subject: [PATCH] =?UTF-8?q?feat:=20RSASSA-PKCS1-v1=5F5=20Using=20SHA-256?=
 =?UTF-8?q?=E3=81=A7=E4=BD=9C=E6=88=90=E3=81=95=E3=82=8C=E3=81=9F=E7=BD=B2?=
 =?UTF-8?q?=E5=90=8D=E3=82=92=E6=A4=9C=E8=A8=BC=E3=81=A7=E3=81=8D=E3=82=8B?=
 =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../v2/HttpMessageSignatureVerifier.kt        | 19 ++++
 .../v2/RsaV1_5Sha256SignatureVerifier.kt      | 13 +++
 .../usbharu/httpsignature/v2/SignatureBase.kt | 14 +--
 .../httpsignature/v2/SignatureVerifier.kt     |  7 ++
 .../httpsignature/v2/VerifyMaterial.kt        |  9 ++
 .../v2/HttpMessageSignatureVerifierTest.kt    | 86 +++++++++++++++++++
 6 files changed, 141 insertions(+), 7 deletions(-)
 create mode 100644 src/main/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifier.kt
 create mode 100644 src/main/kotlin/dev/usbharu/httpsignature/v2/RsaV1_5Sha256SignatureVerifier.kt
 create mode 100644 src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureVerifier.kt
 create mode 100644 src/main/kotlin/dev/usbharu/httpsignature/v2/VerifyMaterial.kt
 create mode 100644 src/test/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifierTest.kt

diff --git a/src/main/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifier.kt b/src/main/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifier.kt
new file mode 100644
index 0000000..be575dc
--- /dev/null
+++ b/src/main/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifier.kt
@@ -0,0 +1,19 @@
+package dev.usbharu.httpsignature.v2
+
+import java.util.*
+
+class HttpMessageSignatureVerifier {
+    fun verify(verifyMaterial: VerifyMaterial, signature: Signature, signatureVerifier: SignatureVerifier): Boolean {
+        val coveredComponents = verifyMaterial.signatureBase.coveredComponents()
+        signature.coveredComponents.all { coveredComponents.contains(it) }
+
+        val signatureBase = verifyMaterial.signatureBase.generateSignatureBase(signature.signatureParameters)
+
+        return signatureVerifier.verify(
+            signatureBase.toByteArray(Charsets.UTF_8),
+            Base64.getDecoder().decode(signature.signature),
+            verifyMaterial.publicKey
+        )
+
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/dev/usbharu/httpsignature/v2/RsaV1_5Sha256SignatureVerifier.kt b/src/main/kotlin/dev/usbharu/httpsignature/v2/RsaV1_5Sha256SignatureVerifier.kt
new file mode 100644
index 0000000..4953ada
--- /dev/null
+++ b/src/main/kotlin/dev/usbharu/httpsignature/v2/RsaV1_5Sha256SignatureVerifier.kt
@@ -0,0 +1,13 @@
+package dev.usbharu.httpsignature.v2
+
+import java.security.PublicKey
+import java.security.Signature
+
+class RsaV1_5Sha256SignatureVerifier : SignatureVerifier {
+    override fun verify(byteArray: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean {
+        val instance = Signature.getInstance("SHA256withRSA")
+        instance.initVerify(publicKey)
+        instance.update(byteArray)
+        return instance.verify(signature)
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureBase.kt b/src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureBase.kt
index 89efec5..e3baf75 100644
--- a/src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureBase.kt
+++ b/src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureBase.kt
@@ -2,18 +2,18 @@ package dev.usbharu.httpsignature.v2
 
 class SignatureBase() {
 
-    private val list = mutableMapOf<String, Component>()
+    private val list = mutableListOf<Component>()
 
     fun addComponent(component: Component) {
-        if (list[component.componentIdentifier] != null) {
+        if (list.indexOf(component) != -1) {
             throw IllegalArgumentException("Component with identifier ${component.componentIdentifier} already exists.")
         }
-        list[component.componentIdentifier] = component
+        list.add(component)
     }
 
     fun generateSignatureBase(signatureParameters: List<SignatureParameter>): String {
         val signatureBase =
-            list.values.joinToString(
+            list.joinToString(
                 separator = "",
                 postfix = "\n"
             ) { component -> "${component.componentIdentifier}: ${component.componentValue}" }
@@ -25,15 +25,15 @@ class SignatureBase() {
 
     fun generateSignatureParameterString(signatureParameters: List<SignatureParameter>): String {
         return (listOf(
-            list.keys.joinToString(
+            list.joinToString(
                 " ",
                 "(",
                 ")"
-            )
+            ) { it.componentIdentifier }
         ) + signatureParameters.map { "${it.name}=${it.value}" }).joinToString(";")
     }
 
     fun coveredComponents(): List<String> {
-        return list.map { it.key }
+        return list.map { it.componentIdentifier }
     }
 }
diff --git a/src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureVerifier.kt b/src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureVerifier.kt
new file mode 100644
index 0000000..27fbc9b
--- /dev/null
+++ b/src/main/kotlin/dev/usbharu/httpsignature/v2/SignatureVerifier.kt
@@ -0,0 +1,7 @@
+package dev.usbharu.httpsignature.v2
+
+import java.security.PublicKey
+
+interface SignatureVerifier {
+    fun verify(byteArray: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean
+}
\ No newline at end of file
diff --git a/src/main/kotlin/dev/usbharu/httpsignature/v2/VerifyMaterial.kt b/src/main/kotlin/dev/usbharu/httpsignature/v2/VerifyMaterial.kt
new file mode 100644
index 0000000..fb45f9c
--- /dev/null
+++ b/src/main/kotlin/dev/usbharu/httpsignature/v2/VerifyMaterial.kt
@@ -0,0 +1,9 @@
+package dev.usbharu.httpsignature.v2
+
+import java.security.PublicKey
+
+data class VerifyMaterial(
+    val signatureBase: SignatureBase,
+    val publicKey: PublicKey,
+    val label: String
+)
diff --git a/src/test/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifierTest.kt b/src/test/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifierTest.kt
new file mode 100644
index 0000000..fdf99f9
--- /dev/null
+++ b/src/test/kotlin/dev/usbharu/httpsignature/v2/HttpMessageSignatureVerifierTest.kt
@@ -0,0 +1,86 @@
+package dev.usbharu.httpsignature.v2
+
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+import java.security.KeyFactory
+import java.security.interfaces.RSAPrivateKey
+import java.security.interfaces.RSAPublicKey
+import java.security.spec.PKCS8EncodedKeySpec
+import java.security.spec.X509EncodedKeySpec
+import java.util.*
+
+class HttpMessageSignatureVerifierTest {
+
+    @Test
+    fun verify() {
+        val privateKey = Base64.getDecoder().decode(
+            "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDH+8IRQdB9vhej" +
+                    "eNQqwxlIutt4Xxjg3eZ5XYm7KgR4jAT/q7+cfsz2/fVTNwDOBpG2H1YJZRLUsrVI" +
+                    "3Pia8k7oFyemYKEUN6kaKioO5C9hB0TaiTGlxAKI7syitvedH1YtHqf4zvqgtI2y" +
+                    "Lv0NAXja3nUoWZWgZg8ZUHWAGhtHd/BpLiyvlwXAydFHl/eN5u8uCZhQD0bKI5KJ" +
+                    "qi8Vcs59Q13+ZNHFdgvNjeD+Hc2AdOmbB5HrW2wREgd1sygBcZ77sSLyleeel8+d" +
+                    "VkDsq/l4MQV5/0PeOn+PI8pJkyQUpqnExqzku354XW9ZE6XjREgBb9M0AEdyBLk5" +
+                    "+YbZwiqrAgMBAAECggEAIDgmKB7xDDvYFcpIbytHo47B/+ldBL2Q0vTdqn3hLTAX" +
+                    "OL80URj3b25dsVknPrToPO4HhTP3jgp3Z+nR/oS+Gb5r8O5DMBKs9+jbJdMK9G2g" +
+                    "tjoW+ZypcTj9VynLSFEy0nTMndVwTlFIkvCRwcqpl07yk9xQXas+ZixZrJiIKeyW" +
+                    "rCmBDJAjUSknljHDnULxAXvk6K7Y5uqPCv9DQ1362ZopY56H0++9ZMaJwr5PYJMT" +
+                    "QoKVeZCGLvfY29rUrhV0/CvC7cfxrPbSuQ7Tr/WrpxFpz9H/Dnc8uePoUEmMP2GM" +
+                    "ozjXaJDQrzOVMOpn/2uGinmrcR5/8ETsYruGG96kAQKBgQDoS4PyiZ93zTv5Yvo/" +
+                    "aWX5IvieMO/w3kRvsdq0IM27Gd+Ck+0C7WBUqljuU4ql10mp67MFkj7ZmECWCAKa" +
+                    "OfE3NtXKqnRgixDhM4Q7nfolhkN08CxRrYP3dBh7HJMDtb2YzPDdwV+PSbL6AM8o" +
+                    "oOvxjABJQk6CaGdmL8sQwnHwgQKBgQDcZCJbwDyLPJo04dXkhPBWoH5hGjbtsAv9" +
+                    "whhjY9IWFN/0KvJfzfoTWtgCkpYT3wgMYBVp0aTextbg1euim7X+iVL6TRq4QaVk" +
+                    "Jv6dRnNZPrY7MlnXxIXE81z6syVjChrJBWi81s14SDKtGOpvghLiKUR29wvGjfER" +
+                    "jY/X1MxFKwKBgAUyOz9fqLuLUb4gYqysdOV/zMPtIFDpB+rftZ615SQ8Te2j1Xdt" +
+                    "S+xY6yhZog5XpIQyi4yiWtmPOFKi1zwP879icKHZ8kR+l+ARwPF8dS4FtNiWzsb8" +
+                    "9KjCZhHK79bzZ8xVOUYcn0CbS2+gOQIVp3F9yjvZSdxM7ZMxmn9Dej0BAoGBAJuf" +
+                    "ZZeOLfJPz8AJvCSKLr+swrDEdwbtqfn8xYXhJacMBHwAm3dFFhH2stNWOP09HwzG" +
+                    "CDjZnWbl1zOaOrJu61saEurF6Vk0mZoX4vChn6/kFX/FdSVkEuVYx04LlBnUN8e8" +
+                    "txGpSBtoN8h88IXevoDOjRbIKZuB/Tjc0jagf8FTAoGAWW8uXsWS4c2nBGNdodqL" +
+                    "xJHcNZVMenHPqdkm7rHEYdf1sdbM7r7Q+oj0cifhbZwaRG9HiRGUAgJerLGEqe+x" +
+                    "vNeYuKRF3A5xBFUTw/t+XFhUZ1sSyvOordp0uNahQqkAx1UQFWUBCEkG2k/X81fY" +
+                    "trEnKP2IjOJDzoXGvc4TG0w="
+        )
+
+        val pkcS8EncodedKeySpec = PKCS8EncodedKeySpec(privateKey)
+        val rsaPrivateKey = KeyFactory.getInstance("RSA").generatePrivate(pkcS8EncodedKeySpec) as RSAPrivateKey
+
+        val publicKey = Base64.getDecoder().decode(
+            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx/vCEUHQfb4Xo3jUKsMZ" +
+                    "SLrbeF8Y4N3meV2JuyoEeIwE/6u/nH7M9v31UzcAzgaRth9WCWUS1LK1SNz4mvJO" +
+                    "6BcnpmChFDepGioqDuQvYQdE2okxpcQCiO7Morb3nR9WLR6n+M76oLSNsi79DQF4" +
+                    "2t51KFmVoGYPGVB1gBobR3fwaS4sr5cFwMnRR5f3jebvLgmYUA9GyiOSiaovFXLO" +
+                    "fUNd/mTRxXYLzY3g/h3NgHTpmweR61tsERIHdbMoAXGe+7Ei8pXnnpfPnVZA7Kv5" +
+                    "eDEFef9D3jp/jyPKSZMkFKapxMas5Lt+eF1vWROl40RIAW/TNABHcgS5OfmG2cIq" +
+                    "qwIDAQAB"
+        )
+
+        val x509EncodedKeySpec = X509EncodedKeySpec(publicKey)
+        val rsaPublicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec) as RSAPublicKey
+
+        val signatureBase = SignatureBaseBuilder()
+            .header("Host", "example.com")
+            .build()
+
+        val material = Material(
+            signatureBase,
+            rsaPrivateKey,
+            "label"
+        )
+
+        val signer = HttpMessageSignatureSigner()
+
+        val sign = signer.sign(material, SignatureParameters().toParameterList(), RsaV1_5Sha256SignatureSigner())
+
+        val httpMessageSignatureVerifier = HttpMessageSignatureVerifier()
+
+        val actual = httpMessageSignatureVerifier.verify(
+            VerifyMaterial(
+                signatureBase, rsaPublicKey, "label"
+            ),
+            sign, RsaV1_5Sha256SignatureVerifier()
+        )
+
+        assertTrue(actual)
+    }
+}
\ No newline at end of file