-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds HMAC signature verification (#28)
closes #11
- Loading branch information
1 parent
aa325ad
commit 9876aef
Showing
8 changed files
with
269 additions
and
14 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
src/main/java/io/camunda/connector/inbound/configs/LocalContextBeanConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.camunda.connector.inbound.configs; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class LocalContextBeanConfiguration { | ||
|
||
@Bean | ||
public ObjectMapper jacksonMapper() { | ||
return new ObjectMapper(); | ||
} | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/io/camunda/connector/inbound/security/signature/HMACAlgoCustomerChoice.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package io.camunda.connector.inbound.security.signature; | ||
|
||
public enum HMACAlgoCustomerChoice { | ||
|
||
sha_1("HmacSHA1", "sha1"), | ||
sha_256("HmacSHA256", "sha256"), | ||
sha_512("HmacSHA512", "sha512"); | ||
|
||
private final String algoReference; | ||
private final String tag; | ||
|
||
HMACAlgoCustomerChoice(final String algoReference, final String tag) { | ||
this.algoReference = algoReference; | ||
this.tag = tag; | ||
} | ||
|
||
public String getAlgoReference() { | ||
return algoReference; | ||
} | ||
|
||
public String getTag() { | ||
return tag; | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/main/java/io/camunda/connector/inbound/security/signature/HMACSignatureValidator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package io.camunda.connector.inbound.security.signature; | ||
|
||
import org.apache.commons.codec.binary.Hex; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.util.StreamUtils; | ||
|
||
import javax.crypto.Mac; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import javax.servlet.http.HttpServletRequest; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.util.Map; | ||
|
||
// TODO: add URL signing and Base64 format | ||
public class HMACSignatureValidator { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(HMACSignatureValidator.class); | ||
|
||
private final byte[] requestBody; | ||
private final Map<String, String> headers; | ||
private final String hmacHeader; | ||
private final String hmacSecretKey; | ||
private final HMACAlgoCustomerChoice hmacAlgo; | ||
|
||
public HMACSignatureValidator( | ||
final byte[] requestBody, | ||
final Map<String, String> headers, | ||
final String hmacHeader, | ||
final String hmacSecretKey, | ||
final HMACAlgoCustomerChoice hmacAlgo) { | ||
this.requestBody = requestBody; | ||
this.headers = headers; | ||
this.hmacHeader = hmacHeader; | ||
this.hmacSecretKey = hmacSecretKey; | ||
this.hmacAlgo = hmacAlgo; | ||
} | ||
|
||
public boolean isRequestValid() throws NoSuchAlgorithmException, InvalidKeyException { | ||
final String providedHmac = headers.get(hmacHeader); | ||
|
||
LOG.debug("Given HMAC from webhook call: {}", providedHmac); | ||
|
||
byte[] signedEntity = requestBody; | ||
|
||
Mac sha256_HMAC = Mac.getInstance(hmacAlgo.getAlgoReference()); | ||
SecretKeySpec secret_key = | ||
new SecretKeySpec(hmacSecretKey.getBytes(StandardCharsets.UTF_8), hmacAlgo.getAlgoReference()); | ||
sha256_HMAC.init(secret_key); | ||
byte[] expectedHmac = sha256_HMAC.doFinal(signedEntity); | ||
|
||
// Some webhooks produce short HMAC message, e.g. aabbcc... | ||
String expectedShortHmacString = Hex.encodeHexString(expectedHmac); | ||
// The other produce longer version, like sha256=aabbcc... | ||
String expectedLongHmacString = hmacAlgo.getTag() + "=" + expectedShortHmacString; | ||
LOG.debug("Computed HMAC from webhook body: {}, {}", expectedShortHmacString, expectedLongHmacString); | ||
|
||
return providedHmac.equals(expectedShortHmacString) || providedHmac.equals(expectedLongHmacString); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
...test/java/io/camunda/connector/inbound/security/signature/HMACSignatureValidatorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package io.camunda.connector.inbound.security.signature; | ||
|
||
import org.assertj.core.api.Assertions; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.util.Map; | ||
import java.util.stream.Stream; | ||
|
||
import static java.nio.charset.StandardCharsets.UTF_8; | ||
import static java.nio.file.Files.readString; | ||
|
||
class HMACSignatureValidatorTest { | ||
|
||
private static final String GH_SHA1_HEADER = "X-Hub-Signature"; | ||
private static final String GH_SHA1_VALUE = "sha1=de81c837cc792e7d21d7bf9feb74cd19d714baca"; | ||
private static final String GH_SHA256_HEADER = "X-Hub-Signature-256"; | ||
private static final String GH_SHA256_LONG_VALUE = "sha256=dd22cfb7ae96875d81bd1a695a0244f2b4c32c0938be0b445f520b0b3e0f43fd"; | ||
private static final String GH_SHA256_SHORT_VALUE = "dd22cfb7ae96875d81bd1a695a0244f2b4c32c0938be0b445f520b0b3e0f43fd"; | ||
private static final String GH_SECRET_KEY = "mySecretKey"; | ||
|
||
|
||
@ParameterizedTest | ||
@MethodSource("provideHMACTestData") | ||
public void hmacSignatureVerificationParametrizedTest(final HMACTestEntry testEntry) | ||
throws IOException, NoSuchAlgorithmException, InvalidKeyException { | ||
HMACSignatureValidator validator = new HMACSignatureValidator( | ||
readString(new File(testEntry.filepathWithBody).toPath(), UTF_8).getBytes(UTF_8), | ||
testEntry.originalRequestHeaders, | ||
testEntry.headerWithHmac, | ||
testEntry.decodedSecretKey, | ||
testEntry.algo | ||
); | ||
Assertions.assertThat(validator.isRequestValid()).isTrue(); | ||
} | ||
|
||
private static Stream<HMACTestEntry> provideHMACTestData() { | ||
return Stream.of( | ||
new HMACTestEntry( | ||
"src/test/resources/hmac/gh-webhook-request.json", | ||
Map.of(GH_SHA256_HEADER, GH_SHA256_LONG_VALUE), | ||
GH_SHA256_HEADER, | ||
GH_SECRET_KEY, | ||
HMACAlgoCustomerChoice.sha_256), | ||
new HMACTestEntry( | ||
"src/test/resources/hmac/gh-webhook-request.json", | ||
Map.of(GH_SHA1_HEADER, GH_SHA1_VALUE), | ||
GH_SHA1_HEADER, | ||
GH_SECRET_KEY, | ||
HMACAlgoCustomerChoice.sha_1), | ||
new HMACTestEntry( | ||
"src/test/resources/hmac/gh-webhook-request.json", | ||
Map.of(GH_SHA256_HEADER, GH_SHA256_SHORT_VALUE), | ||
GH_SHA256_HEADER, | ||
GH_SECRET_KEY, | ||
HMACAlgoCustomerChoice.sha_256) | ||
); | ||
} | ||
|
||
private static class HMACTestEntry { | ||
final String filepathWithBody; | ||
final Map<String, String> originalRequestHeaders; | ||
final String headerWithHmac; | ||
final String decodedSecretKey; | ||
final HMACAlgoCustomerChoice algo; | ||
|
||
public HMACTestEntry(String filepathWithBody, | ||
Map<String, String> originalRequestHeaders, | ||
String headerWithHmac, | ||
String decodedSecretKey, | ||
HMACAlgoCustomerChoice algo) { | ||
this.filepathWithBody = filepathWithBody; | ||
this.originalRequestHeaders = originalRequestHeaders; | ||
this.headerWithHmac = headerWithHmac; | ||
this.decodedSecretKey = decodedSecretKey; | ||
this.algo = algo; | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.