@@ -2,23 +2,197 @@ package decoy
22
33import (
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+
1535type 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
24198func 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+
37292func (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