@@ -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