hmacval

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 14, 2018 License: MIT Imports: 6 Imported by: 0

README

Go HMAC Validator

A generic tiny Golang package to check HMAC signatures generated by Internet Services to guarantee the HTTP request origin.

Usage

Documentation

Package is documented following Golang conventions so you can generate them with godoc command or read them online on godoc.org.

The package is served under a custom domain and a canonical import path is set in order to restrict to import this package through the github.com domain, hence for getting it you have to

go get go.fraixed.es/hmacval

Golang Version & dependencies

The library may work with a few previous Golang releases, however I haven't tested in any than 1.6.

The current dependencies are only used in the tests, which are testify assert and require

Roadmap

Write some benchmarks and do an implementation of using key/value byte slice as payload than a map to compare how map type probably penalize and it may be avoided.

Why

After I implemented this concept in a node module, I thought that would be great to have this generic HMAC validation functionality for Golang, so I took the concept and I developed it in the Go way (this is not a the NodeJS implementation translated to Go).

The same concept and similar functionality for NodeJS

License

MIT, read LICENSE file for more information.

Documentation

Overview

Package hmacval provides a type to verify HMAC signatures generated by Internet providers. It tries to provide a generic type that allow to verify as much providers as possible, without exposing specific functions fo each one nor exposing high level functionalities which are easy to perfom with the Golan package library

Example (Pusher)
secret := "7ad3773142a6692b25b8"
// We create a Val configured to check HMAC digests as Twilio generates
val := hmacval.NewVal(sha256.New, nil, nil, nil, "", "")

// We create an request to simulate how Pusher sends a WebHook
request, err := http.NewRequest("POST", "https://mycompany.com/myapp", bytes.NewBuffer([]byte(`{"time_ms":1327078148132,"events":[{"name":"event_name","some":"data"}]}`)))
request.Header.Set("X-Pusher-Signature", "26537b0c36841dc4e940291893424d4fba6af2a7510701f113e720df5d4f2577")
if err != nil {
	log.Fatalf("Error creating request: %s", err)
}

// We can think the the following lines would be in one (or more) of our http.Handler
digest, err := hex.DecodeString(request.Header.Get("X-Pusher-Signature"))
if err != nil {
	log.Fatalf("Error decoding provided HMAC value: %s", err)
}

body, err := ioutil.ReadAll(request.Body)
if err != nil {
	log.Fatalf("Error reading all the content in Request's body: %s", err)
}
defer closeBody(request)

isValid := val(secret, string(body), nil, digest)
fmt.Println(isValid)
Output:

true
Example (Shopify)
secret := "hush"
// We create a Val configured to check HMAC digests as Shopify generates
val := hmacval.NewVal(
	sha256.New,
	[]string{"signature", "hmac"},
	[]string{"&", "%26", "%", "%25", "=", "%3D"},
	[]string{"&", "%26", "%", "%25"},
	"=",
	"&")

// We create an request to simulate how Shopify would send requests to our application proxy
request, err := http.NewRequest("GET", "http://yourdomain.com?shop=some-shop.myshopify.com&timestamp=1337178173&signature=6e39a2ea9e497af6cb806720da1f1bf3&hmac=c2812f39f84c32c2edaded339a1388abc9829babf351b684ab797f04cd94d4c7", nil)
if err != nil {
	log.Fatalf("Error creating request: %s", err)
}

// We can think the the following lines would be in one (or more) of our http.Handler
pq, err := url.ParseQuery(request.URL.RawQuery)
if err != nil {
	log.Fatalf("Error parsing URL query: %s", err)
}

payload := make(map[string]string, len(pq))
for n, v := range pq {
	// Shopify doesn't define how to join queyr parameters which are present more than once
	// so we assume that we can just join them
	payload[n] = strings.Join(v, "")
}

digest, err := hex.DecodeString(payload["hmac"])
if err != nil {
	log.Fatalf("Error decoding provided HMAC value: %s", err)
}

isValid := val(secret, "", payload, digest)
fmt.Println(isValid)
Output:

true
Example (Twilio)
secret := "12345"
// We create a Val configured to check HMAC digests as Twilio generates
val := hmacval.NewVal(sha1.New, nil, nil, nil, "", "")

// We create an request to simulate how Twilio would send requests to us
body := []byte(`{"Digits":"1234","To":"+18005551212","From":"+14158675309","Caller":"+14158675309","CallSid":"CA1234567890ABCDE"}`)
request, err := http.NewRequest("POST", "https://mycompany.com/myapp.php?foo=1&bar=2", bytes.NewBuffer(body))
request.Header.Set("X-Twilio-Signature", "RSOYDt4T1cUTdK1PDd93/VVr8B8=")
if err != nil {
	log.Fatalf("Error creating request: %s", err)
}

// We can think the the following lines would be in one (or more) of our http.Handler
digest, err := base64.StdEncoding.DecodeString(request.Header.Get("X-Twilio-Signature"))
if err != nil {
	log.Fatalf("Error decoding provided HMAC value: %s", err)
}

payload := make(map[string]string)
jd := json.NewDecoder(request.Body)
defer closeBody(request)

if err := jd.Decode(&payload); err != nil {
	log.Fatalf("Error decoding JSON: %s", err)
}

isValid := val(secret, request.URL.String(), payload, digest)
fmt.Println(isValid)
Output:

true

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrSigKeyNotFound = errors.New("Signature key not found in payload")

ErrSigKeyNotFound is returned when payload doesn't contain a specified key with its own signature as a value

Functions

This section is empty.

Types

type Val

type Val func(secret string, prefix string, payload map[string]string, digest []byte) bool

Val is a function which compares the HMAC digest of the concatenation of the prefix (raw string which is taken without any transformation) and payload, using the provided secret with provided digest. It returns true if they match, otherwise false

func NewVal

func NewVal(h func() hash.Hash, keysToExclude []string, keyRepls []string, valueRepls []string, keyValueLink string, pairsLink string) Val

NewVal creates a Val type value which will use the specified Hash algorithm, exclude the specified keys, make the specified replacements in keys & values, join the each key and value pair and all the pairs with the specified links. Replacements are expressed as pairs (odd slice positions are the strings value to be replaced by the following element in the slice)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL