Go · 1560 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package webhook
4
5 import (
6 "crypto/rand"
7 "encoding/hex"
8 "errors"
9
10 "github.com/tenseleyFlow/shithub/internal/auth/secretbox"
11 )
12
13 // SecretLength is the byte length of a freshly minted webhook secret.
14 // 32 bytes ≈ 256 bits — well above HMAC-SHA256's effective security.
15 const SecretLength = 32
16
17 // GenerateSecret returns a hex-encoded random secret suitable for
18 // dropping into the webhook config form. The hex shape keeps it easy
19 // to paste into receiver code.
20 func GenerateSecret() (string, error) {
21 b := make([]byte, SecretLength)
22 if _, err := rand.Read(b); err != nil {
23 return "", err
24 }
25 return hex.EncodeToString(b), nil
26 }
27
28 // SealSecret AEAD-wraps secret under the supplied box. Returns
29 // (ciphertext, nonce) for storage in webhooks.secret_ciphertext +
30 // webhooks.secret_nonce.
31 func SealSecret(box *secretbox.Box, secret string) (ciphertext, nonce []byte, err error) {
32 if box == nil {
33 return nil, nil, errors.New("webhook: nil box")
34 }
35 if secret == "" {
36 return nil, nil, errors.New("webhook: empty secret")
37 }
38 return box.Seal([]byte(secret))
39 }
40
41 // OpenSecret decrypts the stored ciphertext+nonce. Decryption failure
42 // is a hard error: the deliverer disables the webhook with a precise
43 // reason rather than silently signing with garbage.
44 func OpenSecret(box *secretbox.Box, ciphertext, nonce []byte) (string, error) {
45 if box == nil {
46 return "", errors.New("webhook: nil box")
47 }
48 pt, err := box.Open(ciphertext, nonce)
49 if err != nil {
50 return "", err
51 }
52 return string(pt), nil
53 }
54