diff --git a/FilesProvider.xcodeproj/project.pbxproj b/FilesProvider.xcodeproj/project.pbxproj index c8f628c..830f66c 100644 --- a/FilesProvider.xcodeproj/project.pbxproj +++ b/FilesProvider.xcodeproj/project.pbxproj @@ -132,6 +132,9 @@ 79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; }; 79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; }; 79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; }; + CE27C49B219BFFA0000BE23C /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */; }; + CE27C49C219C041E000BE23C /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */; }; + CE27C49D219C041E000BE23C /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -206,6 +209,7 @@ 79D903531FAB647400D61D31 /* FilesProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesProviderTests.swift; sourceTree = ""; }; 79D903551FAB647400D61D31 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = ""; }; + CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustPolicy.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -352,6 +356,7 @@ 798654321E8874BC002FA550 /* FTPHelper.swift */, 799396971D48C02300086753 /* SMBClient.swift */, 799396981D48C02300086753 /* SMBFileProvider.swift */, + CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */, ); path = Sources; sourceTree = ""; @@ -575,6 +580,7 @@ 799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */, 799396D41D48C02300086753 /* SMB2Tree.swift in Sources */, 7924B1A21D89DAE000589DB7 /* Options.swift in Sources */, + CE27C49D219C041E000BE23C /* ServerTrustPolicy.swift in Sources */, 792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */, 7936BC121E880F5700A6C81C /* FTPFileProvider.swift in Sources */, 79480FF61E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */, @@ -619,6 +625,7 @@ 799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */, 799396D51D48C02300086753 /* SMB2Tree.swift in Sources */, 7924B1A31D89DAE000589DB7 /* Options.swift in Sources */, + CE27C49C219C041E000BE23C /* ServerTrustPolicy.swift in Sources */, 792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */, 7936BC131E880F5700A6C81C /* FTPFileProvider.swift in Sources */, 79480FF71E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */, @@ -663,6 +670,7 @@ 799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */, 799396D61D48C02300086753 /* SMB2Tree.swift in Sources */, 7924B1A41D89DAE000589DB7 /* Options.swift in Sources */, + CE27C49B219BFFA0000BE23C /* ServerTrustPolicy.swift in Sources */, 792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */, 7936BC141E880F5700A6C81C /* FTPFileProvider.swift in Sources */, 79480FF81E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */, diff --git a/Sources/FPSStreamTask.swift b/Sources/FPSStreamTask.swift index 229a7e8..47cbf74 100644 --- a/Sources/FPSStreamTask.swift +++ b/Sources/FPSStreamTask.swift @@ -68,8 +68,8 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate { return FileProviderStreamTask.streamTasks[_taskIdentifier] } - /// Trust all certificates if true, Otherwise validate certificate chain. - public var trustAllCertificates: Bool = false + /// Trust all certificates if `disableEvaluation`, Otherwise validate certificate chain. + public var serverTrustPolicy: ServerTrustPolicy = .performDefaultEvaluation(validateHost: true) /** * An identifier uniquely identifies the task within a given session. @@ -418,7 +418,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate { inputStream.setProperty(securityLevel.rawValue, forKey: .socketSecurityLevelKey) outputStream.setProperty(securityLevel.rawValue, forKey: .socketSecurityLevelKey) - if trustAllCertificates { + if serverTrustPolicy.evaluate() { // ※ Called, After setProperty securityLevel addTrustAllCertificatesSettings() } @@ -638,7 +638,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate { isSecure = true if let inputStream = self.inputStream, let outputStream = self.outputStream, inputStream.property(forKey: .socketSecurityLevelKey) as? String == StreamSocketSecurityLevel.none.rawValue { - if self.trustAllCertificates { + if serverTrustPolicy.evaluate() { // ※ Called, Before setProperty securityLevel self.addTrustAllCertificatesSettings() } diff --git a/Sources/FTPFileProvider.swift b/Sources/FTPFileProvider.swift index 920d5ef..a821ef6 100644 --- a/Sources/FTPFileProvider.swift +++ b/Sources/FTPFileProvider.swift @@ -217,10 +217,10 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera public var securedDataConnection: Bool = true /** - Trust all certificates if true, Otherwise validate certificate chain. - Default is `false`. + Trust all certificates if `disableEvaluation`, Otherwise validate certificate chain. + Default is `performDefaultEvaluation`. */ - public var trustAllCertificates: Bool = false + public var serverTrustPolicy: ServerTrustPolicy = .performDefaultEvaluation(validateHost: true) open func contentsOfDirectory(path: String, completionHandler: @escaping ([FileObject], Error?) -> Void) { self.contentsOfDirectory(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler) @@ -241,7 +241,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera let path = ftpPath(apath) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { self.dispatch_queue.async { @@ -297,7 +297,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera let path = ftpPath(apath) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { self.dispatch_queue.async { @@ -470,7 +470,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { self.dispatch_queue.async { @@ -521,7 +521,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { self.dispatch_queue.async { @@ -585,7 +585,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { self.dispatch_queue.async { @@ -640,7 +640,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { self.dispatch_queue.async { @@ -703,7 +703,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { self.dispatch_queue.async { @@ -785,7 +785,7 @@ extension FTPFileProvider { progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - task.trustAllCertificates = trustAllCertificates + task.serverTrustPolicy = serverTrustPolicy self.ftpLogin(task) { (error) in if let error = error { completionHandler?(error) diff --git a/Sources/FTPHelper.swift b/Sources/FTPHelper.swift index 036ffed..b7eba06 100644 --- a/Sources/FTPHelper.swift +++ b/Sources/FTPHelper.swift @@ -194,7 +194,7 @@ internal extension FTPFileProvider { let passiveTask = self.session.fpstreamTask(withHostName: host, port: port) if self.baseURL?.scheme == "ftps" || self.baseURL?.scheme == "ftpes" || self.baseURL?.port == 990 { - passiveTask.trustAllCertificates = task.trustAllCertificates + passiveTask.serverTrustPolicy = task.serverTrustPolicy passiveTask.startSecureConnection() } passiveTask.securityLevel = .tlSv1 @@ -238,7 +238,7 @@ internal extension FTPFileProvider { let passiveTask = self.session.fpstreamTask(withHostName: host, port: port) if self.baseURL?.scheme == "ftps" || self.baseURL?.scheme == "ftpes" || self.baseURL?.port == 990 { - passiveTask.trustAllCertificates = task.trustAllCertificates + passiveTask.serverTrustPolicy = task.serverTrustPolicy passiveTask.startSecureConnection() } passiveTask.securityLevel = .tlSv1 @@ -260,7 +260,7 @@ internal extension FTPFileProvider { } let activeTask = self.session.fpstreamTask(withNetService: service) if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 { - activeTask.trustAllCertificates = task.trustAllCertificates + activeTask.serverTrustPolicy = task.serverTrustPolicy activeTask.startSecureConnection() } activeTask.resume() diff --git a/Sources/ServerTrustPolicy.swift b/Sources/ServerTrustPolicy.swift new file mode 100644 index 0000000..601b6a8 --- /dev/null +++ b/Sources/ServerTrustPolicy.swift @@ -0,0 +1,83 @@ +// +// ServerTrustPolicy.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +// MARK: - ServerTrustPolicy +/// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when +/// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust +/// with a given set of criteria to determine whether the server trust is valid and the connection should be made. +/// +/// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other +/// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged +/// to route all communication over an HTTPS connection with pinning enabled. +/// +/// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to +/// validate the host provided by the challenge. Applications are encouraged to always +/// validate the host in production environments to guarantee the validity of the server's +/// certificate chain. +/// +/// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid. +/// +public enum ServerTrustPolicy { + case performDefaultEvaluation(validateHost: Bool) + case disableEvaluation + + // MARK: - Evaluation + /// Evaluates whether the server trust is valid. + /// + /// - returns: Whether the server trust is valid. + public func evaluate() -> Bool { + var serverTrustIsValid = false + + switch self { + case .performDefaultEvaluation(_): + break + case .disableEvaluation: + serverTrustIsValid = true + } + + return serverTrustIsValid + } + + /// Evaluates whether the server trust is valid for the given host. + /// + /// - parameter serverTrust: The server trust to evaluate. + /// - parameter host: The host of the challenge protection space. + /// + /// - returns: Whether the server trust is valid. + public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool { + var serverTrustIsValid = false + + switch self { + case let .performDefaultEvaluation(validateHost): + let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) + SecTrustSetPolicies(serverTrust, policy) + case .disableEvaluation: + serverTrustIsValid = true + } + + return serverTrustIsValid + } +}