WebhookSecurityUtils.java

1
package edu.ucsb.cs156.frontiers.utilities;
2
3
import java.nio.charset.StandardCharsets;
4
import java.security.InvalidKeyException;
5
import java.security.MessageDigest;
6
import java.security.NoSuchAlgorithmException;
7
import java.util.regex.Pattern;
8
import javax.crypto.Mac;
9
import javax.crypto.spec.SecretKeySpec;
10
import lombok.extern.slf4j.Slf4j;
11
12
/** Utility class for webhook security validation */
13
@Slf4j
14
public class WebhookSecurityUtils {
15
16
  private WebhookSecurityUtils() {
17
    // Utility Class
18
  }
19
20
  private static final String HMAC_SHA256 = "HmacSHA256";
21
  private static final Pattern GITHUB_SIGNATURE_PATTERN =
22
      Pattern.compile("^sha256=[a-fA-F0-9]{64}$");
23
24
  /**
25
   * Validates GitHub webhook signature using HMAC-SHA256
26
   *
27
   * @param payload the raw payload body as string
28
   * @param signature the GitHub signature header (e.g., "sha256=abc123...")
29
   * @param secret the webhook secret
30
   * @return true if signature is valid, false otherwise
31
   */
32
  public static boolean validateGitHubSignature(String payload, String signature, String secret)
33
      throws NoSuchAlgorithmException, InvalidKeyException {
34 3 1. validateGitHubSignature : negated conditional → KILLED
2. validateGitHubSignature : negated conditional → KILLED
3. validateGitHubSignature : negated conditional → KILLED
    if (payload == null || signature == null || secret == null) {
35
      log.warn("Null values provided for webhook validation");
36 1 1. validateGitHubSignature : replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED
      return false;
37
    }
38
39 1 1. validateGitHubSignature : negated conditional → KILLED
    if (!GITHUB_SIGNATURE_PATTERN.matcher(signature).matches()) {
40
      log.warn("Invalid signature format");
41 1 1. validateGitHubSignature : replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED
      return false;
42
    }
43
44
    String expectedSignature = "sha256=" + calculateHmacSha256(payload, secret);
45
    boolean isValid = safeEquals(signature, expectedSignature);
46
47 1 1. validateGitHubSignature : negated conditional → KILLED
    if (!isValid) {
48
      log.warn("Webhook signature validation failed");
49 1 1. validateGitHubSignature : replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED
      return false;
50
    }
51
52 1 1. validateGitHubSignature : replaced boolean return with false for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED
    return true;
53
  }
54
55
  /**
56
   * Calculates HMAC-SHA256 signature for given payload and secret
57
   *
58
   * @param payload the payload to sign
59
   * @param secret the secret key
60
   * @return hex-encoded signature
61
   */
62
  private static String calculateHmacSha256(String payload, String secret)
63
      throws NoSuchAlgorithmException, InvalidKeyException {
64
    Mac mac = Mac.getInstance(HMAC_SHA256);
65
    SecretKeySpec secretKeySpec =
66
        new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256);
67 1 1. calculateHmacSha256 : removed call to javax/crypto/Mac::init → KILLED
    mac.init(secretKeySpec);
68
    byte[] signature = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
69 1 1. calculateHmacSha256 : replaced return value with "" for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::calculateHmacSha256 → KILLED
    return bytesToHex(signature);
70
  }
71
72
  /**
73
   * Converts byte array to hexadecimal string
74
   *
75
   * @param bytes the byte array
76
   * @return hex string
77
   */
78
  private static String bytesToHex(byte[] bytes) {
79
    StringBuilder result = new StringBuilder();
80
    for (byte b : bytes) {
81
      result.append(String.format("%02x", b));
82
    }
83 1 1. bytesToHex : replaced return value with "" for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::bytesToHex → KILLED
    return result.toString();
84
  }
85
86
  /**
87
   * Constant-time string comparison to prevent timing attacks
88
   *
89
   * @param a first string
90
   * @param b second string
91
   * @return true if strings are equal
92
   */
93
  private static boolean safeEquals(String a, String b) {
94 2 1. safeEquals : replaced boolean return with false for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::safeEquals → KILLED
2. safeEquals : replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::safeEquals → KILLED
    return MessageDigest.isEqual(
95
        a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
96
  }
97
}

Mutations

34

1.1
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_nullSecret()]
negated conditional → KILLED

2.2
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_nullPayload()]
negated conditional → KILLED

3.3
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_nullSignature()]
negated conditional → KILLED

36

1.1
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_nullSignature()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED

39

1.1
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_validSignature()]
negated conditional → KILLED

41

1.1
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_invalidSignatureFormat()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED

47

1.1
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_validSignature()]
negated conditional → KILLED

49

1.1
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_differentSecret()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED

52

1.1
Location : validateGitHubSignature
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_validSignature()]
replaced boolean return with false for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED

67

1.1
Location : calculateHmacSha256
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_validSignature()]
removed call to javax/crypto/Mac::init → KILLED

69

1.1
Location : calculateHmacSha256
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_validSignature()]
replaced return value with "" for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::calculateHmacSha256 → KILLED

83

1.1
Location : bytesToHex
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_validSignature()]
replaced return value with "" for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::bytesToHex → KILLED

94

1.1
Location : safeEquals
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_validSignature()]
replaced boolean return with false for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::safeEquals → KILLED

2.2
Location : safeEquals
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_differentSecret()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::safeEquals → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0