-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Adds
validateWebhook
Function (#172)
- Validate webhook function, confirming HMAC signature - Unit tests added
- Loading branch information
1 parent
820820b
commit ca60b96
Showing
9 changed files
with
288 additions
and
5 deletions.
There are no files selected for viewing
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
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,149 @@ | ||
package com.easypost.utils; | ||
|
||
import org.apache.commons.codec.binary.Hex; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import javax.crypto.Mac; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import java.nio.charset.StandardCharsets; | ||
import java.security.InvalidKeyException; | ||
import java.security.MessageDigest; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.text.Normalizer; | ||
|
||
/** | ||
* Class for various cryptography utilities. | ||
*/ | ||
public abstract class Cryptography { | ||
/** | ||
* Enums for the supported HMAC algorithms. | ||
*/ | ||
public enum HmacAlgorithm { | ||
MD5("HmacMD5"), | ||
SHA1("HmacSHA1"), | ||
SHA256("HmacSHA256"), | ||
SHA512("HmacSHA512"); | ||
|
||
private final String algorithmString; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param algorithmString the algorithm string | ||
*/ | ||
HmacAlgorithm(String algorithmString) { | ||
this.algorithmString = algorithmString; | ||
} | ||
|
||
/** | ||
* Get the algorithm string. | ||
* | ||
* @return the algorithm string. | ||
*/ | ||
String getAlgorithmString() { | ||
return algorithmString; | ||
} | ||
} | ||
|
||
/** | ||
* Hex-encode a byte array to a string. | ||
* | ||
* @param bytes the byte array to hex-encode. | ||
* @return the hex-encoded byte array string. | ||
*/ | ||
public static String hexEncodeToString(byte @NotNull [] bytes) { | ||
return new String(Hex.encodeHex(bytes)); | ||
} | ||
|
||
/** | ||
* Hex-encode a byte array to a char array. | ||
* | ||
* @param bytes the byte array to hex-encode. | ||
* @return the hex-encoded byte array char array. | ||
*/ | ||
public static char[] hexEncode(byte @NotNull [] bytes) { | ||
return Hex.encodeHex(bytes); | ||
} | ||
|
||
/** | ||
* Calculate the HMAC-SHA256 hex digest of a string. | ||
* | ||
* @param data Data to calculate hex digest for. | ||
* @param key Key to use in HMAC calculation. | ||
* @param normalizationForm {@link Normalizer.Form} to use when normalizing key. No normalization when null. | ||
* @return Hex digest of data. | ||
*/ | ||
public static String toHMACSHA256HexDigest(byte @NotNull [] data, @NotNull String key, | ||
@Nullable Normalizer.Form normalizationForm) { | ||
if (normalizationForm != null) { | ||
key = Normalizer.normalize(key, normalizationForm); | ||
} | ||
|
||
byte[] hmacBytes = createHMAC(data, key, HmacAlgorithm.SHA256); | ||
return hexEncodeToString(hmacBytes); | ||
} | ||
|
||
/** | ||
* Calculate the HMAC-SHA256 hex digest of a string. | ||
* | ||
* @param data Data to calculate hex digest for. | ||
* @param key Key to use in HMAC calculation. | ||
* @param normalizationForm {@link Normalizer.Form} to use when normalizing key. No normalization when null. | ||
* @return Hex digest of data. | ||
*/ | ||
public static String toHMACSHA256HexDigest(@NotNull String data, @NotNull String key, | ||
@Nullable Normalizer.Form normalizationForm) { | ||
byte[] dataBytes = data.getBytes(); | ||
return toHMACSHA256HexDigest(dataBytes, key, normalizationForm); | ||
} | ||
|
||
/** | ||
* Calculate the HMAC hex digest of a string. | ||
* | ||
* @param data Data to calculate hex digest for. | ||
* @param key Key to use in HMAC calculation. | ||
* @param algorithm {@link HmacAlgorithm} to use to calculate HMAC. | ||
* @return Hex digest of data. | ||
*/ | ||
public static byte[] createHMAC(byte @NotNull [] data, @NotNull String key, @NotNull HmacAlgorithm algorithm) { | ||
// create HMAC-SHA256 generator and compute hash of data | ||
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); | ||
SecretKeySpec keyHash = new SecretKeySpec(keyBytes, algorithm.algorithmString); | ||
|
||
try { | ||
Mac hmac = Mac.getInstance(algorithm.algorithmString); | ||
hmac.init(keyHash); | ||
return hmac.doFinal(data); | ||
} catch (InvalidKeyException | NoSuchAlgorithmException e) { | ||
throw new IllegalStateException("Cannot initialize Mac Generator", e); | ||
} | ||
} | ||
|
||
/** | ||
* Check whether two signatures match. This is safe against timing attacks. | ||
* | ||
* @param signature1 First signature to check. | ||
* @param signature2 Second signature to check. | ||
* @return True if signatures match, false otherwise. | ||
*/ | ||
public static boolean signaturesMatch(byte @NotNull [] signature1, byte @NotNull [] signature2) { | ||
// after Java SE 6 Update 17, MessageDigest.isEqual() is safe against timing attacks. | ||
// see: https://codahale.com//a-lesson-in-timing-attacks/ | ||
return MessageDigest.isEqual(signature1, signature2); | ||
} | ||
|
||
/** | ||
* Check whether two signatures match. This is safe against timing attacks. | ||
* | ||
* @param signature1 First signature to check. | ||
* @param signature2 Second signature to check. | ||
* @return True if signatures match, false otherwise. | ||
*/ | ||
public static boolean signaturesMatch(@NotNull String signature1, @NotNull String signature2) { | ||
byte[] signature1Bytes = signature1.getBytes(StandardCharsets.UTF_8); | ||
byte[] signature2Bytes = signature2.getBytes(StandardCharsets.UTF_8); | ||
return signaturesMatch(signature1Bytes, signature2Bytes); | ||
} | ||
} | ||
|
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,9 @@ | ||
/** | ||
* Utility classes for the EasyPost API Java client library. | ||
* | ||
* @author EasyPost developers | ||
* @version 1.0 | ||
* @see <a href="https://www.easypost.com/docs/api.html">EasyPost API</a> | ||
* @since 1.0 | ||
*/ | ||
package com.easypost.utils; |
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 @@ | ||
{"result":{"id":"batch_123...","object":"Batch","mode":"test","state":"created","num_shipments":0,"reference":null,"created_at":"2022-07-26T17:22:32Z","updated_at":"2022-07-26T17:22:32Z","scan_form":null,"shipments":[],"status":{"created":0,"queued_for_purchase":0,"creation_failed":0,"postage_purchased":0,"postage_purchase_failed":0},"pickup":null,"label_url":null},"description":"batch.created","mode":"test","previous_attributes":null,"completed_urls":null,"user_id":"user_123...","status":"pending","object":"Event","id":"evt_123..."} |
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