From 4826b8a54f55e0497fb2fa00d02ad3a850b756c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabrielle=20Guimar=C3=A3es=20de=20Oliveira?= Date: Tue, 8 Aug 2023 22:41:08 +0000 Subject: [PATCH] feat: add validation --- src/main/kotlin/Validate.kt | 63 ++++++++++++++++++++++++++++++++ src/test/kotlin/ValidateTests.kt | 31 ++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/main/kotlin/Validate.kt create mode 100644 src/test/kotlin/ValidateTests.kt diff --git a/src/main/kotlin/Validate.kt b/src/main/kotlin/Validate.kt new file mode 100644 index 0000000..8050c32 --- /dev/null +++ b/src/main/kotlin/Validate.kt @@ -0,0 +1,63 @@ +@file:JvmName("OpenSSL") + +package br.com.openpix.sdk + +import io.ktor.util.* +import java.security.KeyFactory +import java.security.PublicKey +import java.security.Signature +import java.security.spec.X509EncodedKeySpec + +/** + * The public key used to verify the signature of the webhook. + * + * @see verify + */ +@Suppress("MaxLineLength") +public const val PUBLIC_KEY: String = + "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDLytOdElranpldnZxRCtJM01NdjNiTFhEdApwdnhCalk0QnNSclNkY2EzcnRBd01jUllZdnhTbmQ3amFnVkxwY3RNaU94UU84aWVVQ0tMU1dIcHNNQWpPL3paCldNS2Jxb0c4TU5waS91M2ZwNnp6MG1jSENPU3FZc1BVVUcxOWJ1VzhiaXM1WloySVpnQk9iV1NwVHZKMGNuajYKSEtCQUE4MkpsbitsR3dTMU13SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=" + +/** + * The certificate used to verify the signature of the webhook. + * + * @see verify + */ +private val publicKey: PublicKey = run { + val base64 = PUBLIC_KEY.decodeBase64String() + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("\\n", "") + .replace("\n", "") + .replace("-----END PUBLIC KEY-----","") + .decodeBase64Bytes() + + KeyFactory + .getInstance("RSA") + .generatePublic(X509EncodedKeySpec(base64)) +} + +/** + * Verifies the signature of the webhook. + * + * ```java + * String payload; + * String signature; + * + * OpenSSL.verify(payload, signature); + * ``` + * + * @param payload The payload of the webhook. + * @param signature The signature of the webhook. + * @return `true` if the signature is valid, `false` otherwise. + */ +public fun verify(payload: String, signature: String): Boolean { + runCatching { + return Signature.getInstance("SHA256withRSA").run { + initVerify(publicKey) + update(payload.toByteArray()) + + verify(signature.decodeBase64Bytes()) + } + }.getOrElse { + return false + } +} diff --git a/src/test/kotlin/ValidateTests.kt b/src/test/kotlin/ValidateTests.kt new file mode 100644 index 0000000..c86996e --- /dev/null +++ b/src/test/kotlin/ValidateTests.kt @@ -0,0 +1,31 @@ +package br.com.openpix.sdk + +import org.assertj.core.api.Assertions.* +import org.junit.jupiter.api.Test + +class ValidateTests { + companion object { + @Suppress("MaxLineLength") + const val PAYLOAD: String = + """{ "pixQrCode": null, "charge": { "status": "COMPLETED", "customer": { "name": "Antonio Victor", "taxID": { "taxID": "12345678976", "type": "BR:CPF" }, "email": "antoniocliente@example.com", "correlationID": "4979ceba-2132-4292-bd90-bee7fb2125e4" }, "value": 1000, "comment": "Pagamento OpenPix", "transactionID": "ea83401ed4834b3ea6f1f283b389af29", "correlationID": "417bae21-3d08-4cdb-9c2d-fee63c89e9e4", "paymentLinkID": "34697ed2-3790-4b60-8512-e7465b142d84", "createdAt": "2021-03-12T12:43:54.528Z", "updatedAt": "2021-03-12T12:44:09.360Z", "brCode": "https://api.openpix.com.br/openpix/openpix/testing?transactionID=ea83401ed4834b3ea6f1f283b389af29" }, "pix": { "charge": { "status": "COMPLETED", "customer": { "name": "Antonio Victor", "taxID": { "taxID": "12345678976", "type": "BR:CPF" }, "email": "antoniocliente@example.com", "correlationID": "4979ceba-2132-4292-bd90-bee7fb2125e4" }, "value": 1000, "comment": "Pagamento OpenPix", "transactionID": "ea83401ed4834b3ea6f1f283b389af29", "correlationID": "417bae21-3d08-4cdb-9c2d-fee63c89e9e4", "paymentLinkID": "34697ed2-3790-4b60-8512-e7465b142d84", "createdAt": "2021-03-12T12:43:54.528Z", "updatedAt": "2021-03-12T12:44:09.360Z" }, "customer": { "correlationID": "9134e286-6f71-427a-bf00-241681624586", "email": "email1@example.com", "name": "Loma", "phone": "+5511999999999", "taxID": { "taxID": "47043622050", "type": "BR:CPF" } }, "payer": { "correlationID": "9134e286-6f71-427a-bf00-241681624586", "email": "email1@example.com", "name": "Loma", "phone": "+5511999999999", "taxID": { "taxID": "47043622050", "type": "BR:CPF" } }, "time": "2021-03-12T12:44:09.269Z", "value": 1, "transactionID": "ea83401ed4834b3ea6f1f283b389af29", "infoPagador": "OpenPix testing" }, "company": { "id": "624f46f9e93f9f521c8308d7", "name": "Pizzaria do José", "taxID": "4722767300014" }, "account": { "clientId": "ZOJ64B9B-ZM1W-89MI-4UCI-OP2LVIU6NY75" } }""" + + @Suppress("MaxLineLength") + const val SIGNATURE: String = + "lL2nnXgmLFGgxJ8+jCDguqouU4ucrIxYJcU5SPrJFaNcJajTJHYVldqc/z4YFIjAjtPEALe699WosgPY08W7CLpidvtm06Qwa4YMB0l/DcTS93O91NdSH/adjugEKiOb76Zj/0jB8mqOmWCFYbweOBa17bssuEkd5Lw7Q5L314Y=" + } + + @Test + fun `verify true`() { + assertThat(verify(PAYLOAD, SIGNATURE)).isTrue() + } + + @Test + fun `verify false with payload`() { + assertThat(verify("{ \"payload\": \"invalid\" }", SIGNATURE)).isFalse() + } + + @Test + fun `verify false with signature`() { + assertThat(verify(PAYLOAD, "no")).isFalse() + } +}