package com.stripe.net; import com.stripe.exception.SignatureVerificationException; import com.stripe.BaseStripeTest; import com.stripe.model.Event; import com.stripe.net.Webhook; import com.google.gson.JsonSyntaxException; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class WebhookTest extends BaseStripeTest { public static String secret = null; public static String payload = null; @Before public void setUpFixtures() throws IOException { secret = "whsec_test_secret"; payload = "{\n \"id\": \"evt_test_webhook\",\n \"object\": \"event\"\n}"; } public String generateSigHeader() throws NoSuchAlgorithmException, InvalidKeyException { Map<String, Object> options = new HashMap<String, Object>(); return generateSigHeader(options); } public String generateSigHeader(Map<String, Object> options) throws NoSuchAlgorithmException, InvalidKeyException { long timestamp = (options.get("timestamp") != null) ? ((Long)options.get("timestamp")).longValue() : Webhook.Util.getTimeNow(); String payload = (options.get("payload") != null) ? (String)options.get("payload") : this.payload; String secret = (options.get("secret") != null) ? (String)options.get("secret") : this.secret; String scheme = (options.get("scheme") != null) ? (String)options.get("scheme") : Webhook.Signature.EXPECTED_SCHEME; String signature = (String)options.get("signature"); if (signature == null) { String payloadToSign = String.format("%d.%s", timestamp, payload); signature = Webhook.Util.computeHmacSHA256(secret, payloadToSign); } String header = String.format("t=%d,%s=%s", timestamp, scheme, signature); return header; } @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void testValidJsonAndHeader() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { String sigHeader = generateSigHeader(); Event event = Webhook.constructEvent(payload, sigHeader, secret); assertEquals("evt_test_webhook", event.getId()); } @Test(expected=JsonSyntaxException.class) public void testInvalidJson() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { String payload = "this is not valid JSON"; Map<String, Object> options = new HashMap<String, Object>(); options.put("payload", payload); String sigHeader = generateSigHeader(options); Webhook.constructEvent(payload, sigHeader, secret); } @Test(expected=SignatureVerificationException.class) public void testValidJsonAndInvalidHeader() throws SignatureVerificationException { String sigHeader = "bad_header"; Webhook.constructEvent(payload, sigHeader, secret); } @Test public void testMalformedHeader() throws SignatureVerificationException { String sigHeader = "i'm not even a real signature header"; thrown.expect(SignatureVerificationException.class); thrown.expectMessage("Unable to extract timestamp and signatures from header"); Webhook.Signature.verifyHeader(payload, sigHeader, secret, 0); } @Test public void testNoSignaturesWithExpectedScheme() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { Map<String, Object> options = new HashMap<String, Object>(); options.put("scheme", "v0"); String sigHeader = generateSigHeader(options); thrown.expect(SignatureVerificationException.class); thrown.expectMessage("No signatures found with expected scheme"); Webhook.Signature.verifyHeader(payload, sigHeader, secret, 0); } @Test public void testNoValidSignatureForPayload() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { Map<String, Object> options = new HashMap<String, Object>(); options.put("signature", "bad_signature"); String sigHeader = generateSigHeader(options); thrown.expect(SignatureVerificationException.class); thrown.expectMessage("No signatures found matching the expected signature for payload"); Webhook.Signature.verifyHeader(payload, sigHeader, secret, 0); } @Test public void testTimestampOutsideTolerance() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { Map<String, Object> options = new HashMap<String, Object>(); options.put("timestamp", Webhook.Util.getTimeNow() - 15); String sigHeader = generateSigHeader(options); thrown.expect(SignatureVerificationException.class); thrown.expectMessage("Timestamp outside the tolerance zone"); Webhook.Signature.verifyHeader(payload, sigHeader, secret, 10); } @Test public void testValidHeaderAndSignature() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { String sigHeader = generateSigHeader(); assertTrue(Webhook.Signature.verifyHeader(payload, sigHeader, secret, 10)); } @Test public void testHeaderContainsValidSignature() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { String sigHeader = generateSigHeader(); sigHeader += ",v1=bad_signature"; assertTrue(Webhook.Signature.verifyHeader(payload, sigHeader, secret, 10)); } @Test public void testTimestampOffButNoTolerance() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException { Map<String, Object> options = new HashMap<String, Object>(); options.put("timestamp", Long.valueOf(12345L)); String sigHeader = generateSigHeader(options); assertTrue(Webhook.Signature.verifyHeader(payload, sigHeader, secret, 0)); } }