Skip to content

Commit 3198263

Browse files
committed
decoy-registrar: add necessary utils from tapdance
1 parent 6a18ec5 commit 3198263

File tree

2 files changed

+335
-10
lines changed

2 files changed

+335
-10
lines changed

pkg/registrars/decoy-registrar/decoy-registrar.go

Lines changed: 264 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,197 @@ package decoy
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"net"
7+
"sync"
88
"time"
99

10+
"github.com/refraction-networking/conjure/pkg/registrars/lib"
1011
pb "github.com/refraction-networking/conjure/proto"
1112
"github.com/refraction-networking/gotapdance/tapdance"
13+
14+
// td imports assets/
15+
td "github.com/refraction-networking/gotapdance/tapdance"
16+
tls "github.com/refraction-networking/utls"
1217
"github.com/sirupsen/logrus"
18+
"google.golang.org/protobuf/proto"
1319
)
1420

21+
/**
22+
* TODO: enable logging
23+
*/
24+
25+
// timeout for sending TD request and getting a response
26+
const deadlineConnectTDStationMin = 11175
27+
const deadlineConnectTDStationMax = 14231
28+
29+
// deadline to establish TCP connection to decoy
30+
const deadlineTCPtoDecoyMin = deadlineConnectTDStationMin
31+
const deadlineTCPtoDecoyMax = deadlineConnectTDStationMax
32+
33+
type DialFunc = func(ctx context.Context, network, addr string) (net.Conn, error)
34+
1535
type DecoyRegistrar struct {
1636

1737
// dialContex is a custom dialer to use when establishing TCP connections
1838
// to decoys. When nil, Dialer.dialContex will be used.
1939
dialContex DialFunc
2040

2141
logger logrus.FieldLogger
42+
43+
// Fields taken from ConjureReg struct
44+
m sync.Mutex
45+
stats *pb.SessionStats
46+
sessionIDStr string
47+
covertAddress string
48+
}
49+
50+
// CurrentClientLibraryVersion returns the current client library version used
51+
// for feature compatibility support between client and server. Currently I
52+
// don't intend to connect this to the library tag version in any way.
53+
//
54+
// When adding new client versions comment out older versions and add new
55+
// version below with a description of the reason for the new version.
56+
func currentClientLibraryVersion() uint32 {
57+
// Support for randomizing destination port for phantom connection
58+
// https://github.com/refraction-networking/gotapdance/pull/108
59+
return 3
60+
61+
// // Selection algorithm update - Oct 27, 2022 -- Phantom selection version rework again to use
62+
// // hkdf for actual uniform distribution across phantom subnets.
63+
// // https://github.com/refraction-networking/conjure/pull/145
64+
// return 2
65+
66+
// // Initial inclusion of client version - added due to update in phantom
67+
// // selection algorithm that is not backwards compatible to older clients.
68+
// return 1
69+
70+
// // No client version indicates any client before this change.
71+
// return 0
72+
}
73+
74+
// RegError - Registration Error passed during registration to indicate failure mode
75+
type RegError struct {
76+
code uint
77+
msg string
78+
}
79+
80+
func NewRegError(code uint, msg string) RegError {
81+
return RegError{code: code, msg: msg}
82+
}
83+
84+
func (err RegError) Error() string {
85+
return fmt.Sprintf("Registration Error [%v]: %v", err.CodeStr(), err.msg)
86+
}
87+
88+
func (err RegError) Code() uint {
89+
return err.code
90+
}
91+
92+
// CodeStr - Get desctriptor associated with error code
93+
func (err RegError) CodeStr() string {
94+
switch err.code {
95+
case Unreachable:
96+
return "UNREACHABLE"
97+
case DialFailure:
98+
return "DIAL_FAILURE"
99+
case NotImplemented:
100+
return "NOT_IMPLEMENTED"
101+
case TLSError:
102+
return "TLS_ERROR"
103+
default:
104+
return "UNKNOWN"
105+
}
106+
}
107+
108+
const (
109+
// Unreachable -Dial Error Unreachable -- likely network unavailable (i.e. ipv6 error)
110+
Unreachable = iota
111+
112+
// DialFailure - Dial Error Other than unreachable
113+
DialFailure
114+
115+
// NotImplemented - Related Function Not Implemented
116+
NotImplemented
117+
118+
// TLSError (Expired, Wrong-Host, Untrusted-Root, ...)
119+
TLSError
120+
121+
// Unknown - Error occurred without obvious explanation
122+
Unknown
123+
)
124+
125+
func (r *DecoyRegistrar) getPbTransport() pb.TransportType {
126+
return r.Transport.ID()
127+
}
128+
129+
func (r *DecoyRegistrar) setTCPToDecoy(tcprtt *uint32) {
130+
r.m.Lock()
131+
defer r.m.Unlock()
132+
133+
if r.stats == nil {
134+
r.stats = &pb.SessionStats{}
135+
}
136+
r.stats.TcpToDecoy = tcprtt
137+
}
138+
139+
func (reg *DecoyRegistrar) setTLSToDecoy(tlsrtt *uint32) {
140+
reg.m.Lock()
141+
defer reg.m.Unlock()
142+
143+
if reg.stats == nil {
144+
reg.stats = &pb.SessionStats{}
145+
}
146+
reg.stats.TlsToDecoy = tlsrtt
147+
}
148+
149+
func (r *DecoyRegistrar) generateClientToStation() (*pb.ClientToStation, error) {
150+
var covert *string
151+
if len(r.covertAddress) > 0 {
152+
//[TODO]{priority:medium} this isn't the correct place to deal with signaling to the station
153+
//transition = pb.C2S_Transition_C2S_SESSION_COVERT_INIT
154+
covert = &r.covertAddress
155+
}
156+
157+
//[reference] Generate ClientToStation protobuf
158+
// transition := pb.C2S_Transition_C2S_SESSION_INIT
159+
currentGen := td.Assets().GetGeneration()
160+
currentLibVer := currentClientLibraryVersion()
161+
transport := reg.getPbTransport()
162+
transportParams, err := reg.getPbTransportParams()
163+
if err != nil {
164+
// Logger().Debugf("%s failed to marshal transport parameters ", reg.sessionIDStr)
165+
}
166+
167+
// remove type url to save space for DNS registration
168+
// for server side changes see https://github.com/refraction-networking/conjure/pull/163
169+
transportParams.TypeUrl = ""
170+
171+
initProto := &pb.ClientToStation{
172+
ClientLibVersion: &currentLibVer,
173+
CovertAddress: covert,
174+
DecoyListGeneration: &currentGen,
175+
V6Support: reg.getV6Support(),
176+
V4Support: reg.getV4Support(),
177+
Transport: &transport,
178+
Flags: reg.generateFlags(),
179+
TransportParams: transportParams,
180+
181+
DisableRegistrarOverrides: &reg.ConjureSession.DisableRegistrarOverrides,
182+
183+
//[TODO]{priority:medium} specify width in C2S because different width might
184+
// be useful in different regions (constant for now.)
185+
}
186+
187+
if len(reg.phantomSNI) > 0 {
188+
initProto.MaskedDecoyServerName = &reg.phantomSNI
189+
}
190+
191+
for (proto.Size(initProto)+AES_GCM_TAG_SIZE)%3 != 0 {
192+
initProto.Padding = append(initProto.Padding, byte(0))
193+
}
194+
195+
return initProto, nil
22196
}
23197

24198
func NewDecoyRegistrar() *DecoyRegistrar {
@@ -34,6 +208,87 @@ func NewDecoyRegistrarWithDialer(dialer DialFunc) *DecoyRegistrar {
34208
}
35209
}
36210

211+
func (r DecoyRegistrar) createTLSConn(dialConn net.Conn, address string, hostname string, deadline time.Time) (*tls.UConn, error) {
212+
var err error
213+
//[reference] TLS to Decoy
214+
config := tls.Config{ServerName: hostname}
215+
if config.ServerName == "" {
216+
// if SNI is unset -- try IP
217+
config.ServerName, _, err = net.SplitHostPort(address)
218+
if err != nil {
219+
return nil, err
220+
}
221+
// Logger().Debugf("%v SNI was nil. Setting it to %v ", r.sessionIDStr, config.ServerName)
222+
}
223+
//[TODO]{priority:medium} parroting Chrome 62 ClientHello -- parrot newer.
224+
tlsConn := tls.UClient(dialConn, &config, tls.HelloChrome_62)
225+
226+
err = tlsConn.BuildHandshakeState()
227+
if err != nil {
228+
return nil, err
229+
}
230+
err = tlsConn.MarshalClientHello()
231+
if err != nil {
232+
return nil, err
233+
}
234+
235+
tlsConn.SetDeadline(deadline)
236+
err = tlsConn.Handshake()
237+
if err != nil {
238+
return nil, err
239+
}
240+
241+
return tlsConn, nil
242+
}
243+
244+
func generateVSP() ([]byte, error) {
245+
c2s, err := reg.generateClientToStation()
246+
if err != nil {
247+
return nil, err
248+
}
249+
250+
//[reference] Marshal ClientToStation protobuf
251+
return proto.Marshal(c2s)
252+
}
253+
254+
func (r *DecoyRegistrar) createRequest(tlsConn *tls.UConn, decoy *pb.TLSDecoySpec) ([]byte, error) {
255+
//[reference] generate and encrypt variable size payload
256+
vsp, err := reg.generateVSP()
257+
if err != nil {
258+
return nil, err
259+
}
260+
if len(vsp) > int(^uint16(0)) {
261+
return nil, fmt.Errorf("Variable-Size Payload exceeds %v", ^uint16(0))
262+
}
263+
encryptedVsp, err := aesGcmEncrypt(vsp, reg.keys.VspKey, reg.keys.VspIv)
264+
if err != nil {
265+
return nil, err
266+
}
267+
268+
//[reference] generate and encrypt fixed size payload
269+
fsp := reg.generateFSP(uint16(len(encryptedVsp)))
270+
encryptedFsp, err := aesGcmEncrypt(fsp, reg.keys.FspKey, reg.keys.FspIv)
271+
if err != nil {
272+
return nil, err
273+
}
274+
275+
var tag []byte // tag will be base-64 style encoded
276+
tag = append(encryptedVsp, reg.keys.Representative...)
277+
tag = append(tag, encryptedFsp...)
278+
279+
httpRequest := generateHTTPRequestBeginning(decoy.GetHostname())
280+
keystreamOffset := len(httpRequest)
281+
keystreamSize := (len(tag)/3+1)*4 + keystreamOffset // we can't use first 2 bits of every byte
282+
wholeKeystream, err := tlsConn.GetOutKeystream(keystreamSize)
283+
if err != nil {
284+
return nil, err
285+
}
286+
keystreamAtTag := wholeKeystream[keystreamOffset:]
287+
httpRequest = append(httpRequest, reverseEncrypt(tag, keystreamAtTag)...)
288+
httpRequest = append(httpRequest, []byte("\r\n\r\n")...)
289+
return httpRequest, nil
290+
}
291+
37292
func (r DecoyRegistrar) Register(cjSession *tapdance.ConjureSession, ctx context.Context) (*tapdance.ConjureReg, error) {
38293
logger := r.logger.WithFields(logrus.Fields{"type": "unidirectional", "sessionID": cjSession.IDString()})
39294

@@ -42,7 +297,7 @@ func (r DecoyRegistrar) Register(cjSession *tapdance.ConjureSession, ctx context
42297
reg, _, err := cjSession.UnidirectionalRegData(pb.RegistrationSource_API.Enum())
43298
if err != nil {
44299
logger.Errorf("Failed to prepare registration data: %v", err)
45-
return nil, ErrRegFailed
300+
return nil, lib.ErrRegFailed
46301
}
47302

48303
// Choose N (width) decoys from decoylist
@@ -102,18 +357,17 @@ func (r DecoyRegistrar) Register(cjSession *tapdance.ConjureSession, ctx context
102357
// randomized sleeping here to break the intraflow signal
103358
toSleep := reg.GetRandomDuration(3000, 212, 3449)
104359
logger.Debugf("Successfully sent registrations, sleeping for: %v", toSleep)
105-
sleepWithContext(ctx, toSleep)
360+
lib.SleepWithContext(ctx, toSleep)
106361

107362
return reg, nil
108363
}
109364

110-
func (r DecoyRegistrar) Send(ctx context.Context, reg *tapdance.ConjureReg, decoy *pb.TLSDecoySpec, dialErrors chan error) {
111-
deadline, deadlineAlreadySet := ctx.Deadline()
365+
func (r *DecoyRegistrar) Send(ctx context.Context, reg *tapdance.ConjureReg, decoy *pb.TLSDecoySpec, dialError chan error) {
112366

367+
deadline, deadlineAlreadySet := ctx.Deadline()
113368
if !deadlineAlreadySet {
114-
deadline = time.Now().Add(tapdance.GetRandomDuration(tapdance.deadlineTCPtoDecoyMin, tapdance.deadlineTCPtoDecoyMax))
369+
deadline = time.Now().Add(getRandomDuration(deadlineTCPtoDecoyMin, deadlineTCPtoDecoyMax))
115370
}
116-
117371
childCtx, childCancelFunc := context.WithDeadline(ctx, deadline)
118372
defer childCancelFunc()
119373

@@ -123,7 +377,7 @@ func (r DecoyRegistrar) Send(ctx context.Context, reg *tapdance.ConjureReg, deco
123377
//[Note] decoy.GetIpAddrStr() will get only v4 addr if a decoy has both
124378
dialConn, err := r.dialContex(childCtx, "tcp", decoy.GetIpAddrStr())
125379

126-
reg.setTCPToDecoy(tapdance.durationToU32ptrMs(time.Since(tcpToDecoyStartTs)))
380+
r.setTCPToDecoy(durationToU32ptrMs(time.Since(tcpToDecoyStartTs)))
127381
if err != nil {
128382
if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "connect: network is unreachable" {
129383
dialError <- RegError{msg: err.Error(), code: Unreachable}
@@ -139,14 +393,14 @@ func (r DecoyRegistrar) Send(ctx context.Context, reg *tapdance.ConjureReg, deco
139393
TLSDeadline := time.Now().Add(delay)
140394

141395
tlsToDecoyStartTs := time.Now()
142-
tlsConn, err := reg.createTLSConn(dialConn, decoy.GetIpAddrStr(), decoy.GetHostname(), TLSDeadline)
396+
tlsConn, err := r.createTLSConn(dialConn, decoy.GetIpAddrStr(), decoy.GetHostname(), TLSDeadline)
143397
if err != nil {
144398
dialConn.Close()
145399
msg := fmt.Sprintf("%v - %v createConn: %v", decoy.GetHostname(), decoy.GetIpAddrStr(), err.Error())
146400
dialError <- RegError{msg: msg, code: TLSError}
147401
return
148402
}
149-
reg.setTLSToDecoy(durationToU32ptrMs(time.Since(tlsToDecoyStartTs)))
403+
r.setTLSToDecoy(durationToU32ptrMs(time.Since(tlsToDecoyStartTs)))
150404

151405
//[reference] Create the HTTP request for the registration
152406
httpRequest, err := reg.createRequest(tlsConn, decoy)

0 commit comments

Comments
 (0)