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
|