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

Mutations

30

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

32

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()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/utilities/WebhookSecurityUtils::validateGitHubSignature → KILLED

35

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

37

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

43

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

45

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

48

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

63

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

65

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

79

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

90

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

91

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

95

1.1
Location : safeEquals
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_differentSecret()]
changed conditional boundary → 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()]
negated conditional → KILLED

96

1.1
Location : safeEquals
Killed by : edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.utilities.WebhookSecurityUtilsTests]/[method:testValidateGitHubSignature_differentSecret()]
Replaced bitwise OR with AND → 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_emptyPayload()]
Replaced XOR with AND → KILLED

98

1.1
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

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()]
negated conditional → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0