2018-11-10 09:45:00 +00:00
|
|
|
//
|
|
|
|
// ConfigurationParser.swift
|
|
|
|
// TunnelKit
|
|
|
|
//
|
|
|
|
// Created by Davide De Rosa on 9/5/18.
|
2019-03-09 10:44:18 +00:00
|
|
|
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
|
2018-11-10 09:45:00 +00:00
|
|
|
//
|
|
|
|
// https://github.com/keeshux
|
|
|
|
//
|
|
|
|
// This file is part of TunnelKit.
|
|
|
|
//
|
|
|
|
// TunnelKit is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// TunnelKit is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import SwiftyBeaver
|
2019-03-18 22:33:36 +00:00
|
|
|
import __TunnelKitNative
|
2018-11-10 09:45:00 +00:00
|
|
|
|
|
|
|
private let log = SwiftyBeaver.self
|
|
|
|
|
2018-11-10 10:24:23 +00:00
|
|
|
/// Provides methods to parse a `SessionProxy.Configuration` from an .ovpn configuration file.
|
2018-11-10 09:45:00 +00:00
|
|
|
public class ConfigurationParser {
|
2018-11-10 10:24:23 +00:00
|
|
|
|
|
|
|
/// Result of the parser.
|
2018-11-10 09:45:00 +00:00
|
|
|
public struct ParsingResult {
|
2018-11-10 10:24:23 +00:00
|
|
|
|
|
|
|
/// Original URL of the configuration file, if parsed from an URL.
|
2018-11-10 09:45:00 +00:00
|
|
|
public let url: URL?
|
2018-11-10 10:24:23 +00:00
|
|
|
|
|
|
|
/// The main endpoint hostname.
|
2018-11-10 09:45:00 +00:00
|
|
|
public let hostname: String
|
|
|
|
|
2018-11-10 10:24:23 +00:00
|
|
|
/// The list of `EndpointProtocol` to which the client can connect to.
|
2018-11-10 09:45:00 +00:00
|
|
|
public let protocols: [EndpointProtocol]
|
2018-11-10 10:24:23 +00:00
|
|
|
|
|
|
|
/// The overall parsed `SessionProxy.Configuration`.
|
2018-11-10 09:45:00 +00:00
|
|
|
public let configuration: SessionProxy.Configuration
|
2018-11-10 10:24:23 +00:00
|
|
|
|
|
|
|
/// The lines of the configuration file stripped of any sensitive data. Lines that
|
|
|
|
/// the parser does not recognize are discarded in the first place.
|
|
|
|
///
|
|
|
|
/// - Seealso: `ConfigurationParser.parsed(...)`
|
2018-11-10 09:45:00 +00:00
|
|
|
public let strippedLines: [String]?
|
|
|
|
|
2019-04-03 08:06:26 +00:00
|
|
|
/// Holds an optional `OptionsError` that didn't block the parser, but it would be worth taking care of.
|
|
|
|
public let warning: OptionsError?
|
2018-11-10 09:45:00 +00:00
|
|
|
}
|
|
|
|
|
2018-11-10 10:24:23 +00:00
|
|
|
/**
|
|
|
|
Parses an .ovpn file from an URL.
|
|
|
|
|
|
|
|
- Parameter url: The URL of the configuration file.
|
2019-03-25 14:51:43 +00:00
|
|
|
- Parameter passphrase: The optional passphrase for encrypted data.
|
2018-11-10 10:24:23 +00:00
|
|
|
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
|
|
|
- Returns: The `ParsingResult` outcome of the parsing.
|
2019-04-03 08:06:26 +00:00
|
|
|
- Throws: `OptionsError` if the configuration file is wrong or incomplete.
|
2018-11-10 10:24:23 +00:00
|
|
|
*/
|
2019-03-25 14:51:43 +00:00
|
|
|
public static func parsed(fromURL url: URL, passphrase: String? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
|
2018-11-10 09:45:00 +00:00
|
|
|
let lines = try String(contentsOf: url).trimmedLines()
|
2019-03-25 14:51:43 +00:00
|
|
|
return try parsed(fromLines: lines, passphrase: passphrase, originalURL: url, returnsStripped: returnsStripped)
|
2018-11-10 09:45:00 +00:00
|
|
|
}
|
|
|
|
|
2018-11-10 10:24:23 +00:00
|
|
|
/**
|
|
|
|
Parses an .ovpn file as an array of lines.
|
|
|
|
|
|
|
|
- Parameter lines: The array of lines holding the configuration.
|
2019-03-25 14:51:43 +00:00
|
|
|
- Parameter passphrase: The optional passphrase for encrypted data.
|
2019-03-25 14:46:15 +00:00
|
|
|
- Parameter originalURL: The optional original URL of the configuration file.
|
2018-11-10 10:24:23 +00:00
|
|
|
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
|
|
|
- Returns: The `ParsingResult` outcome of the parsing.
|
2019-04-03 08:06:26 +00:00
|
|
|
- Throws: `OptionsError` if the configuration file is wrong or incomplete.
|
2018-11-10 10:24:23 +00:00
|
|
|
*/
|
2019-03-25 14:51:43 +00:00
|
|
|
public static func parsed(fromLines lines: [String], passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
|
2019-04-03 08:06:26 +00:00
|
|
|
let options = try OptionsBundle(from: lines, returnsStripped: returnsStripped)
|
2018-11-10 09:45:00 +00:00
|
|
|
|
2019-04-03 08:06:26 +00:00
|
|
|
guard let ca = options.ca else {
|
|
|
|
throw OptionsError.missingConfiguration(option: "ca")
|
2018-11-10 09:45:00 +00:00
|
|
|
}
|
2019-04-03 08:06:26 +00:00
|
|
|
guard let hostname = options.hostname, !options.remotes.isEmpty else {
|
|
|
|
throw OptionsError.missingConfiguration(option: "remote")
|
2018-11-10 09:45:00 +00:00
|
|
|
}
|
2019-04-03 08:06:26 +00:00
|
|
|
let endpointProtocols = options.remotes.map { EndpointProtocol($0.2, $0.1) }
|
2018-11-10 09:45:00 +00:00
|
|
|
|
2019-04-03 08:06:26 +00:00
|
|
|
var optClientKey: CryptoContainer?
|
|
|
|
if let clientKey = options.clientKey, clientKey.isEncrypted {
|
|
|
|
guard let passphrase = passphrase else {
|
|
|
|
throw OptionsError.encryptionPassphrase
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
optClientKey = try clientKey.decrypted(with: passphrase)
|
|
|
|
} catch let e {
|
|
|
|
throw OptionsError.unableToDecrypt(error: e)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
optClientKey = options.clientKey
|
2018-11-10 09:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: ca)
|
2019-04-03 08:06:26 +00:00
|
|
|
sessionBuilder.cipher = options.cipher ?? .aes128cbc
|
|
|
|
sessionBuilder.digest = options.digest ?? .sha1
|
|
|
|
sessionBuilder.compressionFraming = options.compressionFraming ?? .disabled
|
|
|
|
sessionBuilder.compressionAlgorithm = options.compressionAlgorithm ?? .disabled
|
|
|
|
sessionBuilder.tlsWrap = options.tlsWrap
|
|
|
|
sessionBuilder.clientCertificate = options.clientCertificate
|
|
|
|
sessionBuilder.clientKey = optClientKey
|
|
|
|
sessionBuilder.checksEKU = options.checksEKU
|
|
|
|
sessionBuilder.keepAliveInterval = options.keepAliveSeconds
|
|
|
|
sessionBuilder.renegotiatesAfter = options.renegotiateAfterSeconds
|
|
|
|
sessionBuilder.dnsServers = options.dnsServers
|
|
|
|
sessionBuilder.randomizeEndpoint = options.randomizeEndpoint
|
2018-11-10 09:45:00 +00:00
|
|
|
|
|
|
|
return ParsingResult(
|
|
|
|
url: originalURL,
|
|
|
|
hostname: hostname,
|
|
|
|
protocols: endpointProtocols,
|
|
|
|
configuration: sessionBuilder.build(),
|
2019-04-03 08:06:26 +00:00
|
|
|
strippedLines: options.strippedLines,
|
|
|
|
warning: options.warning
|
2018-11-10 09:45:00 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension String {
|
|
|
|
func trimmedLines() -> [String] {
|
|
|
|
return components(separatedBy: .newlines).map {
|
|
|
|
$0.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
}.filter {
|
|
|
|
!$0.isEmpty
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|