@@ -26,13 +26,30 @@ public partial class SslStreamCertificateContext
26
26
private byte [ ] ? _ocspResponse ;
27
27
private DateTimeOffset _ocspExpiration ;
28
28
private DateTimeOffset _nextDownload ;
29
+ // Private copy of the intermediate certificates, in case the user decides to dispose the
30
+ // instances reachable through IntermediateCertificates property.
31
+ private X509Certificate2 [ ] _privateIntermediateCertificates ;
32
+ private X509Certificate2 ? _rootCertificate ;
29
33
private Task < byte [ ] ? > ? _pendingDownload ;
30
34
private List < string > ? _ocspUrls ;
31
- private X509Certificate2 ? _ca ;
32
35
33
36
private SslStreamCertificateContext ( X509Certificate2 target , ReadOnlyCollection < X509Certificate2 > intermediates , SslCertificateTrust ? trust )
34
37
{
35
38
IntermediateCertificates = intermediates ;
39
+ if ( intermediates . Count > 0 )
40
+ {
41
+ _privateIntermediateCertificates = new X509Certificate2 [ intermediates . Count ] ;
42
+
43
+ for ( int i = 0 ; i < intermediates . Count ; i ++ )
44
+ {
45
+ _privateIntermediateCertificates [ i ] = new X509Certificate2 ( intermediates [ i ] ) ;
46
+ }
47
+ }
48
+ else
49
+ {
50
+ _privateIntermediateCertificates = Array . Empty < X509Certificate2 > ( ) ;
51
+ }
52
+
36
53
TargetCertificate = target ;
37
54
Trust = trust ;
38
55
SslContexts = new ConcurrentDictionary < SslProtocols , SafeSslContextHandle > ( ) ;
@@ -76,15 +93,8 @@ partial void SetNoOcspFetch(bool noOcspFetch)
76
93
77
94
partial void AddRootCertificate ( X509Certificate2 ? rootCertificate , ref bool transferredOwnership )
78
95
{
79
- if ( IntermediateCertificates . Count == 0 )
80
- {
81
- _ca = rootCertificate ;
82
- transferredOwnership = true ;
83
- }
84
- else
85
- {
86
- _ca = IntermediateCertificates [ 0 ] ;
87
- }
96
+ _rootCertificate = rootCertificate ;
97
+ transferredOwnership = rootCertificate != null ;
88
98
89
99
if ( ! _staplingForbidden )
90
100
{
@@ -149,7 +159,7 @@ partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool tran
149
159
return new ValueTask < byte [ ] ? > ( pending ) ;
150
160
}
151
161
152
- if ( _ocspUrls is null && _ca is not null )
162
+ if ( _ocspUrls is null && _rootCertificate is not null )
153
163
{
154
164
foreach ( X509Extension ext in TargetCertificate . Extensions )
155
165
{
@@ -192,7 +202,9 @@ partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool tran
192
202
193
203
private async Task < byte [ ] ? > FetchOcspAsync ( )
194
204
{
195
- X509Certificate2 ? caCert = _ca ;
205
+ Debug . Assert ( _rootCertificate != null ) ;
206
+ X509Certificate2 ? caCert = _privateIntermediateCertificates . Length > 0 ? _privateIntermediateCertificates [ 0 ] : _rootCertificate ;
207
+
196
208
Debug . Assert ( _ocspUrls is not null ) ;
197
209
Debug . Assert ( _ocspUrls . Count > 0 ) ;
198
210
Debug . Assert ( caCert is not null ) ;
@@ -211,6 +223,13 @@ partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool tran
211
223
return null ;
212
224
}
213
225
226
+ IntPtr [ ] issuerHandles = ArrayPool < IntPtr > . Shared . Rent ( _privateIntermediateCertificates . Length + 1 ) ;
227
+ for ( int i = 0 ; i < _privateIntermediateCertificates . Length ; i ++ )
228
+ {
229
+ issuerHandles [ i ] = _privateIntermediateCertificates [ i ] . Handle ;
230
+ }
231
+ issuerHandles [ _privateIntermediateCertificates . Length ] = _rootCertificate . Handle ;
232
+
214
233
using ( SafeOcspRequestHandle ocspRequest = Interop . Crypto . X509BuildOcspRequest ( subject , issuer ) )
215
234
{
216
235
byte [ ] rentedBytes = ArrayPool < byte > . Shared . Rent ( Interop . Crypto . GetOcspRequestDerSize ( ocspRequest ) ) ;
@@ -227,7 +246,7 @@ partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool tran
227
246
228
247
if ( ret is not null )
229
248
{
230
- if ( ! Interop . Crypto . X509DecodeOcspToExpiration ( ret , ocspRequest , subject , issuer , out DateTimeOffset expiration ) )
249
+ if ( ! Interop . Crypto . X509DecodeOcspToExpiration ( ret , ocspRequest , subject , issuerHandles . AsSpan ( 0 , _privateIntermediateCertificates . Length + 1 ) , out DateTimeOffset expiration ) )
231
250
{
232
251
ret = null ;
233
252
continue ;
@@ -247,15 +266,27 @@ partial void AddRootCertificate(X509Certificate2? rootCertificate, ref bool tran
247
266
_ocspResponse = ret ;
248
267
_ocspExpiration = expiration ;
249
268
_nextDownload = nextCheckA < nextCheckB ? nextCheckA : nextCheckB ;
250
- _pendingDownload = null ;
251
269
break ;
252
270
}
253
271
}
254
272
273
+ issuerHandles . AsSpan ( ) . Clear ( ) ;
274
+ ArrayPool < IntPtr > . Shared . Return ( issuerHandles ) ;
255
275
ArrayPool < byte > . Shared . Return ( rentedBytes ) ;
256
276
ArrayPool < char > . Shared . Return ( rentedChars . Array ! ) ;
257
277
GC . KeepAlive ( TargetCertificate ) ;
278
+ GC . KeepAlive ( _privateIntermediateCertificates ) ;
279
+ GC . KeepAlive ( _rootCertificate ) ;
258
280
GC . KeepAlive ( caCert ) ;
281
+
282
+ _pendingDownload = null ;
283
+ if ( ret == null )
284
+ {
285
+ // All download attempts failed, don't try again for 5 seconds.
286
+ // This backoff will be applied only if the OCSP staple is not expired.
287
+ // If it is expired, we will force-refresh it during next GetOcspResponseAsync call.
288
+ _nextDownload = DateTimeOffset . UtcNow . AddSeconds ( 5 ) ;
289
+ }
259
290
return ret ;
260
291
}
261
292
}
0 commit comments