2 udpobfs -- simple point-to-point UDP obfuscation proxy
3 Copyright (C) 2023 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
34 "golang.org/x/crypto/blowfish"
35 "golang.org/x/crypto/chacha20"
36 "golang.org/x/crypto/poly1305"
37 "golang.org/x/crypto/sha3"
42 func mustWrite(w io.Writer, data []byte) {
43 if n, err := w.Write(data); err != nil || n != len(data) {
44 log.Fatal("non full write")
48 func incr(buf []byte) (overflow bool) {
49 for i := len(buf) - 1; i >= 0; i-- {
60 ourKey, theirKey []byte
61 ourObfs, theirObfs *blowfish.Cipher
62 ourNonce, theirNonce []byte
63 ourSeq, theirSeq []byte
66 var Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
69 keygen := flag.Bool("keygen", false, "Generate random key")
70 responder := flag.Bool("responder", false, "Are we responder?")
71 bind := flag.String("bind", "[::]:1194", "Address to bind to")
72 dst := flag.String("dst", "[2001:db8::1234]::1194", "Address to connect to")
74 log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
77 key := make([]byte, KeyLen)
78 if _, err := io.ReadFull(rand.Reader, key); err != nil {
81 fmt.Println(Base32Codec.EncodeToString(key))
86 stateReady := make(chan struct{})
89 s := bufio.NewScanner(os.Stdin)
90 h := sha3.NewShake128()
92 key, err := Base32Codec.DecodeString(s.Text())
96 if len(key) != KeyLen {
97 log.Fatal("wrong key length")
100 mustWrite(h, []byte("go.cypherpunks.ru/udpobfs"))
102 iEncKey := make([]byte, chacha20.KeySize)
103 iBlkKey := make([]byte, 32)
104 rEncKey := make([]byte, chacha20.KeySize)
105 rBlkKey := make([]byte, 32)
106 if _, err := io.ReadFull(h, iEncKey); err != nil {
109 if _, err := io.ReadFull(h, iBlkKey); err != nil {
112 if _, err := io.ReadFull(h, rEncKey); err != nil {
115 if _, err := io.ReadFull(h, rBlkKey); err != nil {
118 iObfs, err := blowfish.NewCipher(iBlkKey)
122 rObfs, err := blowfish.NewCipher(rBlkKey)
133 ourNonce: make([]byte, chacha20.NonceSize),
134 theirNonce: make([]byte, chacha20.NonceSize),
142 ourNonce: make([]byte, chacha20.NonceSize),
143 theirNonce: make([]byte, chacha20.NonceSize),
146 newState.ourSeq = newState.ourNonce[4:]
147 newState.theirSeq = newState.theirNonce[4:]
160 addr, err := net.ResolveUDPAddr("udp", *bind)
164 connBind, err := net.ListenUDP("udp", addr)
169 var connLocal *net.UDPConn
170 var connRemote *net.UDPConn
171 var addrLocal *net.UDPAddr
172 var addrRemote *net.UDPAddr
173 addr, err = net.ResolveUDPAddr("udp", *dst)
178 connLocal, err = net.DialUDP("udp", nil, addr)
180 connRemote, err = net.DialUDP("udp", nil, addr)
185 log.Println(*bind, "->", *dst)
188 rx := make([]byte, 1<<14)
189 tx := make([]byte, 1<<14)
192 var s *chacha20.Cipher
194 tag := make([]byte, poly1305.TagSize)
195 var from *net.UDPAddr
198 n, err = connLocal.Read(rx)
200 n, from, err = connBind.ReadFromUDP(rx)
205 if *responder && addrRemote == nil {
208 if !*responder && (addrLocal == nil ||
209 from.Port != addrLocal.Port || !from.IP.Equal(addrLocal.IP)) {
212 if incr(state.ourSeq[5:]) {
213 incr(state.ourSeq[:5])
215 copy(tx, state.ourSeq[5:])
216 s, err = chacha20.NewUnauthenticatedCipher(state.ourKey, state.ourNonce)
221 s.XORKeyStream(polyKey[:], polyKey[:])
223 s.XORKeyStream(tx[8:], rx[:n])
224 p = poly1305.New(&polyKey)
225 mustWrite(p, state.ourSeq)
226 mustWrite(p, tx[8:8+n])
229 state.ourObfs.Encrypt(tx[:8], tx[:8])
231 connBind.WriteTo(tx[:8+n], addrRemote)
233 connRemote.Write(tx[:8+n])
237 go func(state **State) {
238 rx := make([]byte, 1<<14)
239 tx := make([]byte, 1<<14)
242 var s *chacha20.Cipher
244 var from *net.UDPAddr
245 tag := make([]byte, poly1305.TagSize)
246 nonce := make([]byte, chacha20.NonceSize)
248 var seqOur, seqTheir uint32
251 n, from, err = connBind.ReadFromUDP(rx)
253 n, err = connRemote.Read(rx)
259 log.Println("too short")
262 if *responder && (addrRemote == nil ||
263 from.Port != addrRemote.Port || !from.IP.Equal(addrRemote.IP)) {
266 (*state).theirObfs.Decrypt(rx[:8], rx[:8])
267 seqOur = uint32((*state).theirSeq[0])<<16 |
268 uint32((*state).theirSeq[1])<<8 |
269 uint32((*state).theirSeq[2])
270 seqTheir = uint32(rx[0])<<16 | uint32(rx[1])<<8 | uint32(rx[2])
271 if seqOur == seqTheir {
272 log.Println("replay")
275 copy(seq, (*state).theirNonce[:5])
276 copy(seq[5:], rx[:3])
277 if seqTheir < seqOur && incr(seq[:5]) {
278 log.Fatal("seq is overflowed")
280 s, err = chacha20.NewUnauthenticatedCipher((*state).theirKey, nonce)
285 s.XORKeyStream(polyKey[:], polyKey[:])
287 p = poly1305.New(&polyKey)
289 mustWrite(p, rx[8:n])
291 if subtle.ConstantTimeCompare(tag[:5], rx[3:8]) != 1 {
295 copy((*state).theirSeq, seq)
296 s.XORKeyStream(tx, rx[8:n])
298 connLocal.Write(tx[:n-8])
300 connBind.WriteTo(tx[:n-8], addrLocal)
304 exit := make(chan os.Signal, 1)
305 signal.Notify(exit, syscall.SIGTERM, syscall.SIGINT)