Skip to content

Commit 8738ce1

Browse files
theodorsmat-wat
authored andcommitted
Add handshake hooking
Hooking for client/server hello and certificate request messages
1 parent 2c36d63 commit 8738ce1

10 files changed

Lines changed: 336 additions & 76 deletions

config.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/pion/dtls/v2/pkg/crypto/elliptic"
17+
"github.com/pion/dtls/v2/pkg/protocol/handshake"
1718
"github.com/pion/logging"
1819
)
1920

@@ -196,6 +197,23 @@ type Config struct {
196197
// If no PaddingLengthGenerator is specified, padding will not be applied.
197198
// https://datatracker.ietf.org/doc/html/rfc9146#section-4
198199
PaddingLengthGenerator func(uint) uint
200+
201+
// Handshake hooks: hooks can be used for testing invalid messages,
202+
// mimicking other implementations or randomizing fields, which is valuable
203+
// for applications that need censorship-resistance by making
204+
// fingerprinting more difficult.
205+
206+
// ClientHelloMessageHook, if not nil, is called when a Client Hello message is sent
207+
// from a client. The returned handshake message replaces the original message.
208+
ClientHelloMessageHook func(handshake.MessageClientHello) handshake.Message
209+
210+
// ServerHelloMessageHook, if not nil, is called when a Server Hello message is sent
211+
// from a server. The returned handshake message replaces the original message.
212+
ServerHelloMessageHook func(handshake.MessageServerHello) handshake.Message
213+
214+
// CertificateRequestMessageHook, if not nil, is called when a Certificate Request
215+
// message is sent from a server. The returned handshake message replaces the original message.
216+
CertificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message
199217
}
200218

201219
func defaultConnectContextMaker() (context.Context, func()) {

conn.go

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -176,32 +176,35 @@ func createConn(ctx context.Context, nextConn net.PacketConn, rAddr net.Addr, co
176176
}
177177

178178
hsCfg := &handshakeConfig{
179-
localPSKCallback: config.PSK,
180-
localPSKIdentityHint: config.PSKIdentityHint,
181-
localCipherSuites: cipherSuites,
182-
localSignatureSchemes: signatureSchemes,
183-
extendedMasterSecret: config.ExtendedMasterSecret,
184-
localSRTPProtectionProfiles: config.SRTPProtectionProfiles,
185-
serverName: serverName,
186-
supportedProtocols: config.SupportedProtocols,
187-
clientAuth: config.ClientAuth,
188-
localCertificates: config.Certificates,
189-
insecureSkipVerify: config.InsecureSkipVerify,
190-
verifyPeerCertificate: config.VerifyPeerCertificate,
191-
verifyConnection: config.VerifyConnection,
192-
rootCAs: config.RootCAs,
193-
clientCAs: config.ClientCAs,
194-
customCipherSuites: config.CustomCipherSuites,
195-
retransmitInterval: workerInterval,
196-
log: logger,
197-
initialEpoch: 0,
198-
keyLogWriter: config.KeyLogWriter,
199-
sessionStore: config.SessionStore,
200-
ellipticCurves: curves,
201-
localGetCertificate: config.GetCertificate,
202-
localGetClientCertificate: config.GetClientCertificate,
203-
insecureSkipHelloVerify: config.InsecureSkipVerifyHello,
204-
connectionIDGenerator: config.ConnectionIDGenerator,
179+
localPSKCallback: config.PSK,
180+
localPSKIdentityHint: config.PSKIdentityHint,
181+
localCipherSuites: cipherSuites,
182+
localSignatureSchemes: signatureSchemes,
183+
extendedMasterSecret: config.ExtendedMasterSecret,
184+
localSRTPProtectionProfiles: config.SRTPProtectionProfiles,
185+
serverName: serverName,
186+
supportedProtocols: config.SupportedProtocols,
187+
clientAuth: config.ClientAuth,
188+
localCertificates: config.Certificates,
189+
insecureSkipVerify: config.InsecureSkipVerify,
190+
verifyPeerCertificate: config.VerifyPeerCertificate,
191+
verifyConnection: config.VerifyConnection,
192+
rootCAs: config.RootCAs,
193+
clientCAs: config.ClientCAs,
194+
customCipherSuites: config.CustomCipherSuites,
195+
retransmitInterval: workerInterval,
196+
log: logger,
197+
initialEpoch: 0,
198+
keyLogWriter: config.KeyLogWriter,
199+
sessionStore: config.SessionStore,
200+
ellipticCurves: curves,
201+
localGetCertificate: config.GetCertificate,
202+
localGetClientCertificate: config.GetClientCertificate,
203+
insecureSkipHelloVerify: config.InsecureSkipVerifyHello,
204+
connectionIDGenerator: config.ConnectionIDGenerator,
205+
clientHelloMessageHook: config.ClientHelloMessageHook,
206+
serverHelloMessageHook: config.ServerHelloMessageHook,
207+
certificateRequestMessageHook: config.CertificateRequestMessageHook,
205208
}
206209

207210
// rfc5246#section-7.4.3

e2e/e2e_test.go

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424

2525
"github.com/pion/dtls/v2"
2626
"github.com/pion/dtls/v2/pkg/crypto/selfsign"
27+
"github.com/pion/dtls/v2/pkg/protocol/extension"
28+
"github.com/pion/dtls/v2/pkg/protocol/handshake"
2729
"github.com/pion/transport/v3/test"
2830
)
2931

@@ -33,7 +35,11 @@ const (
3335
messageRetry = 200 * time.Millisecond
3436
)
3537

36-
var errServerTimeout = errors.New("waiting on serverReady err: timeout")
38+
var (
39+
errServerTimeout = errors.New("waiting on serverReady err: timeout")
40+
errHookCiphersFailed = errors.New("hook failed to modify cipherlist")
41+
errHookAPLNFailed = errors.New("hook failed to modify APLN extension")
42+
)
3743

3844
func randomPort(t testing.TB) int {
3945
t.Helper()
@@ -569,6 +575,116 @@ func testPionE2ESimpleRSAClientCert(t *testing.T, server, client func(*comm), op
569575
comm.assert(t)
570576
}
571577

578+
func testPionE2ESimpleClientHelloHook(t *testing.T, server, client func(*comm), opts ...dtlsConfOpts) {
579+
lim := test.TimeOut(time.Second * 30)
580+
defer lim.Stop()
581+
582+
report := test.CheckRoutines(t)
583+
defer report()
584+
585+
t.Run("ClientHello hook", func(t *testing.T) {
586+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
587+
defer cancel()
588+
589+
cert, err := selfsign.GenerateSelfSignedWithDNS("localhost")
590+
if err != nil {
591+
t.Fatal(err)
592+
}
593+
594+
modifiedCipher := dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
595+
supportedList := []dtls.CipherSuiteID{
596+
dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
597+
modifiedCipher,
598+
}
599+
600+
ccfg := &dtls.Config{
601+
Certificates: []tls.Certificate{cert},
602+
VerifyConnection: func(s *dtls.State) error {
603+
if s.CipherSuiteID != modifiedCipher {
604+
return errHookCiphersFailed
605+
}
606+
return nil
607+
},
608+
CipherSuites: supportedList,
609+
ClientHelloMessageHook: func(ch handshake.MessageClientHello) handshake.Message {
610+
ch.CipherSuiteIDs = []uint16{uint16(modifiedCipher)}
611+
return &ch
612+
},
613+
InsecureSkipVerify: true,
614+
}
615+
616+
scfg := &dtls.Config{
617+
Certificates: []tls.Certificate{cert},
618+
CipherSuites: supportedList,
619+
InsecureSkipVerify: true,
620+
}
621+
622+
for _, o := range opts {
623+
o(ccfg)
624+
o(scfg)
625+
}
626+
serverPort := randomPort(t)
627+
comm := newComm(ctx, ccfg, scfg, serverPort, server, client)
628+
defer comm.cleanup(t)
629+
comm.assert(t)
630+
})
631+
}
632+
633+
func testPionE2ESimpleServerHelloHook(t *testing.T, server, client func(*comm), opts ...dtlsConfOpts) {
634+
lim := test.TimeOut(time.Second * 30)
635+
defer lim.Stop()
636+
637+
report := test.CheckRoutines(t)
638+
defer report()
639+
640+
t.Run("ServerHello hook", func(t *testing.T) {
641+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
642+
defer cancel()
643+
644+
cert, err := selfsign.GenerateSelfSignedWithDNS("localhost")
645+
if err != nil {
646+
t.Fatal(err)
647+
}
648+
649+
supportedList := []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM}
650+
651+
apln := "APLN"
652+
653+
ccfg := &dtls.Config{
654+
Certificates: []tls.Certificate{cert},
655+
VerifyConnection: func(s *dtls.State) error {
656+
if s.NegotiatedProtocol != apln {
657+
return errHookAPLNFailed
658+
}
659+
return nil
660+
},
661+
CipherSuites: supportedList,
662+
InsecureSkipVerify: true,
663+
}
664+
665+
scfg := &dtls.Config{
666+
Certificates: []tls.Certificate{cert},
667+
CipherSuites: supportedList,
668+
ServerHelloMessageHook: func(sh handshake.MessageServerHello) handshake.Message {
669+
sh.Extensions = append(sh.Extensions, &extension.ALPN{
670+
ProtocolNameList: []string{apln},
671+
})
672+
return &sh
673+
},
674+
InsecureSkipVerify: true,
675+
}
676+
677+
for _, o := range opts {
678+
o(ccfg)
679+
o(scfg)
680+
}
681+
serverPort := randomPort(t)
682+
comm := newComm(ctx, ccfg, scfg, serverPort, server, client)
683+
defer comm.cleanup(t)
684+
comm.assert(t)
685+
})
686+
}
687+
572688
func TestPionE2ESimple(t *testing.T) {
573689
testPionE2ESimple(t, serverPion, clientPion)
574690
}
@@ -624,3 +740,11 @@ func TestPionE2ESimpleECDSAClientCertCID(t *testing.T) {
624740
func TestPionE2ESimpleRSAClientCertCID(t *testing.T) {
625741
testPionE2ESimpleRSAClientCert(t, serverPion, clientPion, withConnectionIDGenerator(dtls.RandomCIDGenerator(8)))
626742
}
743+
744+
func TestPionE2ESimpleClientHelloHook(t *testing.T) {
745+
testPionE2ESimpleClientHelloHook(t, serverPion, clientPion)
746+
}
747+
748+
func TestPionE2ESimpleServerHelloHook(t *testing.T) {
749+
testPionE2ESimpleServerHelloHook(t, serverPion, clientPion)
750+
}

flight1handler.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,23 +133,31 @@ func flight1Generate(c flightConn, state *State, _ *handshakeCache, cfg *handsha
133133
extensions = append(extensions, &extension.ConnectionID{CID: state.localConnectionID})
134134
}
135135

136+
clientHello := &handshake.MessageClientHello{
137+
Version: protocol.Version1_2,
138+
SessionID: state.SessionID,
139+
Cookie: state.cookie,
140+
Random: state.localRandom,
141+
CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites),
142+
CompressionMethods: defaultCompressionMethods(),
143+
Extensions: extensions,
144+
}
145+
146+
var content handshake.Handshake
147+
148+
if cfg.clientHelloMessageHook != nil {
149+
content = handshake.Handshake{Message: cfg.clientHelloMessageHook(*clientHello)}
150+
} else {
151+
content = handshake.Handshake{Message: clientHello}
152+
}
153+
136154
return []*packet{
137155
{
138156
record: &recordlayer.RecordLayer{
139157
Header: recordlayer.Header{
140158
Version: protocol.Version1_2,
141159
},
142-
Content: &handshake.Handshake{
143-
Message: &handshake.MessageClientHello{
144-
Version: protocol.Version1_2,
145-
SessionID: state.SessionID,
146-
Cookie: state.cookie,
147-
Random: state.localRandom,
148-
CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites),
149-
CompressionMethods: defaultCompressionMethods(),
150-
Extensions: extensions,
151-
},
152-
},
160+
Content: &content,
153161
},
154162
},
155163
}, nil, nil

flight3handler.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -287,23 +287,31 @@ func flight3Generate(_ flightConn, state *State, _ *handshakeCache, cfg *handsha
287287
extensions = append(extensions, &extension.ConnectionID{CID: state.localConnectionID})
288288
}
289289

290+
clientHello := &handshake.MessageClientHello{
291+
Version: protocol.Version1_2,
292+
SessionID: state.SessionID,
293+
Cookie: state.cookie,
294+
Random: state.localRandom,
295+
CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites),
296+
CompressionMethods: defaultCompressionMethods(),
297+
Extensions: extensions,
298+
}
299+
300+
var content handshake.Handshake
301+
302+
if cfg.clientHelloMessageHook != nil {
303+
content = handshake.Handshake{Message: cfg.clientHelloMessageHook(*clientHello)}
304+
} else {
305+
content = handshake.Handshake{Message: clientHello}
306+
}
307+
290308
return []*packet{
291309
{
292310
record: &recordlayer.RecordLayer{
293311
Header: recordlayer.Header{
294312
Version: protocol.Version1_2,
295313
},
296-
Content: &handshake.Handshake{
297-
Message: &handshake.MessageClientHello{
298-
Version: protocol.Version1_2,
299-
SessionID: state.SessionID,
300-
Cookie: state.cookie,
301-
Random: state.localRandom,
302-
CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites),
303-
CompressionMethods: defaultCompressionMethods(),
304-
Extensions: extensions,
305-
},
306-
},
314+
Content: &content,
307315
},
308316
},
309317
}, nil, nil

flight4bhandler.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,21 @@ func flight4bGenerate(_ flightConn, state *State, cache *handshakeCache, cfg *ha
7777
}
7878

7979
cipherSuiteID := uint16(state.cipherSuite.ID())
80-
serverHello := &handshake.Handshake{
81-
Message: &handshake.MessageServerHello{
82-
Version: protocol.Version1_2,
83-
Random: state.localRandom,
84-
SessionID: state.SessionID,
85-
CipherSuiteID: &cipherSuiteID,
86-
CompressionMethod: defaultCompressionMethods()[0],
87-
Extensions: extensions,
88-
},
80+
var serverHello handshake.Handshake
81+
82+
serverHelloMessage := &handshake.MessageServerHello{
83+
Version: protocol.Version1_2,
84+
Random: state.localRandom,
85+
SessionID: state.SessionID,
86+
CipherSuiteID: &cipherSuiteID,
87+
CompressionMethod: defaultCompressionMethods()[0],
88+
Extensions: extensions,
89+
}
90+
91+
if cfg.serverHelloMessageHook != nil {
92+
serverHello = handshake.Handshake{Message: cfg.serverHelloMessageHook(*serverHelloMessage)}
93+
} else {
94+
serverHello = handshake.Handshake{Message: serverHelloMessage}
8995
}
9096

9197
serverHello.Header.MessageSequence = uint16(state.handshakeSendSequence)
@@ -112,7 +118,7 @@ func flight4bGenerate(_ flightConn, state *State, cache *handshakeCache, cfg *ha
112118
Header: recordlayer.Header{
113119
Version: protocol.Version1_2,
114120
},
115-
Content: serverHello,
121+
Content: &serverHello,
116122
},
117123
},
118124
&packet{

0 commit comments

Comments
 (0)