tunnelkit/TunnelKit/Sources/Core/ConfigurationParser.swift

143 lines
5.8 KiB
Swift
Raw Normal View History

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
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]?
/// 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.
- 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.
- Throws: `OptionsError` if the configuration file is wrong or incomplete.
2018-11-10 10:24:23 +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()
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.
- 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.
- Throws: `OptionsError` if the configuration file is wrong or incomplete.
2018-11-10 10:24:23 +00:00
*/
public static func parsed(fromLines lines: [String], passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
let options = try OptionsBundle(from: lines, returnsStripped: returnsStripped)
2018-11-10 09:45:00 +00:00
guard let ca = options.ca else {
throw OptionsError.missingConfiguration(option: "ca")
2018-11-10 09:45:00 +00:00
}
guard let hostname = options.hostname, !options.remotes.isEmpty else {
throw OptionsError.missingConfiguration(option: "remote")
2018-11-10 09:45:00 +00:00
}
let endpointProtocols = options.remotes.map { EndpointProtocol($0.2, $0.1) }
2018-11-10 09:45:00 +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)
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(),
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
}
}
}