From c019cecbe02929e79e2c1c86ba3436a65de2cd78 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sat, 12 Feb 2022 08:37:51 +0100 Subject: [PATCH] Improve some things about OpenVPN.Configuration - Treat empty passphrase as no passphrase - Parse authentication requirement from --auth-user-pass - Overload ConfigurationParser with String parameter - Move OpenVPN fallbacks inline with builder Give a withFallbacks: option to initialize basic fields rather than leaving them nil. --- CHANGELOG.md | 6 ++ .../TunnelKitOpenVPNCore/Configuration.swift | 52 ++++++++--------- .../ConfigurationParser.swift | 56 +++++++++++++++++-- 3 files changed, 84 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd7574..f70b2d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Parse OpenVPN authentication requirement from `--auth-user-pass`. + ## 4.1.0 (2022-02-09) ### Added diff --git a/Sources/TunnelKitOpenVPNCore/Configuration.swift b/Sources/TunnelKitOpenVPNCore/Configuration.swift index 64cb5e5..c13bde3 100644 --- a/Sources/TunnelKitOpenVPNCore/Configuration.swift +++ b/Sources/TunnelKitOpenVPNCore/Configuration.swift @@ -242,6 +242,9 @@ extension OpenVPN { /// The tunnel MTU. public var mtu: Int? + /// Requires username authentication. + public var authUserPass: Bool? + // MARK: Server /// The auth-token returned by the server. @@ -300,7 +303,18 @@ extension OpenVPN { /// Policies for redirecting traffic through the VPN gateway. public var routingPolicies: [RoutingPolicy]? - public init() { + /** + Creates a `ConfigurationBuilder`. + + - Parameter withFallbacks: If `true`, initializes builder with fallback values rather than nil. + */ + public init(withFallbacks: Bool = false) { + if withFallbacks { + cipher = Fallback.cipher + digest = Fallback.digest + compressionFraming = Fallback.compressionFraming + compressionAlgorithm = Fallback.compressionAlgorithm + } } /** @@ -332,6 +346,7 @@ extension OpenVPN { randomizeEndpoint: randomizeEndpoint, usesPIAPatches: usesPIAPatches, mtu: mtu, + authUserPass: authUserPass, authToken: authToken, peerId: peerId, ipv4: ipv4, @@ -348,24 +363,6 @@ extension OpenVPN { routingPolicies: routingPolicies ) } - - // MARK: Shortcuts - - public var fallbackCipher: Cipher { - return cipher ?? Fallback.cipher - } - - public var fallbackDigest: Digest { - return digest ?? Fallback.digest - } - - public var fallbackCompressionFraming: CompressionFraming { - return compressionFraming ?? Fallback.compressionFraming - } - - public var fallbackCompressionAlgorithm: CompressionAlgorithm { - return compressionAlgorithm ?? Fallback.compressionAlgorithm - } } /// The immutable configuration for `OpenVPNSession`. @@ -437,6 +434,9 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.mtu` public let mtu: Int? + /// - Seealso: `ConfigurationBuilder.authUserPass` + public let authUserPass: Bool? + /// - Seealso: `ConfigurationBuilder.authToken` public let authToken: String? @@ -502,15 +502,16 @@ extension OpenVPN.Configuration { /** Returns a `ConfigurationBuilder` to use this configuration as a starting point for a new one. + - Parameter withFallbacks: If `true`, initializes builder with fallback values rather than nil. - Returns: An editable `ConfigurationBuilder` initialized with this configuration. */ - public func builder() -> OpenVPN.ConfigurationBuilder { + public func builder(withFallbacks: Bool = false) -> OpenVPN.ConfigurationBuilder { var builder = OpenVPN.ConfigurationBuilder() - builder.cipher = cipher + builder.cipher = cipher ?? (withFallbacks ? OpenVPN.Fallback.cipher : nil) builder.dataCiphers = dataCiphers - builder.digest = digest - builder.compressionFraming = compressionFraming - builder.compressionAlgorithm = compressionAlgorithm + builder.digest = digest ?? (withFallbacks ? OpenVPN.Fallback.digest : nil) + builder.compressionFraming = compressionFraming ?? (withFallbacks ? OpenVPN.Fallback.compressionFraming : nil) + builder.compressionAlgorithm = compressionAlgorithm ?? (withFallbacks ? OpenVPN.Fallback.compressionAlgorithm : nil) builder.ca = ca builder.clientCertificate = clientCertificate builder.clientKey = clientKey @@ -527,6 +528,7 @@ extension OpenVPN.Configuration { builder.randomizeEndpoint = randomizeEndpoint builder.usesPIAPatches = usesPIAPatches builder.mtu = mtu + builder.authUserPass = authUserPass builder.authToken = authToken builder.peerId = peerId builder.ipv4 = ipv4 @@ -549,7 +551,6 @@ extension OpenVPN.Configuration { // MARK: Encoding extension OpenVPN.Configuration { - public func print() { guard let endpointProtocols = endpointProtocols else { fatalError("No sessionConfiguration.endpointProtocols set") @@ -558,6 +559,7 @@ extension OpenVPN.Configuration { log.info("\tCipher: \(fallbackCipher)") log.info("\tDigest: \(fallbackDigest)") log.info("\tCompression framing: \(fallbackCompressionFraming)") + log.info("\tUsername authentication: \(authUserPass ?? false)") if let compressionAlgorithm = compressionAlgorithm, compressionAlgorithm != .disabled { log.info("\tCompression algorithm: \(compressionAlgorithm)") } else { diff --git a/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift b/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift index a7ff99d..7970131 100644 --- a/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift +++ b/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift @@ -78,6 +78,8 @@ extension OpenVPN { static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp[46]?|tcp[46]?))?") + static let authUserPass = NSRegularExpression("^auth-user-pass") + static let eku = NSRegularExpression("^remote-cert-tls +server") static let remoteRandom = NSRegularExpression("^remote-random") @@ -178,7 +180,7 @@ extension OpenVPN { } /** - Parses an .ovpn file from an URL. + Parses a configuration from a .ovpn file. - Parameter url: The URL of the configuration file. - Parameter passphrase: The optional passphrase for encrypted data. @@ -187,8 +189,39 @@ extension OpenVPN { - Throws: `ConfigurationError` if the configuration file is wrong or incomplete. */ public static func parsed(fromURL url: URL, passphrase: String? = nil, returnsStripped: Bool = false) throws -> Result { - let lines = try String(contentsOf: url).trimmedLines() - return try parsed(fromLines: lines, isClient: true, passphrase: passphrase, originalURL: url, returnsStripped: returnsStripped) + let contents = try String(contentsOf: url) + return try parsed( + fromContents: contents, + passphrase: passphrase, + originalURL: url, + returnsStripped: returnsStripped + ) + } + + /** + Parses a configuration from a string. + + - Parameter contents: The contents of the configuration file. + - Parameter passphrase: The optional passphrase for encrypted data. + - Parameter originalURL: The optional original URL of the configuration file. + - Parameter returnsStripped: When `true`, stores the stripped file into `Result.strippedLines`. Defaults to `false`. + - Returns: The `Result` outcome of the parsing. + - Throws: `ConfigurationError` if the configuration file is wrong or incomplete. + */ + public static func parsed( + fromContents contents: String, + passphrase: String? = nil, + originalURL: URL? = nil, + returnsStripped: Bool = false + ) throws -> Result { + let lines = contents.trimmedLines() + return try parsed( + fromLines: lines, + isClient: true, + passphrase: passphrase, + originalURL: originalURL, + returnsStripped: returnsStripped + ) } /** @@ -202,7 +235,13 @@ extension OpenVPN { - Returns: The `Result` outcome of the parsing. - Throws: `ConfigurationError` if the configuration file is wrong or incomplete. */ - public static func parsed(fromLines lines: [String], isClient: Bool = false, passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> Result { + public static func parsed( + fromLines lines: [String], + isClient: Bool = false, + passphrase: String? = nil, + originalURL: URL? = nil, + returnsStripped: Bool = false + ) throws -> Result { var optStrippedLines: [String]? = returnsStripped ? [] : nil var optWarning: ConfigurationError? var unsupportedError: ConfigurationError? @@ -229,6 +268,7 @@ extension OpenVPN { var optDefaultProto: SocketType? var optDefaultPort: UInt16? var optRemotes: [(String, UInt16?, SocketType?)] = [] // address, port, socket + var authUserPass = false var optChecksEKU: Bool? var optRandomizeEndpoint: Bool? var optMTU: Int? @@ -537,6 +577,10 @@ extension OpenVPN { } optMTU = Int(str) } + Regex.authUserPass.enumerateComponents(in: line) { _ in + isHandled = true + authUserPass = true + } // MARK: Server @@ -687,10 +731,11 @@ extension OpenVPN { sessionBuilder.compressionAlgorithm = optCompressionAlgorithm sessionBuilder.ca = optCA sessionBuilder.clientCertificate = optClientCertificate + sessionBuilder.authUserPass = authUserPass if let clientKey = optClientKey, clientKey.isEncrypted { // FIXME: remove dependency on TLSBox - guard let passphrase = passphrase else { + guard let passphrase = passphrase, !passphrase.isEmpty else { throw ConfigurationError.encryptionPassphrase } do { @@ -746,6 +791,7 @@ extension OpenVPN { sessionBuilder.hostname = nil } + sessionBuilder.authUserPass = authUserPass sessionBuilder.checksEKU = optChecksEKU sessionBuilder.randomizeEndpoint = optRandomizeEndpoint sessionBuilder.mtu = optMTU