11import * as net from 'net' ;
2+ import * as tls from 'tls' ;
23import * as http2 from 'http2' ;
34import { expect } from "chai" ;
5+ import { TlsHelloData , trackClientHellos } from 'read-tls-client-hello' ;
46
57import { getLocal } from "../../.." ;
68import {
@@ -9,96 +11,190 @@ import {
911 makeDestroyable ,
1012 nodeOnly ,
1113 openRawSocket ,
12- delay ,
1314 getHttp2Response ,
14- cleanup
15+ cleanup ,
16+ openRawTlsSocket ,
17+ DestroyableServer
1518} from "../../test-utils" ;
19+ import { getCA } from '../../../src/util/certificates' ;
1620
1721nodeOnly ( ( ) => {
1822 describe ( "Unknown protocol handling" , ( ) => {
1923
2024 describe ( "with SOCKS & unknown protocol passthrough enabled" , ( ) => {
2125
2226 let server = getLocal ( {
27+ https : {
28+ keyPath : './test/fixtures/test-ca.key' ,
29+ certPath : './test/fixtures/test-ca.pem' ,
30+ } ,
2331 socks : true ,
2432 passthrough : [ 'unknown-protocol' ]
2533 } ) ;
2634
27- // Simple TCP echo server:
28- let remoteServer = makeDestroyable ( net . createServer ( ( socket ) => {
29- socket . on ( 'data' , ( data ) => {
30- socket . end ( data ) ;
31- } ) ;
32- } ) ) ;
33- let remotePort ! : number ;
34-
3535 beforeEach ( async ( ) => {
3636 await server . start ( ) ;
3737
38- remoteServer . listen ( ) ;
39- await new Promise ( ( resolve , reject ) => {
40- remoteServer . on ( 'listening' , resolve ) ;
41- remoteServer . on ( 'error' , reject ) ;
42- } ) ;
43- remotePort = ( remoteServer . address ( ) as net . AddressInfo ) . port ;
44-
4538 // No unexpected errors here please:
4639 await server . on ( 'tls-client-error' , ( e ) => expect . fail ( `TLS error: ${ e . failureCause } ` ) ) ;
4740 await server . on ( 'client-error' , ( e ) => expect . fail ( `Client error: ${ e . errorCode } ` ) ) ;
4841 } ) ;
4942
5043 afterEach ( async ( ) => {
5144 await server . stop ( ) ;
52- await remoteServer . destroy ( ) ;
5345 } ) ;
5446
55- it ( "can tunnel an unknown protocol over SOCKS, if enabled" , async ( ) => {
56- const socksSocket = await openSocksSocket ( server , 'localhost' , remotePort ) ;
57- const response = await sendRawRequest ( socksSocket , '123456789' ) ;
58- expect ( response ) . to . equal ( '123456789' ) ;
59- } ) ;
47+ describe ( "to a raw TCP server" , ( ) => {
48+
49+ // Simple TCP echo server:
50+ let remoteServer = makeDestroyable ( net . createServer ( ( socket ) => {
51+ socket . on ( 'data' , ( data ) => {
52+ socket . end ( data ) ;
53+ } ) ;
54+ } ) ) ;
55+ let remotePort ! : number ;
56+
57+ beforeEach ( async ( ) => {
58+ remoteServer . listen ( ) ;
59+ await new Promise ( ( resolve , reject ) => {
60+ remoteServer . on ( 'listening' , resolve ) ;
61+ remoteServer . on ( 'error' , reject ) ;
62+ } ) ;
63+ remotePort = ( remoteServer . address ( ) as net . AddressInfo ) . port ;
64+ } ) ;
65+
66+ afterEach ( async ( ) => {
67+ await remoteServer . destroy ( ) ;
68+ } ) ;
69+
70+ it ( "can tunnel an unknown protocol over SOCKS, if enabled" , async ( ) => {
71+ const socksSocket = await openSocksSocket ( server , 'localhost' , remotePort ) ;
72+ const response = await sendRawRequest ( socksSocket , '123456789' ) ;
73+ expect ( response ) . to . equal ( '123456789' ) ;
74+ } ) ;
75+
76+ it ( "can tunnel an unknown protocol over HTTP, if enabled" , async ( ) => {
77+ const tunnel = await openRawSocket ( server ) ;
78+
79+ tunnel . write ( `CONNECT localhost:${ remotePort } HTTP/1.1\r\n\r\n` ) ;
80+ const connectResponse = await new Promise < Buffer > ( ( resolve , reject ) => {
81+ tunnel . on ( 'data' , resolve ) ;
82+ tunnel . on ( 'error' , reject ) ;
83+ } ) ;
6084
61- it ( "can tunnel an unknown protocol over HTTP, if enabled" , async ( ) => {
62- const tunnel = await openRawSocket ( server ) ;
85+ expect ( connectResponse . toString ( ) ) . to . equal ( 'HTTP/1.1 200 OK\r\n\r\n' ) ;
6386
64- tunnel . write ( `CONNECT localhost:${ remotePort } HTTP/1.1\r\n\r\n` ) ;
65- const connectResponse = await new Promise < Buffer > ( ( resolve , reject ) => {
66- tunnel . on ( 'data' , resolve ) ;
67- tunnel . on ( 'error' , reject ) ;
87+ tunnel . write ( 'hello world' ) ;
88+ const unknownProtocolResponse = await new Promise < Buffer > ( ( resolve , reject ) => {
89+ tunnel . on ( 'data' , resolve ) ;
90+ tunnel . on ( 'error' , reject ) ;
91+ } ) ;
92+
93+ expect ( unknownProtocolResponse . toString ( ) ) . to . equal ( 'hello world' ) ;
94+ tunnel . end ( ) ;
6895 } ) ;
6996
70- expect ( connectResponse . toString ( ) ) . to . equal ( 'HTTP/1.1 200 OK\r\n\r\n' ) ;
97+ it ( "can tunnel an unknown protocol over HTTP/2, if enabled" , async ( ) => {
98+ const proxyClient = http2 . connect ( server . url ) ;
99+
100+ const tunnel = proxyClient . request ( {
101+ ':method' : 'CONNECT' ,
102+ ':authority' : `localhost:${ remotePort } `
103+ } ) ;
104+ const proxyResponse = await getHttp2Response ( tunnel ) ;
105+ expect ( proxyResponse [ ':status' ] ) . to . equal ( 200 ) ;
106+
107+ tunnel . write ( 'hello world' ) ;
108+ const unknownProtocolResponse = await new Promise < Buffer > ( ( resolve , reject ) => {
109+ tunnel . on ( 'data' , resolve ) ;
110+ tunnel . on ( 'error' , reject ) ;
111+ } ) ;
71112
72- tunnel . write ( 'hello world' ) ;
73- const unknownProtocolResponse = await new Promise < Buffer > ( ( resolve , reject ) => {
74- tunnel . on ( 'data' , resolve ) ;
75- tunnel . on ( 'error' , reject ) ;
113+ expect ( unknownProtocolResponse . toString ( ) ) . to . equal ( 'hello world' ) ;
114+ tunnel . end ( ) ;
115+
116+ await cleanup ( tunnel , proxyClient ) ;
76117 } ) ;
77118
78- expect ( unknownProtocolResponse . toString ( ) ) . to . equal ( 'hello world' ) ;
79- tunnel . end ( ) ;
80119 } ) ;
81120
82- it ( "can tunnel an unknown protocol over HTTP/2, if enabled" , async ( ) => {
83- const proxyClient = http2 . connect ( server . url ) ;
121+ describe ( "to a TLS-but-not-HTTP server" , ( ) => {
122+
123+ // TLS echo server: unwraps TLS, then echos
124+ let remoteServer : DestroyableServer < tls . Server > ;
125+ let remotePort ! : number ;
126+
127+ // Track client hellos on connections
128+ before ( async ( ) => {
129+ // Dynamically generate certs, just like Mockttp itself, but for raw 'echo' only. We use our
130+ // test CA which should be trusted by Node due to NODE_EXTRA_CA_CERTS settings in package.json.
131+ const ca = await getCA ( {
132+ keyPath : './test/fixtures/test-ca.key' ,
133+ certPath : './test/fixtures/test-ca.pem' ,
134+ } ) ;
135+ const defaultCert = await ca . generateCertificate ( 'localhost.test' ) ;
136+
137+ remoteServer = makeDestroyable ( tls . createServer ( {
138+ key : defaultCert . key ,
139+ cert : defaultCert . cert ,
140+ ca : [ defaultCert . ca ] ,
141+ SNICallback : async ( domain : string , cb : Function ) => {
142+ const generatedCert = await ca . generateCertificate ( domain ) ;
143+ cb ( null , tls . createSecureContext ( {
144+ key : generatedCert . key ,
145+ cert : generatedCert . cert ,
146+ ca : generatedCert . ca
147+ } ) ) ;
148+ } ,
149+ ALPNProtocols : [ 'echo' ]
150+ } , ( socket ) => {
151+ hellos . push ( socket . tlsClientHello ) ;
152+ socket . on ( 'data' , ( data ) => {
153+ socket . end ( data ) ;
154+ } ) ;
155+ } ) ) ;
156+
157+ trackClientHellos ( remoteServer ) ;
158+ } ) ;
159+
160+ // Store the client hellos for reference
161+ let hellos : Array < TlsHelloData | undefined > = [ ] ;
84162
85- const tunnel = proxyClient . request ( {
86- ':method' : 'CONNECT' ,
87- ':authority' : `localhost:${ remotePort } `
163+ beforeEach ( async ( ) => {
164+ remoteServer . listen ( ) ;
165+ await new Promise ( ( resolve , reject ) => {
166+ remoteServer . on ( 'listening' , resolve ) ;
167+ remoteServer . on ( 'error' , reject ) ;
168+ } ) ;
169+ remotePort = ( remoteServer . address ( ) as net . AddressInfo ) . port ;
170+
171+ hellos = [ ] ;
88172 } ) ;
89- const proxyResponse = await getHttp2Response ( tunnel ) ;
90- expect ( proxyResponse [ ':status' ] ) . to . equal ( 200 ) ;
91173
92- tunnel . write ( 'hello world' ) ;
93- const unknownProtocolResponse = await new Promise < Buffer > ( ( resolve , reject ) => {
94- tunnel . on ( 'data' , resolve ) ;
95- tunnel . on ( 'error' , reject ) ;
174+ afterEach ( async ( ) => {
175+ await remoteServer . destroy ( ) ;
96176 } ) ;
97177
98- expect ( unknownProtocolResponse . toString ( ) ) . to . equal ( 'hello world' ) ;
99- tunnel . end ( ) ;
178+ it ( "can tunnel an unknown protocol using TLS over SOCKS, if enabled" , async ( ) => {
179+ const socksSocket = await openSocksSocket ( server , 'localhost' , remotePort ) ;
180+
181+ const tlsSocket = await openRawTlsSocket ( socksSocket , {
182+ servername : 'server.test' ,
183+ ALPNProtocols : [ 'echo' ]
184+ } ) ;
185+
186+ const response = await sendRawRequest ( tlsSocket , '123456789' ) ;
187+ expect ( response ) . to . equal ( '123456789' ) ;
188+
189+ // We're terminating TLS, so we can't perfectly forward everything (client certs, really)
190+ // but we should be able to mirror all the common bits:
191+ expect ( hellos . length ) . to . equal ( 1 ) ;
192+ const destinationTlsHello = hellos [ 0 ] ! ;
193+
194+ expect ( destinationTlsHello . alpnProtocols ) . to . deep . equal ( [ 'echo' ] ) ;
195+ expect ( destinationTlsHello . serverName ) . to . equal ( 'server.test' ) ;
196+ } ) ;
100197
101- await cleanup ( tunnel , proxyClient ) ;
102198 } ) ;
103199
104200 } ) ;
0 commit comments