1/* SPDX-License-Identifier: MIT
2 *
3 * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
4 */
5
6package device
7
8import (
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
18type 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
30type 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
44func (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
69func (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
86func (st *CookieChecker) CheckMAC2(msg []byte, 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
117func (st *CookieChecker) CreateReply(
118	msg []byte,
119	recv uint32,
120	src []byte,
121) (*MessageCookieReply, error) {
122
123	st.RLock()
124
125	// refresh cookie secret
126
127	if time.Since(st.mac2.secretSet) > CookieRefreshTime {
128		st.RUnlock()
129		st.Lock()
130		_, err := rand.Read(st.mac2.secret[:])
131		if err != nil {
132			st.Unlock()
133			return nil, err
134		}
135		st.mac2.secretSet = time.Now()
136		st.Unlock()
137		st.RLock()
138	}
139
140	// derive cookie
141
142	var cookie [blake2s.Size128]byte
143	func() {
144		mac, _ := blake2s.New128(st.mac2.secret[:])
145		mac.Write(src)
146		mac.Sum(cookie[:0])
147	}()
148
149	// encrypt cookie
150
151	size := len(msg)
152
153	smac2 := size - blake2s.Size128
154	smac1 := smac2 - blake2s.Size128
155
156	reply := new(MessageCookieReply)
157	reply.Type = MessageCookieReplyType
158	reply.Receiver = recv
159
160	_, err := rand.Read(reply.Nonce[:])
161	if err != nil {
162		st.RUnlock()
163		return nil, err
164	}
165
166	xchapoly, _ := chacha20poly1305.NewX(st.mac2.encryptionKey[:])
167	xchapoly.Seal(reply.Cookie[:0], reply.Nonce[:], cookie[:], msg[smac1:smac2])
168
169	st.RUnlock()
170
171	return reply, nil
172}
173
174func (st *CookieGenerator) Init(pk NoisePublicKey) {
175	st.Lock()
176	defer st.Unlock()
177
178	func() {
179		hash, _ := blake2s.New256(nil)
180		hash.Write([]byte(WGLabelMAC1))
181		hash.Write(pk[:])
182		hash.Sum(st.mac1.key[:0])
183	}()
184
185	func() {
186		hash, _ := blake2s.New256(nil)
187		hash.Write([]byte(WGLabelCookie))
188		hash.Write(pk[:])
189		hash.Sum(st.mac2.encryptionKey[:0])
190	}()
191
192	st.mac2.cookieSet = time.Time{}
193}
194
195func (st *CookieGenerator) ConsumeReply(msg *MessageCookieReply) bool {
196	st.Lock()
197	defer st.Unlock()
198
199	if !st.mac2.hasLastMAC1 {
200		return false
201	}
202
203	var cookie [blake2s.Size128]byte
204
205	xchapoly, _ := chacha20poly1305.NewX(st.mac2.encryptionKey[:])
206	_, err := xchapoly.Open(cookie[:0], msg.Nonce[:], msg.Cookie[:], st.mac2.lastMAC1[:])
207
208	if err != nil {
209		return false
210	}
211
212	st.mac2.cookieSet = time.Now()
213	st.mac2.cookie = cookie
214	return true
215}
216
217func (st *CookieGenerator) AddMacs(msg []byte) {
218
219	size := len(msg)
220
221	smac2 := size - blake2s.Size128
222	smac1 := smac2 - blake2s.Size128
223
224	mac1 := msg[smac1:smac2]
225	mac2 := msg[smac2:]
226
227	st.Lock()
228	defer st.Unlock()
229
230	// set mac1
231
232	func() {
233		mac, _ := blake2s.New128(st.mac1.key[:])
234		mac.Write(msg[:smac1])
235		mac.Sum(mac1[:0])
236	}()
237	copy(st.mac2.lastMAC1[:], mac1)
238	st.mac2.hasLastMAC1 = true
239
240	// set mac2
241
242	if time.Since(st.mac2.cookieSet) > CookieRefreshTime {
243		return
244	}
245
246	func() {
247		mac, _ := blake2s.New128(st.mac2.cookie[:])
248		mac.Write(msg[:smac2])
249		mac.Sum(mac2[:0])
250	}()
251}
252