1
1
/*
2
2
Package signature implements signature verification for MessageBird webhooks.
3
3
4
- To use define a new validator using your MessageBird Signing key. You can use the
5
- ValidRequest method, just pass the request and base url as parameters:
4
+ Deprecated: package signature is no longer supported. Use package
5
+ signature_jwt instead.
6
6
7
- validator := signature.NewValidator([]byte("your signing key"))
8
- baseUrl := "https://yourdomain.com"
9
- if err := validator.ValidRequest(r, baseUrl); err != nil {
7
+ To use define a new validator using your MessageBird Signing key. You can use the
8
+ ValidRequest method, just pass the request as a parameter:
9
+
10
+ validator := signature.NewValidator("your signing key")
11
+ if err := validator.ValidRequest(r); err != nil {
10
12
// handle error
11
13
}
12
14
13
15
Or use the handler as a middleware for your server:
14
16
15
- http.Handle("/path", validator.Validate(YourHandler, baseUrl ))
17
+ http.Handle("/path", validator.Validate(YourHandler))
16
18
17
19
It will reject the requests that contain invalid signatures.
20
+ The validator uses a 5ms seconds window to accept requests as valid, to change
21
+ this value, set the ValidityWindow to the disired duration.
22
+ Take into account that the validity window works around the current time:
23
+ [now - ValidityWindow/2, now + ValidityWindow/2]
18
24
*/
19
25
package signature
20
26
21
27
import (
22
28
"bytes"
29
+ "crypto/hmac"
23
30
"crypto/sha256"
24
- "encoding/hex "
31
+ "encoding/base64 "
25
32
"fmt"
26
33
"io/ioutil"
27
34
"net/http"
28
35
"net/url"
36
+ "strconv"
29
37
"time"
30
-
31
- "github.com/golang-jwt/jwt"
32
38
)
33
39
34
- const signatureHeader = "MessageBird-Signature-JWT"
40
+ const (
41
+ tsHeader = "MessageBird-Request-Timestamp"
42
+ sHeader = "MessageBird-Signature"
43
+ )
35
44
36
- // TimeFunc provides the current time same as time.Now but can be overridden for testing .
37
- var TimeFunc = time .Now
45
+ // ValidityWindow defines the time window in which to validate a request .
46
+ var ValidityWindow = 5 * time .Second
38
47
39
- // allowedMethods lists the signing methods that we accept. We only allow symmetric-key
40
- // algorithms as our customer signing keys are currently all simple byte strings. HMAC is
41
- // also the only symkey signature method that is required by the RFC7518 Section 3.1 and
42
- // thus should be supported by all JWT implementations.
43
- var allowedMethods = []string {
44
- jwt .SigningMethodHS256 .Name ,
45
- jwt .SigningMethodHS384 .Name ,
46
- jwt .SigningMethodHS512 .Name ,
48
+ // StringToTime converts from Unicode Epoch encoded timestamps to the time.Time type.
49
+ func stringToTime (s string ) (time.Time , error ) {
50
+ sec , err := strconv .ParseInt (s , 10 , 64 )
51
+ if err != nil {
52
+ return time.Time {}, err
53
+ }
54
+ return time .Unix (sec , 0 ), nil
47
55
}
48
56
49
57
// Validator type represents a MessageBird signature validator.
50
58
type Validator struct {
51
- SigningKey [] byte // Signing Key provided by MessageBird.
59
+ SigningKey string // Signing Key provided by MessageBird.
52
60
}
53
61
54
62
// NewValidator returns a signature validator object.
55
- func NewValidator (signingKey []byte ) * Validator {
63
+ // Deprecated: Use signature_jwt.NewValidator(string) instead.
64
+ func NewValidator (signingKey string ) * Validator {
56
65
return & Validator {
57
66
SigningKey : signingKey ,
58
67
}
59
68
}
60
69
61
- // ValidSignature is a method that takes care of the signature validation of
62
- // incoming requests.
63
- func (v * Validator ) ValidSignature (signature , url string , payload []byte ) error {
64
- parser := jwt.Parser {ValidMethods : allowedMethods }
65
- keyFn := func (* jwt.Token ) (interface {}, error ) { return v .SigningKey , nil }
66
-
67
- claims := Claims {
68
- receivedTime : TimeFunc (),
69
- correctURLHash : sha256Hash ([]byte (url )),
70
- }
71
- if payload != nil && len (payload ) != 0 {
72
- claims .correctPayloadHash = sha256Hash (payload )
70
+ // validTimestamp validates if the MessageBird-Request-Timestamp is a valid
71
+ // date and if the request is older than the validator Period.
72
+ func (v * Validator ) validTimestamp (ts string ) bool {
73
+ t , err := stringToTime (ts )
74
+ if err != nil {
75
+ return false
73
76
}
77
+ diff := time .Now ().Add (ValidityWindow / 2 ).Sub (t )
78
+ return diff < ValidityWindow && diff > 0
79
+ }
74
80
75
- if _ , err := parser .ParseWithClaims (signature , & claims , keyFn ); err != nil {
76
- return fmt .Errorf ("invalid jwt: %w" , err )
81
+ // calculateSignature calculates the MessageBird-Signature using HMAC_SHA_256
82
+ // encoding and the timestamp, query params and body from the request:
83
+ // signature = HMAC_SHA_256(
84
+ // TIMESTAMP + \n + QUERY_PARAMS + \n + SHA_256_SUM(BODY),
85
+ // signing_key)
86
+ func (v * Validator ) calculateSignature (ts , qp string , b []byte ) ([]byte , error ) {
87
+ var m bytes.Buffer
88
+ bh := sha256 .Sum256 (b )
89
+ fmt .Fprintf (& m , "%s\n %s\n %s" , ts , qp , bh [:])
90
+ mac := hmac .New (sha256 .New , []byte (v .SigningKey ))
91
+ if _ , err := mac .Write (m .Bytes ()); err != nil {
92
+ return nil , err
77
93
}
94
+ return mac .Sum (nil ), nil
95
+ }
78
96
79
- return nil
97
+ // validSignature takes the timestamp, query params and body from the request,
98
+ // calculates the expected signature and compares it to the one sent by MessageBird.
99
+ func (v * Validator ) validSignature (ts , rqp string , b []byte , rs string ) bool {
100
+ uqp , err := url .Parse ("?" + rqp )
101
+ if err != nil {
102
+ return false
103
+ }
104
+ es , err := v .calculateSignature (ts , uqp .Query ().Encode (), b )
105
+ if err != nil {
106
+ return false
107
+ }
108
+ drs , err := base64 .StdEncoding .DecodeString (rs )
109
+ if err != nil {
110
+ return false
111
+ }
112
+ return hmac .Equal (drs , es )
80
113
}
81
114
82
115
// ValidRequest is a method that takes care of the signature validation of
83
116
// incoming requests.
84
- func (v * Validator ) ValidRequest (r * http.Request , baseUrl string ) error {
85
- base , err := url .Parse (baseUrl )
86
- if err != nil {
87
- return fmt .Errorf ("error parsing base url: %v" , err )
88
- }
89
- signature := r .Header .Get (signatureHeader )
90
- if signature == "" {
91
- return fmt .Errorf ("signature not found" )
117
+ // Deprecated: Use signature_jwt.Validator.ValidateSignature(*http.Request, string) instead.
118
+ func (v * Validator ) ValidRequest (r * http.Request ) error {
119
+ ts := r .Header .Get (tsHeader )
120
+ rs := r .Header .Get (sHeader )
121
+ if ts == "" || rs == "" {
122
+ return fmt .Errorf ("Unknown host: %s" , r .Host )
92
123
}
93
124
b , _ := ioutil .ReadAll (r .Body )
94
- if err := v . ValidSignature ( signature , base . ResolveReference ( r .URL ). String () , b ); err != nil {
95
- return fmt .Errorf ("invalid signature : %s" , err . Error () )
125
+ if ! v . validTimestamp ( ts ) || ! v . validSignature ( ts , r .URL . RawQuery , b , rs ) {
126
+ return fmt .Errorf ("Unknown host : %s" , r . Host )
96
127
}
97
128
r .Body = ioutil .NopCloser (bytes .NewBuffer (b ))
98
129
return nil
@@ -101,21 +132,13 @@ func (v *Validator) ValidRequest(r *http.Request, baseUrl string) error {
101
132
// Validate is a handler wrapper that takes care of the signature validation of
102
133
// incoming requests and rejects them if invalid or pass them on to your handler
103
134
// otherwise.
104
- func (v * Validator ) Validate (h http.Handler , baseUrl string ) http.Handler {
135
+ // Deprecated: Use signature_jwt.Validator.Validate(http.Handler) instead.
136
+ func (v * Validator ) Validate (h http.Handler ) http.Handler {
105
137
return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
106
- if err := v .ValidRequest (r , baseUrl ); err != nil {
138
+ if err := v .ValidRequest (r ); err != nil {
107
139
http .Error (w , "" , http .StatusUnauthorized )
108
140
return
109
141
}
110
142
h .ServeHTTP (w , r )
111
143
})
112
144
}
113
-
114
- func sha256Hash (data []byte ) string {
115
- if data == nil {
116
- return ""
117
- }
118
-
119
- h := sha256 .Sum256 (data )
120
- return hex .EncodeToString (h [:])
121
- }
0 commit comments