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/>.
25 "golang.org/x/crypto/blowfish"
26 "lukechampine.com/blake3"
30 App = "go.cypherpunks.ru/udpobfs/v2"
36 func MustNewBlowfish(key []byte) *blowfish.Cipher {
37 ciph, err := blowfish.NewCipher(key)
44 type CryptoState struct {
45 rxEnc, rxMAC *blake3.Hasher
46 txEnc, txMAC *blake3.Hasher
47 rxObfs, txObfs *blowfish.Cipher
48 rxSeq, txSeq [SeqLen]byte
51 func NewCryptoState(seed []byte, isInit bool) *CryptoState {
52 var hInitEncKey, hInitMACKey [32]byte
53 var hRespEncKey, hRespMACKey [32]byte
54 var obfsInitKey, obfsRespKey [32]byte
55 blake3.DeriveKey(hInitEncKey[:], App+" init enc", seed)
56 blake3.DeriveKey(hInitMACKey[:], App+" init mac", seed)
57 blake3.DeriveKey(hRespEncKey[:], App+" resp enc", seed)
58 blake3.DeriveKey(hRespMACKey[:], App+" resp mac", seed)
59 blake3.DeriveKey(obfsInitKey[:], App+" init obfs", seed)
60 blake3.DeriveKey(obfsRespKey[:], App+" resp obfs", seed)
61 hInitEnc := blake3.New(32, hInitEncKey[:])
62 hInitMAC := blake3.New(MACLen, hInitMACKey[:])
63 hRespEnc := blake3.New(32, hRespEncKey[:])
64 hRespMAC := blake3.New(MACLen, hRespMACKey[:])
65 obfsInit := MustNewBlowfish(obfsInitKey[:])
66 obfsResp := MustNewBlowfish(obfsRespKey[:])
67 state := CryptoState{}
69 state.rxEnc = hRespEnc
70 state.rxMAC = hRespMAC
71 state.rxObfs = obfsResp
72 state.txEnc = hInitEnc
73 state.txMAC = hInitMAC
74 state.txObfs = obfsInit
76 state.rxEnc = hInitEnc
77 state.rxMAC = hInitMAC
78 state.rxObfs = obfsInit
79 state.txEnc = hRespEnc
80 state.txMAC = hRespMAC
81 state.txObfs = obfsResp
86 func (state *CryptoState) Tx(dst, src []byte) []byte {
87 if len(dst) != SeqLen+len(src) {
88 log.Fatal("unexpected dst len")
90 if Incr(state.txSeq[MACLen:]) {
91 Incr(state.txSeq[:MACLen])
93 copy(dst, state.txSeq[:])
95 MustWrite(state.txEnc, state.txSeq[:])
96 if _, err := io.ReadFull(state.txEnc.XOF(), dst[SeqLen:]); err != nil {
99 subtle.XORBytes(dst[SeqLen:], dst[SeqLen:], src)
101 MustWrite(state.txMAC, dst)
102 state.txMAC.Sum(dst[:0])
103 state.txObfs.Encrypt(dst[:SeqLen], dst[:SeqLen])
104 return dst[:len(src)+SeqLen]
107 func (state *CryptoState) Rx(dst, src []byte) []byte {
108 if len(dst) != len(src) {
109 log.Fatal("unexpected dst len")
111 state.rxObfs.Decrypt(dst[:SeqLen], src[:SeqLen])
112 var theirMAC [MACLen]byte
113 copy(theirMAC[:], dst)
114 copy(dst, state.rxSeq[:MACLen])
116 MustWrite(state.rxMAC, dst[:SeqLen])
117 MustWrite(state.rxMAC, src[SeqLen:])
118 var ourMAC [MACLen]byte
119 state.rxMAC.Sum(ourMAC[:0])
120 if subtle.ConstantTimeCompare(ourMAC[:], theirMAC[:]) != 1 {
123 MustWrite(state.rxMAC, dst[:SeqLen])
124 MustWrite(state.rxMAC, src[SeqLen:])
125 state.rxMAC.Sum(ourMAC[:0])
126 if subtle.ConstantTimeCompare(ourMAC[:], theirMAC[:]) != 1 {
130 copy(state.rxSeq[:], dst)
132 MustWrite(state.rxEnc, dst[:SeqLen])
133 if _, err := io.ReadFull(state.rxEnc.XOF(), dst); err != nil {
136 subtle.XORBytes(dst, dst, src[SeqLen:])
137 return dst[:len(src)-SeqLen]