@@ -5,181 +5,155 @@ import (
5
5
"encoding/base64"
6
6
"encoding/binary"
7
7
"hash"
8
- "sync"
9
8
10
9
"golang.org/x/crypto/blake2s"
11
10
"golang.org/x/crypto/chacha20"
12
11
)
13
12
14
13
const (
15
- nameMaxLen = 127
16
- keyLen = 32
17
- macLen = 15
18
- timeLen = 8
19
- versionLen = 1
20
- version = 0
14
+ nameMaxLen = 127
15
+ keyLen = 32
16
+ macLen = 16
17
+ headerLen = 8
18
+ macHeaderLen = macLen + headerLen
19
+ version = 1
21
20
)
22
21
23
- func (s * SecureCookie ) prepareCompactKeys () {
22
+ func (s * SecureCookie ) prepareCompact () {
24
23
// initialize for compact encoding even if no genCompact set to allow
25
24
// two step migration.
26
- s .compactHashKey = blake2s .Sum256 (s .hashKey )
27
- bl , _ := blake2s .New256 (s .compactHashKey [:])
25
+ hashKey := blake2s .Sum256 (s .hashKey )
26
+
27
+ bl , _ := blake2s .New256 (hashKey [:])
28
28
_ , _ = bl .Write (s .blockKey )
29
29
copy (s .compactBlockKey [:], bl .Sum (nil ))
30
- }
31
30
32
- func ( s * SecureCookie ) encodeCompact ( name string , serialized [] byte ) ( string , error ) {
33
- if len ( name ) > nameMaxLen {
34
- return "" , errNameTooLong
31
+ s . macPool . New = func () interface {} {
32
+ hsh , _ := blake2s . New128 ( hashKey [:])
33
+ return & macbuf { Hash : hsh }
35
34
}
35
+ }
36
36
37
+ func (s * SecureCookie ) encodeCompact (name string , serialized []byte ) (string , error ) {
37
38
// Check length
38
- encodedLen := base64 .URLEncoding .EncodedLen (len (serialized ) + macLen + timeLen + versionLen )
39
+ encodedLen := base64 .URLEncoding .EncodedLen (len (serialized ) + macLen + headerLen )
39
40
if s .maxLength != 0 && encodedLen > s .maxLength {
40
41
return "" , errEncodedValueTooLong
41
42
}
42
43
43
44
// form message
44
- r := make ([]byte , versionLen + macLen + timeLen + len (serialized ))
45
- r [0 ] = version
46
- m := r [versionLen :]
47
- tag , body := m [:macLen ], m [macLen :]
48
- binary .LittleEndian .PutUint64 (body , uint64 (timeShift (timestampNano ())))
49
- copy (body [timeLen :], serialized )
45
+ r := make ([]byte , headerLen + macLen + len (serialized ))
46
+ macHeader , body := r [:macHeaderLen ], r [macHeaderLen :]
47
+ copy (body , serialized )
48
+
49
+ header , mac := macHeader [:headerLen ], macHeader [headerLen :]
50
+ binary .BigEndian .PutUint64 (header , uint64 (timeShift (timestampNano ())))
51
+ header [0 ] = version // it is made free in timestamp
50
52
51
53
// Mac
52
- s .compactMac (version , name , body , tag )
54
+ s .compactMac (header , name , body , mac )
53
55
54
56
// Encrypt (if needed)
55
- s .compactXorStream (tag , body )
57
+ s .compactXorStream (macHeader , body )
56
58
57
59
// Encode
58
60
return base64 .RawURLEncoding .EncodeToString (r ), nil
59
61
}
60
62
61
63
func (s * SecureCookie ) decodeCompact (name string , encoded string , dest interface {}) error {
62
- if len (name ) > nameMaxLen {
63
- return errNameTooLong
64
- }
65
-
66
64
decoded , err := base64 .RawURLEncoding .DecodeString (encoded )
67
65
if err != nil {
68
66
return cookieError {cause : err , typ : decodeError , msg : "base64 decode failed" }
69
67
}
70
68
71
- if len (encoded ) < macLen + timeLen + versionLen {
69
+ if len (encoded ) < macHeaderLen {
72
70
return errValueToDecodeTooShort
73
71
}
74
72
73
+ macHeader , body := decoded [:macHeaderLen ], decoded [macHeaderLen :]
74
+ header , mac := macHeader [:headerLen ], macHeader [headerLen :]
75
+
75
76
// Decompose
76
- if decoded [0 ] != version {
77
+ if header [0 ] != version {
77
78
// there is only version currently
78
79
return errVersionDoesntMatch
79
80
}
80
81
81
- m := decoded [versionLen :]
82
- tag , body := m [:macLen ], m [macLen :]
83
-
84
- // Decrypt (if need)
85
- s .compactXorStream (tag , body )
86
-
87
82
// Check time
88
- ts := int64 (binary .LittleEndian .Uint64 (body ))
89
- now := timeShift ( timestampNano () )
90
- if s .maxAge > 0 && ts + secsShift (s .maxAge ) < now {
83
+ ts := timeUnshift ( int64 (binary .BigEndian .Uint64 (header ) ))
84
+ now := timestampNano ()
85
+ if s .maxAge > 0 && ts + secs2nano (s .maxAge ) < now {
91
86
return errTimestampExpired
92
87
}
93
- if s .minAge > 0 && ts + secsShift (s .minAge ) > now {
88
+ if s .minAge > 0 && ts + secs2nano (s .minAge ) > now {
94
89
return errTimestampExpired
95
90
}
96
- if ! timeValid (ts ) {
97
- // We are checking bytes we explicitely leaved as zero as preliminary
98
- // MAC check. We could do it because ChaCha20 has no known plaintext
99
- // issues.
100
- return ErrMacInvalid
101
- }
102
91
103
- // Verify
104
- var mac [macLen ]byte
105
- s .compactMac (version , name , body , mac [:])
106
- if subtle .ConstantTimeCompare (mac [:], tag ) == 0 {
92
+ // Decrypt (if need)
93
+ s .compactXorStream (macHeader , body )
94
+
95
+ // Check MAC
96
+ var macCheck [macLen ]byte
97
+ s .compactMac (header , name , body , macCheck [:])
98
+ if subtle .ConstantTimeCompare (mac , macCheck [:]) == 0 {
107
99
return ErrMacInvalid
108
100
}
109
101
110
102
// Deserialize
111
- if err := s .sz .Deserialize (body [ timeLen :] , dest ); err != nil {
103
+ if err := s .sz .Deserialize (body , dest ); err != nil {
112
104
return cookieError {cause : err , typ : decodeError }
113
105
}
114
106
115
107
return nil
116
108
}
117
109
118
- var macPool = sync.Pool {New : func () interface {} {
119
- hsh , _ := blake2s .New256 (nil )
120
- return & macbuf {Hash : hsh }
121
- }}
122
-
123
110
type macbuf struct {
124
111
hash.Hash
125
- buf [ 3 + nameMaxLen ]byte
126
- sum [ 32 ]byte
112
+ nameLen [ 4 ]byte
113
+ sum [ 16 ]byte
127
114
}
128
115
129
116
func (m * macbuf ) Reset () {
130
117
m .Hash .Reset ()
131
- m .buf = [3 + nameMaxLen ]byte {}
132
- m .sum = [32 ]byte {}
118
+ m .sum = [16 ]byte {}
133
119
}
134
120
135
- func (s * SecureCookie ) compactMac (version byte , name string , body , mac []byte ) {
136
- enc := macPool .Get ().(* macbuf )
137
-
138
- // While it is not "recommended" way to mix key in, it is still valid
139
- // because 1) Blake2b is not susceptible to length-extention attack, 2)
140
- // "recommended" way does almost same, just stores key length in other place
141
- // (it mixes length into constan iv itself).
142
- enc .buf [0 ] = version
143
- // name should not be longer than 127 bytes to fallback to varint in a future
144
- enc .buf [1 ] = byte (len (name ))
145
- enc .buf [2 ] = keyLen
146
- copy (enc .buf [3 :], name )
147
-
148
- _ , _ = enc .Write (enc .buf [:3 + len (name )])
149
- _ , _ = enc .Write (s .hashKey [:])
121
+ func (s * SecureCookie ) compactMac (header []byte , name string , body , mac []byte ) {
122
+ enc := s .macPool .Get ().(* macbuf )
123
+
124
+ binary .BigEndian .PutUint32 (enc .nameLen [:], uint32 (len (name )))
125
+ _ , _ = enc .Write (header )
126
+ _ , _ = enc .Write (enc .nameLen [:])
127
+ _ , _ = enc .Write ([]byte (name ))
150
128
_ , _ = enc .Write (body )
151
129
152
130
copy (mac , enc .Sum (enc .sum [:0 ]))
153
131
154
132
enc .Reset ()
155
- macPool .Put (enc )
133
+ s . macPool .Put (enc )
156
134
}
157
135
158
- func (s * SecureCookie ) compactXorStream (tag , body []byte ) {
136
+ func (s * SecureCookie ) compactXorStream (nonce , body []byte ) {
159
137
if len (s .blockKey ) == 0 { // no blockKey - no encryption
160
138
return
161
139
}
162
- key := s .compactBlockKey
163
- // Mix remaining tag bytes into key.
164
- // We may do it because ChaCha20 has no related keys issues.
165
- key [29 ] ^= tag [12 ]
166
- key [30 ] ^= tag [13 ]
167
- key [31 ] ^= tag [14 ]
168
- stream , err := chacha20 .NewUnauthenticatedCipher (key [:], tag [:12 ])
140
+ stream , err := chacha20 .NewUnauthenticatedCipher (s .compactBlockKey [:], nonce )
169
141
if err != nil {
170
142
panic ("stream initialization failed" )
171
143
}
172
144
stream .XORKeyStream (body , body )
173
145
}
174
146
147
+ // timeShift ensures high byte is zero to use it for version
175
148
func timeShift (t int64 ) int64 {
176
- return t >> 16
149
+ return t >> 8
177
150
}
178
151
179
- func timeValid (t int64 ) bool {
180
- return (t >> (64 - 16 )) == 0
152
+ // timeUnshift restores timestamp to nanoseconds + clears high byte
153
+ func timeUnshift (t int64 ) int64 {
154
+ return t << 8
181
155
}
182
156
183
- func secsShift (t int64 ) int64 {
184
- return ( t * 1000000000 ) >> 16
157
+ func secs2nano (t int64 ) int64 {
158
+ return t * 1000000000
185
159
}
0 commit comments