All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.box.sdk.BoxWebHookSignatureVerifier Maven / Gradle / Ivy

There is a newer version: 4.11.1
Show newest version
package com.box.sdk;

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;

import com.box.sdk.internal.pool.MacPool;

/**
 * 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;

        /**
         * Algorithm name by Box.
         */
        private final String name;

        /**
         * Algorithm name according to the Java provider.
         */
        private final String javaProviderName;

        static {
            Map algorithmByName = new ConcurrentHashMap();
            for (BoxSignatureAlgorithm algorithm : BoxSignatureAlgorithm.values()) {
                algorithmByName.put(algorithm.name, algorithm);
            }
            ALGORITHM_BY_NAME = Collections.unmodifiableMap(algorithmByName);
        }

        /**
         * 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);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy