diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2022-05-19 11:52:58 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2022-05-30 15:00:06 +0200 |
| commit | cba0e565f97e8fa18854408519b78174b12ef69a (patch) | |
| tree | e819007519da2243d161e3399dee838f4714b5d9 | |
| parent | 212ed0fc0b97ff0418a9e7ab30dce1984b3769a8 (diff) | |
| download | mullvadvpn-cba0e565f97e8fa18854408519b78174b12ef69a.tar.xz mullvadvpn-cba0e565f97e8fa18854408519b78174b12ef69a.zip | |
Introduce new settings manager and settings revision (v2)
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 54 | ||||
| -rw-r--r-- | ios/MullvadVPN/DNSSettings.swift (renamed from ios/MullvadVPN/TunnelSettings.swift) | 61 | ||||
| -rw-r--r-- | ios/MullvadVPN/PrivateKeyWithMetadata.swift | 100 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsManager/SettingsManager.swift | 256 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift | 98 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsManager/TunnelSettingsV2+REST.swift | 21 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift | 69 | ||||
| -rw-r--r-- | ios/MullvadVPN/TunnelSettingsManager.swift | 258 |
8 files changed, 483 insertions, 434 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 7517459efb..8e5de87959 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -22,6 +22,10 @@ 58095C592762155700890776 /* RESTRetryStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58095C582762155700890776 /* RESTRetryStrategy.swift */; }; 580EE20624B3222200F9D8A1 /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580EE20524B3222200F9D8A1 /* ExclusivityController.swift */; }; 580EE22424B3243100F9D8A1 /* AsyncBlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580EE22324B3243100F9D8A1 /* AsyncBlockOperation.swift */; }; + 580F8B8328197881002E0998 /* TunnelSettingsV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580F8B8228197881002E0998 /* TunnelSettingsV2.swift */; }; + 580F8B8428197884002E0998 /* TunnelSettingsV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580F8B8228197881002E0998 /* TunnelSettingsV2.swift */; }; + 580F8B8628197958002E0998 /* DNSSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580F8B8528197958002E0998 /* DNSSettings.swift */; }; + 580F8B872819795C002E0998 /* DNSSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580F8B8528197958002E0998 /* DNSSettings.swift */; }; 5811DE50239014550011EB53 /* NEVPNStatus+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */; }; 5815039724D6ECAE00C9C50E /* CustomFormatLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039624D6ECAE00C9C50E /* CustomFormatLogHandler.swift */; }; 5815039824D6ECAE00C9C50E /* CustomFormatLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039624D6ECAE00C9C50E /* CustomFormatLogHandler.swift */; }; @@ -74,6 +78,7 @@ 5840250522B11AB700E4CFEC /* MullvadEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */; }; 5840BE35279EDB16002836BA /* OperationCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5840BE34279EDB16002836BA /* OperationCompletion.swift */; }; 5842102E282D3FC200F24E46 /* ResultBlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5842102D282D3FC200F24E46 /* ResultBlockOperation.swift */; }; + 58421034282E4B1500F24E46 /* TunnelSettingsV2+REST.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58421033282E4B1500F24E46 /* TunnelSettingsV2+REST.swift */; }; 584592612639B4A200EF967F /* ConsentContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584592602639B4A200EF967F /* ConsentContentView.swift */; }; 5846226526E0D9630035F7C2 /* ProductsRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846226426E0D9630035F7C2 /* ProductsRequestOperation.swift */; }; 5846227126E229F20035F7C2 /* AppStoreSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227026E229F20035F7C2 /* AppStoreSubscription.swift */; }; @@ -95,8 +100,6 @@ 584EBDBD2747C98F00A0C9FD /* NSAttributedString+Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584EBDBC2747C98F00A0C9FD /* NSAttributedString+Markdown.swift */; }; 5850366825A47AC700A43E93 /* IPAddressRange+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5850366725A47AC700A43E93 /* IPAddressRange+Codable.swift */; }; 5850367F25A481D800A43E93 /* IPAddressRange+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5850366725A47AC700A43E93 /* IPAddressRange+Codable.swift */; }; - 5850368C25A49E2200A43E93 /* PrivateKeyWithMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C6B35322BB87C4003C19AD /* PrivateKeyWithMetadata.swift */; }; - 5850368D25A49E2200A43E93 /* PrivateKeyWithMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C6B35322BB87C4003C19AD /* PrivateKeyWithMetadata.swift */; }; 58554F73280AFA5A00013055 /* RESTAuthenticationProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58554F72280AFA5A00013055 /* RESTAuthenticationProxy.swift */; }; 58554F77280AFD5C00013055 /* RESTTaskIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58554F76280AFD5C00013055 /* RESTTaskIdentifier.swift */; }; 58554F79280B037400013055 /* RESTAccessTokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58554F78280B037400013055 /* RESTAccessTokenManager.swift */; }; @@ -151,14 +154,15 @@ 5875960A26F371FC00BF6711 /* TunnelIPCSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5875960926F371FC00BF6711 /* TunnelIPCSession.swift */; }; 5875960B26F3723000BF6711 /* TunnelIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5845F841236CBACD00B2D93C /* TunnelIPC.swift */; }; 5877153023981F7B001F8237 /* WireguardKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */; }; + 5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; 58781CC922AE7CA8009B9D8E /* RelayConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */; }; 58781CCE22AE8918009B9D8E /* RelayConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */; }; 58781CD522AFBA39009B9D8E /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; }; 5878BA1426DD0B01004147D7 /* OSLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA4F26CA690600283BF8 /* OSLogHandler.swift */; }; 587A01FC23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */; }; - 587AD7C623421D7000E93A53 /* TunnelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettings.swift */; }; - 587AD7C723421D8600E93A53 /* TunnelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettings.swift */; }; 587AD7CA2342283900E93A53 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C92342283900E93A53 /* Account.swift */; }; + 587AD7C623421D7000E93A53 /* TunnelSettingsV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */; }; + 587AD7C723421D8600E93A53 /* TunnelSettingsV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */; }; 587B7536266528A200DEF7E9 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B7535266528A200DEF7E9 /* NotificationManager.swift */; }; 587B753B2666467500DEF7E9 /* NotificationBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B753A2666467500DEF7E9 /* NotificationBannerView.swift */; }; 587B753D2666468F00DEF7E9 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B753C2666468F00DEF7E9 /* NotificationController.swift */; }; @@ -202,8 +206,6 @@ 58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */; }; 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF642344A36000C9BBD5 /* KeychainError.swift */; }; 58AEEF662344A37400C9BBD5 /* KeychainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF642344A36000C9BBD5 /* KeychainError.swift */; }; - 58AEEF6B2344A46200C9BBD5 /* TunnelSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF6A2344A46200C9BBD5 /* TunnelSettingsManager.swift */; }; - 58AEEF6C2344A49D00C9BBD5 /* TunnelSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF6A2344A46200C9BBD5 /* TunnelSettingsManager.swift */; }; 58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584B26F3237434D00073B10E /* RelaySelectorTests.swift */; }; 58B0A2A9238EE6A100BC001D /* RelayConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */; }; 58B0A2AA238EE6A900BC001D /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; }; @@ -295,6 +297,7 @@ 58FEAFB92750DA2F003C1625 /* AddressCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEAFB82750DA2F003C1625 /* AddressCache.swift */; }; 58FEEB46260A028D00A621A8 /* GeoJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB45260A028D00A621A8 /* GeoJSON.swift */; }; 58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */; }; + 58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -355,6 +358,8 @@ 58095C582762155700890776 /* RESTRetryStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRetryStrategy.swift; sourceTree = "<group>"; }; 580EE20524B3222200F9D8A1 /* ExclusivityController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExclusivityController.swift; sourceTree = "<group>"; }; 580EE22324B3243100F9D8A1 /* AsyncBlockOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncBlockOperation.swift; sourceTree = "<group>"; }; + 580F8B8228197881002E0998 /* TunnelSettingsV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV2.swift; sourceTree = "<group>"; }; + 580F8B8528197958002E0998 /* DNSSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSSettings.swift; sourceTree = "<group>"; }; 5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEVPNStatus+Debug.swift"; sourceTree = "<group>"; }; 5815039324D6EB7200C9C50E /* LogRotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRotation.swift; sourceTree = "<group>"; }; 5815039624D6ECAE00C9C50E /* CustomFormatLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFormatLogHandler.swift; sourceTree = "<group>"; }; @@ -391,6 +396,7 @@ 5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadEndpoint.swift; sourceTree = "<group>"; }; 5840BE34279EDB16002836BA /* OperationCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationCompletion.swift; sourceTree = "<group>"; }; 5842102D282D3FC200F24E46 /* ResultBlockOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultBlockOperation.swift; sourceTree = "<group>"; }; + 58421033282E4B1500F24E46 /* TunnelSettingsV2+REST.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelSettingsV2+REST.swift"; sourceTree = "<group>"; }; 584592602639B4A200EF967F /* ConsentContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentContentView.swift; sourceTree = "<group>"; }; 5845F841236CBACD00B2D93C /* TunnelIPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelIPC.swift; sourceTree = "<group>"; }; 5846226426E0D9630035F7C2 /* ProductsRequestOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsRequestOperation.swift; sourceTree = "<group>"; }; @@ -446,8 +452,8 @@ 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraints.swift; sourceTree = "<group>"; }; 58781CD422AFBA39009B9D8E /* RelaySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = "<group>"; }; 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderHost.swift; sourceTree = "<group>"; }; - 587AD7C523421D7000E93A53 /* TunnelSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettings.swift; sourceTree = "<group>"; }; 587AD7C92342283900E93A53 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; }; + 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV1.swift; sourceTree = "<group>"; }; 587B7535266528A200DEF7E9 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; }; 587B753A2666467500DEF7E9 /* NotificationBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBannerView.swift; sourceTree = "<group>"; }; 587B753C2666468F00DEF7E9 /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = "<group>"; }; @@ -485,7 +491,6 @@ 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSwitch.swift; sourceTree = "<group>"; }; 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSwitchContainer.swift; sourceTree = "<group>"; }; 58AEEF642344A36000C9BBD5 /* KeychainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainError.swift; sourceTree = "<group>"; }; - 58AEEF6A2344A46200C9BBD5 /* TunnelSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManager.swift; sourceTree = "<group>"; }; 58B0A2A0238EE67E00BC001D /* MullvadVPNTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadVPNTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58B0A2A4238EE67E00BC001D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 58B3F30E2742708B00A2DD38 /* HeaderBarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderBarButton.swift; sourceTree = "<group>"; }; @@ -500,7 +505,6 @@ 58BFA5C522A7C97F00A6173D /* RelayCacheTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTracker.swift; sourceTree = "<group>"; }; 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationConfiguration.swift; sourceTree = "<group>"; }; 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInputGroupView.swift; sourceTree = "<group>"; }; - 58C6B35322BB87C4003C19AD /* PrivateKeyWithMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateKeyWithMetadata.swift; sourceTree = "<group>"; }; 58CB0EDF24B86751001EF0D8 /* RESTAPIProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTAPIProxy.swift; sourceTree = "<group>"; }; 58CC40EE24A601900019D96E /* ObserverList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObserverList.swift; sourceTree = "<group>"; }; 58CCA00F224249A1004F3011 /* ConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewController.swift; sourceTree = "<group>"; }; @@ -575,6 +579,7 @@ 58FEAFB82750DA2F003C1625 /* AddressCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCache.swift; sourceTree = "<group>"; }; 58FEEB45260A028D00A621A8 /* GeoJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoJSON.swift; sourceTree = "<group>"; }; 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticKeyboardResponder.swift; sourceTree = "<group>"; }; + 58FF2C02281BDE02009EF542 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -649,6 +654,17 @@ path = Operations; sourceTree = "<group>"; }; + 580F8B88281A79A7002E0998 /* SettingsManager */ = { + isa = PBXGroup; + children = ( + 58FF2C02281BDE02009EF542 /* SettingsManager.swift */, + 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */, + 580F8B8228197881002E0998 /* TunnelSettingsV2.swift */, + 58421033282E4B1500F24E46 /* TunnelSettingsV2+REST.swift */, + ); + path = SettingsManager; + sourceTree = "<group>"; + }; 5815039F24D6ECF200C9C50E /* Logging */ = { isa = PBXGroup; children = ( @@ -881,6 +897,7 @@ 587EB66F27143B6500123C75 /* DataSourceSnapshot.swift */, 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */, 58B9EB142489139B00095626 /* DisplayChainedError.swift */, + 580F8B8528197958002E0998 /* DNSSettings.swift */, 5892A45D265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift */, 58FEEB45260A028D00A621A8 /* GeoJSON.swift */, 58B3F30E2742708B00A2DD38 /* HeaderBarButton.swift */, @@ -915,7 +932,6 @@ 587EB6732714520600123C75 /* PreferencesDataSourceDelegate.swift */, 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */, 587EB671271451E300123C75 /* PreferencesViewModel.swift */, - 58C6B35322BB87C4003C19AD /* PrivateKeyWithMetadata.swift */, 58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */, 58EF580A25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift */, 58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */, @@ -936,6 +952,7 @@ 58EE2E38272FF814003BFF93 /* SettingsDataSource.swift */, 58EE2E39272FF814003BFF93 /* SettingsDataSourceDelegate.swift */, 584D26C5270C8741004EA533 /* SettingsDNSTextCell.swift */, + 580F8B88281A79A7002E0998 /* SettingsManager */, 58E6771E24ADFE7800AA26E7 /* SettingsNavigationController.swift */, 584D26C1270C8542004EA533 /* SettingsStaticTextFooterView.swift */, 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */, @@ -951,8 +968,6 @@ 58E0A98727C8F46300FE6BDD /* Tunnel.swift */, 585DA88D26B031D100B8C587 /* TunnelIPC */, 5823FA5726CE4A4100283BF8 /* TunnelManager */, - 587AD7C523421D7000E93A53 /* TunnelSettings.swift */, - 58AEEF6A2344A46200C9BBD5 /* TunnelSettingsManager.swift */, 5891BF5025E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift */, 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */, 58CCA0152242560B004F3011 /* UIColor+Palette.swift */, @@ -1289,6 +1304,7 @@ 5846227126E229F20035F7C2 /* AppStoreSubscription.swift in Sources */, 5820675B26E6576800655B05 /* RelayCache.swift in Sources */, 5846226526E0D9630035F7C2 /* ProductsRequestOperation.swift in Sources */, + 58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */, 587EB672271451E300123C75 /* PreferencesViewModel.swift in Sources */, 58095C512760BBB500890776 /* AddressCacheTracker.swift in Sources */, 584D26C6270C8741004EA533 /* SettingsDNSTextCell.swift in Sources */, @@ -1351,7 +1367,6 @@ 58A99ED3240014A0006599E9 /* ConsentViewController.swift in Sources */, 58CCA0162242560B004F3011 /* UIColor+Palette.swift in Sources */, 58095C4F2760BA9100890776 /* AddressCacheStore.swift in Sources */, - 58AEEF6B2344A46200C9BBD5 /* TunnelSettingsManager.swift in Sources */, 587CBFE322807F530028DED3 /* UIColor+Helpers.swift in Sources */, 588527B4276B4F2F00BAA373 /* SetAccountOperation.swift in Sources */, 585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */, @@ -1375,7 +1390,6 @@ 584B17AB27637DE40057F3B8 /* ReloadTunnelOperation.swift in Sources */, 5820676426E771DB00655B05 /* TunnelManagerError.swift in Sources */, 585B4B8726D9098900555C4C /* TunnelErrorNotificationProvider.swift in Sources */, - 5850368C25A49E2200A43E93 /* PrivateKeyWithMetadata.swift in Sources */, 58FEAFB92750DA2F003C1625 /* AddressCache.swift in Sources */, 58B67B482602079E008EF58E /* RelaySelector.swift in Sources */, 58DF28A52417CB4B00E836B0 /* AppStorePaymentManager.swift in Sources */, @@ -1418,6 +1432,7 @@ 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */, 581503A624D6F4AE00C9C50E /* Logging.swift in Sources */, 58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */, + 580F8B8628197958002E0998 /* DNSSettings.swift in Sources */, 58FB865526E8BF3100F188BC /* AppStorePaymentManagerError.swift in Sources */, 58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */, 580EE22424B3243100F9D8A1 /* AsyncBlockOperation.swift in Sources */, @@ -1426,6 +1441,7 @@ 58B9EB152489139B00095626 /* DisplayChainedError.swift in Sources */, 587B753F2668E5A700DEF7E9 /* NotificationContainerView.swift in Sources */, 58554F79280B037400013055 /* RESTAccessTokenManager.swift in Sources */, + 58421034282E4B1500F24E46 /* TunnelSettingsV2+REST.swift in Sources */, 58F2E144276A13F300A79513 /* StartTunnelOperation.swift in Sources */, 5868BD33261DCD2600E6027F /* CustomSplitViewController.swift in Sources */, 58CCA01E2242787B004F3011 /* AccountTextField.swift in Sources */, @@ -1443,7 +1459,7 @@ 585DA89B26B146B300B8C587 /* TunnelIPCCoding.swift in Sources */, 5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */, 585DA89626B0328000B8C587 /* TunnelIPCResponse.swift in Sources */, - 587AD7C623421D7000E93A53 /* TunnelSettings.swift in Sources */, + 587AD7C623421D7000E93A53 /* TunnelSettingsV1.swift in Sources */, 581503A324D6F1EC00C9C50E /* ChainedError+Logger.swift in Sources */, 58E20771274672CA00DE5D77 /* LaunchViewController.swift in Sources */, 584D26C4270C855B004EA533 /* PreferencesDataSource.swift in Sources */, @@ -1460,6 +1476,7 @@ 58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */, 587EB67027143B6500123C75 /* DataSourceSnapshot.swift in Sources */, 585DA88A26B027A300B8C587 /* RESTCoding.swift in Sources */, + 580F8B8328197881002E0998 /* TunnelSettingsV2.swift in Sources */, 587B753D2666468F00DEF7E9 /* NotificationController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1473,14 +1490,13 @@ 585DA89726B0328000B8C587 /* TunnelIPCResponse.swift in Sources */, 587C575426D2615F005EF767 /* PacketTunnelOptions.swift in Sources */, 58BFA5CD22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */, - 5850368D25A49E2200A43E93 /* PrivateKeyWithMetadata.swift in Sources */, 5820675826E652AF00655B05 /* RelayCacheIO.swift in Sources */, 584D26C0270C550E004EA533 /* AnyIPAddress.swift in Sources */, 5820675726E652A600655B05 /* REST.swift in Sources */, 585DA88F26B031E200B8C587 /* TunnelIPCCoding.swift in Sources */, 5806767C27048E9B00C858CB /* PacketTunnelProvider.swift in Sources */, 585DA89426B0323E00B8C587 /* TunnelIPCRequest.swift in Sources */, - 587AD7C723421D8600E93A53 /* TunnelSettings.swift in Sources */, + 587AD7C723421D8600E93A53 /* TunnelSettingsV1.swift in Sources */, 5875960B26F3723000BF6711 /* TunnelIPC.swift in Sources */, 58AEEF662344A37400C9BBD5 /* KeychainError.swift in Sources */, 582AD44127BE6178002A6BFC /* CodingErrors+ChainedError.swift in Sources */, @@ -1491,11 +1507,12 @@ 585DA89A26B0329200B8C587 /* PacketTunnelStatus.swift in Sources */, 585DA88526B0270700B8C587 /* ServerRelaysResponse.swift in Sources */, 581503A724D6F4AE00C9C50E /* Logging.swift in Sources */, - 58AEEF6C2344A49D00C9BBD5 /* TunnelSettingsManager.swift in Sources */, + 580F8B8428197884002E0998 /* TunnelSettingsV2.swift in Sources */, 581503A424D6F1EC00C9C50E /* ChainedError+Logger.swift in Sources */, 5815039824D6ECAE00C9C50E /* CustomFormatLogHandler.swift in Sources */, 5840250522B11AB700E4CFEC /* MullvadEndpoint.swift in Sources */, 58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */, + 580F8B872819795C002E0998 /* DNSSettings.swift in Sources */, 58655DCF27DA0A5D00911834 /* TunnelMonitorConfiguration.swift in Sources */, 5815039E24D6ECE600C9C50E /* TextFileOutputStream.swift in Sources */, 585DA87826B024A900B8C587 /* CachedRelays.swift in Sources */, @@ -1508,6 +1525,7 @@ 58781CCE22AE8918009B9D8E /* RelayConstraints.swift in Sources */, 581503A024D6F01E00C9C50E /* LogRotation.swift in Sources */, 58781CD522AFBA39009B9D8E /* RelaySelector.swift in Sources */, + 5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */, 5820675926E652BE00655B05 /* RESTCoding.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/MullvadVPN/TunnelSettings.swift b/ios/MullvadVPN/DNSSettings.swift index a9d8c5b063..bee5b1d7f1 100644 --- a/ios/MullvadVPN/TunnelSettings.swift +++ b/ios/MullvadVPN/DNSSettings.swift @@ -1,68 +1,13 @@ // -// TunnelSettings.swift +// DNSSettings.swift // MullvadVPN // -// Created by pronebird on 19/06/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. +// Created by pronebird on 27/04/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. // import Foundation import struct Network.IPv4Address -import class WireGuardKitTypes.PublicKey -import struct WireGuardKitTypes.IPAddressRange - -/// A struct that holds a tun interface configuration. -struct InterfaceSettings: Codable, Equatable { - var privateKey: PrivateKeyWithMetadata - var nextPrivateKey: PrivateKeyWithMetadata? - - var addresses: [IPAddressRange] - var dnsSettings: DNSSettings - - var publicKey: PublicKey { - return privateKey.publicKeyWithMetadata.publicKey - } - - private enum CodingKeys: String, CodingKey { - case privateKey, nextPrivateKey, addresses, dnsSettings - } - - init(privateKey: PrivateKeyWithMetadata = PrivateKeyWithMetadata(), nextPrivateKey: PrivateKeyWithMetadata? = nil, addresses: [IPAddressRange] = [], dnsSettings: DNSSettings = DNSSettings()) { - self.privateKey = privateKey - self.nextPrivateKey = nextPrivateKey - self.addresses = addresses - self.dnsSettings = dnsSettings - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - privateKey = try container.decode(PrivateKeyWithMetadata.self, forKey: .privateKey) - addresses = try container.decode([IPAddressRange].self, forKey: .addresses) - - // Added in 2022.1 - nextPrivateKey = try container.decodeIfPresent(PrivateKeyWithMetadata.self, forKey: .nextPrivateKey) - - // Provide default value, since `dnsSettings` key does not exist in <= 2021.2 - dnsSettings = try container.decodeIfPresent(DNSSettings.self, forKey: .dnsSettings) - ?? DNSSettings() - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(privateKey, forKey: .privateKey) - try container.encode(nextPrivateKey, forKey: .nextPrivateKey) - try container.encode(addresses, forKey: .addresses) - try container.encode(dnsSettings, forKey: .dnsSettings) - } -} - -/// A struct that holds the configuration passed via `NETunnelProviderProtocol`. -struct TunnelSettings: Codable, Equatable { - var relayConstraints = RelayConstraints() - var interface = InterfaceSettings() -} /// A struct describing Mullvad DNS blocking options. struct DNSBlockingOptions: OptionSet, Codable { diff --git a/ios/MullvadVPN/PrivateKeyWithMetadata.swift b/ios/MullvadVPN/PrivateKeyWithMetadata.swift deleted file mode 100644 index 04e73f784c..0000000000 --- a/ios/MullvadVPN/PrivateKeyWithMetadata.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// PrivateKeyWithMetadata.swift -// MullvadVPN -// -// Created by pronebird on 20/06/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import class WireGuardKitTypes.PrivateKey -import class WireGuardKitTypes.PublicKey - -/// A struct holding a private WireGuard key with associated metadata -struct PrivateKeyWithMetadata: Equatable { - - /// When the key was created - let creationDate: Date - - /// Private key - let privateKey: PrivateKey - - /// Public key metadata - var publicKeyWithMetadata: PublicKeyWithMetadata { - return PublicKeyWithMetadata(publicKey: privateKey.publicKey, createdAt: creationDate) - } - - /// Public key - var publicKey: PublicKey { - return privateKey.publicKey - } - - /// Initialize the new private key - init() { - privateKey = PrivateKey() - creationDate = Date() - } - - /// Initialize with the existing private key - init(privateKey: PrivateKey, createdAt: Date) { - self.privateKey = privateKey - creationDate = createdAt - } - -} - -/// A struct holding a public WireGuard key with associated metadata -struct PublicKeyWithMetadata: Equatable { - /// Refers to private key creation date - let creationDate: Date - - /// Public key - let publicKey: PublicKey - - init(publicKey: PublicKey, createdAt: Date) { - self.publicKey = publicKey - creationDate = createdAt - } - - /// Returns a base64 encoded string representation that can be used for displaying the key in - /// the user interface - func stringRepresentation(maxLength: Int? = nil) -> String { - let base64EncodedKey = publicKey.base64Key - - if let maxLength = maxLength, maxLength < base64EncodedKey.count { - return base64EncodedKey.prefix(maxLength) + "..." - } else { - return base64EncodedKey - } - } -} - -extension PrivateKeyWithMetadata: Codable { - - private enum CodingKeys: String, CodingKey { - case privateKeyData, creationDate - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(privateKey.rawValue, forKey: .privateKeyData) - try container.encode(creationDate, forKey: .creationDate) - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let privateKeyBytes = try container.decode(Data.self, forKey: .privateKeyData) - - guard let privateKey = PrivateKey(rawValue: privateKeyBytes) else { - throw DecodingError.dataCorruptedError( - forKey: CodingKeys.privateKeyData, - in: container, - debugDescription: "Invalid key data" - ) - } - - self.privateKey = privateKey - self.creationDate = try container.decode(Date.self, forKey: .creationDate) - } -} diff --git a/ios/MullvadVPN/SettingsManager/SettingsManager.swift b/ios/MullvadVPN/SettingsManager/SettingsManager.swift new file mode 100644 index 0000000000..41945b807d --- /dev/null +++ b/ios/MullvadVPN/SettingsManager/SettingsManager.swift @@ -0,0 +1,256 @@ +// +// SettingsManager.swift +// MullvadVPN +// +// Created by pronebird on 29/04/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import Logging + +enum SettingsManager {} + +struct LegacyTunnelSettings { + let accountNumber: String + let tunnelSettings: TunnelSettingsV1 +} + +let keychainServiceName = "Mullvad VPN" + +enum KeychainAccountName: String, CaseIterable { + case settings = "Settings" + case lastUsedAccount = "LastUsedAccount" +} + +extension SettingsManager { + + // MARK: - + + static func getLastUsedAccount() throws -> String { + var query = createDefaultAttributes(accountName: .lastUsedAccount) + query[kSecReturnData] = true + + var result: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &result) + + guard status == errSecSuccess else { + throw KeychainError(code: status) + } + + let data = result as! Data + + return String(data: data, encoding: .utf8)! + } + + static func setLastUsedAccount(_ string: String?) throws { + let query = createDefaultAttributes(accountName: .lastUsedAccount) + + guard let string = string else { + switch SecItemDelete(query as CFDictionary) { + case errSecSuccess, errSecItemNotFound: + return + case let status: + throw KeychainError(code: status) + } + } + + let data = string.data(using: .utf8)! + var status = SecItemUpdate( + query as CFDictionary, + [kSecValueData: data] as CFDictionary + ) + + switch status { + case errSecItemNotFound: + var insert = query + insert[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlock + insert[kSecValueData] = data + + status = SecItemAdd(insert as CFDictionary, nil) + if status != errSecSuccess { + throw KeychainError(code: status) + } + case errSecSuccess: + break + default: + throw KeychainError(code: status) + } + } + + // MARK: - + + static func readSettings() throws -> TunnelSettingsV2 { + var query = createDefaultAttributes(accountName: .settings) + query[kSecReturnData] = true + + var result: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &result) + + guard status == errSecSuccess else { + throw KeychainError(code: status) + } + + let data = result as! Data + + let decoder = JSONDecoder() + return try decoder.decode(TunnelSettingsV2.self, from: data) + } + + static func writeSettings(_ settings: TunnelSettingsV2) throws { + let encoder = JSONEncoder() + let data = try encoder.encode(settings) + + let query = createDefaultAttributes(accountName: .settings) + var status = SecItemUpdate( + query as CFDictionary, + [kSecValueData: data] as CFDictionary + ) + + switch status { + case errSecItemNotFound: + var insert = query + insert[kSecAttrAccessGroup] = ApplicationConfiguration.securityGroupIdentifier + insert[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlock + insert[kSecValueData] = data + + status = SecItemAdd(insert as CFDictionary, nil) + if status != errSecSuccess { + throw KeychainError(code: status) + } + case errSecSuccess: + break + default: + throw KeychainError(code: status) + } + } + + static func deleteSettings() throws { + let query = createDefaultAttributes(accountName: .settings) + let status = SecItemDelete(query as CFDictionary) + if status != errSecSuccess { + throw KeychainError(code: status) + } + } + + private static func createDefaultAttributes(accountName: KeychainAccountName) -> [CFString: Any] { + return [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: keychainServiceName, + kSecAttrAccount: accountName.rawValue + ] + } + + // MARK: - Legacy settings support + + private static let logger = Logger(label: "SettingsManager") + + static func readLegacySettings() throws -> [LegacyTunnelSettings] { + let query: [CFString: Any] = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: keychainServiceName, + kSecReturnAttributes: true, + kSecReturnData: true, + kSecMatchLimit: kSecMatchLimitAll + ] + + var result: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &result) + + guard status == errSecSuccess else { + throw KeychainError(code: status) + } + + guard let items = result as? [[CFString: Any]] else { + return [] + } + + return items.filter(Self.filterLegacySettings) + .compactMap { item -> LegacyTunnelSettings? in + guard let accountNumber = item[kSecAttrAccount] as? String, + let data = item[kSecValueData] as? Data else { + return nil + } + do { + let tunnelSettings = try JSONDecoder().decode( + TunnelSettingsV1.self, + from: data + ) + + return LegacyTunnelSettings( + accountNumber: accountNumber, + tunnelSettings: tunnelSettings + ) + } catch { + logger.error( + chainedError: AnyChainedError(error), + message: "Failed to decode legacy settings." + ) + return nil + } + } + } + + static func deleteLegacySettings() { + let query: [CFString: Any] = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: keychainServiceName, + kSecReturnAttributes: true, + kSecMatchLimit: kSecMatchLimitAll + ] + + var result: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &result) + + guard status == errSecSuccess else { + let error = KeychainError(code: status) + + if error != .itemNotFound { + logger.error( + chainedError: AnyChainedError(error), + message: "Failed to list legacy settings." + ) + } + + return + } + + guard let items = result as? [[CFString: Any]] else { + return + } + + items.filter(Self.filterLegacySettings) + .enumerated() + .forEach { (index, item) in + guard let account = item[kSecAttrAccount] else { + return + } + + let deleteQuery: [CFString: Any] = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: keychainServiceName, + kSecAttrAccount: account + ] + + let status = SecItemDelete(deleteQuery as CFDictionary) + if status == errSecSuccess { + logger.debug("Removed legacy settings entry \(index).") + } else { + let error = KeychainError(code: status) + + logger.error( + chainedError: AnyChainedError(error), + message: "Failed to remove legacy settings entry \(index)." + ) + } + } + } + + private static func filterLegacySettings(_ item: [CFString: Any]) -> Bool { + guard let accountNumber = item[kSecAttrAccount] as? String else { + return false + } + + return KeychainAccountName(rawValue: accountNumber) == nil + } +} diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift new file mode 100644 index 0000000000..0d7facb554 --- /dev/null +++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift @@ -0,0 +1,98 @@ +// +// TunnelSettingsV1.swift +// MullvadVPN +// +// Created by pronebird on 19/06/2019. +// Copyright © 2019 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import struct Network.IPv4Address +import class WireGuardKitTypes.PublicKey +import class WireGuardKitTypes.PrivateKey +import struct WireGuardKitTypes.IPAddressRange + +/// A struct that holds the configuration passed via `NETunnelProviderProtocol`. +struct TunnelSettingsV1: Codable, Equatable { + var relayConstraints = RelayConstraints() + var interface = InterfaceSettings() +} + +/// A struct that holds a tun interface configuration. +struct InterfaceSettings: Codable, Equatable { + var privateKey: PrivateKeyWithMetadata + var nextPrivateKey: PrivateKeyWithMetadata? + + var addresses: [IPAddressRange] + var dnsSettings: DNSSettings + + private enum CodingKeys: String, CodingKey { + case privateKey, nextPrivateKey, addresses, dnsSettings + } + + init( + privateKey: PrivateKeyWithMetadata = PrivateKeyWithMetadata(), + nextPrivateKey: PrivateKeyWithMetadata? = nil, + addresses: [IPAddressRange] = [], + dnsSettings: DNSSettings = DNSSettings() + ) + { + self.privateKey = privateKey + self.nextPrivateKey = nextPrivateKey + self.addresses = addresses + self.dnsSettings = dnsSettings + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + privateKey = try container.decode(PrivateKeyWithMetadata.self, forKey: .privateKey) + addresses = try container.decode([IPAddressRange].self, forKey: .addresses) + + // Added in 2022.1 + nextPrivateKey = try container.decodeIfPresent(PrivateKeyWithMetadata.self, forKey: .nextPrivateKey) + + // Provide default value, since `dnsSettings` key does not exist in <= 2021.2 + dnsSettings = try container.decodeIfPresent(DNSSettings.self, forKey: .dnsSettings) + ?? DNSSettings() + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(privateKey, forKey: .privateKey) + try container.encode(nextPrivateKey, forKey: .nextPrivateKey) + try container.encode(addresses, forKey: .addresses) + try container.encode(dnsSettings, forKey: .dnsSettings) + } +} + +/// A struct holding a private WireGuard key with associated metadata +struct PrivateKeyWithMetadata: Equatable, Codable { + private enum CodingKeys: String, CodingKey { + case privateKey = "privateKeyData", creationDate + } + + /// When the key was created + let creationDate: Date + + /// Private key + let privateKey: PrivateKey + + /// Public key + var publicKey: PublicKey { + return privateKey.publicKey + } + + /// Initialize the new private key + init() { + privateKey = PrivateKey() + creationDate = Date() + } + + /// Initialize with the existing private key + init(privateKey: PrivateKey, createdAt: Date) { + self.privateKey = privateKey + creationDate = createdAt + } +} diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2+REST.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2+REST.swift new file mode 100644 index 0000000000..438bd69481 --- /dev/null +++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2+REST.swift @@ -0,0 +1,21 @@ +// +// TunnelSettingsV2+REST.swift +// MullvadVPN +// +// Created by pronebird on 13/05/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import class WireGuardKitTypes.PrivateKey + +extension StoredDeviceData { + mutating func update(from device: REST.Device) { + identifier = device.id + name = device.name + creationDate = device.created + hijackDNS = device.hijackDNS + ipv4Address = device.ipv4Address + ipv6Address = device.ipv6Address + } +} diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift new file mode 100644 index 0000000000..942e3a27fd --- /dev/null +++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift @@ -0,0 +1,69 @@ +// +// TunnelSettingsV2.swift +// MullvadVPN +// +// Created by pronebird on 27/04/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import struct Network.IPv4Address +import class WireGuardKitTypes.PublicKey +import class WireGuardKitTypes.PrivateKey +import struct WireGuardKitTypes.IPAddressRange + +struct TunnelSettingsV2: Codable, Equatable { + /// Mullvad account data. + var account: StoredAccountData + + /// Device data. + var device: StoredDeviceData + + /// Relay constraints. + var relayConstraints: RelayConstraints + + /// DNS settings. + var dnsSettings: DNSSettings +} + +struct StoredAccountData: Codable, Equatable { + /// Account identifier. + var identifier: String + + /// Account number. + var number: String + + /// Account expiry. + var expiry: Date +} + +struct StoredDeviceData: Codable, Equatable { + /// Device creation date. + var creationDate: Date + + /// Device identifier. + var identifier: String + + /// Device name. + var name: String + + /// Whether relay hijacks DNS from this device. + var hijackDNS: Bool + + /// IPv4 address assigned to device. + var ipv4Address: IPAddressRange + + /// IPv6 address assignged to device. + var ipv6Address: IPAddressRange + + /// WireGuard key data. + var wgKeyData: StoredWgKeyData +} + +struct StoredWgKeyData: Codable, Equatable { + /// Private key creation date. + var creationDate: Date + + /// Private key. + var privateKey: PrivateKey +} diff --git a/ios/MullvadVPN/TunnelSettingsManager.swift b/ios/MullvadVPN/TunnelSettingsManager.swift deleted file mode 100644 index b4aec2b8ba..0000000000 --- a/ios/MullvadVPN/TunnelSettingsManager.swift +++ /dev/null @@ -1,258 +0,0 @@ -// -// TunnelSettingsManager.swift -// MullvadVPN -// -// Created by pronebird on 02/10/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import Security - -/// Service name used for keychain items -private let kServiceName = "Mullvad VPN" - -enum TunnelSettingsManager {} - -extension TunnelSettingsManager { - - enum Error: ChainedError { - /// A failure to encode the given tunnel settings - case encode(Swift.Error) - - /// A failure to decode the data stored in Keychain - case decode(Swift.Error) - - /// A failure to add a new entry to Keychain - case addEntry(Keychain.Error) - - /// A failure to update the existing entry in Keychain - case updateEntry(Keychain.Error) - - /// A failure to remove an entry in Keychain - case removeEntry(Keychain.Error) - - /// A failure to query the entry in Keychain - case lookupEntry(Keychain.Error) - - /// Missing attributes required to perform an operation. - case missingRequiredAttributes - - var errorDescription: String? { - switch self { - case .encode: - return "Failure to encode settings." - case .decode: - return "Failure to decode settings." - case .addEntry: - return "Failure to add keychain entry." - case .updateEntry: - return "Failure to update keychain entry." - case .removeEntry: - return "Failure to remove keychain entry." - case .lookupEntry: - return "Failure to lookup keychain entry." - case .missingRequiredAttributes: - return "Keychain entry is missing required set of attributes." - } - } - } - - typealias Result<T> = Swift.Result<T, Error> - - /// Keychain access level that should be used for all items containing tunnel settings - private static let keychainAccessibleLevel = Keychain.Accessible.afterFirstUnlock - - enum KeychainSearchTerm { - case accountToken(String) - case persistentReference(Data) - - /// Returns `Keychain.Attributes` appropriate for adding or querying the item - fileprivate func makeKeychainAttributes() -> Keychain.Attributes { - var attributes = Keychain.Attributes() - attributes.class = .genericPassword - - switch self { - case .accountToken(let accountToken): - attributes.account = accountToken - attributes.service = kServiceName - - case .persistentReference(let persistentReference): - attributes.valuePersistentReference = persistentReference - } - - return attributes - } - } - - struct KeychainEntry { - let accountToken: String - let tunnelSettings: TunnelSettings - } - - static func load(searchTerm: KeychainSearchTerm) -> Result<KeychainEntry> { - var query = searchTerm.makeKeychainAttributes() - query.return = [.data, .attributes] - - return Keychain.findFirst(query: query) - .mapError { .lookupEntry($0) } - .flatMap { (attributes) in - guard let account = attributes?.account, let data = attributes?.valueData else { - return .failure(.missingRequiredAttributes) - } - - return Self.decode(data: data) - .map { KeychainEntry(accountToken: account, tunnelSettings: $0) } - } - } - - static func add(configuration: TunnelSettings, account: String) -> Result<()> { - Self.encode(tunnelConfig: configuration) - .flatMap { (data) -> Result<()> in - var attributes = KeychainSearchTerm.accountToken(account) - .makeKeychainAttributes() - - // Share the item with the application group - attributes.accessGroup = ApplicationConfiguration.securityGroupIdentifier - - // Make sure the keychain item is available after the first unlock to enable - // automatic key rotation in background (from the packet tunnel process) - attributes.accessible = Self.keychainAccessibleLevel - - // Store value - attributes.valueData = data - - return Keychain.add(attributes) - .mapError { .addEntry($0) } - .map { _ in () } - } - } - - /// This is a migration path for the existing Keychain entries created by 2020.2 or before. - /// - /// - Set the appropriate `accessible` so that the Packet Tunnel can access the tunnel - /// configuration when the device is locked. - /// - Add revision field - /// - /// - Returns: A boolean that indicates whether the entry was up to date prior to the - /// migration request. - - static func migrateKeychainEntry(searchTerm: KeychainSearchTerm) -> Result<Bool> { - var queryAttributes = searchTerm.makeKeychainAttributes() - queryAttributes.return = [.attributes] - - return Keychain.findFirst(query: queryAttributes) - .mapError { .lookupEntry($0) } - .flatMap { itemAttributes -> Result<Bool> in - let searchAttributes = searchTerm.makeKeychainAttributes() - var updateAttributes = Keychain.Attributes() - - // Fix the accessibility permission for the Keychain entry - if itemAttributes?.accessible != Self.keychainAccessibleLevel { - updateAttributes.accessible = Self.keychainAccessibleLevel - } - - // Return immediately if nothing to update (i.e the keychain query is empty) - if updateAttributes.keychainRepresentation().isEmpty { - return .success(false) - } else { - return Keychain.update(query: searchAttributes, update: updateAttributes) - .mapError { .updateEntry($0) } - .map { true } - } - } - } - - /// Reads the tunnel settings from Keychain, then passes it to the given closure for - /// modifications, saves the result back to Keychain. - /// - /// The given block may run multiple times if Keychain entry was changed between read and write - /// operations. - static func update(searchTerm: KeychainSearchTerm, - using changeConfiguration: (inout TunnelSettings) -> Void) -> Result<TunnelSettings> - { - var searchQuery = searchTerm.makeKeychainAttributes() - searchQuery.return = [.attributes, .data] - - let result = Keychain.findFirst(query: searchQuery) - .mapError { .lookupEntry($0) } - .flatMap { itemAttributes -> Result<TunnelSettings> in - guard let serializedData = itemAttributes?.valueData, - let account = itemAttributes?.account else { return .failure(.missingRequiredAttributes) } - - return Self.decode(data: serializedData) - .flatMap { (tunnelConfig) -> Result<TunnelSettings> in - var tunnelConfig = tunnelConfig - changeConfiguration(&tunnelConfig) - - return Self.encode(tunnelConfig: tunnelConfig) - .flatMap { (newData) -> Result<TunnelSettings> in - // `SecItemUpdate` does not accept query parameters when using - // persistent reference, so constraint the query to account - // token instead now when we know it - let updateQuery = KeychainSearchTerm - .accountToken(account) - .makeKeychainAttributes() - - var updateAttributes = Keychain.Attributes() - updateAttributes.valueData = newData - - return Keychain.update(query: updateQuery, update: updateAttributes) - .mapError { .updateEntry($0) } - .map { tunnelConfig } - } - } - } - - return result - } - - static func remove(searchTerm: KeychainSearchTerm) -> Result<()> { - return Keychain.delete(query: searchTerm.makeKeychainAttributes()) - .mapError { .removeEntry($0) } - } - - /// Get a persistent reference to the Keychain item for the given account token - static func getPersistentKeychainReference(account: String) -> Result<Data> { - var query = KeychainSearchTerm.accountToken(account) - .makeKeychainAttributes() - query.return = [.persistentReference] - - return Keychain.findFirst(query: query) - .mapError { .lookupEntry($0) } - .flatMap { attributes -> Result<Data> in - guard let persistentReference = attributes?.valuePersistentReference else { - return .failure(.missingRequiredAttributes) - } - return .success(persistentReference) - } - } - - /// Verify that the keychain entry exists. - /// Returns an error in case of failure to access Keychain. - static func exists(searchTerm: KeychainSearchTerm) -> Result<Bool> { - let query = searchTerm.makeKeychainAttributes() - - return Keychain.findFirst(query: query) - .map({ (attributes) -> Bool in - return true - }) - .flatMapError({ (error) -> Result<Bool> in - if case .itemNotFound = error { - return .success(false) - } else { - return .failure(.lookupEntry(error)) - } - }) - } - - private static func encode(tunnelConfig: TunnelSettings) -> Result<Data> { - return Swift.Result { try JSONEncoder().encode(tunnelConfig) } - .mapError { .encode($0) } - } - - private static func decode(data: Data) -> Result<TunnelSettings> { - return Swift.Result { try JSONDecoder().decode(TunnelSettings.self, from: data) } - .mapError { .decode($0) } - } -} |
