forked from carl-mastrangelo/pixur
-
Notifications
You must be signed in to change notification settings - Fork 1
/
xsrf.go
126 lines (109 loc) · 3.65 KB
/
xsrf.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
package handlers
import (
"context"
"crypto/subtle"
"encoding/base64"
"encoding/binary"
"io"
"net/http"
"time"
)
const (
xsrfTokenIssuedAtLength = 48 / 8
xsrfTokenExpiresLength = 48 / 8
xsrfTokenRandLength = 48 / 8
xsrfTokenLength = xsrfTokenIssuedAtLength + xsrfTokenExpiresLength + xsrfTokenRandLength
xsrfTokenLifetime = time.Hour * 24 * 365 * 10
)
var (
b64XsrfEnc = base64.URLEncoding
b64XsrfTokenLength = b64XsrfEnc.EncodedLen(xsrfTokenLength)
)
type incomingXsrfTokenKey struct{}
type outgoingXsrfTokenKey struct{}
func ctxFromIncomingXsrfToken(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, incomingXsrfTokenKey{}, token)
}
func incomingXsrfTokenFromCtx(ctx context.Context) (string, bool) {
token, ok := ctx.Value(incomingXsrfTokenKey{}).(string)
return token, ok
}
func ctxFromOutgoingXsrfToken(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, outgoingXsrfTokenKey{}, token)
}
func outgoingXsrfTokenFromCtx(ctx context.Context) (string, bool) {
token, ok := ctx.Value(outgoingXsrfTokenKey{}).(string)
return token, ok
}
func outgoingXsrfTokenOrEmptyFromCtx(ctx context.Context) string {
if token, ok := outgoingXsrfTokenFromCtx(ctx); ok {
return token
}
return ""
}
func newXsrfToken(random io.Reader, now func() time.Time) (string, error) {
xsrfTokenRand := make([]byte, xsrfTokenRandLength)
if _, err := io.ReadFull(random, xsrfTokenRand); err != nil {
return "", err
}
theTime := now()
xsrfTokenIssuedAt := make([]byte, 8)
binary.BigEndian.PutUint64(xsrfTokenIssuedAt, uint64(theTime.Unix()))
xsrfTokenExpires := make([]byte, 8)
binary.BigEndian.PutUint64(xsrfTokenExpires, uint64(theTime.Add(xsrfTokenLifetime).Unix()))
xsrfToken := make([]byte, 0, xsrfTokenLength)
xsrfToken = append(xsrfToken, xsrfTokenIssuedAt[len(xsrfTokenIssuedAt)-xsrfTokenIssuedAtLength:]...)
xsrfToken = append(xsrfToken, xsrfTokenExpires[len(xsrfTokenExpires)-xsrfTokenExpiresLength:]...)
xsrfToken = append(xsrfToken, xsrfTokenRand...)
b64XsrfToken := make([]byte, b64XsrfTokenLength)
b64XsrfEnc.Encode(b64XsrfToken, xsrfToken)
return string(b64XsrfToken), nil
}
func newXsrfCookie(token string, now func() time.Time, pt *paths, secure bool) *http.Cookie {
return &http.Cookie{
Name: pt.pr.XsrfCookie(),
Value: token,
Path: pt.Root().EscapedPath(), // Has to be accessible from root, reset from previous
Expires: now().Add(xsrfTokenLifetime),
Secure: secure,
HttpOnly: true,
}
}
// incomingXsrfTokensFromReq extracts the cookie and header xsrf tokens from r
func incomingXsrfTokensFromReq(r *http.Request, pt *paths) (string, string, error) {
c, err := r.Cookie(pt.pr.XsrfCookie())
if err == http.ErrNoCookie {
return "", "", &HTTPErr{
Code: http.StatusUnauthorized,
Message: "missing xsrf cookie",
Cause: err,
}
} else if err != nil {
// this can't happen according to the http docs
return "", "", &HTTPErr{
Code: http.StatusInternalServerError,
Message: "can't get xsrf token from cookie",
Cause: err,
}
}
f := r.PostFormValue(pt.pr.Xsrf())
return c.Value, f, nil
}
// TODO: check the expiration time of the token
// checkXsrfTokens extracts the xsrf tokens and make sure they match
func checkXsrfTokens(cookie, header string) error {
// check the encoded length, not the binary length
if len(cookie) != b64XsrfTokenLength {
return &HTTPErr{
Code: http.StatusUnauthorized,
Message: "wrong length xsrf token",
}
}
if subtle.ConstantTimeCompare([]byte(header), []byte(cookie)) != 1 {
return &HTTPErr{
Code: http.StatusUnauthorized,
Message: "xsrf tokens don't match",
}
}
return nil
}