-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use installed Android cacerts for URLSession #5163
base: main
Are you sure you want to change the base?
Changes from all commits
7f52f2c
35b9955
52453cb
b367d5b
2179d94
b6d7191
9fdcfd1
a8bc4f3
bda9bd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -220,6 +220,64 @@ extension _EasyHandle { | |
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionCAINFO, caInfo).asError() | ||
} | ||
return | ||
} else { | ||
// When no certificate file has been specified, assemble all the certificate files | ||
// from the Android certificate store and writes them to a single `cacerts.pem` file | ||
|
||
// See https://github.com/apple/swift-nio-ssl/blob/main/Sources/NIOSSL/AndroidCABundle.swift | ||
let certsFolders = [ | ||
"/apex/com.android.conscrypt/cacerts", // >= Android14 | ||
"/system/etc/security/cacerts" // < Android14 | ||
] | ||
|
||
let aggregateCertPath = NSTemporaryDirectory() + "/cacerts-\(UUID().uuidString).pem" | ||
|
||
if FileManager.default.createFile(atPath: aggregateCertPath, contents: nil) == false { | ||
return | ||
} | ||
|
||
guard let fs = FileHandle(forWritingAtPath: aggregateCertPath) else { | ||
return | ||
} | ||
|
||
// write a header | ||
fs.write(""" | ||
## Bundle of CA Root Certificates | ||
## Auto-generated on \(Date()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may want to give this a specific format instead of just relying on the debug description. |
||
## by aggregating certificates from: \(certsFolders) | ||
|
||
""".data(using: .utf8)!) | ||
|
||
// Go through each folder and load each certificate file (ending with ".0"), | ||
// and append them together into a single aggreagate file that curl can load. | ||
// The .0 files will contain some extra metadata, but libcurl only cares about the | ||
// -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- sections, | ||
// so we can naïvely concatenate them all and libcurl will understand the bundle. | ||
for certsFolder in certsFolders { | ||
let certsFolderURL = URL(fileURLWithPath: certsFolder) | ||
if (try? certsFolderURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) != true { continue } | ||
let certURLs = try! FileManager.default.contentsOfDirectory(at: certsFolderURL, includingPropertiesForKeys: [.isRegularFileKey, .isReadableKey]) | ||
for certURL in certURLs { | ||
// certificate files have names like "53a1b57a.0" | ||
if certURL.pathExtension != "0" { continue } | ||
do { | ||
try? fs.write(contentsOf: Data(contentsOf: certURL)) | ||
} catch { | ||
// ignore individual errors and soldier on… | ||
continue | ||
} | ||
} | ||
} | ||
|
||
try! fs.close() | ||
|
||
aggregateCertPath.withCString { pathPtr in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// note that it would be nice to use CFURLSessionOptionCAPATH instead | ||
// (see https://curl.se/libcurl/c/CURLOPT_CAPATH.html) | ||
// but it requires `c_rehash` to be run on the folder, which Android doesn't do | ||
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionCAINFO, UnsafeMutablePointer(mutating: pathPtr)).asError() | ||
} | ||
return | ||
} | ||
#endif | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this file have to be cleaned up at some point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I'm not sure exactly when libcurl will read this file, and so we don't know when we can delete it. In the case of an Android app,
NSTemporaryDirectory()
will be the app'scaches
folder, and so will eventually be cleaned up, but still leaving stay files around like this is not good form.I think the best solution would be rather than using CURLOPT_CAINFO (as exposed through
CFURLSessionOptionCAINFO
), we would instead use CURLOPT_CAINFO_BLOB, which would mean we wouldn't need to create any temporary file at all. But not only does this not currently have any equivalentCFURLSessionOption*
property, it would also require bringing in thecurl_blob
struct from libcurl, which seemed a bit much just for this Android feature. OTOH, I don't see any other way to avoid the issue of leaving behind old aggregate certificate files.If you think it is worth investigating, I can look into it…