Skip to content

Commit 4695a77

Browse files
authored
Prefix bdapi fix and Override testing (#188)
completed and tested MVP for prefix transport
1 parent 2607919 commit 4695a77

File tree

15 files changed

+651
-131
lines changed

15 files changed

+651
-131
lines changed

application/lib/registration_ingest.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,9 @@ func (rm *RegistrationManager) NewRegistrationC2SWrapper(c2sw *pb.C2SWrapper, in
413413
dstPort = int(rr.GetDstPort())
414414
}
415415

416-
if rr.TransportParams != nil {
416+
// if Transport param overrides are defined and the client indicated that overrides are
417+
// allowed, apply the overrides while creating the registration
418+
if rr.GetTransportParams() != nil && !c2s.GetDisableRegistrarOverrides() {
417419
c2s.TransportParams = rr.GetTransportParams()
418420
}
419421

application/transports/wrapping/min/client.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/refraction-networking/conjure/pkg/core"
1010
pb "github.com/refraction-networking/gotapdance/protobuf"
1111
"google.golang.org/protobuf/proto"
12+
"google.golang.org/protobuf/types/known/anypb"
1213
)
1314

1415
// ClientTransport implements the client side transport interface for the Min transport. The
@@ -43,9 +44,21 @@ func (t *ClientTransport) GetParams() (proto.Message, error) {
4344
return t.Parameters, nil
4445
}
4546

47+
// ParseParams gives the specific transport an option to parse a generic object into parameters
48+
// provided by the station in the registration response during registration.
49+
func (t ClientTransport) ParseParams(data *anypb.Any) (any, error) {
50+
if data == nil {
51+
return nil, nil
52+
}
53+
54+
var m = &pb.GenericTransportParams{}
55+
err := transports.UnmarshalAnypbTo(data, m)
56+
return m, err
57+
}
58+
4659
// SetParams allows the caller to set parameters associated with the transport, returning an
4760
// error if the provided generic message is not compatible.
48-
func (t *ClientTransport) SetParams(p any) error {
61+
func (t *ClientTransport) SetParams(p any, unchecked ...bool) error {
4962
params, ok := p.(*pb.GenericTransportParams)
5063
if !ok {
5164
return fmt.Errorf("unable to parse params")

application/transports/wrapping/obfs4/client.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
pt "git.torproject.org/pluggable-transports/goptlib.git"
99
"gitlab.com/yawning/obfs4.git/transports/obfs4"
1010
"google.golang.org/protobuf/proto"
11+
"google.golang.org/protobuf/types/known/anypb"
1112

1213
"github.com/refraction-networking/conjure/application/transports"
1314
pb "github.com/refraction-networking/gotapdance/protobuf"
@@ -45,7 +46,7 @@ func (t *ClientTransport) GetParams() (proto.Message, error) {
4546

4647
// SetParams allows the caller to set parameters associated with the transport, returning an
4748
// error if the provided generic message is not compatible.
48-
func (t *ClientTransport) SetParams(p any) error {
49+
func (t *ClientTransport) SetParams(p any, unchecked ...bool) error {
4950
params, ok := p.(*pb.GenericTransportParams)
5051
if !ok {
5152
return fmt.Errorf("unable to parse params")
@@ -55,6 +56,18 @@ func (t *ClientTransport) SetParams(p any) error {
5556
return nil
5657
}
5758

59+
// ParseParams gives the specific transport an option to parse a generic object into parameters
60+
// provided by the station in the registration response during registration.
61+
func (t ClientTransport) ParseParams(data *anypb.Any) (any, error) {
62+
if data == nil {
63+
return nil, nil
64+
}
65+
66+
var m = &pb.GenericTransportParams{}
67+
err := transports.UnmarshalAnypbTo(data, m)
68+
return m, err
69+
}
70+
5871
// GetDstPort returns the destination port that the client should open the phantom connection to
5972
func (t *ClientTransport) GetDstPort(seed []byte) (uint16, error) {
6073
if t.Parameters == nil || !t.Parameters.GetRandomizeDstPort() {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
# Prefix Transport
3+
4+
**TLDR** - This transport allows up to prepend conjure connections with bytes that look like the
5+
initialization of other protocols. This can help to circumvent blocking in some areas and better
6+
understand censorship regimes, but is generally a short term solution.
7+
8+
The `Prefix_Min` transport is a strictly improved version of the existing `Min` transport and we
9+
suggest migration.
10+
11+
## Description
12+
13+
This package implements the prefix transport for the conjure refraction-networking system. The
14+
prefix transport operates in much the same way as the min transport, sending a tag in the fist
15+
packet signalling to the station that the flow has knowledge of a secret shared with the station by
16+
a previous registration.
17+
18+
TODO: Comparison to min transport
19+
20+
### Prefixes Supported by Default
21+
22+
TODO: The prefixes supported by default are as follows.
23+
24+
### Ports
25+
26+
TODO: Prefixes have default ports associated with them, but also allow port randomization.
27+
28+
### :warning: Sharp Edges :warning:
29+
30+
In general this transport will not properly mimic the protocols that are sent as a prefix and should
31+
not be expected to do so.
32+
33+
## Integrating the Prefix Transport
34+
35+
Though the client dialer allows the use of TrasnportType for compatibility reasons, the prefix
36+
transport requires use of the newer Client Transport interface (`TransportConfig` in the dialer)
37+
which is implemented by the `prefix.ClientTransport` object.
38+
39+
TODO: code change example.
40+
41+
## Adding a Prefix / Bidirectional Registration Prefix Overrides
42+
43+
In order to add a prefix ...
44+
45+
## :construction: Road-Map
46+
47+
These features are not necessarily planned or landing imminently, they are simply things that would
48+
be nice to have.
49+
50+
- [ ] **Server Side Prefix Override From File** - file format shared between station and Reg server
51+
describing available prefixes outside of defaults.
52+
53+
- [ ] **TagEncodings** - Allow the tag to (by prefix configuration) be encoded using an encoder
54+
expected by the station, Base64 for example.
55+
56+
- [ ] **StreamEncodings** - Allow the Stream of client bytes to (by configuration) encoded /
57+
encrypted using a scheme expected by the station, AES or Base64 for example.
58+
59+
- [ ] **Randomization** - indicate segments of the prefix to be filled from a random source.
60+
61+
- [ ] **Prefix Revocation** - If there is a prefix that is known to be blocked and we don't want
62+
clients to use it, but we still want them to roll a random prefix, how do we do this?

application/transports/wrapping/prefix/client.go

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package prefix
22

33
import (
4+
"bufio"
45
"crypto/rand"
56
"fmt"
67
"io"
@@ -11,6 +12,7 @@ import (
1112
"github.com/refraction-networking/conjure/pkg/core"
1213
pb "github.com/refraction-networking/gotapdance/protobuf"
1314
"google.golang.org/protobuf/proto"
15+
"google.golang.org/protobuf/types/known/anypb"
1416
)
1517

1618
// ClientTransport implements the client side transport interface for the Min transport. The
@@ -42,6 +44,7 @@ type ClientParams struct {
4244
// behavior like a rand prefix for example.
4345
type Prefix interface {
4446
Bytes() []byte
47+
FlushAfterPrefix() bool
4548
ID() PrefixID
4649
DstPort([]byte) uint16
4750
}
@@ -91,16 +94,42 @@ func (t *ClientTransport) GetParams() (proto.Message, error) {
9194
return t.parameters, nil
9295
}
9396

97+
// ParseParams gives the specific transport an option to parse a generic object into parameters
98+
// provided by the station in the registration response during registration.
99+
func (t ClientTransport) ParseParams(data *anypb.Any) (any, error) {
100+
if data == nil {
101+
return nil, nil
102+
}
103+
104+
var m = &pb.PrefixTransportParams{}
105+
err := transports.UnmarshalAnypbTo(data, m)
106+
return m, err
107+
}
108+
94109
// SetParams allows the caller to set parameters associated with the transport, returning an
95110
// error if the provided generic message is not compatible or the parameters are otherwise invalid
96-
func (t *ClientTransport) SetParams(p any) error {
111+
func (t *ClientTransport) SetParams(p any, unchecked ...bool) error {
97112
prefixParams, ok := p.(*pb.PrefixTransportParams)
98113
if !ok {
99-
return ErrBadParams
114+
return fmt.Errorf("%w, incorrect param type", ErrBadParams)
100115
}
101116

102117
if prefixParams == nil {
103-
return ErrBadParams
118+
return fmt.Errorf("%w, nil params", ErrBadParams)
119+
}
120+
121+
if len(unchecked) != 0 && unchecked[0] {
122+
// Overwrite the prefix bytes and type without checking the default set. This is used for
123+
// RegResponse where the registrar may override the chosen prefix with a prefix outside of
124+
// the prefixes that the client known about.
125+
t.parameters = prefixParams
126+
t.Prefix = &clientPrefix{
127+
bytes: prefixParams.GetPrefix(),
128+
id: PrefixID(prefixParams.GetPrefixId()),
129+
flushAfterPrefix: prefixParams.GetFlushAfterPrefix(),
130+
}
131+
132+
return nil
104133
}
105134

106135
if prefix, ok := DefaultPrefixes[PrefixID(prefixParams.GetPrefixId())]; ok {
@@ -163,26 +192,6 @@ func (t *ClientTransport) GetDstPort(seed []byte) (uint16, error) {
163192
return t.Prefix.DstPort(seed), nil
164193
}
165194

166-
// Build is specific to the Prefix transport, providing a utility function for building the
167-
// prefix that the client should write to the wire before sending any client bytes.
168-
func (t *ClientTransport) Build() ([]byte, error) {
169-
if t.Prefix == nil {
170-
return nil, ErrBadParams
171-
}
172-
173-
// Send hmac(seed, str) bytes to indicate to station (min transport)
174-
prefix := t.Prefix.Bytes()
175-
176-
if t.TagObfuscator == nil {
177-
t.TagObfuscator = transports.CTRObfuscator{}
178-
}
179-
obfuscatedID, err := t.TagObfuscator.Obfuscate(t.connectTag, t.stationPublicKey[:])
180-
if err != nil {
181-
return nil, err
182-
}
183-
return append(prefix, obfuscatedID...), nil
184-
}
185-
186195
// PrepareKeys provides an opportunity for the transport to integrate the station public key
187196
// as well as bytes from the deterministic random generator associated with the registration
188197
// that this ClientTransport is attached to.
@@ -195,29 +204,52 @@ func (t *ClientTransport) PrepareKeys(pubkey [32]byte, sharedSecret []byte, hkdf
195204
// WrapConn gives the transport the opportunity to perform a handshake and wrap / transform the
196205
// incoming and outgoing bytes send by the implementing client.
197206
func (t *ClientTransport) WrapConn(conn net.Conn) (net.Conn, error) {
198-
// Send hmac(seed, str) bytes to indicate to station (min transport) generated during Prepare(...)
199-
200-
// // Send hmac(seed, str) bytes to indicate to station (min transport)
201-
// connectTag := core.ConjureHMAC(reg.keys.SharedSecret, "PrefixTransportHMACString")
207+
if t.Prefix == nil {
208+
return nil, ErrBadParams
209+
}
202210

203-
prefix, err := t.Build()
204-
if err != nil {
205-
return nil, fmt.Errorf("failed to build prefix: %w", err)
211+
if t.TagObfuscator == nil {
212+
t.TagObfuscator = transports.CTRObfuscator{}
206213
}
207214

208-
_, err = conn.Write(prefix)
215+
obfuscatedID, err := t.TagObfuscator.Obfuscate(t.connectTag, t.stationPublicKey[:])
209216
if err != nil {
210217
return nil, err
211218
}
219+
220+
w := bufio.NewWriter(conn)
221+
222+
var msg []byte = t.Prefix.Bytes()
223+
if t.Prefix.FlushAfterPrefix() {
224+
if _, err := w.Write(msg); err != nil {
225+
return nil, err
226+
}
227+
228+
w.Flush()
229+
if _, err := w.Write(obfuscatedID); err != nil {
230+
return nil, err
231+
}
232+
233+
w.Flush()
234+
} else {
235+
msg = append(msg, obfuscatedID...)
236+
if _, err := w.Write(msg); err != nil {
237+
return nil, err
238+
}
239+
240+
w.Flush()
241+
}
242+
212243
return conn, nil
213244
}
214245

215246
// ---
216247

217248
type clientPrefix struct {
218-
bytes []byte
219-
id PrefixID
220-
port uint16
249+
bytes []byte
250+
id PrefixID
251+
port uint16
252+
flushAfterPrefix bool
221253

222254
// // Function allowing encoding / transformation of obfuscated ID bytes after they have been
223255
// // obfuscated. Examples - base64 encode, padding
@@ -242,6 +274,10 @@ func (c *clientPrefix) DstPort([]byte) uint16 {
242274
return c.port
243275
}
244276

277+
func (c *clientPrefix) FlushAfterPrefix() bool {
278+
return c.flushAfterPrefix
279+
}
280+
245281
// ---
246282

247283
// TryFromID returns a Prefix based on the Prefix ID. This is useful for non-static prefixes like the
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package prefix
2+
3+
import "encoding/hex"
4+
5+
var httpGetComplete []byte = d("474554202f20485454502f312e310d0a4163636570743a202a2f2a0d0a436f6e6e656374696f6e3a20636c6f73650d0a0d0a")
6+
7+
var tlsCompleteCHSNI []byte = d("1603010200010001fc0303a08b89dd2ce2e5bc764a91bbd5cae46fdc7062e2dd6e7eb891fcec639e228c0e2062bcb02f5174081bbcd30f87015ebaca6d74a11b23de273ff5b85a3ef89f704600208a8a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035010001932a2a000000230000002b000706eaea030403030010000e000c02683208687474702f312e31446900050003026832001b0003020002000000160014000011746c7366696e6765727072696e742e696f000b00020100000a000a00088a8a001d001700180033002b00298a8a000100001d0020a02f03fcf4a86e3df6c3f79aa659be0a5209946f9fd0e8fe2b3cc1b664f0985f00120000000d0012001004030804040105030805050108060601002d00020101000500050100000000ff01000100001700004a4a000100001500c6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
8+
9+
var tlsCompleteCHNoSNI []byte = d("1603010200010001fc0303a937d48cce916eb20df06d75f71a5954ea7b6876217ce17956c140ebd891716e20a079e04d2379c8dcd78d3f1f8075f7deb4dab5f06c014c73bf45478a486154090020eaea130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035010001935a5a0000ff01000100001b00030200020012000000170000000d0012001004030804040105030805050108060601000500050100000000446900050003026832002d00020101000b000201000010000e000c02683208687474702f312e3100230000002b000706eaea03040303000a000a00085a5a001d001700180033002b00295a5a000100001d00207df3a2aa3c8403fc92d8307833f51eb2576c43057afffecab53ea0bad45840642a2a000100001500e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
10+
11+
func d(in string) []byte {
12+
out, err := hex.DecodeString(in)
13+
if err != nil {
14+
panic(err)
15+
}
16+
return out
17+
}

0 commit comments

Comments
 (0)