1 // udpobfs -- simple point-to-point UDP obfuscation proxy
2 // Copyright (C) 2023-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 "golang.org/x/crypto/blowfish"
24 "lukechampine.com/blake3"
28 App = "go.cypherpunks.ru/udpobfs/v2"
34 func MustNewBlowfish(key []byte) *blowfish.Cipher {
35 ciph, err := blowfish.NewCipher(key)
42 type CryptoState struct {
43 rxEnc, rxMAC *blake3.Hasher
44 txEnc, txMAC *blake3.Hasher
45 rxObfs, txObfs *blowfish.Cipher
46 rxSeq, txSeq [SeqLen]byte
49 func NewCryptoState(seed []byte, isInit bool) *CryptoState {
50 var hInitEncKey, hInitMACKey [32]byte
51 var hRespEncKey, hRespMACKey [32]byte
52 var obfsInitKey, obfsRespKey [32]byte
53 blake3.DeriveKey(hInitEncKey[:], App+" init enc", seed)
54 blake3.DeriveKey(hInitMACKey[:], App+" init mac", seed)
55 blake3.DeriveKey(hRespEncKey[:], App+" resp enc", seed)
56 blake3.DeriveKey(hRespMACKey[:], App+" resp mac", seed)
57 blake3.DeriveKey(obfsInitKey[:], App+" init obfs", seed)
58 blake3.DeriveKey(obfsRespKey[:], App+" resp obfs", seed)
59 hInitEnc := blake3.New(32, hInitEncKey[:])
60 hInitMAC := blake3.New(MACLen, hInitMACKey[:])
61 hRespEnc := blake3.New(32, hRespEncKey[:])
62 hRespMAC := blake3.New(MACLen, hRespMACKey[:])
63 obfsInit := MustNewBlowfish(obfsInitKey[:])
64 obfsResp := MustNewBlowfish(obfsRespKey[:])
65 state := CryptoState{}
67 state.rxEnc = hRespEnc
68 state.rxMAC = hRespMAC
69 state.rxObfs = obfsResp
70 state.txEnc = hInitEnc
71 state.txMAC = hInitMAC
72 state.txObfs = obfsInit
74 state.rxEnc = hInitEnc
75 state.rxMAC = hInitMAC
76 state.rxObfs = obfsInit
77 state.txEnc = hRespEnc
78 state.txMAC = hRespMAC
79 state.txObfs = obfsResp
84 func (state *CryptoState) Tx(dst, src []byte) []byte {
85 if len(dst) != SeqLen+len(src) {
86 log.Fatal("unexpected dst len")
88 if Incr(state.txSeq[MACLen:]) {
89 Incr(state.txSeq[:MACLen])
91 copy(dst, state.txSeq[:])
93 MustWrite(state.txEnc, state.txSeq[:])
94 if _, err := io.ReadFull(state.txEnc.XOF(), dst[SeqLen:]); err != nil {
97 subtle.XORBytes(dst[SeqLen:], dst[SeqLen:], src)
99 MustWrite(state.txMAC, dst)
100 state.txMAC.Sum(dst[:0])
101 state.txObfs.Encrypt(dst[:SeqLen], dst[:SeqLen])
102 return dst[:len(src)+SeqLen]
105 func (state *CryptoState) Rx(dst, src []byte) []byte {
106 if len(dst) != len(src) {
107 log.Fatal("unexpected dst len")
109 state.rxObfs.Decrypt(dst[:SeqLen], src[:SeqLen])
110 var theirMAC [MACLen]byte
111 copy(theirMAC[:], dst)
112 copy(dst, state.rxSeq[:MACLen])
114 MustWrite(state.rxMAC, dst[:SeqLen])
115 MustWrite(state.rxMAC, src[SeqLen:])
116 var ourMAC [MACLen]byte
117 state.rxMAC.Sum(ourMAC[:0])
118 if subtle.ConstantTimeCompare(ourMAC[:], theirMAC[:]) != 1 {
121 MustWrite(state.rxMAC, dst[:SeqLen])
122 MustWrite(state.rxMAC, src[SeqLen:])
123 state.rxMAC.Sum(ourMAC[:0])
124 if subtle.ConstantTimeCompare(ourMAC[:], theirMAC[:]) != 1 {
128 copy(state.rxSeq[:], dst)
130 MustWrite(state.rxEnc, dst[:SeqLen])
131 if _, err := io.ReadFull(state.rxEnc.XOF(), dst); err != nil {
134 subtle.XORBytes(dst, dst, src[SeqLen:])
135 return dst[:len(src)-SeqLen]