1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Buffers ;
5
+ using System . Diagnostics ;
4
6
using System . Net . Security ;
5
7
using System . Security . Authentication ;
6
8
using System . Security . Cryptography ;
7
9
using System . Security . Cryptography . X509Certificates ;
10
+ using System . Threading . Tasks ;
8
11
using Microsoft . Quic ;
9
12
using static Microsoft . Quic . MsQuic ;
10
13
@@ -63,18 +66,122 @@ public SslConnectionOptions(QuicConnection connection, bool isClient,
63
66
_certificateChainPolicy = certificateChainPolicy ;
64
67
}
65
68
66
- public unsafe int ValidateCertificate ( QUIC_BUFFER * certificatePtr , QUIC_BUFFER * chainPtr , out X509Certificate2 ? certificate )
69
+ internal async Task < bool > StartAsyncCertificateValidation ( IntPtr certificatePtr , IntPtr chainPtr )
70
+ {
71
+ //
72
+ // The provided data pointers are valid only while still inside this function, so they need to be
73
+ // copied to separate buffers which are then handed off to threadpool.
74
+ //
75
+
76
+ X509Certificate2 ? certificate = null ;
77
+
78
+ byte [ ] ? certDataRented = null ;
79
+ Memory < byte > certData = default ;
80
+ byte [ ] ? chainDataRented = null ;
81
+ Memory < byte > chainData = default ;
82
+
83
+ if ( certificatePtr != IntPtr . Zero )
84
+ {
85
+ if ( MsQuicApi . UsesSChannelBackend )
86
+ {
87
+ // provided data is a pointer to a CERT_CONTEXT
88
+ certificate = new X509Certificate2 ( certificatePtr ) ;
89
+ // TODO: what about chainPtr?
90
+ }
91
+ else
92
+ {
93
+ unsafe
94
+ {
95
+ // On non-SChannel backends we specify USE_PORTABLE_CERTIFICATES and the contents are buffers
96
+ // with DER encoded cert and chain.
97
+ QUIC_BUFFER * certificateBuffer = ( QUIC_BUFFER * ) certificatePtr ;
98
+ QUIC_BUFFER * chainBuffer = ( QUIC_BUFFER * ) chainPtr ;
99
+
100
+ if ( certificateBuffer ->Length > 0 )
101
+ {
102
+ certDataRented = ArrayPool < byte > . Shared . Rent ( ( int ) certificateBuffer ->Length ) ;
103
+ certData = certDataRented . AsMemory ( 0 , ( int ) certificateBuffer ->Length ) ;
104
+ certificateBuffer ->Span . CopyTo ( certData . Span ) ;
105
+ }
106
+
107
+ if ( chainBuffer ->Length > 0 )
108
+ {
109
+ chainDataRented = ArrayPool < byte > . Shared . Rent ( ( int ) chainBuffer ->Length ) ;
110
+ chainData = chainDataRented . AsMemory ( 0 , ( int ) chainBuffer ->Length ) ;
111
+ chainBuffer ->Span . CopyTo ( chainData . Span ) ;
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ // We wan't to do the certificate validation asynchronously, but due to a bug in MsQuic, we need to call the callback synchronously on some versions
118
+ if ( MsQuicApi . SupportsAsyncCertValidation )
119
+ {
120
+ // force yield to the thread pool to free up MsQuic worker thread.
121
+ await Task . CompletedTask . ConfigureAwait ( ConfigureAwaitOptions . ForceYielding ) ;
122
+ }
123
+
124
+ // certificatePtr and chainPtr are invalid beyond this point
125
+
126
+ QUIC_TLS_ALERT_CODES result ;
127
+ try
128
+ {
129
+ if ( certData . Length > 0 )
130
+ {
131
+ Debug . Assert ( certificate == null ) ;
132
+ certificate = new X509Certificate2 ( certData . Span ) ;
133
+ }
134
+
135
+ result = _connection . _sslConnectionOptions . ValidateCertificate ( certificate , certData . Span , chainData . Span ) ;
136
+ _connection . _remoteCertificate = certificate ;
137
+ }
138
+ catch ( Exception ex )
139
+ {
140
+ certificate ? . Dispose ( ) ;
141
+ _connection . _connectedTcs . TrySetException ( ex ) ;
142
+ result = QUIC_TLS_ALERT_CODES . USER_CANCELED ;
143
+ }
144
+ finally
145
+ {
146
+ if ( certDataRented != null )
147
+ {
148
+ ArrayPool < byte > . Shared . Return ( certDataRented ) ;
149
+ }
150
+
151
+ if ( chainDataRented != null )
152
+ {
153
+ ArrayPool < byte > . Shared . Return ( chainDataRented ) ;
154
+ }
155
+ }
156
+
157
+ if ( MsQuicApi . SupportsAsyncCertValidation )
158
+ {
159
+ int status = MsQuicApi . Api . ConnectionCertificateValidationComplete (
160
+ _connection . _handle ,
161
+ result == QUIC_TLS_ALERT_CODES . SUCCESS ? ( byte ) 1 : ( byte ) 0 ,
162
+ result ) ;
163
+
164
+ if ( MsQuic . StatusFailed ( status ) )
165
+ {
166
+ if ( NetEventSource . Log . IsEnabled ( ) )
167
+ {
168
+ NetEventSource . Error ( _connection , $ "{ _connection } ConnectionCertificateValidationComplete failed with { ThrowHelper . GetErrorMessageForStatus ( status ) } ") ;
169
+ }
170
+ }
171
+ }
172
+
173
+ return result == QUIC_TLS_ALERT_CODES . SUCCESS ;
174
+ }
175
+
176
+ private QUIC_TLS_ALERT_CODES ValidateCertificate ( X509Certificate2 ? certificate , Span < byte > certData , Span < byte > chainData )
67
177
{
68
178
SslPolicyErrors sslPolicyErrors = SslPolicyErrors . None ;
69
- IntPtr certificateBuffer = 0 ;
70
- int certificateLength = 0 ;
71
179
bool wrapException = false ;
72
180
73
181
X509Chain ? chain = null ;
74
- X509Certificate2 ? result = null ;
75
182
try
76
183
{
77
- if ( certificatePtr is not null )
184
+ if ( certificate is not null )
78
185
{
79
186
chain = new X509Chain ( ) ;
80
187
if ( _certificateChainPolicy != null )
@@ -96,51 +203,34 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
96
203
chain . ChainPolicy . ApplicationPolicy . Add ( _isClient ? s_serverAuthOid : s_clientAuthOid ) ;
97
204
}
98
205
99
- if ( MsQuicApi . UsesSChannelBackend )
206
+ if ( chainData . Length > 0 )
100
207
{
101
- result = new X509Certificate2 ( ( IntPtr ) certificatePtr ) ;
208
+ X509Certificate2Collection additionalCertificates = new X509Certificate2Collection ( ) ;
209
+ additionalCertificates . Import ( chainData ) ;
210
+ chain . ChainPolicy . ExtraStore . AddRange ( additionalCertificates ) ;
102
211
}
103
- else
104
- {
105
- if ( certificatePtr ->Length > 0 )
106
- {
107
- certificateBuffer = ( IntPtr ) certificatePtr ->Buffer ;
108
- certificateLength = ( int ) certificatePtr ->Length ;
109
- result = new X509Certificate2 ( certificatePtr ->Span ) ;
110
- }
111
212
112
- if ( chainPtr ->Length > 0 )
113
- {
114
- X509Certificate2Collection additionalCertificates = new X509Certificate2Collection ( ) ;
115
- additionalCertificates . Import ( chainPtr ->Span ) ;
116
- chain . ChainPolicy . ExtraStore . AddRange ( additionalCertificates ) ;
117
- }
118
- }
119
- }
120
-
121
- if ( result is not null )
122
- {
123
213
bool checkCertName = ! chain ! . ChainPolicy ! . VerificationFlags . HasFlag ( X509VerificationFlags . IgnoreInvalidName ) ;
124
- sslPolicyErrors |= CertificateValidation . BuildChainAndVerifyProperties ( chain ! , result , checkCertName , ! _isClient , TargetHostNameHelper . NormalizeHostName ( _targetHost ) , certificateBuffer , certificateLength ) ;
214
+ sslPolicyErrors |= CertificateValidation . BuildChainAndVerifyProperties ( chain ! , certificate , checkCertName , ! _isClient , TargetHostNameHelper . NormalizeHostName ( _targetHost ) , certData ) ;
125
215
}
126
216
else if ( _certificateRequired )
127
217
{
128
218
sslPolicyErrors |= SslPolicyErrors . RemoteCertificateNotAvailable ;
129
219
}
130
220
131
- int status = QUIC_STATUS_SUCCESS ;
221
+ QUIC_TLS_ALERT_CODES result = QUIC_TLS_ALERT_CODES . SUCCESS ;
132
222
if ( _validationCallback is not null )
133
223
{
134
224
wrapException = true ;
135
- if ( ! _validationCallback ( _connection , result , chain , sslPolicyErrors ) )
225
+ if ( ! _validationCallback ( _connection , certificate , chain , sslPolicyErrors ) )
136
226
{
137
227
wrapException = false ;
138
228
if ( _isClient )
139
229
{
140
230
throw new AuthenticationException ( SR . net_quic_cert_custom_validation ) ;
141
231
}
142
232
143
- status = QUIC_STATUS_USER_CANCELED ;
233
+ result = QUIC_TLS_ALERT_CODES . BAD_CERTIFICATE ;
144
234
}
145
235
}
146
236
else if ( sslPolicyErrors != SslPolicyErrors . None )
@@ -150,15 +240,13 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
150
240
throw new AuthenticationException ( SR . Format ( SR . net_quic_cert_chain_validation , sslPolicyErrors ) ) ;
151
241
}
152
242
153
- status = QUIC_STATUS_HANDSHAKE_FAILURE ;
243
+ result = QUIC_TLS_ALERT_CODES . BAD_CERTIFICATE ;
154
244
}
155
245
156
- certificate = result ;
157
- return status ;
246
+ return result ;
158
247
}
159
248
catch ( Exception ex )
160
249
{
161
- result ? . Dispose ( ) ;
162
250
if ( wrapException )
163
251
{
164
252
throw new QuicException ( QuicError . CallbackError , null , SR . net_quic_callback_error , ex ) ;
0 commit comments