diff --git a/pom.xml b/pom.xml index cdec1f160..75d692e91 100644 --- a/pom.xml +++ b/pom.xml @@ -23,16 +23,45 @@ + 11 11 - UTF-8 UTF-8 - 1.6.16 adyen https://sonarcloud.io + + + + 2.21.0 + 2.11.0 + 1.18.0 + 1.9.0 + 1.6.16 + 2.2.30 + 3.1.0 + 2.3.1 + 5.5 + + + 3.16.4 + 3.0 5.11.4 5.21.0 + + + 3.14.1 + 3.5.0 + 0.8.14 + 3.3.1 + 3.12.0 + 3.2.8 + 0.7.0 + 3.6.0 + 5.1.9 + 3.2.1 + 3.5.2 + 1.34.1 scm:git:git@github.com:Adyen/adyen-java-api-library.git @@ -44,34 +73,22 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + ${maven-compiler-plugin-version} org.apache.maven.plugins maven-jar-plugin - 3.5.0 + ${maven-jar-plugin-version} ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - - javax.xml.bind - jaxb-api - 2.3.1 - - - org.jacoco jacoco-maven-plugin - 0.8.14 + ${jacoco-maven-plugin-version} prepare-agent @@ -95,7 +112,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + ${maven-source-plugin-version} attach-sources @@ -108,7 +125,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.12.0 + ${maven-javadoc-plugin-version} attach-javadocs @@ -121,7 +138,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.8 + ${maven-gpg-plugin-version} sign-artifacts @@ -136,7 +153,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.7.0 + ${central-publishing-maven-plugin-version} true central @@ -149,7 +166,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.6.0 + ${maven-checkstyle-plugin-version} checkstyle.xml checkstyle-suppressions.xml @@ -169,7 +186,7 @@ org.apache.felix maven-bundle-plugin - 5.1.9 + ${maven-bundle-plugin-version} bundle-manifest @@ -189,7 +206,7 @@ com.diffplug.spotless spotless-maven-plugin - 3.2.1 + ${spotless-maven-plugin-version} @@ -204,11 +221,11 @@ src/test/java/**/*.java - 1.34.1 + ${google-java-format-version} true - + google-java-format @@ -218,100 +235,94 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.2 + ${maven-surefire-plugin-version} - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.21.0 - + com.fasterxml.jackson.core jackson-databind - 2.21.0 + ${com.fasterxml.jackson-version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${com.fasterxml.jackson-version} com.google.code.gson gson - 2.11.0 - compile + ${gson-version} - org.apache.httpcomponents.client5 - httpclient5 - 5.5 - compile + commons-codec + commons-codec + ${commons-codec-version} - - org.junit.jupiter - junit-jupiter - ${junit-jupiter-version} - test + io.gsonfire + gson-fire + ${gson-fire-version} - org.mockito - mockito-junit-jupiter - ${mockito-version} - test + io.swagger + swagger-annotations + ${swagger-core-version} - org.mockito - mockito-core - ${mockito-version} - test + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-v3-version} - org.hamcrest - hamcrest - 3.0 - test + jakarta.ws.rs + jakarta.ws.rs-api + ${jakarta.ws.rs-api-version} javax.xml.bind jaxb-api - 2.3.1 - compile + ${jaxb-api-version} - io.swagger.core.v3 - swagger-annotations - 2.2.30 - compile + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5-version} + + - jakarta.ws.rs - jakarta.ws.rs-api - 3.1.0 - compile + com.squareup.okio + okio + ${okio-version} + test - io.gsonfire - gson-fire - 1.9.0 - compile + org.hamcrest + hamcrest + ${hamcrest-version} + test - com.squareup.okio - okio - 3.16.4 + org.junit.jupiter + junit-jupiter + ${junit-jupiter-version} test - commons-codec - commons-codec - 1.18.0 + org.mockito + mockito-core + ${mockito-version} + test - - io.swagger - swagger-annotations - ${swagger-core-version} - compile + org.mockito + mockito-junit-jupiter + ${mockito-version} + test diff --git a/src/main/java/com/adyen/util/HMACValidator.java b/src/main/java/com/adyen/util/HMACValidator.java index 75784542c..5f783101d 100644 --- a/src/main/java/com/adyen/util/HMACValidator.java +++ b/src/main/java/com/adyen/util/HMACValidator.java @@ -14,7 +14,7 @@ * * Adyen Java API Library * - * Copyright (c) 2017 Adyen B.V. + * Copyright (c) 2026 Adyen B.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. */ @@ -31,8 +31,9 @@ import java.util.List; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import javax.xml.bind.DatatypeConverter; +import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; /** Utility class for generating and validating HMAC signatures used in Adyen webhooks. */ public class HMACValidator { @@ -48,7 +49,7 @@ public class HMACValidator { * @param data the data to sign * @param key the HMAC key in hexadecimal format * @return the Base64-encoded HMAC signature - * @throws IllegalArgumentException if the data or key is null + * @throws IllegalArgumentException if the data or key is null or if key is invalid * @throws SignatureException if signature generation fails */ public String calculateHMAC(String data, String key) @@ -61,7 +62,7 @@ public String calculateHMAC(String data, String key) throw new IllegalArgumentException("HMAC key is not provided"); } - byte[] rawKey = DatatypeConverter.parseHexBinary(key); + byte[] rawKey = Hex.decodeHex(key); SecretKeySpec signingKey = new SecretKeySpec(rawKey, HMAC_SHA256_ALGORITHM); Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM); @@ -71,6 +72,8 @@ public String calculateHMAC(String data, String key) return new String(Base64.encodeBase64(rawHmac)); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Missing data or key: " + e.getMessage()); + } catch (DecoderException e) { + throw new IllegalArgumentException("Invalid Hex HMAC key: " + key); } catch (Exception e) { throw new SignatureException("Failed to generate HMAC: " + e.getMessage()); } diff --git a/src/test/java/com/adyen/util/HMACValidatorTest.java b/src/test/java/com/adyen/util/HMACValidatorTest.java index 3070bab33..2962a8b3a 100644 --- a/src/test/java/com/adyen/util/HMACValidatorTest.java +++ b/src/test/java/com/adyen/util/HMACValidatorTest.java @@ -22,8 +22,8 @@ package com.adyen.util; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.adyen.BaseTest; import com.adyen.model.notification.NotificationRequestItem; @@ -77,7 +77,7 @@ public void testValidateHMACWithoutEscaping() throws SignatureException { } @Test - public void testValidateHMACNullHmacSignature() throws SignatureException { + public void testValidateHMACNullHmacSignature() { String notificationJson = "{\n" + " \"additionalData\": {\n" @@ -94,18 +94,17 @@ public void testValidateHMACNullHmacSignature() throws SignatureException { + " \"reason\": \"will contain the url to the report\",\n" + " \"success\": \"true\"\n" + "}"; - try { - NotificationRequestItem notificationRequest = - new Gson().fromJson(notificationJson, NotificationRequestItem.class); - boolean result = new HMACValidator().validateHMAC(notificationRequest, HMAC_KEY); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Missing hmacSignature", e.getMessage()); - } + NotificationRequestItem notificationRequest = + new Gson().fromJson(notificationJson, NotificationRequestItem.class); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new HMACValidator().validateHMAC(notificationRequest, HMAC_KEY)); + assertEquals("Missing hmacSignature", exception.getMessage()); } @Test - public void testValidateHMACEmptyHmacSignature() throws SignatureException { + public void testValidateHMACEmptyHmacSignature() { String notificationJson = "{\n" + " \"additionalData\": {\n" @@ -123,14 +122,13 @@ public void testValidateHMACEmptyHmacSignature() throws SignatureException { + " \"reason\": \"will contain the url to the report\",\n" + " \"success\": \"true\"\n" + "}"; - try { - NotificationRequestItem notificationRequest = - new Gson().fromJson(notificationJson, NotificationRequestItem.class); - boolean result = new HMACValidator().validateHMAC(notificationRequest, HMAC_KEY); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Missing hmacSignature", e.getMessage()); - } + NotificationRequestItem notificationRequest = + new Gson().fromJson(notificationJson, NotificationRequestItem.class); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new HMACValidator().validateHMAC(notificationRequest, HMAC_KEY)); + assertEquals("Missing hmacSignature", exception.getMessage()); } @Test @@ -157,32 +155,44 @@ public void testValidateHMACMissingOptionalField() throws SignatureException { @Test public void testValidateHMACEmptyNotificationRequest() { - try { - new HMACValidator().getDataToSign((NotificationRequestItem) null); - } catch (IllegalArgumentException e) { - assertEquals("Missing NotificationRequestItem.", e.getMessage()); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new HMACValidator().getDataToSign((NotificationRequestItem) null)); + assertEquals("Missing NotificationRequestItem.", exception.getMessage()); } @Test public void testCalculateHMACNullPayload() { - try { - new HMACValidator().calculateHMAC((String) null, HMAC_KEY); - } catch (IllegalArgumentException e) { - assertEquals("Missing data or key: payload data is not provided", e.getMessage()); - } catch (SignatureException e) { - fail(); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new HMACValidator().calculateHMAC((String) null, HMAC_KEY)); + assertEquals("Missing data or key: payload data is not provided", exception.getMessage()); } @Test public void testCalculateHMACNullKey() { - try { - new HMACValidator().calculateHMAC("TestPayload", null); - } catch (IllegalArgumentException e) { - assertEquals("Missing data or key: HMAC key is not provided", e.getMessage()); - } catch (SignatureException e) { - fail(); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new HMACValidator().calculateHMAC("TestPayload", null)); + assertEquals("Missing data or key: HMAC key is not provided", exception.getMessage()); + } + + @Test + public void testCalculateHMACInvalidKeyFormat() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new HMACValidator().calculateHMAC("TestPayload", "BadKey")); + assertEquals("Invalid Hex HMAC key: BadKey", exception.getMessage()); + } + + @Test + public void testCalculateHMAC() throws SignatureException { + var hmac = new HMACValidator(); + var out = hmac.calculateHMAC("TestPayload", HMAC_KEY); + assertEquals("YA9/UDjldIO4Gq68X4ATYYoEaPYH4f4k2uzBBzEF1PA=", out); } }