7
7
8
8
namespace Microsoft . AspNetCore . Server . Kestrel . Core . Middleware ;
9
9
10
- internal sealed class TlsListenerMiddleware
10
+ internal sealed class TlsListener
11
11
{
12
- private readonly ConnectionDelegate _next ;
13
12
private readonly Action < ConnectionContext , ReadOnlySequence < byte > > _tlsClientHelloBytesCallback ;
14
13
15
- public TlsListenerMiddleware ( ConnectionDelegate next , Action < ConnectionContext , ReadOnlySequence < byte > > tlsClientHelloBytesCallback )
14
+ public TlsListener ( Action < ConnectionContext , ReadOnlySequence < byte > > tlsClientHelloBytesCallback )
16
15
{
17
- _next = next ;
18
16
_tlsClientHelloBytesCallback = tlsClientHelloBytesCallback ;
19
17
}
20
18
21
19
/// <summary>
22
20
/// Sniffs the TLS Client Hello message, and invokes a callback if found.
23
21
/// </summary>
24
- internal async Task OnTlsClientHelloAsync ( ConnectionContext connection )
22
+ internal async Task OnTlsClientHelloAsync ( ConnectionContext connection , CancellationToken cancellationToken )
25
23
{
26
24
var input = connection . Transport . Input ;
27
25
ClientHelloParseState parseState = ClientHelloParseState . NotEnoughData ;
26
+ short recordLength = - 1 ; // remembers the length of TLS record to not re-parse header on every iteration
28
27
29
28
while ( true )
30
29
{
31
- var result = await input . ReadAsync ( ) ;
30
+ var result = await input . ReadAsync ( cancellationToken ) ;
32
31
var buffer = result . Buffer ;
33
32
34
33
try
@@ -40,7 +39,7 @@ internal async Task OnTlsClientHelloAsync(ConnectionContext connection)
40
39
break ;
41
40
}
42
41
43
- parseState = TryParseClientHello ( buffer , out var clientHelloBytes ) ;
42
+ parseState = TryParseClientHello ( buffer , ref recordLength , out var clientHelloBytes ) ;
44
43
if ( parseState == ClientHelloParseState . NotEnoughData )
45
44
{
46
45
// if no data will be added, and we still lack enough bytes
@@ -74,8 +73,6 @@ internal async Task OnTlsClientHelloAsync(ConnectionContext connection)
74
73
}
75
74
}
76
75
}
77
-
78
- await _next ( connection ) ;
79
76
}
80
77
81
78
/// <summary>
@@ -85,10 +82,25 @@ internal async Task OnTlsClientHelloAsync(ConnectionContext connection)
85
82
/// TLS 1.2: https://datatracker.ietf.org/doc/html/rfc5246#section-6.2
86
83
/// TLS 1.3: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1
87
84
/// </summary>
88
- private static ClientHelloParseState TryParseClientHello ( ReadOnlySequence < byte > buffer , out ReadOnlySequence < byte > clientHelloBytes )
85
+ private static ClientHelloParseState TryParseClientHello ( ReadOnlySequence < byte > buffer , ref short recordLength , out ReadOnlySequence < byte > clientHelloBytes )
89
86
{
90
87
clientHelloBytes = default ;
91
88
89
+ // in case bad actor will be sending a TLS client hello one byte at a time
90
+ // and we know the expected length of TLS client hello,
91
+ // we can check and fail quickly here instead of re-parsing the TLS client hello "header" on each iteration
92
+ if ( recordLength != - 1 && buffer . Length < 5 + recordLength )
93
+ {
94
+ return ClientHelloParseState . NotEnoughData ;
95
+ }
96
+
97
+ // this means we finally got a full tls record, so we can return without parsing again
98
+ if ( recordLength != - 1 )
99
+ {
100
+ clientHelloBytes = buffer . Slice ( 0 , 5 + recordLength ) ;
101
+ return ClientHelloParseState . ValidTlsClientHello ;
102
+ }
103
+
92
104
if ( buffer . Length < 6 )
93
105
{
94
106
return ClientHelloParseState . NotEnoughData ;
@@ -109,7 +121,7 @@ private static ClientHelloParseState TryParseClientHello(ReadOnlySequence<byte>
109
121
}
110
122
111
123
// Record length
112
- if ( ! reader . TryReadBigEndian ( out short recordLength ) )
124
+ if ( ! reader . TryReadBigEndian ( out recordLength ) )
113
125
{
114
126
return ClientHelloParseState . NotTlsClientHello ;
115
127
}
0 commit comments