diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Interaction.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Interaction.swift index e035a35..0bf6e74 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Interaction.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Interaction.swift @@ -100,10 +100,22 @@ extension TunnelKitProvider { case socketActivity /// Credentials authentication failed. - case authenticationFailed + case authentication + + /// TLS could not be initialized (e.g. malformed CA or client PEMs). + case tlsInitialization + + /// TLS server verification failed. + case tlsServerVerification /// TLS handshake failed. - case tlsFailed + case tlsHandshake + + /// The encryption logic could not be initialized (e.g. PRNG, algorithms). + case encryptionInitialization + + /// Data encryption/decryption failed. + case encryptionData /// Tunnel timed out. case timeout diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index 71ba869..f8f25a1 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -575,7 +575,7 @@ extension TunnelKitProvider: SessionProxyDelegate { extension TunnelKitProvider { - // MARK: Helpers + // MARK: Logging private func configureLogging(debug: Bool, customFormat: String? = nil) { let logLevel: SwiftyBeaver.Level = (debug ? .debug : .info) @@ -603,38 +603,6 @@ extension TunnelKitProvider { } } - private func setErrorStatus(with error: Error) { - guard let lastErrorKey = cfg.lastErrorKey else { - return - } - let providerError: ProviderError - if let se = error as? SessionError { - switch se { - case .badCredentials: - providerError = .authenticationFailed - - case .peerVerification, .tlsError: - providerError = .tlsFailed - - case .negotiationTimeout, .pingTimeout: - providerError = .timeout - - default: - providerError = .unexpectedReply - } - } else { - providerError = error as? ProviderError ?? .linkError - } - defaults?.set(providerError.rawValue, forKey: lastErrorKey) - } - - private func clearErrorStatus() { - guard let lastErrorKey = cfg.lastErrorKey else { - return - } - defaults?.removeObject(forKey: lastErrorKey) - } - private func logCurrentSSID() { if let ssid = observer.currentWifiNetworkName() { log.debug("Current SSID: '\(ssid)'") @@ -647,4 +615,59 @@ extension TunnelKitProvider { // let anyObject = object as AnyObject // return Unmanaged.passUnretained(anyObject).toOpaque() // } + + // MARK: Errors + + private func setErrorStatus(with error: Error) { + guard let lastErrorKey = cfg.lastErrorKey else { + return + } + defaults?.set(unifiedError(from: error).rawValue, forKey: lastErrorKey) + } + + private func clearErrorStatus() { + guard let lastErrorKey = cfg.lastErrorKey else { + return + } + defaults?.removeObject(forKey: lastErrorKey) + } + + private func unifiedError(from error: Error) -> ProviderError { + if let te = error.tunnelKitErrorCode() { + switch te { + case .cryptoBoxRandomGenerator, .cryptoBoxAlgorithm: + return .encryptionInitialization + + case .cryptoBoxEncryption, .cryptoBoxHMAC: + return .encryptionData + + case .tlsBoxCA, .tlsBoxClientCertificate, .tlsBoxClientKey: + return .tlsInitialization + + case .tlsBoxServerCertificate, .tlsBoxServerEKU: + return .tlsServerVerification + + case .tlsBoxHandshake: + return .tlsHandshake + + case .dataPathOverflow, .dataPathPeerIdMismatch: + return .unexpectedReply + } + } else if let se = error as? SessionError { + switch se { + case .negotiationTimeout, .pingTimeout: + return .timeout + + case .badCredentials: + return .authentication + + case .failedLinkWrite: + return .linkError + + default: + return .unexpectedReply + } + } + return error as? ProviderError ?? .linkError + } } diff --git a/TunnelKit/Sources/Core/Errors.h b/TunnelKit/Sources/Core/Errors.h index 953ca7a..29d4068 100644 --- a/TunnelKit/Sources/Core/Errors.h +++ b/TunnelKit/Sources/Core/Errors.h @@ -37,6 +37,7 @@ #import extern NSString *const TunnelKitErrorDomain; +extern NSString *const TunnelKitErrorKey; typedef NS_ENUM(NSInteger, TunnelKitErrorCode) { TunnelKitErrorCodeCryptoBoxRandomGenerator = 101, @@ -45,9 +46,9 @@ typedef NS_ENUM(NSInteger, TunnelKitErrorCode) { TunnelKitErrorCodeCryptoBoxAlgorithm = 104, TunnelKitErrorCodeTLSBoxCA = 201, TunnelKitErrorCodeTLSBoxHandshake = 202, - TunnelKitErrorCodeTLSBoxGeneric = 203, TunnelKitErrorCodeTLSBoxClientCertificate = 204, TunnelKitErrorCodeTLSBoxClientKey = 205, + TunnelKitErrorCodeTLSBoxServerCertificate = 206, TunnelKitErrorCodeDataPathOverflow = 301, TunnelKitErrorCodeDataPathPeerIdMismatch = 302 }; diff --git a/TunnelKit/Sources/Core/Errors.m b/TunnelKit/Sources/Core/Errors.m index 711f315..66f9055 100644 --- a/TunnelKit/Sources/Core/Errors.m +++ b/TunnelKit/Sources/Core/Errors.m @@ -36,4 +36,5 @@ #import "Errors.h" -NSString *const TunnelKitErrorDomain = @"TunnelKitNative"; +NSString *const TunnelKitErrorDomain = @"TunnelKitNative"; +NSString *const TunnelKitErrorKey = @"TunnelKitErrorKey"; diff --git a/TunnelKit/Sources/Core/SessionError.swift b/TunnelKit/Sources/Core/SessionError.swift index 4f37b6d..632fe28 100644 --- a/TunnelKit/Sources/Core/SessionError.swift +++ b/TunnelKit/Sources/Core/SessionError.swift @@ -36,6 +36,7 @@ // import Foundation +import __TunnelKitNative /// The possible errors raised/thrown during `SessionProxy` operation. public enum SessionError: String, Error { @@ -43,9 +44,6 @@ public enum SessionError: String, Error { /// The negotiation timed out. case negotiationTimeout - /// The peer failed to verify. - case peerVerification - /// The VPN session id is missing. case missingSessionId @@ -55,9 +53,6 @@ public enum SessionError: String, Error { /// The connection key is wrong or wasn't expected. case badKey - /// The TLS negotiation failed. - case tlsError - /// The control packet has an incorrect prefix payload. case wrongControlDataPrefix @@ -73,3 +68,18 @@ public enum SessionError: String, Error { /// The server couldn't ping back before timeout. case pingTimeout } + +extension Error { + func isTunnelKitError() -> Bool { + let te = self as NSError + return te.domain == TunnelKitErrorDomain + } + + func tunnelKitErrorCode() -> TunnelKitErrorCode? { + let te = self as NSError + guard te.domain == TunnelKitErrorDomain else { + return nil + } + return TunnelKitErrorCode(rawValue: te.code) + } +} diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index a9b3469..53a6d94 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -41,18 +41,6 @@ import __TunnelKitNative private let log = SwiftyBeaver.self -private extension Error { - func isTunnelError() -> Bool { - let te = self as NSError - return te.domain == TunnelKitErrorDomain - } - - func isDataPathOverflow() -> Bool { - let te = self as NSError - return te.domain == TunnelKitErrorDomain && te.code == TunnelKitErrorCode.dataPathOverflow.rawValue - } -} - /// Observes major events notified by a `SessionProxy`. public protocol SessionProxyDelegate: class { @@ -209,8 +197,9 @@ public class SessionProxy { // WARNING: runs in notification source queue (we know it's "queue", but better be safe than sorry) tlsObserver = NotificationCenter.default.addObserver(forName: .TLSBoxPeerVerificationError, object: nil, queue: nil) { (notification) in + let error = notification.userInfo?[TunnelKitErrorKey] as? Error self.queue.async { - self.deferStop(.shutdown, SessionError.peerVerification) + self.deferStop(.shutdown, error) } } @@ -614,7 +603,15 @@ public class SessionProxy { return } - guard let cipherTextOut = try? negotiationKey.tls.pullCipherText() else { + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.auth: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } log.verbose("TLS.auth: Still can't pull ciphertext") return } @@ -637,7 +634,15 @@ public class SessionProxy { log.debug("TLS.ifconfig: Put plaintext (PUSH_REQUEST)") try? negotiationKey.tls.putPlainText("PUSH_REQUEST\0") - guard let cipherTextOut = try? negotiationKey.tls.pullCipherText() else { + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.auth: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } log.verbose("TLS.ifconfig: Still can't pull ciphertext") return } @@ -717,8 +722,16 @@ public class SessionProxy { return } - guard let cipherTextOut = try? negotiationKey.tls.pullCipherText() else { - deferStop(.shutdown, SessionError.tlsError) + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.connect: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } + deferStop(.shutdown, e) return } @@ -745,9 +758,18 @@ public class SessionProxy { log.debug("TLS.connect: Put received ciphertext (\(cipherTextIn.count) bytes)") try? negotiationKey.tls.putCipherText(cipherTextIn) - if let cipherTextOut = try? negotiationKey.tls.pullCipherText() { + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() log.debug("TLS.connect: Send pulled ciphertext (\(cipherTextOut.count) bytes)") enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.connect: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } + log.verbose("TLS.connect: No available ciphertext to pull") } if negotiationKey.shouldOnTLSConnect() { @@ -976,7 +998,7 @@ public class SessionProxy { tunnel?.writePackets(decryptedPackets, completionHandler: nil) } catch let e { - guard !e.isTunnelError() else { + guard !e.isTunnelKitError() else { deferStop(.shutdown, e) return } @@ -1011,7 +1033,7 @@ public class SessionProxy { // log.verbose("Data: \(encryptedPackets.count) packets successfully written to LINK") } } catch let e { - guard !e.isTunnelError() else { + guard !e.isTunnelKitError() else { deferStop(.shutdown, e) return } diff --git a/TunnelKit/Sources/Core/TLSBox.m b/TunnelKit/Sources/Core/TLSBox.m index c3850f2..1598c18 100644 --- a/TunnelKit/Sources/Core/TLSBox.m +++ b/TunnelKit/Sources/Core/TLSBox.m @@ -51,7 +51,10 @@ static BOOL TLSBoxIsOpenSSLLoaded; int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { if (!ok) { - [[NSNotificationCenter defaultCenter] postNotificationName:TLSBoxPeerVerificationErrorNotification object:nil]; + NSError *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxCA); + [[NSNotificationCenter defaultCenter] postNotificationName:TLSBoxPeerVerificationErrorNotification + object:nil + userInfo:@{TunnelKitErrorKey: error}]; } return ok; } @@ -201,7 +204,7 @@ int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { } if ((ret < 0) && !BIO_should_retry(self.bioCipherTextOut)) { if (error) { - *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxGeneric); + *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxHandshake); } } return nil; @@ -219,7 +222,7 @@ int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { } if ((ret < 0) && !BIO_should_retry(self.bioPlainText)) { if (error) { - *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxGeneric); + *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxHandshake); } } return NO; @@ -241,7 +244,7 @@ int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { const int ret = BIO_write(self.bioCipherTextIn, text, (int)length); if (ret != length) { if (error) { - *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxGeneric); + *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxHandshake); } return NO; } @@ -262,7 +265,7 @@ int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { const int ret = BIO_write(self.bioPlainText, text, (int)length); if (ret != length) { if (error) { - *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxGeneric); + *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxHandshake); } return NO; }