diff --git a/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SecureConnection.swift b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SecureConnection.swift new file mode 100644 index 00000000..8257edb8 --- /dev/null +++ b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SecureConnection.swift @@ -0,0 +1,118 @@ +import Foundation +import Network +import Security + +/// SecureConnection handles the TLS connection with the SoftEther VPN server +class SecureConnection { + + // MARK: - Properties + + private var connection: NWConnection? + private let host: String + private let port: UInt16 + private let queue = DispatchQueue(label: "com.softether.connection", qos: .userInitiated) + + // MARK: - Initialization + + /// Initialize a secure connection + /// - Parameters: + /// - host: Server hostname or IP address + /// - port: Server port number + init(host: String, port: UInt16) { + self.host = host + self.port = port + } + + // MARK: - Public Methods + + /// Connect to the server using TLS + /// - Parameter completion: Callback with connection result + func connect(completion: @escaping (Bool, Error?) -> Void) { + let hostEndpoint = NWEndpoint.Host(host) + let portEndpoint = NWEndpoint.Port(rawValue: port)! + + // Create TLS parameters + let tlsOptions = NWProtocolTLS.Options() + + // Configure TLS for maximum compatibility with SoftEther + let securityOptions = tlsOptions.securityProtocolOptions + sec_protocol_options_set_tls_min_version(securityOptions, .TLSv12) + sec_protocol_options_set_tls_max_version(securityOptions, .TLSv13) + + // Allow all cipher suites for compatibility + sec_protocol_options_set_cipher_suites(securityOptions, nil, 0) + + // Disable certificate validation for initial development (ENABLE IN PRODUCTION) + sec_protocol_options_set_verify_block(securityOptions, { (_, _, trustResult, _) in + return true // Accept all certificates for testing + }, queue) + + // Create TCP options with TLS + let tcpOptions = NWProtocolTCP.Options() + tcpOptions.enableKeepalive = true + tcpOptions.keepaliveIdle = 30 + + // Create connection parameters + let parameters = NWParameters(tls: tlsOptions, tcp: tcpOptions) + + // Create the connection + connection = NWConnection(host: hostEndpoint, port: portEndpoint, using: parameters) + + // Set up state handling + connection?.stateUpdateHandler = { [weak self] state in + switch state { + case .ready: + completion(true, nil) + case .failed(let error): + self?.disconnect() + completion(false, error) + case .cancelled: + completion(false, NSError(domain: "SoftEtherError", code: 1000, userInfo: [NSLocalizedDescriptionKey: "Connection cancelled"])) + default: + break + } + } + + // Start the connection + connection?.start(queue: queue) + } + + /// Disconnect from the server + func disconnect() { + connection?.cancel() + connection = nil + } + + /// Send data to the server + /// - Parameters: + /// - data: Data to send + /// - completion: Callback with error if any + func send(data: Data, completion: @escaping (Error?) -> Void) { + guard let connection = connection, connection.state == .ready else { + completion(NSError(domain: "SoftEtherError", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Connection not ready"])) + return + } + + connection.send(content: data, completion: .contentProcessed { error in + completion(error) + }) + } + + /// Receive data from the server + /// - Parameter completion: Callback with received data and error if any + func receive(completion: @escaping (Data?, Error?) -> Void) { + guard let connection = connection, connection.state == .ready else { + completion(nil, NSError(domain: "SoftEtherError", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Connection not ready"])) + return + } + + connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, isComplete, error in + completion(data, error) + + if isComplete { + // Connection was closed by the peer + self.disconnect() + } + } + } +} \ No newline at end of file diff --git a/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherClientSignature.swift b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherClientSignature.swift new file mode 100644 index 00000000..54c8c78a --- /dev/null +++ b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherClientSignature.swift @@ -0,0 +1,90 @@ +import Foundation + +/// Handles the specific client signature format that SoftEther expects +class SoftEtherClientSignature { + + // MARK: - Constants + + private enum Constants { + static let clientBuildNumber: UInt32 = 5187 + static let clientVersion: UInt32 = 5_02_0000 + clientBuildNumber + static let clientString = "SoftEther VPN Client" + static let softEtherMagic: [UInt8] = [0x5E, 0x68] // 'Se' in hex + + // Protocol identification constants from SoftEther source + static let cedar = "CEDAR" + static let sessionKey = "sessionkey" + static let protocol1 = "PROTOCOL" + static let protocol2 = "PROTOCOL2" + } + + // MARK: - Public Methods + + /// Generate the client signature packet that identifies this client as a legitimate SoftEther VPN client + /// - Returns: Data containing the formatted client signature + static func generateSignature() -> Data { + var data = Data() + + // 1. Add SoftEther magic bytes + data.append(contentsOf: Constants.softEtherMagic) + + // 2. Add client version in network byte order (big endian) + data.appendUInt32(Constants.clientVersion) + + // 3. Add client build number in network byte order + data.appendUInt32(Constants.clientBuildNumber) + + // 4. Add cedar protocol identifier + if let cedarData = Constants.cedar.data(using: .ascii) { + data.append(cedarData) + data.append(0) // null terminator + } + + // 5. Add client string with null terminator + if let clientString = (Constants.clientString + "\0").data(using: .ascii) { + data.append(clientString) + } + + // 6. Add protocol identifiers + if let protocolData = (Constants.protocol1 + "\0").data(using: .ascii) { + data.append(protocolData) + } + + if let protocol2Data = (Constants.protocol2 + "\0").data(using: .ascii) { + data.append(protocol2Data) + } + + // 7. Add session key marker + if let sessionKeyData = (Constants.sessionKey + "\0").data(using: .ascii) { + data.append(sessionKeyData) + } + + // 8. Add random data for session key (typically 20 bytes) + let randomSessionKey = SoftEtherCrypto.randomBytes(count: 20) + data.append(randomSessionKey) + + // 9. Calculate and append SHA-1 hash of the entire data for integrity verification + let hash = SoftEtherCrypto.sha1(data) + data.append(hash) + + return data + } + + /// Verify a server response to the client signature + /// - Parameter data: Response data from server + /// - Returns: True if valid response, false otherwise + static func verifyServerResponse(_ data: Data) -> Bool { + // Basic validation - a real implementation would parse and validate the server response format + // This is a minimal check to see if we have enough data and it starts with the magic bytes + guard data.count >= 8 else { + return false + } + + // Check if response starts with SoftEther magic bytes + if data[0] == Constants.softEtherMagic[0] && data[1] == Constants.softEtherMagic[1] { + return true + } + + return false + } +} \ No newline at end of file diff --git a/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherCrypto.swift b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherCrypto.swift new file mode 100644 index 00000000..fddceca6 --- /dev/null +++ b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherCrypto.swift @@ -0,0 +1,97 @@ +import Foundation +import CryptoKit + +/// Handles encryption operations for SoftEther protocol +class SoftEtherCrypto { + + // MARK: - Constants + + private enum Constants { + static let sha1Size = 20 + static let md5Size = 16 + } + + // MARK: - Public Methods + + /// Generate secure random bytes + /// - Parameter count: Number of random bytes to generate + /// - Returns: Data containing random bytes + static func randomBytes(count: Int) -> Data { + var data = Data(count: count) + _ = data.withUnsafeMutableBytes { + SecRandomCopyBytes(kSecRandomDefault, count, $0.baseAddress!) + } + return data + } + + /// Calculate SHA-1 hash + /// - Parameter data: Input data + /// - Returns: SHA-1 hash of the input data + static func sha1(_ data: Data) -> Data { + let digest = SHA1.hash(data: data) + return Data(digest) + } + + /// Calculate MD5 hash + /// - Parameter data: Input data + /// - Returns: MD5 hash of the input data + static func md5(_ data: Data) -> Data { + let digest = Insecure.MD5.hash(data: data) + return Data(digest) + } + + /// Encrypt data using RC4 algorithm (for SoftEther compatibility) + /// - Parameters: + /// - data: Data to encrypt + /// - key: Encryption key + /// - Returns: Encrypted data + static func rc4Encrypt(data: Data, key: Data) -> Data { + let rc4 = RC4(key: key) + return rc4.process(data) + } + + /// Decrypt data using RC4 algorithm (for SoftEther compatibility) + /// - Parameters: + /// - data: Data to decrypt + /// - key: Decryption key + /// - Returns: Decrypted data + static func rc4Decrypt(data: Data, key: Data) -> Data { + // RC4 is symmetric, so encryption and decryption are the same operation + return rc4Encrypt(data: data, key: key) + } +} + +/// Simple RC4 implementation for SoftEther compatibility +/// Note: RC4 is considered insecure, but SoftEther uses it in parts of its protocol +private class RC4 { + private var state: [UInt8] + + init(key: Data) { + state = Array(0...255) + var j: Int = 0 + + // Key scheduling algorithm + for i in 0..<256 { + let keyByte = key[i % key.count] + j = (j + Int(state[i]) + Int(keyByte)) & 0xFF + state.swapAt(i, j) + } + } + + func process(_ data: Data) -> Data { + var result = Data(count: data.count) + var i: Int = 0 + var j: Int = 0 + + // Generate keystream and XOR with plaintext + for k in 0..= Int(Constants.headerSize) else { + return nil + } + + // Parse header + let typeValue = data.readUInt32(at: 0) + self.packetId = data.readUInt32(at: 4) + let dataSize = data.readUInt32(at: 8) + + // Validate packet + guard let type = PacketType(rawValue: typeValue), + dataSize <= Constants.maxPacketSize, + data.count >= Int(Constants.headerSize + dataSize) else { + return nil + } + + self.packetType = type + + // Extract payload + let startIndex = Int(Constants.headerSize) + let endIndex = startIndex + Int(dataSize) + self.packetData = data.subdata(in: startIndex.. Data { + var result = Data(capacity: Int(Constants.headerSize) + packetData.count) + + // Write header + result.appendUInt32(packetType.rawValue) + result.appendUInt32(packetId) + result.appendUInt32(UInt32(packetData.count)) + result.appendUInt32(0) // Reserved + + // Write payload + result.append(packetData) + + return result + } + + /// Get the packet type + /// - Returns: Packet type + func getType() -> UInt32 { + return packetType.rawValue + } + + /// Get the packet ID + /// - Returns: Packet ID + func getId() -> UInt32 { + return packetId + } + + /// Get the packet payload + /// - Returns: Packet payload data + func getData() -> Data { + return packetData + } +} + +// MARK: - Extensions + +extension Data { + /// Read a UInt32 value from the data at specified offset + /// - Parameter offset: Offset to read from + /// - Returns: UInt32 value in big-endian order + func readUInt32(at offset: Int) -> UInt32 { + let slice = self.subdata(in: offset..<(offset + 4)) + return slice.withUnsafeBytes { $0.load(as: UInt32.self).bigEndian } + } + + /// Append a UInt32 value to the data in big-endian order + /// - Parameter value: UInt32 value to append + mutating func appendUInt32(_ value: UInt32) { + var bigEndian = value.bigEndian + append(UnsafeBufferPointer(start: &bigEndian, count: 1)) + } +} \ No newline at end of file diff --git a/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherProtocol.swift b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherProtocol.swift new file mode 100644 index 00000000..e655f391 --- /dev/null +++ b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherProtocol.swift @@ -0,0 +1,184 @@ +import Foundation +import Network +import Security +import CryptoKit + +/// SoftEtherProtocol manages the communication between iOS client and SoftEther VPN server +class SoftEtherProtocol { + + // MARK: - Properties + + private var secureConnection: SecureConnection? + private var isConnected = false + private var host: String = "" + private var port: UInt16 = 443 + private var nextPacketId: UInt32 = 1 + + // MARK: - Public Methods + + /// Connect to a SoftEther VPN server + /// - Parameters: + /// - host: The server hostname or IP address + /// - port: The server port (default: 443) + /// - completion: Callback with connection result + public func connect(to host: String, port: UInt16 = 443, completion: @escaping (Bool, Error?) -> Void) { + self.host = host + self.port = port + + // Create a secure connection + secureConnection = SecureConnection(host: host, port: port) + + // Connect using TLS + secureConnection?.connect { [weak self] success, error in + guard let self = self, success else { + completion(false, error ?? NSError(domain: "SoftEtherError", code: 1, userInfo: [NSLocalizedDescriptionKey: "TLS connection failed"])) + return + } + + // After successful TLS connection, send the client signature + self.sendClientSignature { success, error in + if success { + self.isConnected = true + } + completion(success, error) + } + } + } + + /// Disconnect from the server + public func disconnect() { + secureConnection?.disconnect() + isConnected = false + } + + // MARK: - Private Methods + + /// Send the SoftEther client signature to identify as a legitimate client + /// - Parameter completion: Callback with result + private func sendClientSignature(completion: @escaping (Bool, Error?) -> Void) { + // Generate client signature using our specialized class + let signatureData = SoftEtherClientSignature.generateSignature() + + // Create a packet with the signature data + let packetId = self.nextPacketId + self.nextPacketId += 1 + + let packet = SoftEtherPacket(type: 0x01, id: packetId, data: signatureData) + let packetData = packet.serialize() + + print("Sending client signature packet: \(packetData.count) bytes") + + // Send the packet + secureConnection?.send(data: packetData) { [weak self] error in + guard let self = self else { return } + + if let error = error { + print("Error sending client signature: \(error)") + completion(false, error) + return + } + + // After sending signature, wait for server response + self.receiveServerResponse { success, error in + completion(success, error) + } + } + } + + /// Receive and process server response after sending signature + /// - Parameter completion: Callback with result + private func receiveServerResponse(completion: @escaping (Bool, Error?) -> Void) { + secureConnection?.receive { data, error in + if let error = error { + print("Error receiving server response: \(error)") + completion(false, error) + return + } + + guard let data = data, data.count > 4 else { + let error = NSError(domain: "SoftEtherError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid server response"]) + print("Invalid server response: insufficient data") + completion(false, error) + return + } + + print("Received server response: \(data.count) bytes") + + // Parse the response packet + guard let packet = SoftEtherPacket(fromData: data) else { + let error = NSError(domain: "SoftEtherError", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid packet format"]) + print("Could not parse server response packet") + completion(false, error) + return + } + + // Verify the response + let packetData = packet.getData() + let isValid = SoftEtherClientSignature.verifyServerResponse(packetData) + + if isValid { + print("Server accepted our client signature") + completion(true, nil) + } else { + print("Server rejected our client signature") + let error = NSError(domain: "SoftEtherError", code: 4, userInfo: [NSLocalizedDescriptionKey: "Server rejected client signature"]) + completion(false, error) + } + } + } + + /// Send a data packet to the server + /// - Parameters: + /// - data: Data to send + /// - completion: Callback with result + func sendData(data: Data, completion: @escaping (Bool, Error?) -> Void) { + guard isConnected else { + completion(false, NSError(domain: "SoftEtherError", code: 5, userInfo: [NSLocalizedDescriptionKey: "Not connected to server"])) + return + } + + let packetId = self.nextPacketId + self.nextPacketId += 1 + + let packet = SoftEtherPacket(type: 0x05, id: packetId, data: data) + let packetData = packet.serialize() + + secureConnection?.send(data: packetData) { error in + if let error = error { + completion(false, error) + return + } + + completion(true, nil) + } + } + + /// Receive data from the server + /// - Parameter completion: Callback with received data and result + func receiveData(completion: @escaping (Data?, Bool, Error?) -> Void) { + guard isConnected else { + completion(nil, false, NSError(domain: "SoftEtherError", code: 5, userInfo: [NSLocalizedDescriptionKey: "Not connected to server"])) + return + } + + secureConnection?.receive { data, error in + if let error = error { + completion(nil, false, error) + return + } + + guard let data = data, data.count > 4 else { + completion(nil, false, NSError(domain: "SoftEtherError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid server response"])) + return + } + + // Parse the packet + guard let packet = SoftEtherPacket(fromData: data) else { + completion(nil, false, NSError(domain: "SoftEtherError", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid packet format"])) + return + } + + completion(packet.getData(), true, nil) + } + } +} \ No newline at end of file diff --git a/SoftEtherVPN-iOS/SoftEtherVPN-iOS/SoftEtherVPNClient.swift b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/SoftEtherVPNClient.swift new file mode 100644 index 00000000..4ff66179 --- /dev/null +++ b/SoftEtherVPN-iOS/SoftEtherVPN-iOS/SoftEtherVPNClient.swift @@ -0,0 +1,149 @@ +import Foundation +import UIKit + +/// SoftEtherVPNClient provides a simple interface for connecting to SoftEther VPN servers +public class SoftEtherVPNClient { + + // MARK: - Properties + + private let protocol: SoftEtherProtocol + private var connectionState: ConnectionState = .disconnected + + // MARK: - Public Types + + /// Connection states for the VPN client + public enum ConnectionState { + case disconnected + case connecting + case connected + case disconnecting + case error(Error) + } + + /// Connection delegate to receive state updates + public protocol ConnectionDelegate: AnyObject { + func connectionStateDidChange(_ state: ConnectionState) + } + + /// Weak reference to the delegate + public weak var delegate: ConnectionDelegate? + + // MARK: - Initialization + + public init() { + self.protocol = SoftEtherProtocol() + } + + // MARK: - Public Methods + + /// Connect to a SoftEther VPN server + /// - Parameters: + /// - host: Server hostname or IP address + /// - port: Server port (default: 443) + /// - completion: Optional completion handler + public func connect(to host: String, port: UInt16 = 443, completion: ((Bool, Error?) -> Void)? = nil) { + // Update state + connectionState = .connecting + delegate?.connectionStateDidChange(connectionState) + + // Connect using the protocol implementation + protocol.connect(to: host, port: port) { [weak self] success, error in + guard let self = self else { return } + + if success { + self.connectionState = .connected + } else if let error = error { + self.connectionState = .error(error) + } else { + self.connectionState = .disconnected + } + + self.delegate?.connectionStateDidChange(self.connectionState) + completion?(success, error) + } + } + + /// Disconnect from the server + /// - Parameter completion: Optional completion handler + public func disconnect(completion: (() -> Void)? = nil) { + // Update state + connectionState = .disconnecting + delegate?.connectionStateDidChange(connectionState) + + // Disconnect + protocol.disconnect() + + // Update state again + connectionState = .disconnected + delegate?.connectionStateDidChange(connectionState) + + completion?() + } + + /// Get the current connection state + /// - Returns: Current ConnectionState + public func getConnectionState() -> ConnectionState { + return connectionState + } + + /// Check if currently connected + /// - Returns: True if connected, false otherwise + public func isConnected() -> Bool { + if case .connected = connectionState { + return true + } + return false + } + + // MARK: - Example Usage + + /// Example showing how to use this class in a view controller + public static func exampleUsage() -> String { + return """ + // In your view controller: + + private let vpnClient = SoftEtherVPNClient() + + override func viewDidLoad() { + super.viewDidLoad() + + // Set delegate + vpnClient.delegate = self + } + + @IBAction func connectButtonTapped(_ sender: UIButton) { + if vpnClient.isConnected() { + vpnClient.disconnect() + } else { + vpnClient.connect(to: "vpn.example.com") { success, error in + if !success { + print("Failed to connect: \\(error?.localizedDescription ?? "Unknown error")") + } + } + } + } + + // MARK: - ConnectionDelegate + + extension YourViewController: SoftEtherVPNClient.ConnectionDelegate { + func connectionStateDidChange(_ state: SoftEtherVPNClient.ConnectionState) { + switch state { + case .connected: + connectButton.setTitle("Disconnect", for: .normal) + statusLabel.text = "Connected" + case .connecting: + statusLabel.text = "Connecting..." + case .disconnecting: + statusLabel.text = "Disconnecting..." + case .disconnected: + connectButton.setTitle("Connect", for: .normal) + statusLabel.text = "Disconnected" + case .error(let error): + statusLabel.text = "Error: \\(error.localizedDescription)" + connectButton.setTitle("Connect", for: .normal) + } + } + } + """ + } +} \ No newline at end of file