com.box.sdk.BoxWebHookSignatureVerifier Maven / Gradle / Ivy
The newest version!
package com.box.sdk;
import com.box.sdk.internal.pool.MacPool;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Signature verifier for Webhook Payload.
*
* @since 2.2.1
*/
public class BoxWebHookSignatureVerifier {
/**
* Reference to UTF_8 {@link Charset}.
*/
private static final Charset UTF_8 = Charset.forName("UTF-8");
/**
* Versions supported by this implementation.
*/
private static final Set SUPPORTED_VERSIONS = Collections.singleton("1");
/**
* Algorithms supported by this implementation.
*/
private static final Set SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(
EnumSet.of(BoxSignatureAlgorithm.HMAC_SHA256));
/**
* {@link Mac}-s pool.
*/
private static final MacPool MAC_POOL = new MacPool();
/**
* Primary key setup within the Box.
*/
private final String primarySignatureKey;
/**
* Secondary key setup within the Box.
*/
private final String secondarySignatureKey;
/**
* Creates a new instance of verifier specified with given primary and secondary keys. Primary key and secondary key
* are needed for rotating purposes, at least at one has to be valid.
*
* @param primarySignatureKey primary signature key for web-hooks (can not be null)
* @param secondarySignatureKey secondary signature key for web-hooks (can be null)
* @throws IllegalArgumentException primary key can not be null
*/
public BoxWebHookSignatureVerifier(String primarySignatureKey, String secondarySignatureKey) {
if (primarySignatureKey == null && secondarySignatureKey == null) {
throw new IllegalArgumentException("At least primary or secondary signature key must be provided!");
}
this.primarySignatureKey = primarySignatureKey;
this.secondarySignatureKey = secondarySignatureKey;
}
/**
* Verifies given web-hook information.
*
* @param signatureVersion signature version received from web-hook
* @param signatureAlgorithm signature algorithm received from web-hook
* @param primarySignature primary signature received from web-hook
* @param secondarySignature secondary signature received from web-hook
* @param webHookPayload payload of web-hook
* @param deliveryTimestamp devilery timestamp received from web-hook
* @return true, if given payload is successfully verified against primary and secondary signatures, false otherwise
*/
public boolean verify(String signatureVersion, String signatureAlgorithm, String primarySignature,
String secondarySignature, String webHookPayload, String deliveryTimestamp) {
// enforce versions supported by this implementation
if (!SUPPORTED_VERSIONS.contains(signatureVersion)) {
return false;
}
// enforce algorithms supported by this implementation
BoxSignatureAlgorithm algorithm = BoxSignatureAlgorithm.byName(signatureAlgorithm);
if (!SUPPORTED_ALGORITHMS.contains(algorithm)) {
return false;
}
// check primary key signature if primary key exists
if (this.primarySignatureKey != null && this.verify(this.primarySignatureKey, algorithm, primarySignature,
webHookPayload, deliveryTimestamp)) {
return true;
}
// check secondary key signature if secondary key exists
if (this.secondarySignatureKey != null && this.verify(this.secondarySignatureKey, algorithm, secondarySignature,
webHookPayload, deliveryTimestamp)) {
return true;
}
// default strategy is false, to minimize security issues
return false;
}
/**
* Verifies a provided signature.
*
* @param key for which signature key
* @param actualAlgorithm current signature algorithm
* @param actualSignature current signature
* @param webHookPayload for signing
* @param deliveryTimestamp for signing
* @return true if verification passed
*/
private boolean verify(String key, BoxSignatureAlgorithm actualAlgorithm, String actualSignature,
String webHookPayload, String deliveryTimestamp) {
if (actualSignature == null) {
return false;
}
byte[] actual = Base64.decode(actualSignature);
byte[] expected = this.signRaw(actualAlgorithm, key, webHookPayload, deliveryTimestamp);
return Arrays.equals(expected, actual);
}
/**
* Calculates signature for a provided information.
*
* @param algorithm for which algorithm
* @param key used by signing
* @param webHookPayload for singing
* @param deliveryTimestamp for signing
* @return calculated signature
*/
public String sign(BoxSignatureAlgorithm algorithm, String key, String webHookPayload, String deliveryTimestamp) {
return Base64.encode(this.signRaw(algorithm, key, webHookPayload, deliveryTimestamp));
}
/**
* Calculates signature for a provided information.
*
* @param algorithm for which algorithm
* @param key used by signing
* @param webHookPayload for singing
* @param deliveryTimestamp for signing
* @return calculated signature
*/
private byte[] signRaw(BoxSignatureAlgorithm algorithm, String key, String webHookPayload,
String deliveryTimestamp) {
Mac mac = MAC_POOL.acquire(algorithm.javaProviderName);
try {
mac.init(new SecretKeySpec(key.getBytes(UTF_8), algorithm.javaProviderName));
mac.update(UTF_8.encode(webHookPayload));
mac.update(UTF_8.encode(deliveryTimestamp));
return mac.doFinal();
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("Invalid key: ", e);
} finally {
MAC_POOL.release(mac);
}
}
/**
* Box Signature Algorithms.
*/
public enum BoxSignatureAlgorithm {
/**
* HmacSHA256 algorithm.
*/
HMAC_SHA256("HmacSHA256", "HmacSHA256");
/**
* @see #byName(String)
*/
private static final Map ALGORITHM_BY_NAME;
static {
Map algorithmByName = new ConcurrentHashMap();
for (BoxSignatureAlgorithm algorithm : BoxSignatureAlgorithm.values()) {
algorithmByName.put(algorithm.name, algorithm);
}
ALGORITHM_BY_NAME = Collections.unmodifiableMap(algorithmByName);
}
/**
* Algorithm name by Box.
*/
private final String name;
/**
* Algorithm name according to the Java provider.
*/
private final String javaProviderName;
/**
* Constructor.
*
* @param name algorithm name by Box
* @param javaProviderName algorithm name according to the Java provider
*/
BoxSignatureAlgorithm(String name, String javaProviderName) {
this.name = javaProviderName;
this.javaProviderName = javaProviderName;
}
/**
* Resolves {@link BoxSignatureAlgorithm} according to its name.
*
* @param name of algorithm
* @return resolved {@link BoxSignatureAlgorithm} or null if does not exist
*/
private static BoxSignatureAlgorithm byName(String name) {
return ALGORITHM_BY_NAME.get(name);
}
}
}