@@ -19,31 +19,68 @@ public class AndroidBootstrap {
19
19
/// See https://github.com/apple/swift-nio-ssl/blob/d1088ebe0789d9eea231b40741831f37ab654b61/Sources/NIOSSL/AndroidCABundle.swift#L30
20
20
@available ( macOS 13 . 0 , iOS 16 . 0 , * )
21
21
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
+
22
25
// if someone else has already set URLSessionCertificateAuthorityInfoFile then do not override unless forced
23
26
if !force && getenv ( " URLSessionCertificateAuthorityInfoFile " ) != nil {
24
27
return
25
28
}
26
29
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
+
27
65
var cacheFolder = try FileManager . default. url ( for: . cachesDirectory, in: . userDomainMask, appropriateFor: nil , create: false )
28
66
var cacheFolderIsDir : Bool = false
29
67
if !FileManager. default. fileExists ( atPath: cacheFolder. path, isDirectory: & cacheFolderIsDir) || !cacheFolderIsDir {
30
68
cacheFolder = URL . temporaryDirectory
31
69
}
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 " )
35
71
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
40
72
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
41
80
try FileManager . default. removeItem ( atPath: generatedCacertsURL. path)
42
81
}
43
82
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 )
47
84
let fs = try FileHandle ( forWritingTo: generatedCacertsURL)
48
85
defer { try ? fs. close ( ) }
49
86
@@ -60,30 +97,28 @@ public class AndroidBootstrap {
60
97
// The .0 files will contain some extra metadata, but libcurl only cares about the
61
98
// -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- sections,
62
99
// 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
78
117
}
79
118
}
80
119
}
81
120
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
87
122
}
88
123
}
89
124
#endif
0 commit comments