Skip to content

Commit

Permalink
go: add function to verify ignoring timestamp enforcement (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
svix-frank authored Dec 9, 2021
1 parent 1d6088f commit c494db5
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 12 deletions.
44 changes: 37 additions & 7 deletions go/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,28 @@ func NewWebhook(secret string) (*Webhook, error) {
}, nil
}

// Verify validates the payload against the svix signature headers
// using the webhooks signing secret.
//
// Returns an error if the body or headers are missing/unreadable
// or if the signature doesn't match.
func (wh *Webhook) Verify(payload []byte, headers http.Header) error {
return wh.verify(payload, headers, true)
}

// VerifyIgnoringTimestamp validates the payload against the svix signature headers
// using the webhooks signing secret.
//
// Returns an error if the body or headers are missing/unreadable
// or if the signature doesn't match.
//
// WARNING: This function does not check the signature's timestamp.
// We recommend using the `Verify` function instead.
func (wh *Webhook) VerifyIgnoringTimestamp(payload []byte, headers http.Header) error {
return wh.verify(payload, headers, false)
}

func (wh *Webhook) verify(payload []byte, headers http.Header, enforceTolerance bool) error {
msgId := headers.Get("svix-id")
msgSignature := headers.Get("svix-signature")
msgTimestamp := headers.Get("svix-timestamp")
Expand All @@ -53,12 +74,17 @@ func (wh *Webhook) Verify(payload []byte, headers http.Header) error {
}
}

// enforce timestamp tolerance
timestamp, err := verifyTimestamp(msgTimestamp)
timestamp, err := parseTimestampHeader(msgTimestamp)
if err != nil {
return err
}

if enforceTolerance {
if err := verifyTimestamp(timestamp); err != nil {
return err
}
}

computedSignature, err := wh.Sign(msgId, timestamp, payload)
if err != nil {
return err
Expand Down Expand Up @@ -96,20 +122,24 @@ func (wh *Webhook) Sign(msgId string, timestamp time.Time, payload []byte) (stri

}

func verifyTimestamp(timestampHeader string) (time.Time, error) {
now := time.Now()
func parseTimestampHeader(timestampHeader string) (time.Time, error) {
timeInt, err := strconv.ParseInt(timestampHeader, 10, 64)
if err != nil {
return time.Time{}, errInvalidHeaders
}
timestamp := time.Unix(timeInt, 0)
return timestamp, nil
}

func verifyTimestamp(timestamp time.Time) error {
now := time.Now()

if now.Sub(timestamp) > tolerance {
return time.Time{}, errMessageTooOld
return errMessageTooOld
}
if timestamp.Unix() > now.Add(tolerance).Unix() {
return time.Time{}, errMessageTooNew
return errMessageTooNew
}

return timestamp, nil
return nil
}
42 changes: 37 additions & 5 deletions go/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ func newTestPayload(timestamp time.Time) *testPayload {
func TestWebhook(t *testing.T) {

testCases := []struct {
name string
testPayload *testPayload
modifyPayload func(*testPayload)
expectedErr bool
name string
testPayload *testPayload
modifyPayload func(*testPayload)
noEnforceTimestamp bool
expectedErr bool
}{
{
name: "valid signature is valid",
Expand Down Expand Up @@ -124,6 +125,33 @@ func TestWebhook(t *testing.T) {
},
expectedErr: false,
},
{
name: "old timestamp passes when ignoring tolerance",
testPayload: newTestPayload(time.Now().Add(tolerance * -1)),
noEnforceTimestamp: true,
expectedErr: false,
},
{
name: "new timestamp passes when ignoring tolerance",
testPayload: newTestPayload(time.Now().Add(tolerance * 1)),
noEnforceTimestamp: true,
expectedErr: false,
},
{
name: "valid timestamp passes when ignoring tolerance",
testPayload: newTestPayload(time.Now()),
noEnforceTimestamp: true,
expectedErr: false,
},
{
name: "invalid timestamp fails when ignoring tolerance",
testPayload: newTestPayload(time.Now()),
modifyPayload: func(tp *testPayload) {
tp.header.Set("svix-timestamp", fmt.Sprint(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()))
},
noEnforceTimestamp: true,
expectedErr: true,
},
}

for _, tc := range testCases {
Expand All @@ -136,7 +164,11 @@ func TestWebhook(t *testing.T) {
t.Error(err)
continue
}
err = wh.Verify(tc.testPayload.payload, tc.testPayload.header)
if tc.noEnforceTimestamp {
err = wh.VerifyIgnoringTimestamp(tc.testPayload.payload, tc.testPayload.header)
} else {
err = wh.Verify(tc.testPayload.payload, tc.testPayload.header)
}
if err != nil && !tc.expectedErr {
t.Errorf("%s: failed with err %s but shouldn't have", tc.name, err.Error())
} else if err == nil && tc.expectedErr {
Expand Down

0 comments on commit c494db5

Please sign in to comment.