Add support for validating MS Teams outgoing webhooks

This commit is contained in:
Emil Henry Flakk 2021-02-21 14:43:05 +01:00
parent 390e3bd772
commit 43a10ec3c2
2 changed files with 111 additions and 10 deletions

View file

@ -234,6 +234,39 @@ func CheckPayloadSignature512(payload []byte, secret, signature string) (string,
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
}
func CheckMSTeamsSignature(r *Request, signingKey string) (bool, error) {
if r.Headers == nil {
return false, nil
}
// Check if the signing key is valid
if signingKey == "" {
return false, errors.New("signature validation key can not be empty")
}
secret, err := base64.StdEncoding.DecodeString(signingKey)
if err != nil {
return false, errors.New("signature validation key must be valid base64")
}
// Check if a valid HMAC header was provided
if _, ok := r.Headers["Authorization"]; !ok {
return false, nil
}
headerParts := strings.SplitN(r.Headers["Authorization"].(string), " ", 2)
if len(headerParts) != 2 || headerParts[0] != "HMAC" {
return false, errors.New("malformed 'Authorization' header")
}
providedSignature := headerParts[1]
mac := hmac.New(sha256.New, secret)
mac.Write(r.Body)
expectedSignature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) {
return false, &SignatureError{Signature: providedSignature}
}
return true, nil
}
func CheckScalrSignature(r *Request, signingKey string, checkDate bool) (bool, error) {
if r.Headers == nil {
return false, nil
@ -896,16 +929,17 @@ type MatchRule struct {
// Constants for the MatchRule type
const (
MatchValue string = "value"
MatchRegex string = "regex"
MatchHMACSHA1 string = "payload-hmac-sha1"
MatchHMACSHA256 string = "payload-hmac-sha256"
MatchHMACSHA512 string = "payload-hmac-sha512"
MatchHashSHA1 string = "payload-hash-sha1"
MatchHashSHA256 string = "payload-hash-sha256"
MatchHashSHA512 string = "payload-hash-sha512"
IPWhitelist string = "ip-whitelist"
ScalrSignature string = "scalr-signature"
MatchValue string = "value"
MatchRegex string = "regex"
MatchHMACSHA1 string = "payload-hmac-sha1"
MatchHMACSHA256 string = "payload-hmac-sha256"
MatchHMACSHA512 string = "payload-hmac-sha512"
MatchHashSHA1 string = "payload-hash-sha1"
MatchHashSHA256 string = "payload-hash-sha256"
MatchHashSHA512 string = "payload-hash-sha512"
IPWhitelist string = "ip-whitelist"
ScalrSignature string = "scalr-signature"
MSTeamsSignature string = "msteams-signature"
)
// Evaluate MatchRule will return based on the type
@ -916,6 +950,9 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) {
if r.Type == ScalrSignature {
return CheckScalrSignature(req, r.Secret, true)
}
if r.Type == MSTeamsSignature {
return CheckMSTeamsSignature(req, r.Secret)
}
arg, err := r.Parameter.Get(req)
if err == nil {

View file

@ -192,6 +192,70 @@ func TestCheckScalrSignature(t *testing.T) {
}
}
var checkMSTeamsSignatureTests = []struct {
description string
headers map[string]interface{}
body []byte
secret string
expectedSignature string
ok bool
}{
{
"Valid signature",
map[string]interface{}{"Authorization": "HMAC gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE="},
[]byte(`{"a": "b"}`), "bmV2ZXJnb25uYWdpdmV5b3V1cA==",
"gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE=", true,
},
{
"Wrong signature",
map[string]interface{}{"Authorization": "HMAC 1337TlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE="},
[]byte(`{"a": "b"}`), "bmV2ZXJnb25uYWdpdmV5b3V1cA==",
"gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE=", false,
},
{
"Missing Authorization header",
map[string]interface{}{"Different-Header": "HMAC wrong"},
[]byte(`{"a": "b"}`), "bmV2ZXJnb25uYWdpdmV5b3V1cA==",
"gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE=", false,
},
{
"Malformed Authorization header",
map[string]interface{}{"Authorization": "HMAC 123---"},
[]byte(`{"a": "b"}`), "bmV2ZXJnb25uYWdpdmV5b3V1cA==",
"gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE=", false,
},
{
"Missing signing key",
map[string]interface{}{"Authorization": "HMAC gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE="},
[]byte(`{"a": "b"}`), "",
"gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE=", false,
},
{
"Malformed signing key",
map[string]interface{}{"Authorization": "HMAC gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE="},
[]byte(`{"a": "b"}`), "---2ZXJnb25uYWdpdmV5b3V1cA==",
"gpjdTlOlaReTBLRFdwqdXhLqG7hFXVYTBorGDpaW5UE=", false,
},
}
func TestCheckMSTeamsSignature(t *testing.T) {
for _, testCase := range checkMSTeamsSignatureTests {
r := &Request{
Headers: testCase.headers,
Body: testCase.body,
}
valid, err := CheckMSTeamsSignature(r, testCase.secret)
if valid != testCase.ok {
t.Errorf("failed to check MS Teams signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
testCase.description, testCase.ok, valid)
}
if err != nil && testCase.secret != "" && strings.Contains(err.Error(), testCase.expectedSignature) {
t.Errorf("error message should not disclose expected mac: %s on test case %s", err, testCase.description)
}
}
}
var checkIPWhitelistTests = []struct {
addr string
ipRange string