GO: GIN c.Request.Body mehrfach lesen

Um eine Signaturprüfung für „X-Hub-Signature-256“ machen zu können muss man einen Hash erzeugen mit dem kompletten Body + dem Secret. Der Hash der hier erzeugt wird wird vom sender im Header „X-Hub-Signature-256“ mitgesendet. Sind die beiden Hashes gleich ist sichergestellt das der Body nicht verändert wurde und das das richtige Secret auf der anderen Seite verwendet wurde.

Das Problem auf das ich gestoßen bin ist das c.Request.Body ein Stream ist, wenn dieser einmal ausgelesen wurde dann steht dieser auf EOF. Somit war es nicht mehr möglich den Body mit c.ShouldBindJSON einzulesen, hier kommt wenn man die Error Variable ausließt der Hinweis EOF.

package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

func VerifySignature(payload []byte, signature string, secret string) bool {
	key := hmac.New(sha256.New, []byte(secret))
	key.Write([]byte(string(payload)))
	computedSignature := "sha256=" + hex.EncodeToString(key.Sum(nil))
	log.Printf("computed signature: %s", computedSignature)

	return computedSignature == signature
}

func Sync(c *gin.Context) {
	secret := "supersecret"

	// Generate copy of c.Request.Body
	bodyCopy := new(bytes.Buffer)
	_, err := io.Copy(bodyCopy, c.Request.Body)
	if err != nil {
		log.Println(err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Error reading API token"})
		c.Abort()
		return
	}
	// Write copy to bodyData
	bodyData := bodyCopy.Bytes()

	// Replace the body with a reader that reads from the buffer
	c.Request.Body = io.NopCloser(bytes.NewReader(bodyData))

	// Read body
	payload, _ := ioutil.ReadAll(c.Request.Body)

	// Set body again for c.ShouldBindJSON
	c.Request.Body = io.NopCloser(bytes.NewReader(bodyData))

	// Verify signature
	if !VerifySignature(payload, c.GetHeader("X-Hub-Signature-256"), secret) {
		c.AbortWithStatus(http.StatusUnauthorized)
		log.Println("signatures don't match")

	} else {

		var jsonx interface{}

		err = c.ShouldBindJSON(&jsonx)
		if err != nil {
			fmt.Println("error:", err)
		}

		b, err := json.MarshalIndent(jsonx, "", "  ")
		if err != nil {
			fmt.Println("error:", err)
		}
		fmt.Print(string(b))

	}
}

func main() {
	r := gin.Default()
	r.POST("/sync", Sync)

	r.Run(":8080")

}

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.