func (vg *Vangoh) createHeadersString(r *http.Request) string {
	if len(vg.includedHeaders) == 0 {
		return ""
	}
	var sanitizedHeaders = make(map[string][]string)
	for _, compiledRegex := range vg.includedHeaders {
		for header := range r.Header {
			lowerHeader := strings.ToLower(header)
			if _, found := sanitizedHeaders[lowerHeader]; found {
				continue
			}
			if compiledRegex.MatchString(header) {
				sanitizedHeaders[lowerHeader] = r.Header[header]
			}
		}
	}
	var orderedHeaders []string
	for header := range sanitizedHeaders {
		orderedHeaders = append(orderedHeaders, header)
	}
	sort.Strings(orderedHeaders)
	var buffer bytes.Buffer
	for header := range orderedHeaders {
		buffer.WriteString(orderedHeaders[header])
		buffer.WriteString(":")
		var sanitizedValues []string
		for i := range sanitizedHeaders[orderedHeaders[header]] {
			str := sanitizedHeaders[orderedHeaders[header]][i]
			str = strings.TrimSpace(str)
			str = strings.Replace(str, "\n", "", -1)
			sanitizedValues = append(sanitizedValues, str)
		}
		for i := range sanitizedValues {
			buffer.WriteString(sanitizedValues[i])
			if i < (len(sanitizedValues) - 1) {
				buffer.WriteString(",")
			}
		}
		buffer.WriteString(newline)
	}
	return buffer.String()
}
Create the canonicalized header string part of a request's signature body.
package vangoh
import (
	"bytes"
	"crypto"
	"crypto/hmac"
	_ "crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"errors"
	"hash"
	"net/http"
	"regexp"
	"sort"
	"strings"
	"time"
)
const AuthRegex = "^[A-Za-z0-9_]+ [A-Za-z0-9_/+]+:" +
	"(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"
const newline = "\u000A"
var SupportedDateFormatNames = []string{
	time.RFC822,
	time.RFC822Z,
	time.RFC850,
	time.ANSIC,
	time.RFC1123,
	time.RFC1123Z,
}
var clock = struct{ Now func() time.Time }{Now: time.Now}
type Vangoh struct {
	singleProvider *secretProvider
	providersByOrg map[string]secretProvider
	algorithm func() hash.Hash
	includedHeaders map[string]*regexp.Regexp
	maxTimeSkew time.Duration
	customDateHeader string
	debug bool
}
func New() *Vangoh {
	return &Vangoh{
		singleProvider:  nil,
		providersByOrg:  make(map[string]secretProvider),
		algorithm:       crypto.SHA256.New,
		includedHeaders: make(map[string]*regexp.Regexp),
		maxTimeSkew:     time.Minute * 15,
		debug:           false,
	}
}
func NewSingleProvider(provider secretProvider) *Vangoh {
	vg := New()
	vg.singleProvider = &provider
	return vg
}
func (vg *Vangoh) AddProvider(org string, skp secretProvider) error {
	if vg.singleProvider != nil {
		return errors.New("cannot add a provider when created for a single provider")
	}
	if _, ok := vg.providersByOrg[org]; ok {
		return errors.New("cannot add more than one keyProvider for the same org tag")
	}
	vg.providersByOrg[org] = skp
	return nil
}
func (vg *Vangoh) SetAlgorithm(algorithm func() hash.Hash) {
	vg.algorithm = algorithm
}
func (vg *Vangoh) SetDebug(debug bool) {
	vg.debug = debug
}
func (vg *Vangoh) GetDebug() bool {
	return vg.debug
}
func (vg *Vangoh) IncludeHeader(headerRegex string) error {
	var regexBuf bytes.Buffer
	if !strings.HasPrefix(headerRegex, "^") {
		regexBuf.WriteString("^")
	}
	regexBuf.WriteString(headerRegex)
	if !strings.HasSuffix(headerRegex, "$") {
		regexBuf.WriteString("$")
	}
	regex := regexBuf.String()
	compiled, err := regexp.Compile(regex)
	if err != nil {
		return err
	}
	vg.includedHeaders[regex] = compiled
	return nil
}
func (vg *Vangoh) SetMaxTimeSkew(timeSkew time.Duration) {
	vg.maxTimeSkew = timeSkew
}
func (vg *Vangoh) SetCustomDateHeader(headerName string) {
	vg.customDateHeader = headerName
}
func (vg *Vangoh) getDateHeaderFromRequest(r *http.Request) string {
	dateHeader := ""
	if vg.customDateHeader != "" {
		dateHeader = strings.TrimSpace(r.Header.Get(vg.customDateHeader))
	}
	if dateHeader == "" {
		dateHeader = strings.TrimSpace(r.Header.Get("Date"))
	}
	return dateHeader
}
func (vg *Vangoh) AuthenticateRequest(r *http.Request) *AuthenticationError {
	authHeader := strings.TrimSpace(r.Header.Get("Authorization"))
	if authHeader == "" {
		return ErrorAuthHeaderMissing
	}
	match, err := regexp.Match(AuthRegex, []byte(authHeader))
	if err != nil || !match {
		return ErrorAuthHeaderMalformed
	}
	orgSplit := strings.Split(authHeader, " ")
	org := orgSplit[0]
	keySplit := strings.Split(orgSplit[1], ":")
	key := []byte(keySplit[0])
	actualSignatureB64 := keySplit[1]
	dateHeader := vg.getDateHeaderFromRequest(r)
	if dateHeader == "" {
		return ErrorDateHeaderMissing
	}
	date, err := multiFormatDateParse(SupportedDateFormatNames, dateHeader)
	if err != nil {
		return ErrorDateHeaderMalformed
	}
	present := clock.Now()
	if present.Sub(date) > vg.maxTimeSkew || date.Sub(present) > vg.maxTimeSkew {
		return ErrorDateHeaderTooSkewed
	}
	var provider secretProvider
	if vg.singleProvider != nil {
		provider = *vg.singleProvider
	} else {
		var exists bool
		provider, exists = vg.providersByOrg[org]
		if !exists {
			return ErrorAuthOrgUnknown
		}
	}
	cbPayload := &CallbackPayload{}
	var secret []byte
	switch provider := provider.(type) {
	case SecretProviderWithCallback:
		secret, err = provider.GetSecret(key, cbPayload)
	case SecretProvider:
		secret, err = provider.GetSecret(key)
	}
	if err != nil {
		return ErrorInProviderKeyLookup
	}
	if secret == nil {
		return ErrorSecretNotFound
	}
	expectedSignature := vg.ConstructSignature(r, secret)
	expectedSignatureB64 := base64.StdEncoding.EncodeToString(expectedSignature)
	if subtle.ConstantTimeCompare([]byte(expectedSignatureB64), []byte(actualSignatureB64)) != 1 {
		return ErrorHMACSignatureMismatch
	}
	switch provider := provider.(type) {
	case SecretProviderWithCallback:
		provider.SuccessCallback(r, cbPayload)
	}
	return nil
}
func (vg *Vangoh) ConstructSignature(r *http.Request, secret []byte) []byte {
	signingString := vg.CreateSigningString(r)
	mac := hmac.New(vg.algorithm, secret)
	mac.Write([]byte(signingString))
	return mac.Sum(nil)
}
func multiFormatDateParse(formats []string, dateStr string) (time.Time, error) {
	for index := range formats {
		if date, err := time.Parse(formats[index], dateStr); err == nil {
			return date, nil
		}
	}
	return time.Time{}, errors.New("Date does not match any valid format")
}
func (vg *Vangoh) CreateSigningString(r *http.Request) string {
	var buffer bytes.Buffer
	buffer.WriteString(r.Method)
	buffer.WriteString(newline)
	buffer.WriteString(r.Header.Get("Content-MD5"))
	buffer.WriteString(newline)
	buffer.WriteString(r.Header.Get("Content-Type"))
	buffer.WriteString(newline)
	dateHeader := vg.getDateHeaderFromRequest(r)
	buffer.WriteString(dateHeader)
	buffer.WriteString(newline)
	customHeaders := vg.createHeadersString(r)
	buffer.WriteString(customHeaders)
	buffer.WriteString(r.URL.Path)
	return buffer.String()
}