Skip to content

Commit f17b4c2

Browse files
authored
Merge pull request #3 from skiptools/cache-cacerts
Do not recreate the cacerts cache unless the source certificates have changed
2 parents 62e1cc8 + 0f56ee6 commit f17b4c2

File tree

1 file changed

+65
-30
lines changed

1 file changed

+65
-30
lines changed

Sources/AndroidNative/AndroidNative.swift

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,68 @@ public class AndroidBootstrap {
1919
/// See https://github.com/apple/swift-nio-ssl/blob/d1088ebe0789d9eea231b40741831f37ab654b61/Sources/NIOSSL/AndroidCABundle.swift#L30
2020
@available(macOS 13.0, iOS 16.0, *)
2121
public static func setupCACerts(force: Bool = false, fromCertficateFolders certsFolders: [String] = ["/system/etc/security/cacerts", "/apex/com.android.conscrypt/cacerts"]) throws {
22+
//setenv("URLSessionCertificateAuthorityInfoFile", "INSECURE_SSL_NO_VERIFY", 1) // disables all certificate verification
23+
//setenv("URLSessionCertificateAuthorityInfoFile", "/system/etc/security/cacerts/", 1) // doesn't work for directories
24+
2225
// if someone else has already set URLSessionCertificateAuthorityInfoFile then do not override unless forced
2326
if !force && getenv("URLSessionCertificateAuthorityInfoFile") != nil {
2427
return
2528
}
2629

30+
// get a list of all the certificate URLs
31+
var certURLs: [URL] = []
32+
for certsFolder in certsFolders {
33+
let certsFolderURL = URL(fileURLWithPath: certsFolder)
34+
if (try? certsFolderURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) != true { continue }
35+
let certFolderURLs = try FileManager.default.contentsOfDirectory(at: certsFolderURL, includingPropertiesForKeys: [.isRegularFileKey, .isReadableKey, .fileSizeKey, .contentModificationDateKey])
36+
for certURL in certFolderURLs {
37+
//logger.debug("setupCACerts: certURL=\(certURL)")
38+
// certificate files have names like "53a1b57a.0"
39+
if certURL.pathExtension != "0" { continue }
40+
do {
41+
if try certURL.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile == false { continue }
42+
if try certURL.resourceValues(forKeys: [.isReadableKey]).isReadable == false { continue }
43+
certURLs.append(certURL)
44+
} catch {
45+
//logger.warning("setupCACerts: error reading certificate file \(certURL.path): \(error)")
46+
continue
47+
}
48+
}
49+
}
50+
certURLs = certURLs.sorted { $0.path < $1.path }
51+
52+
// generate a checksum of all the certificate URL names and their sizes and modification times in order to define the aggregate file name
53+
// we do this so was can safely cache the aggregate certificate file without re-creating it every time
54+
var urlSummary = ""
55+
for certURL in certURLs {
56+
urlSummary.append(certURL.path)
57+
urlSummary.append("|")
58+
urlSummary.append((try? certURL.resourceValues(forKeys: [.fileSizeKey]).fileSize?.description) ?? "")
59+
urlSummary.append("|")
60+
urlSummary.append((try? certURL.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate?.timeIntervalSince1970.description) ?? "")
61+
urlSummary.append("|")
62+
}
63+
let checksum = crc32Checksum(of: urlSummary.data(using: .utf8) ?? Data())
64+
2765
var cacheFolder = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
2866
var cacheFolderIsDir: Bool = false
2967
if !FileManager.default.fileExists(atPath: cacheFolder.path, isDirectory: &cacheFolderIsDir) || !cacheFolderIsDir {
3068
cacheFolder = URL.temporaryDirectory
3169
}
32-
//logger.debug("setupCACerts: \(cacheFolder)")
33-
let generatedCacertsURL = cacheFolder.appendingPathComponent("cacerts-aggregate.pem")
34-
//logger.debug("setupCACerts: generatedCacertsURL=\(generatedCacertsURL)")
70+
let generatedCacertsURL = cacheFolder.appendingPathComponent("cacerts-aggregate-\(checksum).pem")
3571

36-
let contents = try FileManager.default.contentsOfDirectory(at: cacheFolder, includingPropertiesForKeys: nil)
37-
//logger.debug("setupCACerts: cacheFolder=\(cacheFolder) contents=\(contents)")
38-
39-
// clear any previous generated certificates file that may have been created by this app
4072
if FileManager.default.fileExists(atPath: generatedCacertsURL.path) {
73+
// cached aggregate file already exists; just re-use
74+
if !force {
75+
setenv("URLSessionCertificateAuthorityInfoFile", generatedCacertsURL.path, 1)
76+
return
77+
}
78+
79+
// clear any previous generated certificates file that may have been created by this app
4180
try FileManager.default.removeItem(atPath: generatedCacertsURL.path)
4281
}
4382

44-
let created = FileManager.default.createFile(atPath: generatedCacertsURL.path, contents: nil)
45-
//logger.debug("setupCACerts: created file: \(created): \(generatedCacertsURL.path)")
46-
83+
_ = FileManager.default.createFile(atPath: generatedCacertsURL.path, contents: nil)
4784
let fs = try FileHandle(forWritingTo: generatedCacertsURL)
4885
defer { try? fs.close() }
4986

@@ -60,30 +97,28 @@ public class AndroidBootstrap {
6097
// The .0 files will contain some extra metadata, but libcurl only cares about the
6198
// -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- sections,
6299
// so we can naïvely concatenate them all and libcurl will understand the bundle.
63-
for certsFolder in certsFolders {
64-
let certsFolderURL = URL(fileURLWithPath: certsFolder)
65-
if (try? certsFolderURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) != true { continue }
66-
let certURLs = try FileManager.default.contentsOfDirectory(at: certsFolderURL, includingPropertiesForKeys: [.isRegularFileKey, .isReadableKey])
67-
for certURL in certURLs {
68-
//logger.debug("setupCACerts: certURL=\(certURL)")
69-
// certificate files have names like "53a1b57a.0"
70-
if certURL.pathExtension != "0" { continue }
71-
do {
72-
if try certURL.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile == false { continue }
73-
if try certURL.resourceValues(forKeys: [.isReadableKey]).isReadable == false { continue }
74-
try fs.write(contentsOf: try Data(contentsOf: certURL))
75-
} catch {
76-
//logger.warning("setupCACerts: error reading certificate file \(certURL.path): \(error)")
77-
continue
100+
for certURL in certURLs {
101+
try fs.write(contentsOf: try Data(contentsOf: certURL))
102+
}
103+
104+
setenv("URLSessionCertificateAuthorityInfoFile", generatedCacertsURL.path, 1)
105+
}
106+
107+
private static func crc32Checksum(of data: Data) -> UInt32 {
108+
var crc: UInt32 = 0xFFFFFFFF
109+
110+
for byte in data {
111+
crc = crc ^ UInt32(byte)
112+
for _ in 0..<8 {
113+
if crc & 1 == 1 {
114+
crc = (crc >> 1) ^ 0xEDB88320
115+
} else {
116+
crc = crc >> 1
78117
}
79118
}
80119
}
81120

82-
83-
//setenv("URLSessionCertificateAuthorityInfoFile", "INSECURE_SSL_NO_VERIFY", 1) // disables all certificate verification
84-
//setenv("URLSessionCertificateAuthorityInfoFile", "/system/etc/security/cacerts/", 1) // doesn't work for directories
85-
setenv("URLSessionCertificateAuthorityInfoFile", generatedCacertsURL.path, 1)
86-
//logger.debug("setupCACerts: set URLSessionCertificateAuthorityInfoFile=\(generatedCacertsURL.path)")
121+
return ~crc
87122
}
88123
}
89124
#endif

0 commit comments

Comments
 (0)