forked from justinas/nosurf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
token.go
166 lines (140 loc) · 4.1 KB
/
token.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
158
159
160
161
162
163
164
165
166
package nosurf
import (
"crypto/rand"
"crypto/subtle"
"fmt"
"io"
"math/big"
)
const (
tokenLength = 32
)
/*
There are two types of tokens.
* The unmasked "real" token consists of 32 random bytes.
It is stored in a cookie (base64-encoded) and it's the
"reference" value that sent tokens get compared to.
* The masked "sent" token consists of 64 bytes:
32 byte key used for one-time pad masking and
32 byte "real" token masked with the said key.
It is used as a value (base64-encoded as well)
in forms and/or headers.
Upon processing, both tokens are base64-decoded
and then treated as 32/64 byte slices.
*/
// A token is generated by returning tokenLength bytes
// from crypto/rand
func generateToken() []byte {
bytes := make([]byte, tokenLength)
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
panic(err)
}
return bytes
}
func EncodeData(data []byte) string {
//return base64.StdEncoding.EncodeToString(data)
// Removed because symbols like "+" make problems?!
return encodeToBase62(data)
}
func encodeToBase62(data []byte) string {
var bigInt big.Int
bigInt.SetBytes(data)
return bigInt.Text(62)
}
func decodeFromBase62(encoded string) ([]byte, error) {
var bigInt big.Int
_, ok := bigInt.SetString(encoded, 62)
if !ok {
return nil, fmt.Errorf("invalid base62 string: %s", encoded)
}
return bigInt.Bytes(), nil
}
func DecodeData(data string) []byte {
decoded, err := decodeFromBase62(data)
//decoded, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return nil
}
return decoded
}
// VerifyToken verifies the sent token equals the real one
// and returns a bool value indicating if tokens are equal.
// Supports masked tokens. realToken comes from Token(r) and
// sentToken is token sent unusual way.
func VerifyToken(realToken, sentToken string) bool {
//r, err := base64.StdEncoding.DecodeString(realToken)
r, err := decodeFromBase62(realToken)
if err != nil {
return false
}
//if len(r) == 2*tokenLength {
r = unmaskToken(r)
//}
//s, err := base64.StdEncoding.DecodeString(sentToken)
s, err := decodeFromBase62(sentToken)
if err != nil {
return false
}
//if len(s) == 2*tokenLength {
s = unmaskToken(s)
//}
return tokensEqual(r, s)
}
// VerifyTokenDebug verifies the sent token equals the real one
// and returns a bool value indicating if tokens are equal.
// Supports masked tokens. realToken comes from Token(r) and
// sentToken is token sent unusual way.
func VerifyTokenDebug(realToken, sentToken string) bool {
//log.Println("realToken", realToken)
//log.Println("sentToken", sentToken)
//r, err := base64.StdEncoding.DecodeString(realToken)
r, err := decodeFromBase62(realToken)
if err != nil {
return false
}
//log.Println("decoded realToken", realToken, len(r), r, err)
//if len(r) == 2*tokenLength {
r = unmaskToken(r)
//log.Println("unmasked realToken", len(r), r)
//}
//s, err := base64.StdEncoding.DecodeString(sentToken)
s, err := decodeFromBase62(sentToken)
if err != nil {
return false
}
//log.Println("decoded sentToken", sentToken, len(s), s, err)
//if len(s) == 2*tokenLength {
s = unmaskToken(s)
//log.Println("unmasked sentToken", len(s), s)
//}
return tokensEqual(r, s)
}
// verifyToken expects the realToken to be unmasked and the sentToken to be masked
func verifyToken(realToken, sentToken []byte) bool {
realN := len(realToken)
sentN := len(sentToken)
// sentN == tokenLength means the token is unmasked
// sentN == 2*tokenLength means the token is masked.
if realN == tokenLength && sentN == 2*tokenLength {
return tokensEqual(realToken, unmaskToken(sentToken))
}
return false
}
// tokensEqual expects both tokens to be unmasked
func tokensEqual(realToken, sentToken []byte) bool {
return len(realToken) == tokenLength &&
len(sentToken) == tokenLength &&
subtle.ConstantTimeCompare(realToken, sentToken) == 1
}
func checkForPRNG() {
// Check that cryptographically secure PRNG is available
// In case it's not, panic.
buf := make([]byte, 1)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
panic(fmt.Sprintf("crypto/rand is unavailable: Read() failed with %#v", err))
}
}
func init() {
checkForPRNG()
}