-
Notifications
You must be signed in to change notification settings - Fork 0
/
helpers.go
120 lines (101 loc) · 3.53 KB
/
helpers.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
package csrf
import (
"crypto/subtle"
"encoding/base64"
"fmt"
"html/template"
"net/http"
"net/url"
"github.com/boyfinal/opm"
)
// Token returns a masked CSRF token ready for passing into HTML template or
// a JSON response body. An empty token will be returned if the middleware
// has not been applied (which will fail subsequent validation).
func Token(c opm.Context) string {
if maskedToken, ok := c.Get(tokenKey).(string); ok {
return maskedToken
}
return ""
}
// TemplateField is a template helper for html/template that provides an <input> field
// populated with a CSRF token.
//
// Example:
//
// // The following tag in our form.tmpl template:
// <input type="hidden" name="csrf.Token" value="{{.csrf}}">
//
// // ... becomes:
// <input type="hidden" name="csrf.Token" value="<token>">
//
func TemplateField(c opm.Context) template.HTML {
if name, ok := c.Get(formKey).(string); ok {
fragment := fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, name, Token(c))
return template.HTML(fragment)
}
return template.HTML("")
}
// mask returns a unique-per-request token to mitigate the BREACH attack
// as per http://breachattack.com/#mitigations
//
// The token is generated by XOR'ing a one-time-pad and the base (session) CSRF
// token and returning them together as a 64-byte slice. This effectively
// randomises the token on a per-request basis without breaking multiple browser
// tabs/windows.
func mask(realToken []byte) string {
otp, err := opm.GenerateRandBytes(tokenLength)
if err != nil {
return ""
}
// XOR the OTP with the real token to generate a masked token. Append the
// OTP to the front of the masked token to allow unmasking in the subsequent
// request.
return base64.StdEncoding.EncodeToString(append(otp, opm.Xor(otp, realToken)...))
}
// unmask splits the issued token (one-time-pad + masked token) and returns the
// unmasked request token for comparison.
func unmask(issued []byte) []byte {
// Issued tokens are always masked and combined with the pad.
if len(issued) != tokenLength*2 {
return nil
}
// We now know the length of the byte slice.
otp := issued[tokenLength:]
masked := issued[:tokenLength]
// Unmask the token by XOR'ing it against the OTP used to mask it.
return opm.Xor(otp, masked)
}
// requestToken returns the issued token (pad + masked token) from the HTTP POST
// body or HTTP header. It will return nil if the token fails to decode.
func (cs *csrf) requestToken(r *http.Request) []byte {
// 1. Check the HTTP header first.
issued := r.Header.Get(cs.opts.RequestHeader)
// 2. Fall back to the POST (form) value.
if issued == "" {
issued = r.PostFormValue(cs.opts.FieldName)
}
// 3. Finally, fall back to the multipart form (if set).
if issued == "" && r.MultipartForm != nil {
vals := r.MultipartForm.Value[cs.opts.FieldName]
if len(vals) > 0 {
issued = vals[0]
}
}
// Decode the "issued" (pad + masked) token sent in the request. Return a
// nil byte slice on a decoding error (this will fail upstream).
decoded, err := base64.StdEncoding.DecodeString(issued)
if err != nil {
return nil
}
return decoded
}
// sameOrigin returns true if URLs a and b share the same origin. The same
// origin is defined as host (which includes the port) and scheme.
func sameOrigin(a, b *url.URL) bool {
return (a.Scheme == b.Scheme && a.Host == b.Host)
}
// compare securely (constant-time) compares the unmasked token from the request
// against the real token from the session.
func compareTokens(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}