-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhmacfilter.go
157 lines (141 loc) · 3.87 KB
/
hmacfilter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package middleware
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"net/http"
"strconv"
"strings"
"time"
)
type hmacFilter struct {
next http.Handler
validate func(*http.Request, []byte) (bool, error)
}
type HmacParams struct {
Provider string
Secret string
HmacSource string
NonceSource string
TimeSource string
Encoding string
IncludeURL bool
}
func (hm hmacFilter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Could not read request from IP %s to %s", r.RemoteAddr, r.URL)
w.WriteHeader(403)
return
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
valid, err := hm.validate(r, body)
if err != nil {
log.Printf("error validating HMAC for request from IP %s to %s: %v", r.RemoteAddr, r.URL, err)
}
if valid {
hm.next.ServeHTTP(w, r)
} else {
w.WriteHeader(403)
log.Printf("IP %s is not permitted to access %s : invalid HMAC: \n", r.RemoteAddr, r.URL)
}
}
func HmacFilter(params HmacParams) func(http.Handler) http.Handler {
var validateFn func(*http.Request, []byte) (bool, error)
switch params.Provider {
case "github":
{
validateFn = GithubValidation(params)
}
default:
{
validateFn = DefaultValidation(params)
}
}
fn := func(next http.Handler) http.Handler {
return hmacFilter{next, validateFn}
}
return fn
}
func DefaultValidation(params HmacParams) func(r *http.Request, message []byte) (bool, error) {
return func(r *http.Request, message []byte) (bool, error) {
messageMAC := []byte(r.Header.Get(params.HmacSource))
if len(params.Secret) == 0 {
err := fmt.Errorf("empty HMAC secret")
return false, err
}
secret := []byte(params.Secret)
var err error
if params.Encoding != "" {
switch strings.ToLower(params.Encoding) {
case "base64":
{
secret, err = base64.StdEncoding.DecodeString(string(secret))
if err != nil {
return false, fmt.Errorf("invalid secret: %w", err)
}
messageMAC, err = base64.StdEncoding.DecodeString(string(messageMAC))
if err != nil {
return false, fmt.Errorf("invalid signature in header %s: %w", params.HmacSource, err)
}
}
case "hex":
{
secret, err = hex.DecodeString(string(secret))
if err != nil {
return false, fmt.Errorf("invalid secret: %w", err)
}
messageMAC, err = hex.DecodeString(string(messageMAC))
if err != nil {
return false, fmt.Errorf("invalid signature in header %s: %w", params.HmacSource, err)
}
}
default:
return false, fmt.Errorf("invalid encoding %s", params.Encoding)
}
}
mac := hmac.New(sha256.New, secret)
if params.IncludeURL {
mac.Write([]byte(r.URL.String()))
}
if params.NonceSource != "" {
mac.Write([]byte(r.Header.Get(params.NonceSource)))
}
if params.TimeSource != "" {
timestamp, err := strconv.ParseInt(r.Header.Get(params.TimeSource), 10, 64)
if err != nil {
return false, fmt.Errorf("error in timestamp from header %s: %w", params.TimeSource, err)
}
sendTime := time.Unix(timestamp, 0)
receiveTime := time.Now()
if receiveTime.Sub(sendTime) > time.Second*2 {
return false, nil
}
}
expected := mac.Sum(nil)
return hmac.Equal(messageMAC, expected), nil
}
}
func GithubValidation(params HmacParams) func(r *http.Request, message []byte) (bool, error) {
return func(r *http.Request, message []byte) (bool, error) {
messageMAC := r.Header.Get("X-Hub-Signature")
if len(messageMAC) != 45 {
err := fmt.Errorf("invalid HMAC header length")
return false, err
}
if len(params.Secret) == 0 {
err := fmt.Errorf("empty HMAC secret")
return false, err
}
mac := hmac.New(sha1.New, []byte(params.Secret))
mac.Write(message)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(messageMAC[5:]), []byte(expected)), nil
}
}