@@ -179,17 +179,33 @@ extension CertificatePolicy {
179
179
180
180
if certChain. count >= 1 , let httpClient = httpClient {
181
181
// Whether cert chain can be trusted depends on OCSP result
182
- self . BoringSSL_OCSP_isGood ( certificate: certChain [ 0 ] , issuer: certChain [ 1 ] , httpClient: httpClient, callbackQueue: callbackQueue, callback: callback)
182
+ ocspClient. checkStatus ( certificate: certChain [ 0 ] , issuer: certChain [ 1 ] , httpClient: httpClient,
183
+ diagnosticsEngine: diagnosticsEngine, callbackQueue: callbackQueue, callback: callback)
183
184
} else {
184
185
wrappedCallback ( . success( ( ) ) )
185
186
}
186
187
}
188
+ #endif
189
+ }
190
+
191
+ #if !os(macOS)
192
+ private let ocspClient = BoringSSLOCSPClient ( )
193
+
194
+ private struct BoringSSLOCSPClient {
195
+ private let resultCache = ThreadSafeKeyValueStore < CacheKey , CacheValue > ( )
196
+
197
+ private let cacheTTL : DispatchTimeInterval
187
198
188
- private func BoringSSL_OCSP_isGood( certificate: Certificate ,
189
- issuer: Certificate ,
190
- httpClient: HTTPClient ,
191
- callbackQueue: DispatchQueue ,
192
- callback: @escaping ( Result < Void , Error > ) -> Void ) {
199
+ init ( cacheTTL: DispatchTimeInterval = . seconds( 300 ) ) {
200
+ self . cacheTTL = cacheTTL
201
+ }
202
+
203
+ func checkStatus( certificate: Certificate ,
204
+ issuer: Certificate ,
205
+ httpClient: HTTPClient ,
206
+ diagnosticsEngine: DiagnosticsEngine ,
207
+ callbackQueue: DispatchQueue ,
208
+ callback: @escaping ( Result < Void , Error > ) -> Void ) {
193
209
let wrappedCallback : ( Result < Void , Error > ) -> Void = { result in callbackQueue. async { callback ( result) } }
194
210
195
211
let ocspURLs = CCryptoBoringSSL_X509_get1_ocsp ( certificate. underlying)
@@ -224,16 +240,27 @@ extension CertificatePolicy {
224
240
225
241
let requestData = Data ( UnsafeBufferPointer ( start: out. pointee, count: count) )
226
242
227
- let results = ThreadSafeArrayStore < Bool > ( )
243
+ let results = ThreadSafeArrayStore < Result < Bool , Error > > ( )
228
244
let group = DispatchGroup ( )
229
245
230
246
// Query each OCSP responder and record result
231
247
for index in 0 ..< ocspURLCount {
232
248
guard let urlStr = CCryptoBoringSSL_sk_OPENSSL_STRING_value ( ocspURLs, numericCast ( index) ) ,
233
249
let url = String ( validatingUTF8: urlStr) . flatMap ( { URL ( string: $0) } ) else {
250
+ results. append ( . failure( OCSPError . badURL) )
234
251
continue
235
252
}
236
253
254
+ let cacheKey = CacheKey ( url: url, request: requestData)
255
+ if let cachedResult = self . resultCache [ cacheKey] {
256
+ if cachedResult. timestamp + self . cacheTTL > DispatchTime . now ( ) {
257
+ results. append ( . success( cachedResult. isCertGood) )
258
+ continue
259
+ } else {
260
+ _ = self . resultCache. removeValue ( forKey: cacheKey)
261
+ }
262
+ }
263
+
237
264
var headers = HTTPClientHeaders ( )
238
265
headers. add ( name: " Content-Type " , value: " application/ocsp-request " )
239
266
url. host. map { headers. add ( name: " Host " , value: " \( $0) " ) }
@@ -246,16 +273,20 @@ extension CertificatePolicy {
246
273
defer { group. leave ( ) }
247
274
248
275
switch result {
249
- case . failure:
250
- return
276
+ case . failure( let error ) :
277
+ results . append ( . failure ( error ) )
251
278
case . success( let response) :
252
- guard let responseData = response. body else { return }
279
+ guard let responseData = response. body else {
280
+ results. append ( . failure( OCSPError . emptyResponseBody) )
281
+ return
282
+ }
253
283
254
284
let bytes = responseData. copyBytes ( )
255
285
// Convert response to bio then OCSP response
256
286
guard let bio = CCryptoBoringSSL_BIO_new ( CCryptoBoringSSL_BIO_s_mem ( ) ) ,
257
287
CCryptoBoringSSL_BIO_write ( bio, bytes, numericCast ( bytes. count) ) > 0 ,
258
288
let response = d2i_OCSP_RESPONSE_bio ( bio, nil ) else {
289
+ results. append ( . failure( OCSPError . responseConversionFailure) )
259
290
return
260
291
}
261
292
defer { CCryptoBoringSSL_BIO_free ( bio) }
@@ -266,6 +297,7 @@ extension CertificatePolicy {
266
297
CCryptoBoringSSL_OBJ_obj2nid ( response. pointee. responseBytes. pointee. responseType) == NID_id_pkix_OCSP_basic,
267
298
let basicResp = OCSP_response_get1_basic ( response) ,
268
299
let basicRespData = basicResp. pointee. tbsResponseData? . pointee else {
300
+ results. append ( . failure( OCSPError . badResponse) )
269
301
return
270
302
}
271
303
defer { OCSP_BASICRESP_free ( basicResp) }
@@ -274,11 +306,14 @@ extension CertificatePolicy {
274
306
for i in 0 ..< sk_OCSP_SINGLERESP_num ( basicRespData. responses) {
275
307
guard let singleResp = sk_OCSP_SINGLERESP_value ( basicRespData. responses, numericCast ( i) ) ,
276
308
let certStatus = singleResp. pointee. certStatus else {
277
- continue
309
+ results. append ( . failure( OCSPError . badResponse) )
310
+ return
278
311
}
279
312
280
313
// Is the certificate in good status?
281
- results. append ( certStatus. pointee. type == V_OCSP_CERTSTATUS_GOOD)
314
+ let isCertGood = certStatus. pointee. type == V_OCSP_CERTSTATUS_GOOD
315
+ results. append ( . success( isCertGood) )
316
+ self . resultCache [ cacheKey] = CacheValue ( isCertGood: isCertGood, timestamp: DispatchTime . now ( ) )
282
317
break
283
318
}
284
319
}
@@ -287,19 +322,59 @@ extension CertificatePolicy {
287
322
288
323
group. notify ( queue: callbackQueue) {
289
324
// If there's no result then something must have gone wrong
290
- guard !results. isEmpty else {
325
+ guard !results. isEmpty, results. compactMap ( { $0. failure } ) . isEmpty else {
326
+ diagnosticsEngine. emit ( error: " OCSP failed. All results: \( results) " )
291
327
return wrappedCallback ( . failure( CertificatePolicyError . ocspFailure) )
292
328
}
293
329
// Is there response "bad status" response?
294
- guard results. get ( ) . first ( where: { !$0 } ) == nil else {
330
+ guard results. compactMap ( { $0 . success } ) . first ( where: { !$0 } ) == nil else {
295
331
return wrappedCallback ( . failure( CertificatePolicyError . invalidCertChain) )
296
332
}
297
333
wrappedCallback ( . success( ( ) ) )
298
334
}
299
335
}
300
- #endif
336
+
337
+ private struct CacheKey : Hashable {
338
+ let url : URL
339
+ let request : Data
340
+ }
341
+
342
+ private struct CacheValue {
343
+ let isCertGood : Bool
344
+ let timestamp : DispatchTime
345
+ }
346
+ }
347
+
348
+ private extension Result {
349
+ var failure : Failure ? {
350
+ switch self {
351
+ case . failure( let failure) :
352
+ return failure
353
+ case . success:
354
+ return nil
355
+ }
356
+ }
357
+
358
+ var success : Success ? {
359
+ switch self {
360
+ case . failure:
361
+ return nil
362
+ case . success( let value) :
363
+ return value
364
+ }
365
+ }
301
366
}
302
367
368
+ private extension HTTPClient {
369
+ static func makeDefault( callbackQueue: DispatchQueue ) -> HTTPClient {
370
+ var httpClientConfig = HTTPClientConfiguration ( )
371
+ httpClientConfig. callbackQueue = callbackQueue
372
+ httpClientConfig. requestTimeout = . seconds( 1 )
373
+ return HTTPClient ( configuration: httpClientConfig)
374
+ }
375
+ }
376
+ #endif
377
+
303
378
// MARK: - Supporting methods and types
304
379
305
380
extension CertificatePolicy {
@@ -411,6 +486,13 @@ enum CertificatePolicyError: Error, Equatable {
411
486
case ocspFailure
412
487
}
413
488
489
+ private enum OCSPError : Error {
490
+ case badURL
491
+ case emptyResponseBody
492
+ case responseConversionFailure
493
+ case badResponse
494
+ }
495
+
414
496
// MARK: - Certificate policies
415
497
416
498
/// Default policy for validating certificates used to sign package collections.
@@ -459,9 +541,7 @@ struct DefaultCertificatePolicy: CertificatePolicy {
459
541
self . diagnosticsEngine = diagnosticsEngine
460
542
461
543
#if !os(macOS)
462
- var httpClientConfig = HTTPClientConfiguration ( )
463
- httpClientConfig. callbackQueue = callbackQueue
464
- self . httpClient = HTTPClient ( configuration: httpClientConfig)
544
+ self . httpClient = HTTPClient . makeDefault ( callbackQueue: callbackQueue)
465
545
#endif
466
546
}
467
547
@@ -547,9 +627,7 @@ struct AppleDeveloperCertificatePolicy: CertificatePolicy {
547
627
self . diagnosticsEngine = diagnosticsEngine
548
628
549
629
#if !os(macOS)
550
- var httpClientConfig = HTTPClientConfiguration ( )
551
- httpClientConfig. callbackQueue = callbackQueue
552
- self . httpClient = HTTPClient ( configuration: httpClientConfig)
630
+ self . httpClient = HTTPClient . makeDefault ( callbackQueue: callbackQueue)
553
631
#endif
554
632
}
555
633
0 commit comments