Add sha256 payload check

This commit is contained in:
Raphaël Pinson 2017-04-06 15:36:35 +02:00
parent af22498d1e
commit 6b1021925b
No known key found for this signature in database
GPG key ID: DF0A493B74BF771E
3 changed files with 59 additions and 15 deletions

View file

@ -1,12 +1,4 @@
[![ghit.me](https://ghit.me/badge.svg?repo=adnanh/webhook)](https://ghit.me/repo/adnanh/webhook) [![Join the chat at https://gitter.im/adnanh/webhook](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/adnanh/webhook?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Flattr this](https://button.flattr.com/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=adnanh&url=https%3A%2F%2Fwww.github.com%2Fadnanh%2Fwebhook) [Donate via PayPal](https://paypal.me/hookdoo) | [Patreon page](https://www.patreon.com/webhook)
# Hookdoo
<a href="https://www.hookdoo.com/?github"><img src="https://my.hookdoo.com/logo/logo-dark-96.png" alt="hookdoo" align="left" /></a>
If you don't have time to waste configuring, hosting, debugging and maintaining your webhook instance, we offer a __SaaS__ solution that has all of the capabilities webhook provides, plus a lot more, and all that packaged in a nice friendly web interface. If you are interested, find out more at [hookdoo website](https://www.hookdoo.com/). If you have any questions, you can contact us at info@hookdoo.com
# What is webhook?
[webhook](https://github.com/adnanh/webhook/) is a lightweight configurable tool written in Go, that allows you to easily create HTTP endpoints (hooks) on your server, which you can use to execute configured commands. You can also pass data from the HTTP request (such as headers, payload or query variables) to your commands. [webhook](https://github.com/adnanh/webhook/) also allows you to specify rules which have to be satisfied in order for the hook to be triggered.

View file

@ -3,6 +3,7 @@ package hook
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
@ -101,6 +102,25 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
return expectedMAC, err
}
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
func CheckPayloadSignature256(payload []byte, secret string, signature string) (string, error) {
if strings.HasPrefix(signature, "sha256=") {
signature = signature[7:]
}
mac := hmac.New(sha256.New, []byte(secret))
_, err := mac.Write(payload)
if err != nil {
return "", err
}
expectedMAC := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
return expectedMAC, &SignatureError{signature}
}
return expectedMAC, err
}
// CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range
// (in CIDR form or a single IP address).
func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) {
@ -605,6 +625,7 @@ const (
MatchValue string = "value"
MatchRegex string = "regex"
MatchHashSHA1 string = "payload-hash-sha1"
MatchHashSHA256 string = "payload-hash-sha256"
IPWhitelist string = "ip-whitelist"
)
@ -623,6 +644,9 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
case MatchHashSHA1:
_, err := CheckPayloadSignature(*body, r.Secret, arg)
return err == nil, err
case MatchHashSHA256:
_, err := CheckPayloadSignature256(*body, r.Secret, arg)
return err == nil, err
}
}
return false, nil

View file

@ -33,6 +33,32 @@ func TestCheckPayloadSignature(t *testing.T) {
}
}
var checkPayloadSignature256Tests = []struct {
payload []byte
secret string
signature string
mac string
ok bool
}{
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
// failures
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
}
func TestCheckPayloadSignature256(t *testing.T) {
for _, tt := range checkPayloadSignature256Tests {
mac, err := CheckPayloadSignature256(tt.payload, tt.secret, tt.signature)
if (err == nil) != tt.ok || mac != tt.mac {
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
}
if err != nil && strings.Contains(err.Error(), tt.mac) {
t.Errorf("error message should not disclose expected mac: %s", err)
}
}
}
var extractParameterTests = []struct {
s string
params interface{}
@ -249,9 +275,10 @@ var matchRuleTests = []struct {
ok bool
err bool
}{
{"value", "", "", "z", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"regex", "^z", "", "z", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
{"value", "", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
{"regex", "^z", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
{"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false},
{"payload-hash-sha256", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), true, false},
// failures
{"value", "", "", "X", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"regex", "^X", "", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
@ -259,6 +286,7 @@ var matchRuleTests = []struct {
// errors
{"regex", "*", "", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
{"payload-hash-sha256", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
// IP whitelisting, valid cases
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range