验证Trello Webhook签名

时间:2015-12-10 19:10:27

标签: security go hmac trello hmacsha1

我无法成功验证来自Trello的webhook请求。这就是我所知道的。

Trello的webhook文档here声明:

  

每个webhook触发器都包含HTTP标头X-Trello-Webhook。标头是HMAC-SHA1哈希的base64摘要。散列内容是完整请求主体和callbackURL的串联,与webhook创建期间提供的内容完全相同。用于签署此文本的密钥是您的应用程序的秘密。

这是可以理解的。他们继续说

  

由于节点中加密实用程序的某些默认值,我们签名的有效负载被视为二进制字符串,而不是utf-8。例如,如果你使用en-dash字符(U + 2013或8211十进制),并在Node中创建一个二进制缓冲区,它将显示为[19]的缓冲区,这是8个最不重要的比特8211.这是在摘要中用于计算SHA-1的值。

这对我来说不太清楚。我的理解是有效载荷的每个字符(body + callbackURL)都被放入一个8位整数,忽略了溢出。 (因为8211 == 0b10000000010011,而0b00010011 == 19)这就是我认为我的问题所在。

我用来容纳Trello的节点有效负载问题的函数是:

func bitShift(s string) []byte {
    var byteString []byte

    // For each rune in the string
    for _, c := range s {

        // Create a byte slice
        b := []byte(string(c))

        // Take the sign off the least significant byte
        tmp := b[len(b)-1] << 1
        tmp = tmp >> 1

        // Append it to the byte string
        byteString = append(byteString, tmp)
    }
    return byteString
}

我也很可能在基本验证步骤中做错了。对我来说看起来没问题,虽然我对此有点新鲜。

// VerifyNotificationHeader ...
func VerifyNotificationHeader(signedHeader, trelloAPISecret string, requestURL *url.URL, body []byte) bool {

    // Put callbackURL and body into byte slice
    urlBytes := bitShift(requestURL.String())
    bitBody := bitShift(string(body))

    // Sign, hash, and encode the payload
    secret := []byte(trelloAPISecret)
    keyHMAC := hmac.New(sha1.New, secret)
    keyHMAC.Write(append(bitBody, urlBytes...))
    signedHMAC := keyHMAC.Sum(nil)
    base64signedHMAC := base64.StdEncoding.EncodeToString(signedHMAC)

    if comp := strings.EqualFold(base64signedHMAC, signedHeader); !comp {
        return false
    }
    return true
}

如果您需要更多信息,请与我们联系。谢谢!

更新:这已解决,请查看答案。

2 个答案:

答案 0 :(得分:2)

你为什么要扔掉MSB?您将每个rune转换为byte,这是无符号的(实际上是uint8的别名),因此该位保存您正在丢失的信息。

您可以考虑使用这样的函数:

func ascii(s string) []byte {
    var ret []byte
    for _, r := range s {
        ret = append(ret, byte(r))
    }
    return ret
}

由于runeint32的别名,因此转化为byte只会删除前24位,这就是您想要的。

(警告:这假设是小端的。)

答案 1 :(得分:1)

我的代码存在两个问题。主要问题是我对requestURL.String()使用了callbackURL

在上面的comments http.Request.URL

  

对于大多数请求,Path和RawQuery以外的字段都将为空。

事实证明,requestURL.String()仅提供[Path]的{​​{1}}部分。正确的callbackURL是

[Scheme]://[Host][Path]

this answer中指出了第二个问题,对于任何身体包含第8位字符的请求,验证将失败。