cookie.go raw
1 /* SPDX-License-Identifier: MIT
2 *
3 * Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
4 */
5
6 package device
7
8 import (
9 "crypto/hmac"
10 "crypto/rand"
11 "sync"
12 "time"
13
14 "golang.org/x/crypto/blake2s"
15 "golang.org/x/crypto/chacha20poly1305"
16 )
17
18 type CookieChecker struct {
19 sync.RWMutex
20 mac1 struct {
21 key [blake2s.Size]byte
22 }
23 mac2 struct {
24 secret [blake2s.Size]byte
25 secretSet time.Time
26 encryptionKey [chacha20poly1305.KeySize]byte
27 }
28 }
29
30 type CookieGenerator struct {
31 sync.RWMutex
32 mac1 struct {
33 key [blake2s.Size]byte
34 }
35 mac2 struct {
36 cookie [blake2s.Size128]byte
37 cookieSet time.Time
38 hasLastMAC1 bool
39 lastMAC1 [blake2s.Size128]byte
40 encryptionKey [chacha20poly1305.KeySize]byte
41 }
42 }
43
44 func (st *CookieChecker) Init(pk NoisePublicKey) {
45 st.Lock()
46 defer st.Unlock()
47
48 // mac1 state
49
50 func() {
51 hash, _ := blake2s.New256(nil)
52 hash.Write([]byte(WGLabelMAC1))
53 hash.Write(pk[:])
54 hash.Sum(st.mac1.key[:0])
55 }()
56
57 // mac2 state
58
59 func() {
60 hash, _ := blake2s.New256(nil)
61 hash.Write([]byte(WGLabelCookie))
62 hash.Write(pk[:])
63 hash.Sum(st.mac2.encryptionKey[:0])
64 }()
65
66 st.mac2.secretSet = time.Time{}
67 }
68
69 func (st *CookieChecker) CheckMAC1(msg []byte) bool {
70 st.RLock()
71 defer st.RUnlock()
72
73 size := len(msg)
74 smac2 := size - blake2s.Size128
75 smac1 := smac2 - blake2s.Size128
76
77 var mac1 [blake2s.Size128]byte
78
79 mac, _ := blake2s.New128(st.mac1.key[:])
80 mac.Write(msg[:smac1])
81 mac.Sum(mac1[:0])
82
83 return hmac.Equal(mac1[:], msg[smac1:smac2])
84 }
85
86 func (st *CookieChecker) CheckMAC2(msg, src []byte) bool {
87 st.RLock()
88 defer st.RUnlock()
89
90 if time.Since(st.mac2.secretSet) > CookieRefreshTime {
91 return false
92 }
93
94 // derive cookie key
95
96 var cookie [blake2s.Size128]byte
97 func() {
98 mac, _ := blake2s.New128(st.mac2.secret[:])
99 mac.Write(src)
100 mac.Sum(cookie[:0])
101 }()
102
103 // calculate mac of packet (including mac1)
104
105 smac2 := len(msg) - blake2s.Size128
106
107 var mac2 [blake2s.Size128]byte
108 func() {
109 mac, _ := blake2s.New128(cookie[:])
110 mac.Write(msg[:smac2])
111 mac.Sum(mac2[:0])
112 }()
113
114 return hmac.Equal(mac2[:], msg[smac2:])
115 }
116
117 func (st *CookieChecker) CreateReply(
118 msg []byte,
119 recv uint32,
120 src []byte,
121 ) (*MessageCookieReply, error) {
122 st.RLock()
123
124 // refresh cookie secret
125
126 if time.Since(st.mac2.secretSet) > CookieRefreshTime {
127 st.RUnlock()
128 st.Lock()
129 _, err := rand.Read(st.mac2.secret[:])
130 if err != nil {
131 st.Unlock()
132 return nil, err
133 }
134 st.mac2.secretSet = time.Now()
135 st.Unlock()
136 st.RLock()
137 }
138
139 // derive cookie
140
141 var cookie [blake2s.Size128]byte
142 func() {
143 mac, _ := blake2s.New128(st.mac2.secret[:])
144 mac.Write(src)
145 mac.Sum(cookie[:0])
146 }()
147
148 // encrypt cookie
149
150 size := len(msg)
151
152 smac2 := size - blake2s.Size128
153 smac1 := smac2 - blake2s.Size128
154
155 reply := new(MessageCookieReply)
156 reply.Type = MessageCookieReplyType
157 reply.Receiver = recv
158
159 _, err := rand.Read(reply.Nonce[:])
160 if err != nil {
161 st.RUnlock()
162 return nil, err
163 }
164
165 xchapoly, _ := chacha20poly1305.NewX(st.mac2.encryptionKey[:])
166 xchapoly.Seal(reply.Cookie[:0], reply.Nonce[:], cookie[:], msg[smac1:smac2])
167
168 st.RUnlock()
169
170 return reply, nil
171 }
172
173 func (st *CookieGenerator) Init(pk NoisePublicKey) {
174 st.Lock()
175 defer st.Unlock()
176
177 func() {
178 hash, _ := blake2s.New256(nil)
179 hash.Write([]byte(WGLabelMAC1))
180 hash.Write(pk[:])
181 hash.Sum(st.mac1.key[:0])
182 }()
183
184 func() {
185 hash, _ := blake2s.New256(nil)
186 hash.Write([]byte(WGLabelCookie))
187 hash.Write(pk[:])
188 hash.Sum(st.mac2.encryptionKey[:0])
189 }()
190
191 st.mac2.cookieSet = time.Time{}
192 }
193
194 func (st *CookieGenerator) ConsumeReply(msg *MessageCookieReply) bool {
195 st.Lock()
196 defer st.Unlock()
197
198 if !st.mac2.hasLastMAC1 {
199 return false
200 }
201
202 var cookie [blake2s.Size128]byte
203
204 xchapoly, _ := chacha20poly1305.NewX(st.mac2.encryptionKey[:])
205 _, err := xchapoly.Open(cookie[:0], msg.Nonce[:], msg.Cookie[:], st.mac2.lastMAC1[:])
206 if err != nil {
207 return false
208 }
209
210 st.mac2.cookieSet = time.Now()
211 st.mac2.cookie = cookie
212 return true
213 }
214
215 func (st *CookieGenerator) AddMacs(msg []byte) {
216 size := len(msg)
217
218 smac2 := size - blake2s.Size128
219 smac1 := smac2 - blake2s.Size128
220
221 mac1 := msg[smac1:smac2]
222 mac2 := msg[smac2:]
223
224 st.Lock()
225 defer st.Unlock()
226
227 // set mac1
228
229 func() {
230 mac, _ := blake2s.New128(st.mac1.key[:])
231 mac.Write(msg[:smac1])
232 mac.Sum(mac1[:0])
233 }()
234 copy(st.mac2.lastMAC1[:], mac1)
235 st.mac2.hasLastMAC1 = true
236
237 // set mac2
238
239 if time.Since(st.mac2.cookieSet) > CookieRefreshTime {
240 return
241 }
242
243 func() {
244 mac, _ := blake2s.New128(st.mac2.cookie[:])
245 mac.Write(msg[:smac2])
246 mac.Sum(mac2[:0])
247 }()
248 }
249