Skip to content

Commit 54e71dd

Browse files
author
khanh.nguyen
committed
Re-added deprecated signature package.
1 parent 7de3beb commit 54e71dd

File tree

6 files changed

+553
-148
lines changed

6 files changed

+553
-148
lines changed

signature/signature.go

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,129 @@
11
/*
22
Package signature implements signature verification for MessageBird webhooks.
33
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.
66
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 {
1012
// handle error
1113
}
1214
1315
Or use the handler as a middleware for your server:
1416
15-
http.Handle("/path", validator.Validate(YourHandler, baseUrl))
17+
http.Handle("/path", validator.Validate(YourHandler))
1618
1719
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]
1824
*/
1925
package signature
2026

2127
import (
2228
"bytes"
29+
"crypto/hmac"
2330
"crypto/sha256"
24-
"encoding/hex"
31+
"encoding/base64"
2532
"fmt"
2633
"io/ioutil"
2734
"net/http"
2835
"net/url"
36+
"strconv"
2937
"time"
30-
31-
"github.com/golang-jwt/jwt"
3238
)
3339

34-
const signatureHeader = "MessageBird-Signature-JWT"
40+
const (
41+
tsHeader = "MessageBird-Request-Timestamp"
42+
sHeader = "MessageBird-Signature"
43+
)
3544

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
3847

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
4755
}
4856

4957
// Validator type represents a MessageBird signature validator.
5058
type Validator struct {
51-
SigningKey []byte // Signing Key provided by MessageBird.
59+
SigningKey string // Signing Key provided by MessageBird.
5260
}
5361

5462
// 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 {
5665
return &Validator{
5766
SigningKey: signingKey,
5867
}
5968
}
6069

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
7376
}
77+
diff := time.Now().Add(ValidityWindow / 2).Sub(t)
78+
return diff < ValidityWindow && diff > 0
79+
}
7480

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
7793
}
94+
return mac.Sum(nil), nil
95+
}
7896

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)
80113
}
81114

82115
// ValidRequest is a method that takes care of the signature validation of
83116
// 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)
92123
}
93124
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)
96127
}
97128
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
98129
return nil
@@ -101,21 +132,13 @@ func (v *Validator) ValidRequest(r *http.Request, baseUrl string) error {
101132
// Validate is a handler wrapper that takes care of the signature validation of
102133
// incoming requests and rejects them if invalid or pass them on to your handler
103134
// 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 {
105137
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 {
107139
http.Error(w, "", http.StatusUnauthorized)
108140
return
109141
}
110142
h.ServeHTTP(w, r)
111143
})
112144
}
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

Comments
 (0)