Webhook signature verification fails

I’m trying to write a webhook endpoint and need to verify the signature, but my computed signature and the one sent in the headers never match.

const secret = "secret" // Matches value input into webhook configuration.

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		receivedSignature := request.Header.Get("X-Gogs-Signature")
		if len(receivedSignature) == 0 {
			fmt.Println(errors.New("receivedSignature header missing"))
		}

		body, err := ioutil.ReadAll(request.Body)
		if err != nil {
			fmt.Println(errors.New("failed to read request body"))
		}

		mac := hmac.New(sha256.New, []byte(secret))
		_, err = mac.Write(body)
		if err != nil {
			fmt.Println(errors.New("failed to write body to MAC"))
		}

		computedSignature := hex.EncodeToString(mac.Sum(nil))

		if !hmac.Equal([]byte(receivedSignature), []byte(computedSignature)) {
			fmt.Println(errors.New("HMAC verification failed"))
		}
		fmt.Println("Computed signature:", computedSignature)
		fmt.Println("Received signature:", receivedSignature)
	})
	http.ListenAndServe(":80", mux)
}

Give ths following output for a pull request:

HMAC verification failed
Computed signature: f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169
Received signature: 1687bd4696492f2d2f36b12b567425eab92d7910734b0fc45d64103ce090a4a6

I’m running the following build, in Docker:

Application version: 0.12.0+dev
Git version: 2.22.2
Go version: go1.13.8
Build time: 2020-04-06 06:53:36 UTC
Build commit: 083ecb7244486d530ddc8587b6b4776d21616353 

Why are the signatures not matching?

Thanks

I can’t say for sure, but sometimes the request body you got could contain extra EOL (i.e. \n, caused by HTTP parsing). Maybe try _, err = mac.Write(bytes.TrimSpace(body))?

Thanks for the tip.

I noticed that all my computed MACs were identical - I’d already read the body of the request (in code omitted from my example) before using it to compute the MAC. I fixed that and it worked.

Moral of the story - don’t try to re-read from a io.ReadCloser!

LOL, good to know!