-
Notifications
You must be signed in to change notification settings - Fork 0
/
webhooks.go
97 lines (79 loc) · 2.52 KB
/
webhooks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package telnyx
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"time"
)
const (
DefaultSignatureTolerance = 300 // 5 minutes
)
type VerificationError struct {
Message string
Detail map[string]string
}
func (e *VerificationError) Error() string {
return e.Message
}
var _ error = &VerificationError{}
func newSignatureVerificationError(payload, signatureHeader, timestampHeader string) error {
return &VerificationError{
Message: "Signature is invalid and does not match the payload",
Detail: map[string]string{
"signatureHeader": signatureHeader,
"timestampHeader": timestampHeader,
"payload": payload,
},
}
}
// VerifyWebhookPayload constructs an event from a webhook payload, signature header, timestamp header and public key.
func VerifyWebhookPayload(payload, signatureHeader, timestampHeader, publicKey string, tolerance ...int) (map[string]interface{}, error) {
tol := DefaultSignatureTolerance
if len(tolerance) > 0 {
tol = tolerance[0]
}
err := verifySignature(payload, signatureHeader, timestampHeader, publicKey, tol)
if err != nil {
return nil, err
}
var jsonPayload map[string]interface{}
err = json.Unmarshal([]byte(payload), &jsonPayload)
if err != nil {
return nil, err
}
return jsonPayload, nil
}
func verifySignature(payload, signatureHeader, timestampHeader, publicKey string, tolerance int) error {
payloadBuffer := []byte(fmt.Sprintf("%s|%s", timestampHeader, payload))
signature, err := base64.StdEncoding.DecodeString(signatureHeader)
if err != nil {
return newSignatureVerificationError(payload, signatureHeader, timestampHeader)
}
pubKey, err := base64.StdEncoding.DecodeString(publicKey)
if err != nil {
return newSignatureVerificationError(payload, signatureHeader, timestampHeader)
}
if len(pubKey) != ed25519.PublicKeySize {
return newSignatureVerificationError(payload, signatureHeader, timestampHeader)
}
if !ed25519.Verify(pubKey, payloadBuffer, signature) {
return newSignatureVerificationError(payload, signatureHeader, timestampHeader)
}
timestampAge, err := strconv.ParseInt(timestampHeader, 10, 64)
if err != nil {
return newSignatureVerificationError(payload, signatureHeader, timestampHeader)
}
if tolerance > 0 && (time.Now().Unix()-timestampAge) > int64(tolerance) {
return &VerificationError{
Message: "Timestamp outside the tolerance zone",
Detail: map[string]string{
"signatureHeader": signatureHeader,
"timestampHeader": timestampHeader,
"payload": payload,
},
}
}
return nil
}