Skip to content

Commit 7f52f2c

Browse files
committed
Use installed Android cacerts for URLSession
1 parent 0129358 commit 7f52f2c

File tree

1 file changed

+58
-0
lines changed

1 file changed

+58
-0
lines changed

Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,64 @@ extension _EasyHandle {
220220
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionCAINFO, caInfo).asError()
221221
}
222222
return
223+
} else {
224+
// When no certificate file has been specified, assemble all the certificate files
225+
// from the Android certificate store and writes them to a single `cacerts.pem` file
226+
//
227+
// See https://android.googlesource.com/platform/frameworks/base/+/8b192b19f264a8829eac2cfaf0b73f6fc188d933%5E%21/#F0
228+
229+
// See https://github.com/apple/swift-nio-ssl/blob/d1088ebe0789d9eea231b40741831f37ab654b61/Sources/NIOSSL/AndroidCABundle.swift#L30
230+
let certsFolders = [
231+
"/apex/com.android.conscrypt/cacerts", // >= Android14
232+
"/system/etc/security/cacerts" // < Android14
233+
]
234+
235+
let aggregateCertPath = NSTemporaryDirectory() + "/cacerts-\(UUID().uuidString).pem"
236+
237+
if FileManager.default.createFile(atPath: aggregateCertPath, contents: nil) == false {
238+
return
239+
}
240+
241+
guard let fs = FileHandle(forWritingAtPath: aggregateCertPath) else {
242+
return
243+
}
244+
245+
// write a header
246+
fs.write("""
247+
## Bundle of CA Root Certificates
248+
## Auto-generated on \(Date())
249+
## by aggregating certificates from: \(certsFolders)
250+
251+
""".data(using: .utf8)!)
252+
253+
// Go through each folder and load each certificate file (ending with ".0"),
254+
// and append them together into a single aggreagate file tha curl can load.
255+
// The .0 files will contain some extra metadata, but libcurl only cares about the
256+
// -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- sections,
257+
// so we can naïvely concatenate them all and libcurl will understand the bundle.
258+
for certsFolder in certsFolders {
259+
let certsFolderURL = URL(fileURLWithPath: certsFolder)
260+
if (try? certsFolderURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) != true { continue }
261+
let certURLs = try! FileManager.default.contentsOfDirectory(at: certsFolderURL, includingPropertiesForKeys: [.isRegularFileKey, .isReadableKey])
262+
for certURL in certURLs {
263+
// certificate files have names like "53a1b57a.0"
264+
if certURL.pathExtension != "0" { continue }
265+
do {
266+
if try certURL.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile != true { continue }
267+
if try certURL.resourceValues(forKeys: [.isReadableKey]).isReadable != true { continue }
268+
try fs.write(contentsOf: try Data(contentsOf: certURL))
269+
} catch {
270+
// ignore individual errors and soldier on…
271+
//logger.warning("bootstrapSSLCertificates: error reading certificate file \(certURL.path): \(error)")
272+
continue
273+
}
274+
}
275+
}
276+
277+
try! fs.close()
278+
279+
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionCAINFO, aggregateCertPath).asError()
280+
return
223281
}
224282
#endif
225283

0 commit comments

Comments
 (0)