mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-12 08:34:43 +00:00
feat: added multiple sig support
This commit is contained in:
parent
569921cd72
commit
11e0031a9f
2 changed files with 68 additions and 11 deletions
|
@ -17,6 +17,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -49,12 +50,18 @@ const (
|
||||||
// SignatureError describes an invalid payload signature passed to Hook.
|
// SignatureError describes an invalid payload signature passed to Hook.
|
||||||
type SignatureError struct {
|
type SignatureError struct {
|
||||||
Signature string
|
Signature string
|
||||||
|
Signatures []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SignatureError) Error() string {
|
func (e *SignatureError) Error() string {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return "<nil>"
|
return "<nil>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.Signatures != nil {
|
||||||
|
return fmt.Sprintf("invalid payload signatures %s", e.Signatures)
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("invalid payload signature %s", e.Signature)
|
return fmt.Sprintf("invalid payload signature %s", e.Signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +101,47 @@ func (e *ParseError) Error() string {
|
||||||
return e.Err.Error()
|
return e.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractCommaSeperatedValues will extract the values matching the key.
|
||||||
|
func ExtractCommaSeperatedValues(source, key string) []string {
|
||||||
|
parts := strings.Split(source, ",")
|
||||||
|
values := make([]string, 0)
|
||||||
|
for _, part := range parts {
|
||||||
|
m, err := url.ParseQuery(part)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get the value.
|
||||||
|
value := m.Get(key)
|
||||||
|
if value != "" {
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractSignatures(signature, key string) []string {
|
||||||
|
// If there are multiple possible matches, let the comma seperated extractor
|
||||||
|
// do it's work.
|
||||||
|
if strings.Contains(signature, ",") {
|
||||||
|
return ExtractCommaSeperatedValues(signature, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// There were no commas, so just trim the prefix (if it even exists) and
|
||||||
|
// pass it back.
|
||||||
|
return []string{
|
||||||
|
strings.TrimPrefix(signature, key+"="),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
||||||
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) {
|
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) {
|
||||||
if secret == "" {
|
if secret == "" {
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
return "", errors.New("signature validation secret can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
signature = strings.TrimPrefix(signature, "sha1=")
|
signatures := ExtractSignatures(signature, "sha1")
|
||||||
|
|
||||||
mac := hmac.New(sha1.New, []byte(secret))
|
mac := hmac.New(sha1.New, []byte(secret))
|
||||||
_, err := mac.Write(payload)
|
_, err := mac.Write(payload)
|
||||||
|
@ -109,10 +150,15 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
|
||||||
}
|
}
|
||||||
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
|
for _, signature := range signatures {
|
||||||
return expectedMAC, &SignatureError{signature}
|
if hmac.Equal([]byte(signature), []byte(expectedMAC)) {
|
||||||
}
|
|
||||||
return expectedMAC, err
|
return expectedMAC, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectedMAC, &SignatureError{
|
||||||
|
Signatures: signatures,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
||||||
|
@ -121,7 +167,7 @@ func CheckPayloadSignature256(payload []byte, secret string, signature string) (
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
return "", errors.New("signature validation secret can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
signature = strings.TrimPrefix(signature, "sha256=")
|
signatures := ExtractSignatures(signature, "sha256")
|
||||||
|
|
||||||
mac := hmac.New(sha256.New, []byte(secret))
|
mac := hmac.New(sha256.New, []byte(secret))
|
||||||
_, err := mac.Write(payload)
|
_, err := mac.Write(payload)
|
||||||
|
@ -151,10 +197,15 @@ func CheckPayloadSignature512(payload []byte, secret string, signature string) (
|
||||||
}
|
}
|
||||||
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
|
for _, signature := range signatures {
|
||||||
return expectedMAC, &SignatureError{signature}
|
if hmac.Equal([]byte(signature), []byte(expectedMAC)) {
|
||||||
}
|
|
||||||
return expectedMAC, err
|
return expectedMAC, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectedMAC, &SignatureError{
|
||||||
|
Signatures: signatures,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
|
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
|
||||||
|
@ -177,7 +228,7 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
|
||||||
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) {
|
if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) {
|
||||||
return false, &SignatureError{providedSignature}
|
return false, &SignatureError{Signature: providedSignature}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkDate {
|
if !checkDate {
|
||||||
|
@ -192,7 +243,7 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
|
||||||
delta := math.Abs(now.Sub(date).Seconds())
|
delta := math.Abs(now.Sub(date).Seconds())
|
||||||
|
|
||||||
if delta > 300 {
|
if delta > 300 {
|
||||||
return false, &SignatureError{"outdated"}
|
return false, &SignatureError{Signature: "outdated"}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,11 @@ var checkPayloadSignatureTests = []struct {
|
||||||
}{
|
}{
|
||||||
{[]byte(`{"a": "z"}`), "secret", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
{[]byte(`{"a": "z"}`), "secret", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
{[]byte(`{"a": "z"}`), "secret", "sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
||||||
// failures
|
// failures
|
||||||
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
||||||
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
|
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
|
||||||
{[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
|
{[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
|
||||||
}
|
}
|
||||||
|
@ -76,8 +79,11 @@ var checkPayloadSignature256Tests = []struct {
|
||||||
}{
|
}{
|
||||||
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
// failures
|
// failures
|
||||||
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
||||||
{[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", false},
|
{[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue