Skip to content

Commit

Permalink
Migrate settings to Version3 and introduce incremental migration scheme.
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet committed Oct 26, 2023
1 parent 7b8db2b commit a379f24
Show file tree
Hide file tree
Showing 14 changed files with 451 additions and 42 deletions.
4 changes: 2 additions & 2 deletions ios/MullvadREST/RESTAuthorization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ extension REST {
typealias Authorization = String

struct AccessTokenProvider: RESTAuthorizationProvider {
private let accessTokenManager: AccessTokenManager
private let accessTokenManager: RESTAccessTokenManagement
private let accountNumber: String

init(accessTokenManager: AccessTokenManager, accountNumber: String) {
init(accessTokenManager: RESTAccessTokenManagement, accountNumber: String) {
self.accessTokenManager = accessTokenManager
self.accountNumber = accountNumber
}
Expand Down
4 changes: 2 additions & 2 deletions ios/MullvadREST/RESTProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,11 @@ extension REST {
}

public class AuthProxyConfiguration: ProxyConfiguration {
public let accessTokenManager: AccessTokenManager
public let accessTokenManager: RESTAccessTokenManagement

public init(
proxyConfiguration: ProxyConfiguration,
accessTokenManager: AccessTokenManager
accessTokenManager: RESTAccessTokenManagement
) {
self.accessTokenManager = accessTokenManager

Expand Down
54 changes: 54 additions & 0 deletions ios/MullvadSettings/MigrationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,65 @@ public struct MigrationManager {
do {
try checkLatestSettingsVersion(in: store)
handleCompletion(.nothing)
} catch is UnsupportedSettingsVersionError {
do {
try upgradeSettingsToLatestVersion(
store: store,
proxyFactory: proxyFactory,
migrationCompleted: migrationCompleted
)
} catch {
handleCompletion(.failure(error))
}
} catch {
handleCompletion(.failure(error))
}
}

private func upgradeSettingsToLatestVersion(
store: SettingsStore,
proxyFactory: REST.ProxyFactory,
migrationCompleted: @escaping (SettingsMigrationResult) -> Void
) throws {
let parser = SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
let settingsData = try store.read(key: SettingsKey.settings)
let settingsVersion = try parser.parseVersion(data: settingsData)

// Special case downgrade attempts as nothing to do
guard settingsVersion <= SchemaVersion.current.rawValue else {
migrationCompleted(.nothing)
return
}

// Handle cases where the saved version is strictly inferior to the current version
guard let savedSchema = SchemaVersion(rawValue: settingsVersion) else {
migrationCompleted(.failure(UnsupportedSettingsVersionError(
storedVersion: settingsVersion,
currentVersion: SchemaVersion.current
)))
return
}

var versionTypeCopy = savedSchema
let savedSettings = try parser.parsePayload(as: versionTypeCopy.settingsType, from: settingsData)
var latestSettings = savedSettings

repeat {
let upgradedVersion = latestSettings.upgradeToNextVersion(
store: store,
proxyFactory: proxyFactory,
parser: parser
)
versionTypeCopy = versionTypeCopy.nextVersion
latestSettings = upgradedVersion
} while versionTypeCopy.rawValue < SchemaVersion.current.rawValue

// Write the latest settings back to the store
let latestVersionPayload = try parser.producePayload(latestSettings, version: SchemaVersion.current.rawValue)
try store.write(latestVersionPayload, for: .settings)
migrationCompleted(.success)
}

private func checkLatestSettingsVersion(in store: SettingsStore) throws {
let settingsVersion: Int
do {
Expand Down
17 changes: 17 additions & 0 deletions ios/MullvadSettings/SettingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,28 @@ private let accountExpiryKey = "accountExpiry"
public enum SettingsManager {
private static let logger = Logger(label: "SettingsManager")

#if DEBUG
private static var _store = KeychainSettingsStore(
serviceName: keychainServiceName,
accessGroup: ApplicationConfiguration.securityGroupIdentifier
)

/// Alternative store used for tests.
internal static var unitTestStore: SettingsStore?

public static var store: SettingsStore {
if let unitTestStore { return unitTestStore }
return _store
}

#else
public static let store: SettingsStore = KeychainSettingsStore(
serviceName: keychainServiceName,
accessGroup: ApplicationConfiguration.securityGroupIdentifier
)

#endif

private static func makeParser() -> SettingsParser {
SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
}
Expand Down
37 changes: 37 additions & 0 deletions ios/MullvadSettings/StoredWgKeyData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// StoredWgKeyData.swift
// MullvadSettings
//
// Created by Marco Nikic on 2023-10-23.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation
import WireGuardKitTypes

public struct StoredWgKeyData: Codable, Equatable {
/// Private key creation date.
public var creationDate: Date

/// Last date a rotation was attempted. Nil if last attempt was successful.
public var lastRotationAttemptDate: Date?

/// Private key.
public var privateKey: PrivateKey

/// Next private key we're trying to rotate to.
/// Added in 2023.3
public var nextPrivateKey: PrivateKey?

public init(
creationDate: Date,
lastRotationAttemptDate: Date? = nil,
privateKey: PrivateKey,
nextPrivateKey: PrivateKey? = nil
) {
self.creationDate = creationDate
self.lastRotationAttemptDate = lastRotationAttemptDate
self.privateKey = privateKey
self.nextPrivateKey = nextPrivateKey
}
}
41 changes: 39 additions & 2 deletions ios/MullvadSettings/TunnelSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@
//

import Foundation
import MullvadREST

/// Alias to the latest version of the `TunnelSettings`.
public typealias LatestTunnelSettings = TunnelSettingsV2
public typealias LatestTunnelSettings = TunnelSettingsV3

/// Protocol all TunnelSettings must adhere to, for upgrade purposes.
public protocol TunnelSettings: Codable {
func upgradeToNextVersion(
store: SettingsStore,
proxyFactory: REST.ProxyFactory,
parser: SettingsParser
) -> any TunnelSettings
}

/// Settings and device state schema versions.
public enum SchemaVersion: Int, Equatable {
Expand All @@ -19,6 +29,33 @@ public enum SchemaVersion: Int, Equatable {
/// New settings format, stored as `TunnelSettingsV2`.
case v2 = 2

/// V2 format with wireGuard obfuscation options, stored as `TunnelSettingsV3`.
case v3 = 3

var settingsType: any TunnelSettings.Type {
switch self {
case .v1: return TunnelSettingsV1.self
case .v2: return TunnelSettingsV2.self
case .v3: return TunnelSettingsV3.self
}
}

var nextVersion: Self {
switch self {
case .v1: return .v2
case .v2: return .v3
case .v3: return .v3
}
}

var nextVersionType: any TunnelSettings.Type {
switch self {
case .v1: return TunnelSettingsV2.self
case .v2: return TunnelSettingsV3.self
case .v3: return TunnelSettingsV3.self
}
}

/// Current schema version.
public static let current = SchemaVersion.v2
public static let current = SchemaVersion.v3
}
11 changes: 10 additions & 1 deletion ios/MullvadSettings/TunnelSettingsV1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,25 @@
//

import Foundation
import MullvadREST
import MullvadTypes
import struct Network.IPv4Address
import struct WireGuardKitTypes.IPAddressRange
import class WireGuardKitTypes.PrivateKey
import class WireGuardKitTypes.PublicKey

/// A struct that holds the configuration passed via `NETunnelProviderProtocol`.
public struct TunnelSettingsV1: Codable, Equatable {
public struct TunnelSettingsV1: Codable, Equatable, TunnelSettings {
public var relayConstraints = RelayConstraints()
public var interface = InterfaceSettings()

public func upgradeToNextVersion(
store: SettingsStore,
proxyFactory: REST.ProxyFactory,
parser: SettingsParser
) -> any TunnelSettings {
TunnelSettingsV2(relayConstraints: relayConstraints, dnsSettings: interface.dnsSettings)
}
}

/// A struct that holds a tun interface configuration.
Expand Down
42 changes: 12 additions & 30 deletions ios/MullvadSettings/TunnelSettingsV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@
//

import Foundation
import MullvadREST
import MullvadTypes
import struct Network.IPv4Address
import struct WireGuardKitTypes.IPAddressRange
import class WireGuardKitTypes.PrivateKey
import class WireGuardKitTypes.PublicKey

public struct TunnelSettingsV2: Codable, Equatable {
public struct TunnelSettingsV2: Codable, Equatable, TunnelSettings {
/// Relay constraints.
public var relayConstraints: RelayConstraints

Expand All @@ -27,31 +24,16 @@ public struct TunnelSettingsV2: Codable, Equatable {
self.relayConstraints = relayConstraints
self.dnsSettings = dnsSettings
}
}

public struct StoredWgKeyData: Codable, Equatable {
/// Private key creation date.
public var creationDate: Date

/// Last date a rotation was attempted. Nil if last attempt was successful.
public var lastRotationAttemptDate: Date?

/// Private key.
public var privateKey: PrivateKey

/// Next private key we're trying to rotate to.
/// Added in 2023.3
public var nextPrivateKey: PrivateKey?

public init(
creationDate: Date,
lastRotationAttemptDate: Date? = nil,
privateKey: PrivateKey,
nextPrivateKey: PrivateKey? = nil
) {
self.creationDate = creationDate
self.lastRotationAttemptDate = lastRotationAttemptDate
self.privateKey = privateKey
self.nextPrivateKey = nextPrivateKey
public func upgradeToNextVersion(
store: SettingsStore,
proxyFactory: REST.ProxyFactory,
parser: SettingsParser
) -> any TunnelSettings {
TunnelSettingsV3(
relayConstraints: relayConstraints,
dnsSettings: dnsSettings,
wireGuardObfuscation: WireGuardObfuscationSettings()
)
}
}
40 changes: 40 additions & 0 deletions ios/MullvadSettings/TunnelSettingsV3.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// TunnelSettingsV3.swift
// MullvadVPN
//
// Created by Marco Nikic on 2023-10-17.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadREST
import MullvadTypes

public struct TunnelSettingsV3: Codable, Equatable, TunnelSettings {
/// Relay constraints.
public var relayConstraints: RelayConstraints

/// DNS settings.
public var dnsSettings: DNSSettings

/// WireGuard obfuscation settings
public var wireGuardObfuscation: WireGuardObfuscationSettings

public init(
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings()
) {
self.relayConstraints = relayConstraints
self.dnsSettings = dnsSettings
self.wireGuardObfuscation = wireGuardObfuscation
}

public func upgradeToNextVersion(
store: SettingsStore,
proxyFactory: REST.ProxyFactory,
parser: SettingsParser
) -> any TunnelSettings {
self
}
}
31 changes: 31 additions & 0 deletions ios/MullvadSettings/WireGuardObfuscationSettings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// WireGuardObfuscationSettings.swift
// MullvadVPN
//
// Created by Marco Nikic on 2023-10-17.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation

public enum WireGuardObfuscationState: Codable {
case automatic
case on
case off
}

public enum WireGuardObfuscationPort: Codable {
case automatic
case port80
case port5001
}

public struct WireGuardObfuscationSettings: Codable, Equatable {
let state: WireGuardObfuscationState
let port: WireGuardObfuscationPort

public init(state: WireGuardObfuscationState = .automatic, port: WireGuardObfuscationPort = .automatic) {
self.state = state
self.port = port
}
}
Loading

0 comments on commit a379f24

Please sign in to comment.