Refactor legacy parsing of provider configuration

Leverage Codable implementation of OpenVPN*.Configuration
This commit is contained in:
Davide De Rosa 2021-01-03 09:48:48 +01:00
parent 6077f51acb
commit 80d99cab6c
4 changed files with 133 additions and 409 deletions

View File

@ -750,12 +750,12 @@
0E23B3F722982AF800304C30 /* AppExtension */ = {
isa = PBXGroup;
children = (
0E23B3F822982AF800304C30 /* OpenVPNTunnelProvider+Interaction.swift */,
0E23B3F922982AF800304C30 /* NEUDPLink.swift */,
0E23B3FA22982AF800304C30 /* ConnectionStrategy.swift */,
0E23B3FB22982AF800304C30 /* OpenVPNTunnelProvider+Configuration.swift */,
0E23B3FC22982AF800304C30 /* NETCPLink.swift */,
0E23B3F922982AF800304C30 /* NEUDPLink.swift */,
0E23B3FD22982AF800304C30 /* OpenVPNTunnelProvider.swift */,
0E23B3FB22982AF800304C30 /* OpenVPNTunnelProvider+Configuration.swift */,
0E23B3F822982AF800304C30 /* OpenVPNTunnelProvider+Interaction.swift */,
);
path = AppExtension;
sourceTree = "<group>";

View File

@ -42,3 +42,20 @@ public extension DispatchQueue {
asyncAfter(deadline: .now() + after, execute: block)
}
}
/// :nodoc:
func fromDictionary<T: Decodable>(_ type: T.Type, _ dictionary: [String: Any]) throws -> T {
let data = try JSONSerialization.data(withJSONObject: dictionary, options: .fragmentsAllowed)
return try JSONDecoder().decode(T.self, from: data)
}
/// :nodoc:
public extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) as? [String: Any] else {
fatalError("JSONSerialization failed to encode")
}
return dictionary
}
}

View File

@ -41,6 +41,9 @@ import SwiftyBeaver
private let log = SwiftyBeaver.self
extension OpenVPNTunnelProvider {
private struct ExtraKeys {
static let appGroup = "appGroup"
}
// MARK: Configuration
@ -100,24 +103,6 @@ extension OpenVPNTunnelProvider {
versionIdentifier = ConfigurationBuilder.defaults.versionIdentifier
}
fileprivate init(providerConfiguration: [String: Any]) throws {
let S = Configuration.Keys.self
sessionConfiguration = try OpenVPN.Configuration.with(providerConfiguration: providerConfiguration)
prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? ConfigurationBuilder.defaults.prefersResolvedAddresses
resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String]
shouldDebug = providerConfiguration[S.debug] as? Bool ?? ConfigurationBuilder.defaults.shouldDebug
if shouldDebug {
debugLogFormat = providerConfiguration[S.debugLogFormat] as? String
}
masksPrivateData = providerConfiguration[S.masksPrivateData] as? Bool ?? ConfigurationBuilder.defaults.masksPrivateData
versionIdentifier = providerConfiguration[S.versionIdentifier] as? String ?? ConfigurationBuilder.defaults.versionIdentifier
guard !prefersResolvedAddresses || !(resolvedAddresses?.isEmpty ?? true) else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.prefersResolvedAddresses)] is true but no [\(S.resolvedAddresses)]")
}
}
/**
Builds a `OpenVPNTunnelProvider.Configuration` object that will connect to the provided endpoint.
@ -138,79 +123,6 @@ extension OpenVPNTunnelProvider {
/// Offers a bridge between the abstract `OpenVPNTunnelProvider.ConfigurationBuilder` and a concrete `NETunnelProviderProtocol` profile.
public struct Configuration: Codable {
struct Keys {
static let appGroup = "AppGroup"
static let versionIdentifier = "VersionIdentifier"
// MARK: SessionConfiguration
static let cipherAlgorithm = "CipherAlgorithm"
static let digestAlgorithm = "DigestAlgorithm"
static let compressionFraming = "CompressionFraming"
static let compressionAlgorithm = "CompressionAlgorithm"
static let ca = "CA"
static let clientCertificate = "ClientCertificate"
static let clientKey = "ClientKey"
static let tlsWrap = "TLSWrap"
static let tlsSecurityLevel = "TLSSecurityLevel"
static let keepAlive = "KeepAlive"
static let keepAliveTimeout = "KeepAliveTimeout"
static let endpointProtocols = "EndpointProtocols"
static let renegotiatesAfter = "RenegotiatesAfter"
static let checksEKU = "ChecksEKU"
static let checksSANHost = "checksSANHost"
static let sanHost = "sanHost"
static let randomizeEndpoint = "RandomizeEndpoint"
static let usesPIAPatches = "UsesPIAPatches"
static let mtu = "MTU"
static let dnsServers = "DNSServers"
static let searchDomains = "SearchDomains"
static let httpProxy = "HTTPProxy"
static let httpsProxy = "HTTPSProxy"
static let proxyAutoConfigurationURL = "ProxyAutoConfigurationURL"
static let proxyBypassDomains = "ProxyBypassDomains"
static let routingPolicies = "RoutingPolicies"
// MARK: Customization
static let prefersResolvedAddresses = "PrefersResolvedAddresses"
static let resolvedAddresses = "ResolvedAddresses"
// MARK: Debugging
static let debug = "Debug"
static let debugLogFormat = "DebugLogFormat"
static let masksPrivateData = "MasksPrivateData"
}
/// - Seealso: `OpenVPNTunnelProvider.ConfigurationBuilder.sessionConfiguration`
public let sessionConfiguration: OpenVPN.Configuration
@ -318,8 +230,8 @@ extension OpenVPNTunnelProvider {
- Throws: `ProviderError.configuration` if `providerConfiguration` does not contain an app group.
*/
public static func appGroup(from providerConfiguration: [String: Any]) throws -> String {
guard let appGroup = providerConfiguration[Keys.appGroup] as? String else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(Keys.appGroup)]")
guard let appGroup = providerConfiguration[ExtraKeys.appGroup] as? String else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(ExtraKeys.appGroup)]")
}
return appGroup
}
@ -332,8 +244,11 @@ extension OpenVPNTunnelProvider {
- Throws: `ProviderError.configuration` if `providerConfiguration` is incomplete.
*/
public static func parsed(from providerConfiguration: [String: Any]) throws -> Configuration {
let builder = try ConfigurationBuilder(providerConfiguration: providerConfiguration)
return builder.build()
let cfg = try fromDictionary(OpenVPNTunnelProvider.Configuration.self, providerConfiguration)
guard !cfg.prefersResolvedAddresses || !(cfg.resolvedAddresses?.isEmpty ?? true) else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[prefersResolvedAddresses] is true but no [resolvedAddresses]")
}
return cfg
}
/**
@ -343,36 +258,14 @@ extension OpenVPNTunnelProvider {
- Returns: The dictionary representation of `self`.
*/
public func generatedProviderConfiguration(appGroup: String) -> [String: Any] {
let S = Keys.self
guard let ca = sessionConfiguration.ca else {
fatalError("No sessionConfiguration.ca set")
}
guard let endpointProtocols = sessionConfiguration.endpointProtocols else {
fatalError("No sessionConfiguration.endpointProtocols set")
}
var dict: [String: Any] = [
S.appGroup: appGroup,
S.prefersResolvedAddresses: prefersResolvedAddresses,
S.ca: ca.pem,
S.endpointProtocols: endpointProtocols.map { $0.rawValue },
S.debug: shouldDebug
]
sessionConfiguration.store(to: &dict)
if let resolvedAddresses = resolvedAddresses {
dict[S.resolvedAddresses] = resolvedAddresses
}
if let debugLogFormat = debugLogFormat {
dict[S.debugLogFormat] = debugLogFormat
}
if let masksPrivateData = masksPrivateData {
dict[S.masksPrivateData] = masksPrivateData
}
if let versionIdentifier = versionIdentifier {
dict[S.versionIdentifier] = versionIdentifier
}
do {
var dict = try asDictionary()
dict[ExtraKeys.appGroup] = appGroup
return dict
} catch let e {
log.error("Unable to encode OpenVPN.Configuration: \(e)")
}
return [:]
}
/**
@ -451,284 +344,3 @@ public extension UserDefaults {
removeObject(forKey: OpenVPNTunnelProvider.Configuration.dataCountKey)
}
}
// MARK: OpenVPN configuration
private extension OpenVPN.Configuration {
static func with(providerConfiguration: [String: Any]) throws -> OpenVPN.Configuration {
let S = OpenVPNTunnelProvider.Configuration.Keys.self
let E = OpenVPNTunnelProvider.ProviderConfigurationError.self
guard let caPEM = providerConfiguration[S.ca] as? String else {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.ca)]")
}
guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty")
}
var builder = OpenVPNTunnelProvider.ConfigurationBuilder.defaults.sessionConfiguration.builder()
builder.ca = OpenVPN.CryptoContainer(pem: caPEM)
builder.endpointProtocols = try endpointProtocolsStrings.map {
guard let ep = EndpointProtocol(rawValue: $0) else {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] has a badly formed element")
}
return ep
}
if let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String {
builder.cipher = OpenVPN.Cipher(rawValue: cipherAlgorithm)
}
if let digestAlgorithm = providerConfiguration[S.digestAlgorithm] as? String {
builder.digest = OpenVPN.Digest(rawValue: digestAlgorithm)
}
if let compressionFramingValue = providerConfiguration[S.compressionFraming] as? Int, let compressionFraming = OpenVPN.CompressionFraming(rawValue: compressionFramingValue) {
builder.compressionFraming = compressionFraming
}
if let compressionAlgorithmValue = providerConfiguration[S.compressionAlgorithm] as? Int, let compressionAlgorithm = OpenVPN.CompressionAlgorithm(rawValue: compressionAlgorithmValue) {
builder.compressionAlgorithm = compressionAlgorithm
}
if let clientPEM = providerConfiguration[S.clientCertificate] as? String {
guard let keyPEM = providerConfiguration[S.clientKey] as? String else {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.clientKey)]")
}
builder.clientCertificate = OpenVPN.CryptoContainer(pem: clientPEM)
builder.clientKey = OpenVPN.CryptoContainer(pem: keyPEM)
}
if let tlsWrapData = providerConfiguration[S.tlsWrap] as? Data {
do {
builder.tlsWrap = try OpenVPN.TLSWrap.deserialized(tlsWrapData)
} catch {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]")
}
}
if let tlsSecurityLevel = providerConfiguration[S.tlsSecurityLevel] as? Int {
builder.tlsSecurityLevel = tlsSecurityLevel
}
if let keepAliveInterval = providerConfiguration[S.keepAlive] as? TimeInterval {
builder.keepAliveInterval = keepAliveInterval
}
if let keepAliveTimeout = providerConfiguration[S.keepAliveTimeout] as? TimeInterval {
builder.keepAliveTimeout = keepAliveTimeout
}
if let renegotiatesAfter = providerConfiguration[S.renegotiatesAfter] as? TimeInterval {
builder.renegotiatesAfter = renegotiatesAfter
}
if let checksEKU = providerConfiguration[S.checksEKU] as? Bool {
builder.checksEKU = checksEKU
}
if let checksSANHost = providerConfiguration[S.checksSANHost] as? Bool {
builder.checksSANHost = checksSANHost
}
if let sanHost = providerConfiguration[S.sanHost] as? String {
builder.sanHost = sanHost
}
if let randomizeEndpoint = providerConfiguration[S.randomizeEndpoint] as? Bool {
builder.randomizeEndpoint = randomizeEndpoint
}
if let usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool {
builder.usesPIAPatches = usesPIAPatches
}
if let mtu = providerConfiguration[S.mtu] as? Int {
builder.mtu = mtu
}
if let dnsServers = providerConfiguration[S.dnsServers] as? [String] {
builder.dnsServers = dnsServers
}
if let searchDomains = providerConfiguration[S.searchDomains] as? [String] {
builder.searchDomains = searchDomains
}
if let proxyString = providerConfiguration[S.httpProxy] as? String {
guard let proxy = Proxy(rawValue: proxyString) else {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.httpProxy)] has a badly formed element")
}
builder.httpProxy = proxy
}
if let proxyString = providerConfiguration[S.httpsProxy] as? String {
guard let proxy = Proxy(rawValue: proxyString) else {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.httpsProxy)] has a badly formed element")
}
builder.httpsProxy = proxy
}
if let proxyAutoConfigurationURLString = providerConfiguration[S.proxyAutoConfigurationURL] as? String, let proxyAutoConfigurationURL = URL(string: proxyAutoConfigurationURLString) {
builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL
}
if let proxyBypassDomains = providerConfiguration[S.proxyBypassDomains] as? [String] {
builder.proxyBypassDomains = proxyBypassDomains
}
if let routingPoliciesStrings = providerConfiguration[S.routingPolicies] as? [String] {
builder.routingPolicies = try routingPoliciesStrings.map {
guard let policy = OpenVPN.RoutingPolicy(rawValue: $0) else {
throw E.parameter(name: "protocolConfiguration.providerConfiguration[\(S.routingPolicies)] has a badly formed element")
}
return policy
}
}
return builder.build()
}
func store(to dict: inout [String: Any]) {
let S = OpenVPNTunnelProvider.Configuration.Keys.self
if let cipher = cipher {
dict[S.cipherAlgorithm] = cipher.rawValue
}
if let digest = digest {
dict[S.digestAlgorithm] = digest.rawValue
}
if let compressionFraming = compressionFraming {
dict[S.compressionFraming] = compressionFraming.rawValue
}
if let compressionAlgorithm = compressionAlgorithm {
dict[S.compressionAlgorithm] = compressionAlgorithm.rawValue
}
if let clientCertificate = clientCertificate {
dict[S.clientCertificate] = clientCertificate.pem
}
if let clientKey = clientKey {
dict[S.clientKey] = clientKey.pem
}
if let tlsWrapData = tlsWrap?.serialized() {
dict[S.tlsWrap] = tlsWrapData
}
if let tlsSecurityLevel = tlsSecurityLevel {
dict[S.tlsSecurityLevel] = tlsSecurityLevel
}
if let keepAliveSeconds = keepAliveInterval {
dict[S.keepAlive] = keepAliveSeconds
}
if let keepAliveTimeoutSeconds = keepAliveTimeout {
dict[S.keepAliveTimeout] = keepAliveTimeoutSeconds
}
if let renegotiatesAfterSeconds = renegotiatesAfter {
dict[S.renegotiatesAfter] = renegotiatesAfterSeconds
}
if let checksEKU = checksEKU {
dict[S.checksEKU] = checksEKU
}
if let checksSANHost = checksSANHost {
dict[S.checksSANHost] = checksSANHost
}
if let sanHost = sanHost {
dict[S.sanHost] = sanHost
}
if let randomizeEndpoint = randomizeEndpoint {
dict[S.randomizeEndpoint] = randomizeEndpoint
}
if let usesPIAPatches = usesPIAPatches {
dict[S.usesPIAPatches] = usesPIAPatches
}
if let mtu = mtu {
dict[S.mtu] = mtu
}
if let dnsServers = dnsServers {
dict[S.dnsServers] = dnsServers
}
if let searchDomains = searchDomains {
dict[S.searchDomains] = searchDomains
}
if let httpProxy = httpProxy {
dict[S.httpProxy] = httpProxy.rawValue
}
if let httpsProxy = httpsProxy {
dict[S.httpsProxy] = httpsProxy.rawValue
}
if let proxyAutoConfigurationURL = proxyAutoConfigurationURL {
dict[S.proxyAutoConfigurationURL] = proxyAutoConfigurationURL.absoluteString
}
if let proxyBypassDomains = proxyBypassDomains {
dict[S.proxyBypassDomains] = proxyBypassDomains
}
if let routingPolicies = routingPolicies {
dict[S.routingPolicies] = routingPolicies.map { $0.rawValue }
}
}
func print() {
guard let endpointProtocols = endpointProtocols else {
fatalError("No sessionConfiguration.endpointProtocols set")
}
log.info("\tProtocols: \(endpointProtocols)")
log.info("\tCipher: \(fallbackCipher)")
log.info("\tDigest: \(fallbackDigest)")
log.info("\tCompression framing: \(fallbackCompressionFraming)")
if let compressionAlgorithm = compressionAlgorithm, compressionAlgorithm != .disabled {
log.info("\tCompression algorithm: \(compressionAlgorithm)")
} else {
log.info("\tCompression algorithm: disabled")
}
if let _ = clientCertificate {
log.info("\tClient verification: enabled")
} else {
log.info("\tClient verification: disabled")
}
if let tlsWrap = tlsWrap {
log.info("\tTLS wrapping: \(tlsWrap.strategy)")
} else {
log.info("\tTLS wrapping: disabled")
}
if let tlsSecurityLevel = tlsSecurityLevel {
log.info("\tTLS security level: \(tlsSecurityLevel)")
} else {
log.info("\tTLS security level: default")
}
if let keepAliveSeconds = keepAliveInterval, keepAliveSeconds > 0 {
log.info("\tKeep-alive interval: \(keepAliveSeconds) seconds")
} else {
log.info("\tKeep-alive interval: never")
}
if let keepAliveTimeoutSeconds = keepAliveTimeout, keepAliveTimeoutSeconds > 0 {
log.info("\tKeep-alive timeout: \(keepAliveTimeoutSeconds) seconds")
} else {
log.info("\tKeep-alive timeout: never")
}
if let renegotiatesAfterSeconds = renegotiatesAfter, renegotiatesAfterSeconds > 0 {
log.info("\tRenegotiation: \(renegotiatesAfterSeconds) seconds")
} else {
log.info("\tRenegotiation: never")
}
if checksEKU ?? false {
log.info("\tServer EKU verification: enabled")
} else {
log.info("\tServer EKU verification: disabled")
}
if checksSANHost ?? false {
log.info("\tHost SAN verification: enabled (\(sanHost ?? "-"))")
} else {
log.info("\tHost SAN verification: disabled")
}
if randomizeEndpoint ?? false {
log.info("\tRandomize endpoint: true")
}
if let routingPolicies = routingPolicies {
log.info("\tGateway: \(routingPolicies.map { $0.rawValue })")
} else {
log.info("\tGateway: not configured")
}
if let dnsServers = dnsServers, !dnsServers.isEmpty {
log.info("\tDNS: \(dnsServers.maskedDescription)")
} else {
log.info("\tDNS: not configured")
}
if let searchDomains = searchDomains, !searchDomains.isEmpty {
log.info("\tSearch domains: \(searchDomains.maskedDescription)")
}
if let httpProxy = httpProxy {
log.info("\tHTTP proxy: \(httpProxy.maskedDescription)")
}
if let httpsProxy = httpsProxy {
log.info("\tHTTPS proxy: \(httpsProxy.maskedDescription)")
}
if let proxyAutoConfigurationURL = proxyAutoConfigurationURL {
log.info("\tPAC: \(proxyAutoConfigurationURL)")
}
if let proxyBypassDomains = proxyBypassDomains {
log.info("\tProxy bypass domains: \(proxyBypassDomains.maskedDescription)")
}
if let mtu = mtu {
log.info("\tMTU: \(mtu)")
} else {
log.info("\tMTU: default")
}
}
}

View File

@ -35,6 +35,9 @@
//
import Foundation
import SwiftyBeaver
private let log = SwiftyBeaver.self
extension OpenVPN {
@ -506,3 +509,95 @@ extension OpenVPN.Configuration {
return builder
}
}
// MARK: Encoding
extension OpenVPN.Configuration {
func print() {
guard let endpointProtocols = endpointProtocols else {
fatalError("No sessionConfiguration.endpointProtocols set")
}
log.info("\tProtocols: \(endpointProtocols)")
log.info("\tCipher: \(fallbackCipher)")
log.info("\tDigest: \(fallbackDigest)")
log.info("\tCompression framing: \(fallbackCompressionFraming)")
if let compressionAlgorithm = compressionAlgorithm, compressionAlgorithm != .disabled {
log.info("\tCompression algorithm: \(compressionAlgorithm)")
} else {
log.info("\tCompression algorithm: disabled")
}
if let _ = clientCertificate {
log.info("\tClient verification: enabled")
} else {
log.info("\tClient verification: disabled")
}
if let tlsWrap = tlsWrap {
log.info("\tTLS wrapping: \(tlsWrap.strategy)")
} else {
log.info("\tTLS wrapping: disabled")
}
if let tlsSecurityLevel = tlsSecurityLevel {
log.info("\tTLS security level: \(tlsSecurityLevel)")
} else {
log.info("\tTLS security level: default")
}
if let keepAliveSeconds = keepAliveInterval, keepAliveSeconds > 0 {
log.info("\tKeep-alive interval: \(keepAliveSeconds) seconds")
} else {
log.info("\tKeep-alive interval: never")
}
if let keepAliveTimeoutSeconds = keepAliveTimeout, keepAliveTimeoutSeconds > 0 {
log.info("\tKeep-alive timeout: \(keepAliveTimeoutSeconds) seconds")
} else {
log.info("\tKeep-alive timeout: never")
}
if let renegotiatesAfterSeconds = renegotiatesAfter, renegotiatesAfterSeconds > 0 {
log.info("\tRenegotiation: \(renegotiatesAfterSeconds) seconds")
} else {
log.info("\tRenegotiation: never")
}
if checksEKU ?? false {
log.info("\tServer EKU verification: enabled")
} else {
log.info("\tServer EKU verification: disabled")
}
if checksSANHost ?? false {
log.info("\tHost SAN verification: enabled (\(sanHost ?? "-"))")
} else {
log.info("\tHost SAN verification: disabled")
}
if randomizeEndpoint ?? false {
log.info("\tRandomize endpoint: true")
}
if let routingPolicies = routingPolicies {
log.info("\tGateway: \(routingPolicies.map { $0.rawValue })")
} else {
log.info("\tGateway: not configured")
}
if let dnsServers = dnsServers, !dnsServers.isEmpty {
log.info("\tDNS: \(dnsServers.maskedDescription)")
} else {
log.info("\tDNS: not configured")
}
if let searchDomains = searchDomains, !searchDomains.isEmpty {
log.info("\tSearch domains: \(searchDomains.maskedDescription)")
}
if let httpProxy = httpProxy {
log.info("\tHTTP proxy: \(httpProxy.maskedDescription)")
}
if let httpsProxy = httpsProxy {
log.info("\tHTTPS proxy: \(httpsProxy.maskedDescription)")
}
if let proxyAutoConfigurationURL = proxyAutoConfigurationURL {
log.info("\tPAC: \(proxyAutoConfigurationURL)")
}
if let proxyBypassDomains = proxyBypassDomains {
log.info("\tProxy bypass domains: \(proxyBypassDomains.maskedDescription)")
}
if let mtu = mtu {
log.info("\tMTU: \(mtu)")
} else {
log.info("\tMTU: default")
}
}
}