【问题标题】:Verifying Trello Webhook signature验证 Trello Webhook 签名
【发布时间】:2016-03-16 13:21:45
【问题描述】:

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

Trello 的 webhook 文档 here 指出:

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

这是可以理解的。他们接着说

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

这对我来说不太清楚。我的理解是,payload(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
}

如果您需要更多信息,请告诉我。谢谢!

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

【问题讨论】:

  • 您有我们可以匹配的示例请求和签名吗?
  • 感谢您的关注!如果我不能在接下来的一两天内完成这项工作,我将添加测试这些功能所需的所有信息。目前,我不愿意仅仅为了在这里分享秘密而创建一个新的 Trello 帐户。

标签: security go hmac trello hmacsha1


【解决方案1】:

你为什么要扔掉 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 位,这正是您想要的。

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

【讨论】:

  • 我试图做这样的事情并且遇到溢出错误,这就是为什么bitShift 是这样的。这干净多了!谢谢!但我认为它们的输出是一样的,对吧?
  • 在短划线上,是的,因为第 7 位(零索引)为零,但对于任何设置了该位的东西都不是。 bitShift 抛出该位,但 ascii 保留它。例如,bitShift(0b10001110) 产生0b00001110,其中ascii(0b10001110) 只是将字节原样返回:0b10001110
  • 我也很确定bitShift 在那里给出 63 而不是 255 的原因是从 rune 直接转换为 string,但我不确定。
  • 好吧,0xff 我。你说得对。我一直在查看字符串cast as byte slices,因为我对从其他尝试中得到的错误感到困惑。我把这个放进去!
  • @doykle:请注意,只有在保证数据中没有任何非 utf8 字节的情况下才可以这样做。当 go 将字符串转换为符文时,任何无效的 utf8 都会转换为 unicode 替换字符 0xfffd。 play.golang.org/p/0gNumZzJ_V 。我不知道在这种情况下节点库的行为会是什么。
【解决方案2】:

我的代码有两个问题。主要问题是我将requestURL.String() 用于callbackURL

comments上面http.Request.URL

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

原来requestURL.String() 只给出了[Scheme]://[Host][Path][Path] 部分。正确的 callbackURL 是

callbackURL := "https://" + request.Host + request.URL.String()

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-17
    • 2021-12-25
    • 2022-06-28
    • 2019-05-11
    • 2020-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多