| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 | // Copyright 2016 The go-github AUTHORS. All rights reserved.//// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// This file provides functions for validating payloads from GitHub Webhooks.// GitHub API docs: https://developer.github.com/webhooks/securing/#validating-payloads-from-githubpackage githubimport (	"crypto/hmac"	"crypto/sha1"	"crypto/sha256"	"crypto/sha512"	"encoding/hex"	"encoding/json"	"errors"	"fmt"	"hash"	"io/ioutil"	"net/http"	"net/url"	"strings")const (	// sha1Prefix is the prefix used by GitHub before the HMAC hexdigest.	sha1Prefix = "sha1"	// sha256Prefix and sha512Prefix are provided for future compatibility.	sha256Prefix = "sha256"	sha512Prefix = "sha512"	// signatureHeader is the GitHub header key used to pass the HMAC hexdigest.	signatureHeader = "X-Hub-Signature"	// eventTypeHeader is the GitHub header key used to pass the event type.	eventTypeHeader = "X-Github-Event"	// deliveryIDHeader is the GitHub header key used to pass the unique ID for the webhook event.	deliveryIDHeader = "X-Github-Delivery")var (	// eventTypeMapping maps webhooks types to their corresponding go-github struct types.	eventTypeMapping = map[string]string{		"commit_comment":              "CommitCommentEvent",		"create":                      "CreateEvent",		"delete":                      "DeleteEvent",		"deployment":                  "DeploymentEvent",		"deployment_status":           "DeploymentStatusEvent",		"fork":                        "ForkEvent",		"gollum":                      "GollumEvent",		"installation":                "InstallationEvent",		"installation_repositories":   "InstallationRepositoriesEvent",		"issue_comment":               "IssueCommentEvent",		"issues":                      "IssuesEvent",		"label":                       "LabelEvent",		"marketplace_purchase":        "MarketplacePurchaseEvent",		"member":                      "MemberEvent",		"membership":                  "MembershipEvent",		"milestone":                   "MilestoneEvent",		"organization":                "OrganizationEvent",		"org_block":                   "OrgBlockEvent",		"page_build":                  "PageBuildEvent",		"ping":                        "PingEvent",		"project":                     "ProjectEvent",		"project_card":                "ProjectCardEvent",		"project_column":              "ProjectColumnEvent",		"public":                      "PublicEvent",		"pull_request_review":         "PullRequestReviewEvent",		"pull_request_review_comment": "PullRequestReviewCommentEvent",		"pull_request":                "PullRequestEvent",		"push":                        "PushEvent",		"repository":                  "RepositoryEvent",		"release":                     "ReleaseEvent",		"status":                      "StatusEvent",		"team":                        "TeamEvent",		"team_add":                    "TeamAddEvent",		"watch":                       "WatchEvent",	})// genMAC generates the HMAC signature for a message provided the secret key// and hashFunc.func genMAC(message, key []byte, hashFunc func() hash.Hash) []byte {	mac := hmac.New(hashFunc, key)	mac.Write(message)	return mac.Sum(nil)}// checkMAC reports whether messageMAC is a valid HMAC tag for message.func checkMAC(message, messageMAC, key []byte, hashFunc func() hash.Hash) bool {	expectedMAC := genMAC(message, key, hashFunc)	return hmac.Equal(messageMAC, expectedMAC)}// messageMAC returns the hex-decoded HMAC tag from the signature and its// corresponding hash function.func messageMAC(signature string) ([]byte, func() hash.Hash, error) {	if signature == "" {		return nil, nil, errors.New("missing signature")	}	sigParts := strings.SplitN(signature, "=", 2)	if len(sigParts) != 2 {		return nil, nil, fmt.Errorf("error parsing signature %q", signature)	}	var hashFunc func() hash.Hash	switch sigParts[0] {	case sha1Prefix:		hashFunc = sha1.New	case sha256Prefix:		hashFunc = sha256.New	case sha512Prefix:		hashFunc = sha512.New	default:		return nil, nil, fmt.Errorf("unknown hash type prefix: %q", sigParts[0])	}	buf, err := hex.DecodeString(sigParts[1])	if err != nil {		return nil, nil, fmt.Errorf("error decoding signature %q: %v", signature, err)	}	return buf, hashFunc, nil}// ValidatePayload validates an incoming GitHub Webhook event request// and returns the (JSON) payload.// The Content-Type header of the payload can be "application/json" or "application/x-www-form-urlencoded".// If the Content-Type is neither then an error is returned.// secretKey is the GitHub Webhook secret message.//// Example usage:////     func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) {//       payload, err := github.ValidatePayload(r, s.webhookSecretKey)//       if err != nil { ... }//       // Process payload...//     }//func ValidatePayload(r *http.Request, secretKey []byte) (payload []byte, err error) {	var body []byte // Raw body that GitHub uses to calculate the signature.	switch ct := r.Header.Get("Content-Type"); ct {	case "application/json":		var err error		if body, err = ioutil.ReadAll(r.Body); err != nil {			return nil, err		}		// If the content type is application/json,		// the JSON payload is just the original body.		payload = body	case "application/x-www-form-urlencoded":		// payloadFormParam is the name of the form parameter that the JSON payload		// will be in if a webhook has its content type set to application/x-www-form-urlencoded.		const payloadFormParam = "payload"		var err error		if body, err = ioutil.ReadAll(r.Body); err != nil {			return nil, err		}		// If the content type is application/x-www-form-urlencoded,		// the JSON payload will be under the "payload" form param.		form, err := url.ParseQuery(string(body))		if err != nil {			return nil, err		}		payload = []byte(form.Get(payloadFormParam))	default:		return nil, fmt.Errorf("Webhook request has unsupported Content-Type %q", ct)	}	sig := r.Header.Get(signatureHeader)	if err := validateSignature(sig, body, secretKey); err != nil {		return nil, err	}	return payload, nil}// validateSignature validates the signature for the given payload.// signature is the GitHub hash signature delivered in the X-Hub-Signature header.// payload is the JSON payload sent by GitHub Webhooks.// secretKey is the GitHub Webhook secret message.//// GitHub API docs: https://developer.github.com/webhooks/securing/#validating-payloads-from-githubfunc validateSignature(signature string, payload, secretKey []byte) error {	messageMAC, hashFunc, err := messageMAC(signature)	if err != nil {		return err	}	if !checkMAC(payload, messageMAC, secretKey, hashFunc) {		return errors.New("payload signature check failed")	}	return nil}// WebHookType returns the event type of webhook request r.//// GitHub API docs: https://developer.github.com/v3/repos/hooks/#webhook-headersfunc WebHookType(r *http.Request) string {	return r.Header.Get(eventTypeHeader)}// DeliveryID returns the unique delivery ID of webhook request r.//// GitHub API docs: https://developer.github.com/v3/repos/hooks/#webhook-headersfunc DeliveryID(r *http.Request) string {	return r.Header.Get(deliveryIDHeader)}// ParseWebHook parses the event payload. For recognized event types, a// value of the corresponding struct type will be returned (as returned// by Event.ParsePayload()). An error will be returned for unrecognized event// types.//// Example usage:////     func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) {//       payload, err := github.ValidatePayload(r, s.webhookSecretKey)//       if err != nil { ... }//       event, err := github.ParseWebHook(github.WebHookType(r), payload)//       if err != nil { ... }//       switch event := event.(type) {//       case *github.CommitCommentEvent://           processCommitCommentEvent(event)//       case *github.CreateEvent://           processCreateEvent(event)//       ...//       }//     }//func ParseWebHook(messageType string, payload []byte) (interface{}, error) {	eventType, ok := eventTypeMapping[messageType]	if !ok {		return nil, fmt.Errorf("unknown X-Github-Event in message: %v", messageType)	}	event := Event{		Type:       &eventType,		RawPayload: (*json.RawMessage)(&payload),	}	return event.ParsePayload()}
 |