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() + private val list = mutableListOf() 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): 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): String { return (listOf( - list.keys.joinToString( + list.joinToString( " ", "(", ")" - ) + ) { it.componentIdentifier } ) + signatureParameters.map { "${it.name}=${it.value}" }).joinToString(";") } fun coveredComponents(): List { - 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