| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | package webhook |
| 4 | |
| 5 | import ( |
| 6 | "crypto/hmac" |
| 7 | "crypto/sha256" |
| 8 | "encoding/hex" |
| 9 | ) |
| 10 | |
| 11 | // SignSHA256 returns the X-Shithub-Signature-256 header value for the |
| 12 | // given body and per-webhook secret. The format mirrors GitHub's |
| 13 | // (`sha256=<hex>`) so existing receiver libraries verify cleanly. |
| 14 | func SignSHA256(secret, body []byte) string { |
| 15 | mac := hmac.New(sha256.New, secret) |
| 16 | mac.Write(body) |
| 17 | return "sha256=" + hex.EncodeToString(mac.Sum(nil)) |
| 18 | } |
| 19 | |
| 20 | // VerifySHA256 returns true when sig matches HMAC-SHA256(secret, body). |
| 21 | // Uses constant-time compare so the verifier doesn't leak timing info. |
| 22 | // Provided as the receiver-side helper that test code (and any future |
| 23 | // inbound webhook surface) can reuse. |
| 24 | func VerifySHA256(secret, body []byte, sig string) bool { |
| 25 | want := SignSHA256(secret, body) |
| 26 | if len(want) != len(sig) { |
| 27 | return false |
| 28 | } |
| 29 | return hmac.Equal([]byte(want), []byte(sig)) |
| 30 | } |
| 31 |