Skip to content

Commit 415523f

Browse files
committed
Refactor obfuscate and deobfuscate functions to reduce a layer of indirection
1 parent 2f17841 commit 415523f

File tree

6 files changed

+157
-148
lines changed

6 files changed

+157
-148
lines changed

internal/multiplex/obfs.go

Lines changed: 78 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ import (
1111
"golang.org/x/crypto/salsa20"
1212
)
1313

14-
type Obfser func(*Frame, []byte, int) (int, error)
15-
type Deobfser func(*Frame, []byte) error
16-
1714
var u32 = binary.BigEndian.Uint32
1815
var u64 = binary.BigEndian.Uint64
1916
var putU32 = binary.BigEndian.PutUint32
@@ -30,21 +27,15 @@ const (
3027

3128
// Obfuscator is responsible for serialisation, obfuscation, and optional encryption of data frames.
3229
type Obfuscator struct {
33-
// Used in Stream.Write. Add multiplexing headers, encrypt and add TLS header
34-
Obfs Obfser
35-
// Remove TLS header, decrypt and unmarshall frames
36-
Deobfs Deobfser
30+
payloadCipher cipher.AEAD
31+
3732
SessionKey [32]byte
3833

3934
maxOverhead int
4035
}
4136

42-
// MakeObfs returns a function of type Obfser. An Obfser takes three arguments:
43-
// a *Frame with all the field set correctly, a []byte as buffer to put encrypted
44-
// message in, and an int called payloadOffsetInBuf to be used when *Frame.payload
45-
// is in the byte slice used as buffer (2nd argument). payloadOffsetInBuf specifies
46-
// the index at which data belonging to *Frame.Payload starts in the buffer.
47-
func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
37+
// obfuscate adds multiplexing headers, encrypt and add TLS header
38+
func (o *Obfuscator) obfuscate(f *Frame, buf []byte, payloadOffsetInBuf int) (int, error) {
4839
// The method here is to use the first payloadCipher.NonceSize() bytes of the serialised frame header
4940
// as iv/nonce for the AEAD cipher to encrypt the frame payload. Then we use
5041
// the authentication tag produced appended to the end of the ciphertext (of size payloadCipher.Overhead())
@@ -76,109 +67,99 @@ func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
7667
// We can't ensure its uniqueness ourselves, which is why plaintext mode must only be used when the user input
7768
// is already random-like. For Cloak it would normally mean that the user is using a proxy protocol that sends
7869
// encrypted data.
79-
obfs := func(f *Frame, buf []byte, payloadOffsetInBuf int) (int, error) {
80-
payloadLen := len(f.Payload)
81-
if payloadLen == 0 {
82-
return 0, errors.New("payload cannot be empty")
70+
payloadLen := len(f.Payload)
71+
if payloadLen == 0 {
72+
return 0, errors.New("payload cannot be empty")
73+
}
74+
var extraLen int
75+
if o.payloadCipher == nil {
76+
extraLen = salsa20NonceSize - payloadLen
77+
if extraLen < 0 {
78+
// if our payload is already greater than 8 bytes
79+
extraLen = 0
8380
}
84-
var extraLen int
85-
if payloadCipher == nil {
86-
extraLen = salsa20NonceSize - payloadLen
87-
if extraLen < 0 {
88-
// if our payload is already greater than 8 bytes
89-
extraLen = 0
90-
}
91-
} else {
92-
extraLen = payloadCipher.Overhead()
93-
if extraLen < salsa20NonceSize {
94-
return 0, errors.New("AEAD's Overhead cannot be fewer than 8 bytes")
95-
}
81+
} else {
82+
extraLen = o.payloadCipher.Overhead()
83+
if extraLen < salsa20NonceSize {
84+
return 0, errors.New("AEAD's Overhead cannot be fewer than 8 bytes")
9685
}
86+
}
9787

98-
usefulLen := frameHeaderLength + payloadLen + extraLen
99-
if len(buf) < usefulLen {
100-
return 0, errors.New("obfs buffer too small")
101-
}
102-
// we do as much in-place as possible to save allocation
103-
payload := buf[frameHeaderLength : frameHeaderLength+payloadLen]
104-
if payloadOffsetInBuf != frameHeaderLength {
105-
// if payload is not at the correct location in buffer
106-
copy(payload, f.Payload)
107-
}
88+
usefulLen := frameHeaderLength + payloadLen + extraLen
89+
if len(buf) < usefulLen {
90+
return 0, errors.New("obfs buffer too small")
91+
}
92+
// we do as much in-place as possible to save allocation
93+
payload := buf[frameHeaderLength : frameHeaderLength+payloadLen]
94+
if payloadOffsetInBuf != frameHeaderLength {
95+
// if payload is not at the correct location in buffer
96+
copy(payload, f.Payload)
97+
}
10898

109-
header := buf[:frameHeaderLength]
110-
putU32(header[0:4], f.StreamID)
111-
putU64(header[4:12], f.Seq)
112-
header[12] = f.Closing
113-
header[13] = byte(extraLen)
114-
115-
if payloadCipher == nil {
116-
if extraLen != 0 { // read nonce
117-
extra := buf[usefulLen-extraLen : usefulLen]
118-
common.CryptoRandRead(extra)
119-
}
120-
} else {
121-
payloadCipher.Seal(payload[:0], header[:payloadCipher.NonceSize()], payload, nil)
99+
header := buf[:frameHeaderLength]
100+
putU32(header[0:4], f.StreamID)
101+
putU64(header[4:12], f.Seq)
102+
header[12] = f.Closing
103+
header[13] = byte(extraLen)
104+
105+
if o.payloadCipher == nil {
106+
if extraLen != 0 { // read nonce
107+
extra := buf[usefulLen-extraLen : usefulLen]
108+
common.CryptoRandRead(extra)
122109
}
110+
} else {
111+
o.payloadCipher.Seal(payload[:0], header[:o.payloadCipher.NonceSize()], payload, nil)
112+
}
123113

124-
nonce := buf[usefulLen-salsa20NonceSize : usefulLen]
125-
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
114+
nonce := buf[usefulLen-salsa20NonceSize : usefulLen]
115+
salsa20.XORKeyStream(header, header, nonce, &o.SessionKey)
126116

127-
return usefulLen, nil
128-
}
129-
return obfs
117+
return usefulLen, nil
130118
}
131119

132-
// MakeDeobfs returns a function Deobfser. A Deobfser takes in a single byte slice,
133-
// containing the message to be decrypted, and returns a *Frame containing the frame
134-
// information and plaintext
135-
func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Deobfser {
136-
// frame header length + minimum data size (i.e. nonce size of salsa20)
137-
const minInputLen = frameHeaderLength + salsa20NonceSize
138-
deobfs := func(f *Frame, in []byte) error {
139-
if len(in) < minInputLen {
140-
return fmt.Errorf("input size %v, but it cannot be shorter than %v bytes", len(in), minInputLen)
141-
}
120+
// deobfuscate removes TLS header, decrypt and unmarshall frames
121+
func (o *Obfuscator) deobfuscate(f *Frame, in []byte) error {
122+
if len(in) < frameHeaderLength+salsa20NonceSize {
123+
return fmt.Errorf("input size %v, but it cannot be shorter than %v bytes", len(in), frameHeaderLength+salsa20NonceSize)
124+
}
142125

143-
header := in[:frameHeaderLength]
144-
pldWithOverHead := in[frameHeaderLength:] // payload + potential overhead
126+
header := in[:frameHeaderLength]
127+
pldWithOverHead := in[frameHeaderLength:] // payload + potential overhead
145128

146-
nonce := in[len(in)-salsa20NonceSize:]
147-
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
129+
nonce := in[len(in)-salsa20NonceSize:]
130+
salsa20.XORKeyStream(header, header, nonce, &o.SessionKey)
148131

149-
streamID := u32(header[0:4])
150-
seq := u64(header[4:12])
151-
closing := header[12]
152-
extraLen := header[13]
132+
streamID := u32(header[0:4])
133+
seq := u64(header[4:12])
134+
closing := header[12]
135+
extraLen := header[13]
153136

154-
usefulPayloadLen := len(pldWithOverHead) - int(extraLen)
155-
if usefulPayloadLen < 0 || usefulPayloadLen > len(pldWithOverHead) {
156-
return errors.New("extra length is negative or extra length is greater than total pldWithOverHead length")
157-
}
137+
usefulPayloadLen := len(pldWithOverHead) - int(extraLen)
138+
if usefulPayloadLen < 0 || usefulPayloadLen > len(pldWithOverHead) {
139+
return errors.New("extra length is negative or extra length is greater than total pldWithOverHead length")
140+
}
158141

159-
var outputPayload []byte
142+
var outputPayload []byte
160143

161-
if payloadCipher == nil {
162-
if extraLen == 0 {
163-
outputPayload = pldWithOverHead
164-
} else {
165-
outputPayload = pldWithOverHead[:usefulPayloadLen]
166-
}
144+
if o.payloadCipher == nil {
145+
if extraLen == 0 {
146+
outputPayload = pldWithOverHead
167147
} else {
168-
_, err := payloadCipher.Open(pldWithOverHead[:0], header[:payloadCipher.NonceSize()], pldWithOverHead, nil)
169-
if err != nil {
170-
return err
171-
}
172148
outputPayload = pldWithOverHead[:usefulPayloadLen]
173149
}
174-
175-
f.StreamID = streamID
176-
f.Seq = seq
177-
f.Closing = closing
178-
f.Payload = outputPayload
179-
return nil
150+
} else {
151+
_, err := o.payloadCipher.Open(pldWithOverHead[:0], header[:o.payloadCipher.NonceSize()], pldWithOverHead, nil)
152+
if err != nil {
153+
return err
154+
}
155+
outputPayload = pldWithOverHead[:usefulPayloadLen]
180156
}
181-
return deobfs
157+
158+
f.StreamID = streamID
159+
f.Seq = seq
160+
f.Closing = closing
161+
f.Payload = outputPayload
162+
return nil
182163
}
183164

184165
func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (obfuscator Obfuscator, err error) {
@@ -217,7 +198,5 @@ func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (obfuscator Obfu
217198
}
218199
}
219200

220-
obfuscator.Obfs = MakeObfs(sessionKey, payloadCipher)
221-
obfuscator.Deobfs = MakeDeobfs(sessionKey, payloadCipher)
222201
return
223202
}

internal/multiplex/obfs_test.go

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ func TestGenerateObfs(t *testing.T) {
1919
obfsBuf := make([]byte, 512)
2020
_testFrame, _ := quick.Value(reflect.TypeOf(&Frame{}), rand.New(rand.NewSource(42)))
2121
testFrame := _testFrame.Interface().(*Frame)
22-
i, err := obfuscator.Obfs(testFrame, obfsBuf, 0)
22+
i, err := obfuscator.obfuscate(testFrame, obfsBuf, 0)
2323
if err != nil {
2424
ct.Error("failed to obfs ", err)
2525
return
2626
}
2727

2828
var resultFrame Frame
29-
err = obfuscator.Deobfs(&resultFrame, obfsBuf[:i])
29+
err = obfuscator.deobfuscate(&resultFrame, obfsBuf[:i])
3030
if err != nil {
3131
ct.Error("failed to deobfs ", err)
3232
return
@@ -88,40 +88,57 @@ func BenchmarkObfs(b *testing.B) {
8888
c, _ := aes.NewCipher(key[:])
8989
payloadCipher, _ := cipher.NewGCM(c)
9090

91-
obfs := MakeObfs(key, payloadCipher)
91+
obfuscator := Obfuscator{
92+
payloadCipher: payloadCipher,
93+
SessionKey: key,
94+
maxOverhead: payloadCipher.Overhead(),
95+
}
96+
9297
b.SetBytes(int64(len(testFrame.Payload)))
9398
b.ResetTimer()
9499
for i := 0; i < b.N; i++ {
95-
obfs(testFrame, obfsBuf, 0)
100+
obfuscator.obfuscate(testFrame, obfsBuf, 0)
96101
}
97102
})
98103
b.Run("AES128GCM", func(b *testing.B) {
99104
c, _ := aes.NewCipher(key[:16])
100105
payloadCipher, _ := cipher.NewGCM(c)
101106

102-
obfs := MakeObfs(key, payloadCipher)
107+
obfuscator := Obfuscator{
108+
payloadCipher: payloadCipher,
109+
SessionKey: key,
110+
maxOverhead: payloadCipher.Overhead(),
111+
}
103112
b.SetBytes(int64(len(testFrame.Payload)))
104113
b.ResetTimer()
105114
for i := 0; i < b.N; i++ {
106-
obfs(testFrame, obfsBuf, 0)
115+
obfuscator.obfuscate(testFrame, obfsBuf, 0)
107116
}
108117
})
109118
b.Run("plain", func(b *testing.B) {
110-
obfs := MakeObfs(key, nil)
119+
obfuscator := Obfuscator{
120+
payloadCipher: nil,
121+
SessionKey: key,
122+
maxOverhead: salsa20NonceSize,
123+
}
111124
b.SetBytes(int64(len(testFrame.Payload)))
112125
b.ResetTimer()
113126
for i := 0; i < b.N; i++ {
114-
obfs(testFrame, obfsBuf, 0)
127+
obfuscator.obfuscate(testFrame, obfsBuf, 0)
115128
}
116129
})
117130
b.Run("chacha20Poly1305", func(b *testing.B) {
118-
payloadCipher, _ := chacha20poly1305.New(key[:16])
131+
payloadCipher, _ := chacha20poly1305.New(key[:])
119132

120-
obfs := MakeObfs(key, payloadCipher)
133+
obfuscator := Obfuscator{
134+
payloadCipher: payloadCipher,
135+
SessionKey: key,
136+
maxOverhead: payloadCipher.Overhead(),
137+
}
121138
b.SetBytes(int64(len(testFrame.Payload)))
122139
b.ResetTimer()
123140
for i := 0; i < b.N; i++ {
124-
obfs(testFrame, obfsBuf, 0)
141+
obfuscator.obfuscate(testFrame, obfsBuf, 0)
125142
}
126143
})
127144
}
@@ -143,57 +160,70 @@ func BenchmarkDeobfs(b *testing.B) {
143160
b.Run("AES256GCM", func(b *testing.B) {
144161
c, _ := aes.NewCipher(key[:])
145162
payloadCipher, _ := cipher.NewGCM(c)
163+
obfuscator := Obfuscator{
164+
payloadCipher: payloadCipher,
165+
SessionKey: key,
166+
maxOverhead: payloadCipher.Overhead(),
167+
}
146168

147-
obfs := MakeObfs(key, payloadCipher)
148-
n, _ := obfs(testFrame, obfsBuf, 0)
149-
deobfs := MakeDeobfs(key, payloadCipher)
169+
n, _ := obfuscator.obfuscate(testFrame, obfsBuf, 0)
150170

151171
frame := new(Frame)
152172
b.SetBytes(int64(n))
153173
b.ResetTimer()
154174
for i := 0; i < b.N; i++ {
155-
deobfs(frame, obfsBuf[:n])
175+
obfuscator.deobfuscate(frame, obfsBuf[:n])
156176
}
157177
})
158178
b.Run("AES128GCM", func(b *testing.B) {
159179
c, _ := aes.NewCipher(key[:16])
160180
payloadCipher, _ := cipher.NewGCM(c)
161181

162-
obfs := MakeObfs(key, payloadCipher)
163-
n, _ := obfs(testFrame, obfsBuf, 0)
164-
deobfs := MakeDeobfs(key, payloadCipher)
182+
obfuscator := Obfuscator{
183+
payloadCipher: payloadCipher,
184+
SessionKey: key,
185+
maxOverhead: payloadCipher.Overhead(),
186+
}
187+
n, _ := obfuscator.obfuscate(testFrame, obfsBuf, 0)
165188

166189
frame := new(Frame)
167190
b.ResetTimer()
168191
b.SetBytes(int64(n))
169192
for i := 0; i < b.N; i++ {
170-
deobfs(frame, obfsBuf[:n])
193+
obfuscator.deobfuscate(frame, obfsBuf[:n])
171194
}
172195
})
173196
b.Run("plain", func(b *testing.B) {
174-
obfs := MakeObfs(key, nil)
175-
n, _ := obfs(testFrame, obfsBuf, 0)
176-
deobfs := MakeDeobfs(key, nil)
197+
obfuscator := Obfuscator{
198+
payloadCipher: nil,
199+
SessionKey: key,
200+
maxOverhead: salsa20NonceSize,
201+
}
202+
n, _ := obfuscator.obfuscate(testFrame, obfsBuf, 0)
177203

178204
frame := new(Frame)
179205
b.ResetTimer()
180206
b.SetBytes(int64(n))
181207
for i := 0; i < b.N; i++ {
182-
deobfs(frame, obfsBuf[:n])
208+
obfuscator.deobfuscate(frame, obfsBuf[:n])
183209
}
184210
})
185211
b.Run("chacha20Poly1305", func(b *testing.B) {
186-
payloadCipher, _ := chacha20poly1305.New(key[:16])
212+
payloadCipher, _ := chacha20poly1305.New(key[:])
213+
214+
obfuscator := Obfuscator{
215+
payloadCipher: nil,
216+
SessionKey: key,
217+
maxOverhead: payloadCipher.Overhead(),
218+
}
187219

188-
obfs := MakeObfs(key, payloadCipher)
189-
n, _ := obfs(testFrame, obfsBuf, 0)
190-
deobfs := MakeDeobfs(key, payloadCipher)
220+
n, _ := obfuscator.obfuscate(testFrame, obfsBuf, 0)
191221

192222
frame := new(Frame)
193223
b.ResetTimer()
194224
b.SetBytes(int64(n))
195225
for i := 0; i < b.N; i++ {
196-
deobfs(frame, obfsBuf[:n])
226+
obfuscator.deobfuscate(frame, obfsBuf[:n])
197227
}
198228
})
199229
}

0 commit comments

Comments
 (0)