Skip to content

Commit 76008cb

Browse files
authored
Prefix transport (#165)
* skeleton for prefix transport * outline implementatiions for prefix / format transports * add initial default supported prefixes * proper labels for default prefixes * add optional methods to obfuscate session hmacID using ECDHE+AES * passing for min successful test. added XORObfs because collisions would cause N pubkey ops * passing tests for all current prefixes * failing base64 test * prefixes with custom decode supported w/ GET base64 example * apply updated version of client lib * moved things around to accomodate privkey arg to transport construction * prefix transport config and corner case handling * move things around to accommodate pubkeys and client lib versions * missed one update * go mod updates for gotapdance/tapdance * adding registrar overrides for prefix transport * parsing and choosing from prefix list * WIP regserver prefix_transport overrides * passing tests for expected parsing behavior * add lint ignore for legacy code using math/rand * maybe complete prefix override * partial integration into registration server pipeline * prefix transport overrides complete, in theory, for now * update gotapdance version * crypto/ed25519 requires 64 byte [priv][pub] format or it panics * more keysize issues 😑 * conform to new Client Transport interface
1 parent 1441047 commit 76008cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1643
-226
lines changed

application/app_config.toml

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11

2-
# Bool to enable or disable sharing of registrations over API when received over decoy registrar
3-
enable_share_over_api = false
4-
5-
# REST endpoint to share decoy registrations.
6-
preshare_endpoint = ""
7-
8-
# Name of abstract socket to bind proxy to
9-
socket_name = "zmq-proxy"
2+
## ------ Application General ------
103

114
# Absolute path to private key to use when authenticating with servers.
125
# Can be either privkey or privkey || pubkey; only first 32 bytes will
@@ -15,16 +8,16 @@ socket_name = "zmq-proxy"
158
# the station will shutdown).
169
privkey_path = ""
1710

18-
# Time in milliseconds to wait between sending heartbeats.
19-
# Heartbeats are only sent when other traffic doesn't come through;
20-
# i.e. normal messages can "act" as a heartbeat by confirming
21-
# that the connection is alive.
22-
heartbeat_interval = 30000
11+
# Log level, one of the following: info, error, warn, debug, trace
12+
log_level = "error"
2313

24-
# Time in milliseconds after sending a heartbeat to wait for
25-
# a response before the connection is assumed to be dead.
26-
heartbeat_timeout = 1000
14+
# Path to a file containing supplemental prefix specifications for the prefix transport.
15+
supplemental_prefix_path = ""
16+
17+
# Do not include Default prefixes and rely entirely on the prefixes in supplemental_prefix_path
18+
disable_default_prefixes = false
2719

20+
## ------ Liveness Probing ------
2821

2922
# Duration that a phantom IP identified as "LIVE" using a liveness test is
3023
# cached, preventing further lookups to the address. Empty string disables
@@ -50,6 +43,7 @@ cache_expiration_nonlive = "5m"
5043
# cache will have finite capacity and implement LRU eviction.
5144
cache_capacity_nonlive = 0
5245

46+
## ------ Registration / Connection Filters ------
5347

5448
# Allow the station to opt out of either version of internet protocol to limit a
5549
# statio to handling one or the other. For example, v6 on small station deployment
@@ -98,15 +92,36 @@ detector_filter_list = [
9892
"::1",
9993
]
10094

101-
# Log level, one of the following: info, error, warn, debug, trace
102-
log_level = "error"
95+
## ------ GeoIP Info ------
10396

10497
# MaxMind Database files - if empty then the Empty Geoip lookup will be used (effictvely disaling
10598
# lookup). The default path used by the maxmind geoiupdate tool is `/usr/local/share/GeoIP`.
10699
# repo - https://github.com/maxmind/geoipupdate/blob/main/pkg/geoipupdate/defaults_notwin.go#L15
107100
geoip_cc_db_path = ""
108101
geoip_asn_db_path = ""
109102

103+
104+
## ------ ZMQ ------
105+
106+
# Bool to enable or disable sharing of registrations over API when received over decoy registrar
107+
enable_share_over_api = false
108+
109+
# REST endpoint to share decoy registrations.
110+
preshare_endpoint = ""
111+
112+
# Name of abstract socket to bind proxy to
113+
socket_name = "zmq-proxy"
114+
115+
# Time in milliseconds to wait between sending heartbeats.
116+
# Heartbeats are only sent when other traffic doesn't come through;
117+
# i.e. normal messages can "act" as a heartbeat by confirming
118+
# that the connection is alive.
119+
heartbeat_interval = 30000
120+
121+
# Time in milliseconds after sending a heartbeat to wait for
122+
# a response before the connection is assumed to be dead.
123+
heartbeat_timeout = 1000
124+
110125
### ZMQ sockets to connect to and subscribe
111126

112127
## Registration API

application/conns.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ func (cm *connManager) handleNewConn(regManager *cj.RegistrationManager, clientC
9393
return
9494
}
9595

96-
// TODO: if NOT mPort 443: just forward things and return
9796
fdPtr := fd.Fd()
9897
originalDstIP, err := getOriginalDst(fdPtr)
9998
if err != nil {

application/lib/config.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ type Config struct {
1414

1515
// Log verbosity level
1616
LogLevel string `toml:"log_level"`
17+
18+
// Path to private key file
19+
PrivateKeyPath string `toml:"privkey_path"`
20+
21+
// PrefixFilePath provides a path to a file containing supported prefix specifications for the
22+
// prefix transport.
23+
// [TODO] refactor into a more general transport config object
24+
PrefixFilePath string `toml:"supplemental_prefix_path"`
25+
DisableDefaultPrefixes bool `toml:"disable_default_prefixes"`
1726
}
1827

1928
// ParseConfig parses the config from the CJ_STATION_CONFIG environment
@@ -30,3 +39,31 @@ func ParseConfig() (*Config, error) {
3039

3140
return &c, nil
3241
}
42+
43+
// PrivateKeyLength is the expected length of the station (ed25519) private key in bytes.
44+
const PrivateKeyLength = 32
45+
46+
// ParsePrivateKey tries to use either the PrivateKeyPath (`privkey_path`) config variable or the
47+
// CJ_PRIVKEY environment variable to locate the file from which it can parse the station private key
48+
func (c *Config) ParsePrivateKey() ([32]byte, error) {
49+
privkeyPath := c.PrivateKeyPath
50+
if privkeyPath == "" {
51+
privkeyPath = os.Getenv("CJ_PRIVKEY")
52+
}
53+
if privkeyPath == "" {
54+
return [32]byte{}, fmt.Errorf("no path to private key")
55+
}
56+
57+
privkey, err := os.ReadFile(privkeyPath)
58+
if err != nil {
59+
return [32]byte{}, fmt.Errorf("failed to load private key: %w", err)
60+
}
61+
62+
if len(privkey) < PrivateKeyLength {
63+
return [32]byte{}, fmt.Errorf("privkey error - not enough bytes")
64+
}
65+
66+
var out [32]byte
67+
copy(out[:], privkey[:])
68+
return out, nil
69+
}

application/lib/conjure.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package lib
22

33
import (
4-
"crypto/hmac"
54
"crypto/sha256"
65
"io"
76

@@ -44,13 +43,15 @@ func generateObfs4Keys(rand io.Reader) (Obfs4Keys, error) {
4443
return keys, err
4544
}
4645

46+
// ConjureSharedKeys contains keys that the station is required to keep.
4747
type ConjureSharedKeys struct {
4848
SharedSecret []byte
4949
FspKey, FspIv, VspKey, VspIv, MasterSecret, ConjureSeed []byte
5050
Obfs4Keys Obfs4Keys
5151
}
5252

53-
func GenSharedKeys(sharedSecret []byte, tt pb.TransportType) (ConjureSharedKeys, error) {
53+
// GenSharedKeys generates the keys requires to form a Conjure connection based on the SharedSecret
54+
func GenSharedKeys(clientLibVer uint, sharedSecret []byte, tt pb.TransportType) (ConjureSharedKeys, error) {
5455
tdHkdf := hkdf.New(sha256.New, sharedSecret, []byte("conjureconjureconjureconjure"), nil)
5556
keys := ConjureSharedKeys{
5657
SharedSecret: sharedSecret,
@@ -85,16 +86,6 @@ func GenSharedKeys(sharedSecret []byte, tt pb.TransportType) (ConjureSharedKeys,
8586
if tt == pb.TransportType_Obfs4 {
8687
keys.Obfs4Keys, err = generateObfs4Keys(tdHkdf)
8788
}
88-
return keys, err
89-
}
9089

91-
// from client tapdance/conjure.go
92-
func conjureHMAC(key []byte, str string) []byte {
93-
hash := hmac.New(sha256.New, key)
94-
hash.Write([]byte(str))
95-
return hash.Sum(nil)
96-
}
97-
98-
func (k *ConjureSharedKeys) ConjureHMAC(str string) []byte {
99-
return conjureHMAC(k.SharedSecret, str)
90+
return keys, err
10091
}

application/lib/phantom_selector.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
mrand "math/rand"
1111
"net"
1212
"sort"
13+
"time"
1314

1415
wr "github.com/mroth/weightedrand"
1516
"golang.org/x/crypto/hkdf"
@@ -45,6 +46,8 @@ func (sc *SubnetConfig) getSubnetsVarint(seed []byte, weighted bool) []string {
4546
fmt.Println("failed to seed random for weighted rand")
4647
return nil
4748
}
49+
50+
// nolint:staticcheck // here for backwards compatibility with clients
4851
mrand.Seed(seedInt)
4952

5053
choices := make([]wr.Choice, 0, len(sc.WeightedSubnets))
@@ -400,8 +403,11 @@ func SelectAddrFromSubnet(seed []byte, net1 *net.IPNet) (net.IP, error) {
400403
return nil, fmt.Errorf("failed to create seed ")
401404
}
402405

406+
// nolint:staticcheck // here for backwards compatibility with clients
403407
mrand.Seed(seedInt)
404408
randBytes := make([]byte, addrLen/8)
409+
410+
// nolint:staticcheck // here for backwards compatibility with clients
405411
_, err := mrand.Read(randBytes)
406412
if err != nil {
407413
return nil, err
@@ -579,3 +585,9 @@ func (p *PhantomIPSelector) UpdateGeneration(generation uint, subnets *SubnetCon
579585
p.Networks[generation] = subnets
580586
return true
581587
}
588+
589+
func init() {
590+
// NOTE: math/rand is only used for backwards compatibility.
591+
// nolint:staticcheck
592+
mrand.Seed(time.Now().UnixNano())
593+
}

application/lib/proxies_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
var errNotExist = errors.New("not implemented")
2525

2626
// under construction - not finalized or definitive
27+
// TODO: flesh out this test, or disable it. The go routines have a race condition that can result
28+
// in the test being useless.
2729
func TestProxyMockCovertReset(t *testing.T) {
2830

2931
wg := new(sync.WaitGroup)
@@ -108,15 +110,15 @@ func (m *mockConn) RemoteAddr() net.Addr {
108110
}
109111

110112
func (m *mockConn) SetDeadline(t time.Time) error {
111-
return errNotExist
113+
return nil
112114
}
113115

114116
func (m *mockConn) SetReadDeadline(t time.Time) error {
115-
return errNotExist
117+
return nil
116118
}
117119

118120
func (m *mockConn) SetWriteDeadline(t time.Time) error {
119-
return errNotExist
121+
return nil
120122
}
121123

122124
func TestHalfpipeDeadlineEcho(t *testing.T) {

application/lib/registration.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,6 @@ func (r *RegisteredDecoys) RegistrationExists(d *DecoyRegistration) *DecoyRegist
635635
defer r.m.RUnlock()
636636

637637
return r.registrationExists(d)
638-
639638
}
640639

641640
// For use inside of this struct (so no deadlocks on struct mutex)
@@ -647,7 +646,6 @@ func (r *RegisteredDecoys) registrationExists(d *DecoyRegistration) *DecoyRegist
647646
}
648647

649648
identifier := t.GetIdentifier(d)
650-
651649
phantomAddr := d.PhantomIp.String()
652650

653651
_, exists := r.decoys[phantomAddr]

application/lib/registration_ingest.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ func (rm *RegistrationManager) startIngestThread(ctx context.Context, regChan <-
9898
case msg := <-regChan:
9999
newRegs, err := rm.parseRegMessage(msg.([]byte))
100100
if err != nil {
101-
logger.Errorf("Encountered err when creating Reg: %v\n", err)
101+
102+
if !errors.Is(err, ErrLegacyAddrSelectBug) {
103+
logger.Errorf("Encountered err when creating Reg: %v\n", err)
104+
}
102105
continue
103106
}
104107
if len(newRegs) == 0 {
@@ -300,7 +303,10 @@ func (rm *RegistrationManager) parseRegMessage(msg []byte) ([]*DecoyRegistration
300303
if parsed.GetRegistrationPayload().GetV4Support() && rm.EnableIPv4 && sourceAddr.To4() != nil {
301304
reg, err := rm.NewRegistrationC2SWrapper(parsed, false)
302305
if err != nil {
303-
logger.Errorf("Failed to create registration from v4 C2S: %v", err)
306+
307+
if !errors.Is(err, ErrLegacyAddrSelectBug) {
308+
logger.Errorf("Failed to create registration from v4 C2S: %v", err)
309+
}
304310
return nil, err
305311
}
306312

@@ -386,13 +392,28 @@ func (rm *RegistrationManager) NewRegistrationC2SWrapper(c2sw *pb.C2SWrapper, in
386392
c2s := c2sw.GetRegistrationPayload()
387393

388394
// Generate keys from shared secret using HKDF
389-
conjureKeys, err := GenSharedKeys(c2sw.GetSharedSecret(), c2s.GetTransport())
395+
conjureKeys, err := GenSharedKeys(uint(c2s.GetClientLibVersion()), c2sw.GetSharedSecret(), c2s.GetTransport())
390396
if err != nil {
391397
return nil, fmt.Errorf("failed to generate keys: %v", err)
392398
}
393399

394400
regSrc := c2sw.GetRegistrationSource()
395401

402+
// If a C2SWrapper has a registration response at this stage EITHER auth was disabled OR it was
403+
// signed by a registration server and has overrides that should be applied
404+
var dstPort = -1
405+
if rr := c2sw.GetRegistrationResponse(); rr != nil {
406+
if rr.DstPort != nil {
407+
dstPort = int(rr.GetDstPort())
408+
}
409+
410+
if rr.TransportParams != nil {
411+
c2s.TransportParams = rr.GetTransportParams()
412+
}
413+
414+
// TODO: future, apply the ip addresses from the Registration response (rr.IPv4Addr, rr.IPv6Addr)
415+
}
416+
396417
reg, err := rm.NewRegistration(c2s, &conjureKeys, includeV6, &regSrc)
397418
if err != nil || reg == nil {
398419
return nil, fmt.Errorf("failed to build registration: %s", err)
@@ -416,6 +437,10 @@ func (rm *RegistrationManager) NewRegistrationC2SWrapper(c2sw *pb.C2SWrapper, in
416437
return nil, fmt.Errorf("failed geoip asn lookup: %w", err)
417438
}
418439

440+
if dstPort != -1 {
441+
reg.PhantomPort = uint16(dstPort)
442+
}
443+
419444
return reg, nil
420445
}
421446

application/lib/registration_test.go

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,14 @@ func mockReceiveFromDetector() (pb.ClientToStation, ConjureSharedKeys) {
3030
clientToStation.Flags = &pb.RegistrationFlags{Use_TIL: &t}
3131
clientToStation.ClientLibVersion = &v
3232

33-
conjureKeys, _ := GenSharedKeys(sharedSecret, 0)
33+
conjureKeys, _ := GenSharedKeys(0, sharedSecret, 0)
3434

3535
var testGeneration uint32 = 957
3636
clientToStation.DecoyListGeneration = &testGeneration
3737

3838
return *clientToStation, conjureKeys
3939
}
4040

41-
// func testEqualRegistrations(reg1 *DecoyRegistration, reg2 *DecoyRegistration) bool {
42-
// return true
43-
// }
44-
45-
// // This is not actually working yet
46-
// func TestCreateDecoyRegistration(t *testing.T) {
47-
// rm := NewRegistrationManager(&RegConfig{})
48-
49-
// c2s, keys := mockReceiveFromDetector()
50-
51-
// regSource := pb.RegistrationSource_Detector
52-
53-
// newReg, err := rm.NewRegistration(&c2s, &keys, c2s.GetV6Support(), &regSource)
54-
// if err != nil {
55-
// t.Fatalf("Registration failed: %v", err)
56-
// }
57-
58-
// expectedReg := DecoyRegistration{}
59-
60-
// if !testEqualRegistrations(newReg, &expectedReg) {
61-
// t.Fatalf("Bad registration Created")
62-
// }
63-
// }
64-
6541
func TestRegistrationLookup(t *testing.T) {
6642
rm := NewRegistrationManager(&RegConfig{})
6743

@@ -116,9 +92,7 @@ func TestRegisterForDetectorOnce(t *testing.T) {
11692

11793
// check message
11894
msg := <-channel
119-
if msg == nil {
120-
t.Fatalf("no messages received\n")
121-
}
95+
require.NotNil(t, msg)
12296

12397
// reconstruct IP from message
12498
parsed := pb.StationToDetector{}
@@ -174,9 +148,7 @@ func TestRegisterForDetectorArray(t *testing.T) {
174148

175149
// check message
176150
msg := <-channel
177-
if msg == nil {
178-
t.Fatalf("no messages received %s\n", addr)
179-
}
151+
require.NotNil(t, msg)
180152

181153
// reconstruct IP from message
182154
parsed := pb.StationToDetector{}

0 commit comments

Comments
 (0)