in Sources/PackageCollectionsSigning/Certificate/CertificatePolicy.swift [251:412]
func checkStatus(certificate: Certificate,
issuer: Certificate,
anchorCerts: [Certificate]?,
httpClient: HTTPClient,
callbackQueue: DispatchQueue,
callback: @escaping (Result<Void, Error>) -> Void) {
let wrappedCallback: (Result<Void, Error>) -> Void = { result in callbackQueue.async { callback(result) } }
let ocspURLs = certificate.withUnsafeMutablePointer { CCryptoBoringSSL_X509_get1_ocsp($0) }
defer { CCryptoBoringSSL_sk_OPENSSL_STRING_free(ocspURLs) }
let ocspURLCount = CCryptoBoringSSL_sk_OPENSSL_STRING_num(ocspURLs)
// Nothing to do if no OCSP URLs. Use `supportsOCSP` to require OCSP support if needed.
guard ocspURLCount > 0 else { return wrappedCallback(.success(())) }
// Construct the OCSP request
let digest = CCryptoBoringSSL_EVP_sha1()
let certid = certificate.withUnsafeMutablePointer { certPtr in
issuer.withUnsafeMutablePointer { issPtr in
OCSP_cert_to_id(digest, certPtr, issPtr)
}
}
let request = OCSP_REQUEST_new()
defer { OCSP_REQUEST_free(request) }
guard OCSP_request_add0_id(request, certid) != nil else {
return wrappedCallback(.failure(CertificatePolicyError.ocspSetupFailure))
}
// Write the request binary to memory bio
let bio = CCryptoBoringSSL_BIO_new(CCryptoBoringSSL_BIO_s_mem())
defer { CCryptoBoringSSL_BIO_free(bio) }
guard i2d_OCSP_REQUEST_bio(bio, request) > 0 else {
return wrappedCallback(.failure(CertificatePolicyError.ocspSetupFailure))
}
// Copy from bio to byte array then convert to Data
var count = 0
var out: UnsafePointer<UInt8>?
guard CCryptoBoringSSL_BIO_mem_contents(bio, &out, &count) > 0 else {
return wrappedCallback(.failure(CertificatePolicyError.ocspSetupFailure))
}
let requestData = Data(UnsafeBufferPointer(start: out, count: count))
let results = ThreadSafeArrayStore<Result<Bool, Error>>()
let group = DispatchGroup()
// Query each OCSP responder and record result
for index in 0 ..< ocspURLCount {
guard let urlStr = CCryptoBoringSSL_sk_OPENSSL_STRING_value(ocspURLs, numericCast(index)),
let url = String(validatingUTF8: urlStr).flatMap({ URL(string: $0) }) else {
results.append(.failure(OCSPError.badURL))
continue
}
let cacheKey = CacheKey(url: url, request: requestData)
if let cachedResult = self.resultCache[cacheKey] {
if cachedResult.timestamp + self.cacheTTL > DispatchTime.now() {
results.append(.success(cachedResult.isCertGood))
continue
}
}
var headers = HTTPClientHeaders()
headers.add(name: "Content-Type", value: "application/ocsp-request")
guard let host = url.host else {
results.append(.failure(OCSPError.badURL))
continue
}
headers.add(name: "Host", value: host)
var options = HTTPClientRequest.Options()
options.validResponseCodes = [200]
group.enter()
httpClient.post(url, body: requestData, headers: headers, options: options) { result in
defer { group.leave() }
switch result {
case .failure(let error):
results.append(.failure(error))
case .success(let response):
guard let responseData = response.body else {
results.append(.failure(OCSPError.emptyResponseBody))
return
}
let bytes = responseData.copyBytes()
// Convert response to bio then OCSP response
let bio = CCryptoBoringSSL_BIO_new(CCryptoBoringSSL_BIO_s_mem())
defer { CCryptoBoringSSL_BIO_free(bio) }
guard CCryptoBoringSSL_BIO_write(bio, bytes, numericCast(bytes.count)) > 0 else {
results.append(.failure(OCSPError.responseConversionFailure))
return
}
let response = d2i_OCSP_RESPONSE_bio(bio, nil)
defer { OCSP_RESPONSE_free(response) }
guard let response = response else {
results.append(.failure(OCSPError.responseConversionFailure))
return
}
let basicResp = OCSP_response_get1_basic(response)
defer { OCSP_BASICRESP_free(basicResp) }
guard let basicResp = basicResp else {
results.append(.failure(OCSPError.responseConversionFailure))
return
}
// This is just the OCSP response status, not the certificate's status
guard OCSP_response_status(response) == OCSP_RESPONSE_STATUS_SUCCESSFUL,
CCryptoBoringSSL_OBJ_obj2nid(response.pointee.responseBytes.pointee.responseType) == NID_id_pkix_OCSP_basic else {
results.append(.failure(OCSPError.badResponse))
return
}
let x509Store = CCryptoBoringSSL_X509_STORE_new()
defer { CCryptoBoringSSL_X509_STORE_free(x509Store) }
anchorCerts?.forEach { anchorCert in
_ = anchorCert.withUnsafeMutablePointer { CCryptoBoringSSL_X509_STORE_add_cert(x509Store, $0) }
}
// Verify the OCSP response to make sure we can trust it
guard OCSP_basic_verify(basicResp, nil, x509Store, 0) > 0 else {
results.append(.failure(OCSPError.responseVerificationFailure))
return
}
// Inspect the OCSP response
let basicRespData = basicResp.pointee.tbsResponseData.pointee
for i in 0 ..< sk_OCSP_SINGLERESP_num(basicRespData.responses) {
guard let singleResp = sk_OCSP_SINGLERESP_value(basicRespData.responses, numericCast(i)),
let certStatus = singleResp.pointee.certStatus else {
results.append(.failure(OCSPError.badResponse))
return
}
// Is the certificate in good status?
let isCertGood = certStatus.pointee.type == V_OCSP_CERTSTATUS_GOOD
results.append(.success(isCertGood))
self.resultCache[cacheKey] = CacheValue(isCertGood: isCertGood, timestamp: DispatchTime.now())
break
}
}
}
}
group.notify(queue: callbackQueue) {
// Fail open: As long as no one says the cert is revoked we assume it's ok. If we receive no responses or
// all of them are failures we'd still assume the cert is not revoked.
guard results.compactMap({ $0.success }).first(where: { !$0 }) == nil else {
return wrappedCallback(.failure(CertificatePolicyError.invalidCertChain))
}
wrappedCallback(.success(()))
}
}