diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml
index 1da66de8b5..c333097e52 100644
--- a/app/server/appsmith-server/pom.xml
+++ b/app/server/appsmith-server/pom.xml
@@ -238,6 +238,11 @@
test
+
+ org.bouncycastle
+ bcprov-jdk18on
+ 1.72
+
org.junit.platform
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ApiConstants.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ApiConstants.java
new file mode 100644
index 0000000000..12716eb0b3
--- /dev/null
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ApiConstants.java
@@ -0,0 +1,6 @@
+package com.appsmith.server.constants;
+
+public class ApiConstants {
+ public static final String DATE = "Date";
+ public static final String CLOUD_SERVICES_SIGNATURE = "X-Appsmith-CS-Signature";
+}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/SignatureVerifier.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/SignatureVerifier.java
new file mode 100644
index 0000000000..b744a411b2
--- /dev/null
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/SignatureVerifier.java
@@ -0,0 +1,95 @@
+package com.appsmith.server.helpers;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.signers.Ed25519Signer;
+import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
+import org.pf4j.util.StringUtils;
+import org.springframework.http.HttpHeaders;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Base64;
+
+import static com.appsmith.server.constants.ApiConstants.CLOUD_SERVICES_SIGNATURE;
+import static com.appsmith.server.constants.ApiConstants.DATE;
+
+@Slf4j
+public class SignatureVerifier {
+
+ // Public key for verifying signatures with ED25519 scheme.
+ private static final String PUBLIC_VERIFICATION_KEY =
+ "AAAAC3NzaC1lZDI1NTE5AAAAICNwJ+zx2opXjjOga/YyzRxb2czvNgQ/twA+miCKDIX3";
+
+ private static final String TIMESTAMP = "timestamp";
+
+ private static final String EQUAL = "=";
+
+ private static final AsymmetricKeyParameter publicKeyParameters;
+
+ static {
+ publicKeyParameters =
+ OpenSSHPublicKeyUtil.parsePublicKey(Base64.getDecoder().decode(PUBLIC_VERIFICATION_KEY));
+ }
+
+ /**
+ * Method to verify the API signature from CS.
+ * @param headers Response headers from CS
+ * @return If the signature is valid
+ */
+ public static boolean isSignatureValid(HttpHeaders headers) {
+ if (CollectionUtils.isEmpty(headers.get(CLOUD_SERVICES_SIGNATURE))
+ || CollectionUtils.isEmpty(headers.get(DATE))) {
+ return false;
+ }
+ String signature = headers.get(CLOUD_SERVICES_SIGNATURE).get(0);
+ String date = headers.get(DATE).get(0);
+ if (StringUtils.isNullOrEmpty(signature) || StringUtils.isNullOrEmpty(date)) {
+ return false;
+ }
+ try {
+ if (publicKeyParameters == null) {
+ return false;
+ }
+ return isSignatureValid(signature, date);
+ } catch (Exception exception) {
+ log.debug("Error occurred while verifying CS signature.", exception);
+ return false;
+ }
+ }
+
+ private static boolean isSignatureValid(String signature, String dateHeader) {
+ String[] signatureParts = signature.split("\\.", 2);
+ if (signatureParts.length != 2) {
+ return false;
+ }
+ String signingData = signatureParts[0];
+ String encodedSignature = signatureParts[1];
+
+ // Decode base64 signature and signing data to byte arrays
+ byte[] signatureBytes = Base64.getUrlDecoder().decode(encodedSignature);
+ byte[] signingDataBytes = signingData.getBytes(StandardCharsets.UTF_8);
+
+ // Set up Ed25519 verifier
+ Ed25519Signer verifier = new Ed25519Signer();
+ verifier.init(false, publicKeyParameters);
+ verifier.update(signingDataBytes, 0, signingDataBytes.length);
+
+ // Verify the signature to check if the data is tampered
+ if (verifier.verifySignature(signatureBytes)) {
+ String decodedData = new String(Base64.getDecoder().decode(signingData));
+ // To avoid the replay attacks check if the date provided in the header is within 24hrs and matches with
+ // the date used while encoding the data.
+ // Data format for ref: String.format("data=%s_timestamp=%s", dataset.toString(), date)
+ String timestampFieldFormat = TIMESTAMP + EQUAL;
+ String date =
+ decodedData.substring(decodedData.indexOf(timestampFieldFormat) + timestampFieldFormat.length());
+
+ return dateHeader.equals(date)
+ && Instant.parse(date).isAfter(Instant.now().minus(24, ChronoUnit.HOURS));
+ }
+ return false;
+ }
+}
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/SignatureVerifierTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/SignatureVerifierTest.java
new file mode 100644
index 0000000000..cebd31a8ee
--- /dev/null
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/SignatureVerifierTest.java
@@ -0,0 +1,32 @@
+package com.appsmith.server.helpers;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpHeaders;
+
+import java.time.Instant;
+import java.util.UUID;
+
+import static com.appsmith.server.constants.ApiConstants.CLOUD_SERVICES_SIGNATURE;
+import static com.appsmith.server.constants.ApiConstants.DATE;
+
+class SignatureVerifierTest {
+
+ @Test
+ public void invalidSignature_verifySignatureFormat_returnFalse() {
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set(DATE, Instant.now().toString());
+ headers.set(CLOUD_SERVICES_SIGNATURE, "");
+ Assertions.assertFalse(SignatureVerifier.isSignatureValid(headers));
+ }
+
+ @Test
+ public void invalidSignature_verifySignature_returnFalse() {
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set(DATE, Instant.now().toString());
+ headers.set(CLOUD_SERVICES_SIGNATURE, UUID.randomUUID().toString());
+ Assertions.assertFalse(SignatureVerifier.isSignatureValid(headers));
+ }
+}