<div dir="ltr"><div class="gmail_quote"><div dir="ltr"><div>Hi JP, Trevor, Noisey folks,</div><div><br></div><div>I've got a big problem with WireGuard and denial of service. I worked on the problem some, came up with a mediocre solution, and then figured I'd stop thinking about it during some recent trips, and then review the code again to see if I could come up with fresh ideas with a clear head space. Unfortunately, taking time away from thinking about the problem just made me forget a lot. I have no new solutions or ideas. I'm at an impasse. It's upsetting. I was hoping maybe you could give some fresh insight. Allow me to describe the issue to you.</div><div><br></div><div>As you know, WireGuard is based on an elegant one round trip handshake (Noise_IK). Initiator sends message to responder. Responder sends message to initiator. Then they can exchange data securely. A priori, they each know each others' static public keys. They also might optionally have a pre-shared symmetric key, which seamlessly mixes this into the handshake information to add an additional layer of symmetric crypto. When WireGuard receives a handshake message, it puts it in a queue, and lets it be asynchronously processed by worker threads.</div><div><br></div><div>Here's the problem: validating handshake messages requires a ECDH computation. An optimized multicore implementation of Curve25519 running on brand new Xeon hardware can be DoSd with around 300mbps of bandwidth. The queue very quickly fills up and has to start dropping messages.</div><div><br></div><div>Let's go through a few ideas that don't work.</div><div><br></div><div><i>- Why not just HMAC the handshake message with the pre-shared key? This way, you only have to do an extremely fast HMAC computation to accept or drop the message.</i></div><div>Problem: The PSK is optional; this needs to be DoS-resistant even when running in the non-PSK mode. Also, multiple parties know the PSK for communicating with a single server. Also, trivial replay attack.</div><div><br></div><div><i>- Why not HMAC the handshake message with the public key of the responder?</i></div><div>Problem: Because public keys are public, not private. Also, trivial replay attack.</div><div><br></div><div><i>- Apply IP rate limiting.</i></div><div>Problem: trivially spoofable since WireGuard is UDP-based, not TCP-based.</div><div><br></div><div><i>- Well how does TCP do it?</i></div><div>Message sequence numbers / cookie values. So...</div><div><br></div><div><i>- Then why not use a cookie situation, like DTLS or IKEv2? Here, when the responder is overloaded, it can optionally send a response saying "here's a cookie value. Retransmit your message using this cookie value." The cookie value is actually an HMAC of the initiator's source IP and port with some responder secret that rotates every few seconds/minutes/whatever. The responder keeps a ledger of the last eight or so secrets so it can try them all, hoping one matches that isn't too old. Then, ordinary IP ratelimiting can be applied to messages with the correct cookies, since spoofing will no longer be possible.</i></div><div>The problem here is that WireGuard relies on symmetric roles and protocols of the initiator and responder -- they can change roles at any moment, and they each might be communicating with multiple peers. In the IKEv2/DTLS cookie scheme, the cookie the responder sends back to the initiator is unauthenticated. An attacker can flood an initiator with bogus cookie responses to cause a DoS. Also, this cookie value can be learned via MitM.</div><div>This also ruins the nice "one round trip" property of WireGuard. Though in most cases -- when not overloaded -- the cookie round trip wouldn't be required, sometimes it would, and this does add complexity.</div><div><br></div><div><i>- Proof of work?</i></div><div>Same issue as authentication DTLS/IKEv2, except here it's even worse: an attacker can make an initiator waste lots of CPU cycles.</div><div><br></div><div><br></div><div>So, the best solution I've come up with sort of combines all of these partial dysfunctional ones into a lopsided Frankenstein creation:</div><div><br></div><div>- Each handshake message (initiator->responder, responder->initiator) gets added onto it a hash field.</div><div>- This hash field is an HMAC of the preceding bytes of the messages. It uses as its HMAC key the following:</div><div>  1) The public key of the recipient of the message; concatenated with</div><div>  2) If PSK is being used, the PSK known to the recipient of the message and the recipients various senders; concatenated with</div><div>  3) If the sender has a valid (unexpired) cookie from the recipient, the cookie value.</div><div>- (1) is only useful if the public keys are kept secret.</div><div>- (2) is only useful if in PSK mode and other knowers of the PSK aren't going to launch a DoS attack.</div><div>- (3) is required to be present when the server is under attack. Otherwise it allows HMACs that use as its key only (1) and (2).</div><div><br></div><div>- If the receiver gets a handshake that is HMACd with a key that utilizes (1) and (2), but not (3), and the receiver is under load, it responds with a "cookie required" message.</div><div>- This message needs be authenticated and encrypted. Otherwise the initiator could be sent phony cookies, or cookies could be intercepted.</div><div>- The message contains a random salt value.</div><div>- The message then contains the cookie value (which is an HMAC of the sender's IP/port with a rotating secret as a key) authenticated-encrypted using as a key:</div><div>  1) The public key of the responder; concatenated with</div><div>  2) If PSK is being use, the PSK; concatenated with</div><div>  3) The message's salt value.</div><div>- Both (1) and (2) have the same problems as mentioned above.</div><div><br></div><div>- If the receiver gets a handshake that is HMACd with a key that utilizes (1), (2), and (3), and the server is under load, token bucket rate limiting is done on the IP/port.</div><div><br></div><div><br></div><div>So, that's the scheme. It sort of works, but it's far from perfect, and I'm not satisfied. Here's some untested code I wrote on the plane over a month ago when I was dreaming this up:</div><div><a href="http://0bin.net/paste/4iyjCG-ee310nS6C#p4nUhPnb4BEqxPcuFG+v7U1xrZHzJidboqQicHC92wu" target="_blank">http://0bin.net/paste/4iyjCG-ee310nS6C#p4nUhPnb4BEqxPcuFG+v7U1xrZHzJidboqQicHC92wu</a><br></div><div><br></div><div>I'm not so sure the best way forward. I'm all ears to any ideas you might have.</div><div><br></div><div>Thanks,</div><div>Jason</div></div></div></div>