diff --git a/.jazzy.yaml b/.jazzy.yaml index bdc483a..2c37afa 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -26,13 +26,10 @@ custom_categories: - SocketType - name: OpenVPN children: - - ConfigurationParser - - CryptoContainer + - OpenVPN - SessionError - - SessionProxy - SessionProxyDelegate - SessionReply - - StaticKey - name: AppExtension children: - TunnelKitProvider diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 4e5db07..8193af4 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -11,15 +11,12 @@ 0E011F7B2196D93600BA59EE /* SocketType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E011F792196D93600BA59EE /* SocketType.swift */; }; 0E011F7D2196D97200BA59EE /* EndpointProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E011F7C2196D97200BA59EE /* EndpointProtocol.swift */; }; 0E011F7E2196D97200BA59EE /* EndpointProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E011F7C2196D97200BA59EE /* EndpointProtocol.swift */; }; - 0E011F812196E23700BA59EE /* ConfigurationParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E011F7F2196E20300BA59EE /* ConfigurationParserTests.swift */; }; - 0E011F822196E23800BA59EE /* ConfigurationParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E011F7F2196E20300BA59EE /* ConfigurationParserTests.swift */; }; 0E011F852196E25900BA59EE /* pia-hungary.ovpn in Resources */ = {isa = PBXBuildFile; fileRef = 0E011F832196E25400BA59EE /* pia-hungary.ovpn */; }; 0E011F862196E25A00BA59EE /* pia-hungary.ovpn in Resources */ = {isa = PBXBuildFile; fileRef = 0E011F832196E25400BA59EE /* pia-hungary.ovpn */; }; 0E011F882196E2AB00BA59EE /* ConfigurationParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E011F872196E2AB00BA59EE /* ConfigurationParser.swift */; }; 0E011F892196E2AB00BA59EE /* ConfigurationParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E011F872196E2AB00BA59EE /* ConfigurationParser.swift */; }; - 0E041D092152E6FE0025FE3C /* SessionProxy+TLSWrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E041D082152E6FE0025FE3C /* SessionProxy+TLSWrap.swift */; }; - 0E041D0A2152E6FE0025FE3C /* SessionProxy+TLSWrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E041D082152E6FE0025FE3C /* SessionProxy+TLSWrap.swift */; }; - 0E041D0C2152E80A0025FE3C /* StaticKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E041D0B2152E80A0025FE3C /* StaticKeyTests.swift */; }; + 0E041D092152E6FE0025FE3C /* TLSWrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E041D082152E6FE0025FE3C /* TLSWrap.swift */; }; + 0E041D0A2152E6FE0025FE3C /* TLSWrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E041D082152E6FE0025FE3C /* TLSWrap.swift */; }; 0E07595F20EF6D1400F38FD8 /* CryptoCBC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */; }; 0E07596020EF6D1400F38FD8 /* CryptoCBC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */; }; 0E07596320EF733F00F38FD8 /* CryptoMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07596120EF733F00F38FD8 /* CryptoMacros.h */; }; @@ -32,16 +29,14 @@ 0E07597F20F0060E00F38FD8 /* CryptoAEAD.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */; }; 0E07598020F0060E00F38FD8 /* CryptoAEAD.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */; }; 0E07598120F0060E00F38FD8 /* CryptoAEAD.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */; }; - 0E0B203C227886AD007A3CB9 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */; }; - 0E0B203D227886AD007A3CB9 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */; }; 0E0B20402278A85C007A3CB9 /* RoutingTableEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E0B203E2278A85B007A3CB9 /* RoutingTableEntry.h */; }; 0E0B20412278A85C007A3CB9 /* RoutingTableEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E0B203E2278A85B007A3CB9 /* RoutingTableEntry.h */; }; 0E0B20422278A85C007A3CB9 /* RoutingTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203F2278A85B007A3CB9 /* RoutingTableEntry.m */; }; 0E0B20432278A85C007A3CB9 /* RoutingTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203F2278A85B007A3CB9 /* RoutingTableEntry.m */; }; 0E0C2125212ED29D008AB282 /* SessionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2123212ED29D008AB282 /* SessionError.swift */; }; 0E0C2126212ED29D008AB282 /* SessionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2123212ED29D008AB282 /* SessionError.swift */; }; - 0E0C2127212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2124212ED29D008AB282 /* SessionProxy+Configuration.swift */; }; - 0E0C2128212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2124212ED29D008AB282 /* SessionProxy+Configuration.swift */; }; + 0E0C2127212ED29D008AB282 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2124212ED29D008AB282 /* Configuration.swift */; }; + 0E0C2128212ED29D008AB282 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2124212ED29D008AB282 /* Configuration.swift */; }; 0E11089F1F77B9E800A92462 /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */; }; 0E1108AC1F77B9F900A92462 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1108AB1F77B9F900A92462 /* AppDelegate.swift */; }; 0E1108AE1F77B9F900A92462 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1108AD1F77B9F900A92462 /* ViewController.swift */; }; @@ -50,14 +45,12 @@ 0E1108B61F77B9F900A92462 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108B41F77B9F900A92462 /* LaunchScreen.storyboard */; }; 0E12B29E21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B29D21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift */; }; 0E12B29F21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B29D21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift */; }; - 0E12B2A32145341B00B4BAE9 /* PacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A22145341B00B4BAE9 /* PacketTests.swift */; }; 0E12B2A521454F7F00B4BAE9 /* BidirectionalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */; }; 0E12B2A621454F7F00B4BAE9 /* BidirectionalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */; }; 0E12B2A821456C0200B4BAE9 /* ControlChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */; }; 0E12B2A921456C0200B4BAE9 /* ControlChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */; }; 0E12B2AB2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.swift */; }; 0E12B2AC2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.swift */; }; - 0E245D692135972800B012A2 /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E245D682135972800B012A2 /* PushTests.swift */; }; 0E245D6C2137F73600B012A2 /* CompressionFramingNative.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */; }; 0E39BCE8214B2AB60035E9DE /* ControlPacket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */; }; 0E39BCE9214B2AB60035E9DE /* ControlPacket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */; }; @@ -71,8 +64,8 @@ 0E3B65752249253B00EFF4DA /* tunnelbear.enc.1.ovpn in Resources */ = {isa = PBXBuildFile; fileRef = 0E3B656E224923EC00EFF4DA /* tunnelbear.enc.1.ovpn */; }; 0E3B65762249253F00EFF4DA /* tunnelbear.key in Resources */ = {isa = PBXBuildFile; fileRef = 0E3B65712249247E00EFF4DA /* tunnelbear.key */; }; 0E3B65772249254000EFF4DA /* tunnelbear.key in Resources */ = {isa = PBXBuildFile; fileRef = 0E3B65712249247E00EFF4DA /* tunnelbear.key */; }; - 0E3E0F212108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+SessionReply.swift */; }; - 0E3E0F222108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+SessionReply.swift */; }; + 0E3E0F212108A8CC00B371C1 /* SessionReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionReply.swift */; }; + 0E3E0F222108A8CC00B371C1 /* SessionReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionReply.swift */; }; 0E411B9B2271F90700E0852C /* DNS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E411B992271F90700E0852C /* DNS.h */; }; 0E411B9C2271F90700E0852C /* DNS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E411B992271F90700E0852C /* DNS.h */; }; 0E411B9D2271F90700E0852C /* DNS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E411B9A2271F90700E0852C /* DNS.m */; }; @@ -85,7 +78,6 @@ 0E48AC672271ADA9009B1A98 /* PacketStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E48AC632271ADA9009B1A98 /* PacketStream.m */; }; 0E500EA622493B5B00CAE560 /* tunnelbear.enc.1.key in Resources */ = {isa = PBXBuildFile; fileRef = 0E500EA522493B5B00CAE560 /* tunnelbear.enc.1.key */; }; 0E500EA722493B5B00CAE560 /* tunnelbear.enc.1.key in Resources */ = {isa = PBXBuildFile; fileRef = 0E500EA522493B5B00CAE560 /* tunnelbear.enc.1.key */; }; - 0E50D57521634E0A00FC87A8 /* ControlChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */; }; 0E58BF3322405410006FB157 /* lzoconf.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF2F22405410006FB157 /* lzoconf.h */; }; 0E58BF3422405410006FB157 /* lzoconf.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF2F22405410006FB157 /* lzoconf.h */; }; 0E58BF3522405410006FB157 /* lzodefs.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF3022405410006FB157 /* lzodefs.h */; }; @@ -94,81 +86,24 @@ 0E58BF3822405410006FB157 /* minilzo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF3122405410006FB157 /* minilzo.h */; }; 0E58BF3922405410006FB157 /* minilzo.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF3222405410006FB157 /* minilzo.c */; }; 0E58BF3A22405410006FB157 /* minilzo.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF3222405410006FB157 /* minilzo.c */; }; - 0E58BF3C2240547F006FB157 /* CompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF3B2240547F006FB157 /* CompressionTests.swift */; }; - 0E58BF3D2240547F006FB157 /* CompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF3B2240547F006FB157 /* CompressionTests.swift */; }; 0E58BF4C22405C2F006FB157 /* StandardLZO.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF4922405C2F006FB157 /* StandardLZO.m */; }; 0E58BF4D22405C2F006FB157 /* StandardLZO.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF4922405C2F006FB157 /* StandardLZO.m */; }; 0E58BF502240F98F006FB157 /* CompressionAlgorithmNative.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF4F2240F98E006FB157 /* CompressionAlgorithmNative.h */; }; 0E58BF512240F98F006FB157 /* CompressionAlgorithmNative.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF4F2240F98E006FB157 /* CompressionAlgorithmNative.h */; }; - 0E58BF532240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF522240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift */; }; - 0E58BF542240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF522240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift */; }; + 0E58BF532240FAA6006FB157 /* CompressionAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF522240FAA6006FB157 /* CompressionAlgorithm.swift */; }; + 0E58BF542240FAA6006FB157 /* CompressionAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF522240FAA6006FB157 /* CompressionAlgorithm.swift */; }; 0E58BF5622411F3D006FB157 /* LZO.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF5522411F37006FB157 /* LZO.h */; }; 0E58BF5722411F3E006FB157 /* LZO.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E58BF5522411F37006FB157 /* LZO.h */; }; 0E58BF5922411FEF006FB157 /* LZO.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF5822411FEF006FB157 /* LZO.m */; }; 0E58BF5A22411FEF006FB157 /* LZO.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E58BF5822411FEF006FB157 /* LZO.m */; }; - 0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */; }; 0E749F5F2178885500BB2701 /* SessionProxy+PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */; }; 0E749F602178885500BB2701 /* SessionProxy+PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */; }; 0E749F622178911D00BB2701 /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; }; - 0E85A25A202CC5AF0059E9F9 /* AppExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */; }; 0E9379C91F819A4300CE91B6 /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */; }; 0EA82A282190B220007960EB /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E3251C51F95770D00C108D9 /* TunnelKit.framework */; }; - 0EA82A2F2190B2B9007960EB /* AppExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */; }; - 0EA82A302190B2B9007960EB /* ControlChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */; }; - 0EA82A312190B2B9007960EB /* DataManipulationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */; }; - 0EA82A322190B2B9007960EB /* DataPathEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */; }; - 0EA82A332190B2B9007960EB /* DataPathPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */; }; - 0EA82A342190B2B9007960EB /* DNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */; }; - 0EA82A352190B2B9007960EB /* EncryptionPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */; }; - 0EA82A362190B2B9007960EB /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */; }; - 0EA82A372190B2B9007960EB /* LinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45820F0BD9A004233D7 /* LinkTests.swift */; }; - 0EA82A382190B2B9007960EB /* PacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A22145341B00B4BAE9 /* PacketTests.swift */; }; - 0EA82A392190B2B9007960EB /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E245D682135972800B012A2 /* PushTests.swift */; }; - 0EA82A3A2190B2B9007960EB /* RandomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45620F0BD16004233D7 /* RandomTests.swift */; }; - 0EA82A3B2190B2B9007960EB /* RawPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */; }; - 0EA82A3C2190B2B9007960EB /* StaticKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E041D0B2152E80A0025FE3C /* StaticKeyTests.swift */; }; - 0EA82A3D2190B2B9007960EB /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */; }; 0EA82A3E2190B2BC007960EB /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; }; - 0EB03E202290D22A006D03A0 /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E102290D22A006D03A0 /* ConnectionStrategy.swift */; }; - 0EB03E212290D22A006D03A0 /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E102290D22A006D03A0 /* ConnectionStrategy.swift */; }; - 0EB03E222290D22A006D03A0 /* NETCPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E122290D22A006D03A0 /* NETCPInterface.swift */; }; - 0EB03E232290D22A006D03A0 /* NETCPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E122290D22A006D03A0 /* NETCPInterface.swift */; }; - 0EB03E242290D22A006D03A0 /* NETunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E132290D22A006D03A0 /* NETunnelInterface.swift */; }; - 0EB03E252290D22A006D03A0 /* NETunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E132290D22A006D03A0 /* NETunnelInterface.swift */; }; - 0EB03E262290D22A006D03A0 /* NWTCPConnectionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E142290D22A006D03A0 /* NWTCPConnectionState+Description.swift */; }; - 0EB03E272290D22A006D03A0 /* NWTCPConnectionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E142290D22A006D03A0 /* NWTCPConnectionState+Description.swift */; }; - 0EB03E282290D22A006D03A0 /* NEUDPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E152290D22A006D03A0 /* NEUDPInterface.swift */; }; - 0EB03E292290D22A006D03A0 /* NEUDPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E152290D22A006D03A0 /* NEUDPInterface.swift */; }; - 0EB03E2A2290D22A006D03A0 /* NWUDPSessionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E162290D22A006D03A0 /* NWUDPSessionState+Description.swift */; }; - 0EB03E2B2290D22A006D03A0 /* NWUDPSessionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E162290D22A006D03A0 /* NWUDPSessionState+Description.swift */; }; - 0EB03E2C2290D22A006D03A0 /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E172290D22A006D03A0 /* DNSResolver.swift */; }; - 0EB03E2D2290D22A006D03A0 /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E172290D22A006D03A0 /* DNSResolver.swift */; }; - 0EB03E2E2290D22A006D03A0 /* TunnelKitProvider+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E182290D22A006D03A0 /* TunnelKitProvider+Configuration.swift */; }; - 0EB03E2F2290D22A006D03A0 /* TunnelKitProvider+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E182290D22A006D03A0 /* TunnelKitProvider+Configuration.swift */; }; - 0EB03E302290D22A006D03A0 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E192290D22A006D03A0 /* Keychain.swift */; }; - 0EB03E312290D22A006D03A0 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E192290D22A006D03A0 /* Keychain.swift */; }; - 0EB03E322290D22A006D03A0 /* MemoryDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1A2290D22A006D03A0 /* MemoryDestination.swift */; }; - 0EB03E332290D22A006D03A0 /* MemoryDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1A2290D22A006D03A0 /* MemoryDestination.swift */; }; - 0EB03E342290D22A006D03A0 /* GenericSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1B2290D22A006D03A0 /* GenericSocket.swift */; }; - 0EB03E352290D22A006D03A0 /* GenericSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1B2290D22A006D03A0 /* GenericSocket.swift */; }; - 0EB03E362290D22A006D03A0 /* TunnelKitProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1C2290D22A006D03A0 /* TunnelKitProvider.swift */; }; - 0EB03E372290D22A006D03A0 /* TunnelKitProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1C2290D22A006D03A0 /* TunnelKitProvider.swift */; }; - 0EB03E382290D22A006D03A0 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1D2290D22A006D03A0 /* Utils.swift */; }; - 0EB03E392290D22A006D03A0 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1D2290D22A006D03A0 /* Utils.swift */; }; - 0EB03E3A2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1E2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift */; }; - 0EB03E3B2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1E2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift */; }; - 0EB03E3C2290D22A006D03A0 /* InterfaceObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1F2290D22A006D03A0 /* InterfaceObserver.swift */; }; - 0EB03E3D2290D22A006D03A0 /* InterfaceObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E1F2290D22A006D03A0 /* InterfaceObserver.swift */; }; 0EB03E3F2290D310006D03A0 /* CoreConfiguration+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E3E2290D310006D03A0 /* CoreConfiguration+OpenVPN.swift */; }; 0EB03E402290D310006D03A0 /* CoreConfiguration+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB03E3E2290D310006D03A0 /* CoreConfiguration+OpenVPN.swift */; }; - 0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */; }; - 0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */; }; - 0EB2B45720F0BD16004233D7 /* RandomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45620F0BD16004233D7 /* RandomTests.swift */; }; - 0EB2B45920F0BD9A004233D7 /* LinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45820F0BD9A004233D7 /* LinkTests.swift */; }; - 0EB2B45B20F0BE4C004233D7 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */; }; - 0EB2B45D20F0BF41004233D7 /* RawPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */; }; - 0EB2B45F20F0C098004233D7 /* EncryptionPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */; }; - 0EB2B46120F0C0A4004233D7 /* DataPathPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */; }; 0ECC60D82254981A0020BEAC /* ConfigurationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D72254981A0020BEAC /* ConfigurationError.swift */; }; 0ECC60D92254981A0020BEAC /* ConfigurationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D72254981A0020BEAC /* ConfigurationError.swift */; }; 0ECE3528212EB7770040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; }; @@ -179,19 +114,20 @@ 0ECEB1182252C8E900E9E551 /* tunnelbear.enc.8.key in Resources */ = {isa = PBXBuildFile; fileRef = 0ECEB1142252C8E900E9E551 /* tunnelbear.enc.8.key */; }; 0ECEB11B2252CDAD00E9E551 /* tunnelbear.crt in Resources */ = {isa = PBXBuildFile; fileRef = 0ECEB11A2252CDAD00E9E551 /* tunnelbear.crt */; }; 0ECEB11C2252CDAD00E9E551 /* tunnelbear.crt in Resources */ = {isa = PBXBuildFile; fileRef = 0ECEB11A2252CDAD00E9E551 /* tunnelbear.crt */; }; - 0ED9C8642138139000621BA3 /* SessionProxy+CompressionFraming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */; }; - 0ED9C8652138139000621BA3 /* SessionProxy+CompressionFraming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */; }; + 0ED9C8642138139000621BA3 /* CompressionFraming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED9C8632138139000621BA3 /* CompressionFraming.swift */; }; + 0ED9C8652138139000621BA3 /* CompressionFraming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED9C8632138139000621BA3 /* CompressionFraming.swift */; }; 0EE2F96E2291636B00F56F49 /* IPv4Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F96D2291636B00F56F49 /* IPv4Settings.swift */; }; 0EE2F96F2291636B00F56F49 /* IPv4Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F96D2291636B00F56F49 /* IPv4Settings.swift */; }; 0EE2F9712291638600F56F49 /* IPv6Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F9702291638600F56F49 /* IPv6Settings.swift */; }; 0EE2F9722291638600F56F49 /* IPv6Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F9702291638600F56F49 /* IPv6Settings.swift */; }; 0EE2F974229163C900F56F49 /* Proxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F973229163C900F56F49 /* Proxy.swift */; }; 0EE2F975229163C900F56F49 /* Proxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F973229163C900F56F49 /* Proxy.swift */; }; + 0EE2F97722916A5D00F56F49 /* OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F97622916A5D00F56F49 /* OpenVPN.swift */; }; + 0EE2F97822916A5D00F56F49 /* OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE2F97622916A5D00F56F49 /* OpenVPN.swift */; }; 0EE3B3E421471C3A0027AB17 /* StaticKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE3B3E321471C3A0027AB17 /* StaticKey.swift */; }; 0EE3B3E521471C3A0027AB17 /* StaticKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE3B3E321471C3A0027AB17 /* StaticKey.swift */; }; 0EE7A79820F6296F00B42E6A /* PacketMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79720F6296F00B42E6A /* PacketMacros.m */; }; 0EE7A79920F6296F00B42E6A /* PacketMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79720F6296F00B42E6A /* PacketMacros.m */; }; - 0EE7A7A120F664AC00B42E6A /* DataPathEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */; }; 0EEC49E120B5F7EA008FEB91 /* Allocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42E2006D3C800F81029 /* Allocation.h */; }; 0EEC49E220B5F7F6008FEB91 /* CryptoBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4402006D3C800F81029 /* CryptoBox.h */; }; 0EEC49E320B5F7F6008FEB91 /* DataPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4352006D3C800F81029 /* DataPath.h */; }; @@ -207,8 +143,8 @@ 0EFB902A22788511006405E4 /* RoutingTable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFB902722788511006405E4 /* RoutingTable.h */; }; 0EFB902B22788512006405E4 /* RoutingTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB902822788511006405E4 /* RoutingTable.m */; }; 0EFB902C22788512006405E4 /* RoutingTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB902822788511006405E4 /* RoutingTable.m */; }; - 0EFEB4552006D3C800F81029 /* SessionProxy+EncryptionBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */; }; - 0EFEB4562006D3C800F81029 /* SessionProxy+SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */; }; + 0EFEB4552006D3C800F81029 /* EncryptionBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* EncryptionBridge.swift */; }; + 0EFEB4562006D3C800F81029 /* SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionKey.swift */; }; 0EFEB4582006D3C800F81029 /* MSS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42D2006D3C800F81029 /* MSS.h */; }; 0EFEB4592006D3C800F81029 /* Allocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42E2006D3C800F81029 /* Allocation.h */; }; 0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42F2006D3C800F81029 /* TunnelInterface.swift */; }; @@ -220,7 +156,7 @@ 0EFEB4622006D3C800F81029 /* SecureRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4372006D3C800F81029 /* SecureRandom.swift */; }; 0EFEB4632006D3C800F81029 /* ProtocolMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */; }; 0EFEB4642006D3C800F81029 /* ReplayProtector.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4392006D3C800F81029 /* ReplayProtector.h */; }; - 0EFEB4652006D3C800F81029 /* SessionProxy+Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43A2006D3C800F81029 /* SessionProxy+Authenticator.swift */; }; + 0EFEB4652006D3C800F81029 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43A2006D3C800F81029 /* Authenticator.swift */; }; 0EFEB4662006D3C800F81029 /* ZeroingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43B2006D3C800F81029 /* ZeroingData.swift */; }; 0EFEB4672006D3C800F81029 /* SessionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */; }; 0EFEB4682006D3C800F81029 /* MSS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43D2006D3C800F81029 /* MSS.m */; }; @@ -236,15 +172,15 @@ 0EFEB4742006D3C800F81029 /* CoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44A2006D3C800F81029 /* CoreConfiguration.swift */; }; 0EFEB4752006D3C800F81029 /* Errors.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44B2006D3C800F81029 /* Errors.m */; }; 0EFEB4762006D3C800F81029 /* DataPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44C2006D3C800F81029 /* DataPath.m */; }; - 0EFEB48D2006D7F300F81029 /* SessionProxy+EncryptionBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */; }; - 0EFEB48E2006D7F300F81029 /* SessionProxy+SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */; }; + 0EFEB48D2006D7F300F81029 /* EncryptionBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* EncryptionBridge.swift */; }; + 0EFEB48E2006D7F300F81029 /* SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionKey.swift */; }; 0EFEB4902006D7F300F81029 /* TunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42F2006D3C800F81029 /* TunnelInterface.swift */; }; 0EFEB4912006D7F300F81029 /* TLSBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4302006D3C800F81029 /* TLSBox.m */; }; 0EFEB4922006D7F300F81029 /* ZeroingData.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4312006D3C800F81029 /* ZeroingData.m */; }; 0EFEB4932006D7F300F81029 /* CryptoBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4322006D3C800F81029 /* CryptoBox.m */; }; 0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4372006D3C800F81029 /* SecureRandom.swift */; }; 0EFEB4962006D7F300F81029 /* ProtocolMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */; }; - 0EFEB4972006D7F300F81029 /* SessionProxy+Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43A2006D3C800F81029 /* SessionProxy+Authenticator.swift */; }; + 0EFEB4972006D7F300F81029 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43A2006D3C800F81029 /* Authenticator.swift */; }; 0EFEB4982006D7F300F81029 /* ZeroingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43B2006D3C800F81029 /* ZeroingData.swift */; }; 0EFEB4992006D7F300F81029 /* SessionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */; }; 0EFEB49A2006D7F300F81029 /* MSS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43D2006D3C800F81029 /* MSS.m */; }; @@ -297,22 +233,19 @@ /* Begin PBXFileReference section */ 0E011F792196D93600BA59EE /* SocketType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketType.swift; sourceTree = ""; }; 0E011F7C2196D97200BA59EE /* EndpointProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointProtocol.swift; sourceTree = ""; }; - 0E011F7F2196E20300BA59EE /* ConfigurationParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationParserTests.swift; sourceTree = ""; }; 0E011F832196E25400BA59EE /* pia-hungary.ovpn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "pia-hungary.ovpn"; sourceTree = ""; }; 0E011F872196E2AB00BA59EE /* ConfigurationParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationParser.swift; sourceTree = ""; }; - 0E041D082152E6FE0025FE3C /* SessionProxy+TLSWrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+TLSWrap.swift"; sourceTree = ""; }; - 0E041D0B2152E80A0025FE3C /* StaticKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticKeyTests.swift; sourceTree = ""; }; + 0E041D082152E6FE0025FE3C /* TLSWrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSWrap.swift; sourceTree = ""; }; 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CryptoCBC.m; sourceTree = ""; }; 0E07596120EF733F00F38FD8 /* CryptoMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoMacros.h; sourceTree = ""; }; 0E07596A20EF79AB00F38FD8 /* Crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crypto.h; sourceTree = ""; }; 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoCBC.h; sourceTree = ""; }; 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoAEAD.h; sourceTree = ""; }; 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoAEAD.m; sourceTree = ""; }; - 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingTests.swift; sourceTree = ""; }; 0E0B203E2278A85B007A3CB9 /* RoutingTableEntry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutingTableEntry.h; sourceTree = ""; }; 0E0B203F2278A85B007A3CB9 /* RoutingTableEntry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoutingTableEntry.m; sourceTree = ""; }; 0E0C2123212ED29D008AB282 /* SessionError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionError.swift; sourceTree = ""; }; - 0E0C2124212ED29D008AB282 /* SessionProxy+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+Configuration.swift"; sourceTree = ""; }; + 0E0C2124212ED29D008AB282 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; 0E11089A1F77B9E800A92462 /* TunnelKitTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TunnelKitTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E11089E1F77B9E800A92462 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0E1108A91F77B9F900A92462 /* TunnelKitHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TunnelKitHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -323,12 +256,10 @@ 0E1108B51F77B9F900A92462 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 0E1108B71F77B9F900A92462 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0E12B29D21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+Shortcuts.swift"; sourceTree = ""; }; - 0E12B2A22145341B00B4BAE9 /* PacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTests.swift; sourceTree = ""; }; 0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BidirectionalState.swift; sourceTree = ""; }; 0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlChannel.swift; sourceTree = ""; }; 0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlChannelSerializer.swift; sourceTree = ""; }; 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TunnelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E245D682135972800B012A2 /* PushTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushTests.swift; sourceTree = ""; }; 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressionFramingNative.h; sourceTree = ""; }; 0E3251C51F95770D00C108D9 /* TunnelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TunnelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ControlPacket.h; sourceTree = ""; }; @@ -337,7 +268,7 @@ 0E3B15C62152B05E00984B17 /* CryptoCTR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoCTR.m; sourceTree = ""; }; 0E3B656E224923EC00EFF4DA /* tunnelbear.enc.1.ovpn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.1.ovpn; sourceTree = ""; }; 0E3B65712249247E00EFF4DA /* tunnelbear.key */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.key; sourceTree = ""; }; - 0E3E0F202108A8CC00B371C1 /* SessionProxy+SessionReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+SessionReply.swift"; sourceTree = ""; }; + 0E3E0F202108A8CC00B371C1 /* SessionReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReply.swift; sourceTree = ""; }; 0E411B992271F90700E0852C /* DNS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNS.h; sourceTree = ""; }; 0E411B9A2271F90700E0852C /* DNS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DNS.m; sourceTree = ""; }; 0E411B9F2271FA3300E0852C /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libresolv.tbd; sourceTree = DEVELOPER_DIR; }; @@ -345,70 +276,43 @@ 0E48AC622271ADA8009B1A98 /* PacketStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketStream.h; sourceTree = ""; }; 0E48AC632271ADA9009B1A98 /* PacketStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PacketStream.m; sourceTree = ""; }; 0E500EA522493B5B00CAE560 /* tunnelbear.enc.1.key */ = {isa = PBXFileReference; lastKnownFileType = text; path = tunnelbear.enc.1.key; sourceTree = ""; }; - 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlChannelTests.swift; sourceTree = ""; }; 0E58BF2F22405410006FB157 /* lzoconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lzoconf.h; sourceTree = ""; }; 0E58BF3022405410006FB157 /* lzodefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lzodefs.h; sourceTree = ""; }; 0E58BF3122405410006FB157 /* minilzo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minilzo.h; sourceTree = ""; }; 0E58BF3222405410006FB157 /* minilzo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = minilzo.c; sourceTree = ""; }; - 0E58BF3B2240547F006FB157 /* CompressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompressionTests.swift; sourceTree = ""; }; 0E58BF4922405C2F006FB157 /* StandardLZO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StandardLZO.m; sourceTree = ""; }; 0E58BF4F2240F98E006FB157 /* CompressionAlgorithmNative.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressionAlgorithmNative.h; sourceTree = ""; }; - 0E58BF522240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+CompressionAlgorithm.swift"; sourceTree = ""; }; + 0E58BF522240FAA6006FB157 /* CompressionAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompressionAlgorithm.swift; sourceTree = ""; }; 0E58BF5522411F37006FB157 /* LZO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LZO.h; sourceTree = ""; }; 0E58BF5822411FEF006FB157 /* LZO.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LZO.m; sourceTree = ""; }; - 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSTests.swift; sourceTree = ""; }; 0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0E6479E0212EACD6008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PIA.swift"; sourceTree = ""; }; 0E749F612178911C00BB2701 /* pia-2048.pem */ = {isa = PBXFileReference; lastKnownFileType = text; path = "pia-2048.pem"; sourceTree = ""; }; - 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppExtensionTests.swift; sourceTree = ""; }; 0E85A25B202CCA3D0059E9F9 /* TunnelKitHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TunnelKitHost.entitlements; sourceTree = ""; }; 0EA82A232190B220007960EB /* TunnelKitTests-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TunnelKitTests-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0EA82A272190B220007960EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0EB03E0E2290CF52006D03A0 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 0EB03E102290D22A006D03A0 /* ConnectionStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionStrategy.swift; sourceTree = ""; }; - 0EB03E122290D22A006D03A0 /* NETCPInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NETCPInterface.swift; sourceTree = ""; }; - 0EB03E132290D22A006D03A0 /* NETunnelInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NETunnelInterface.swift; sourceTree = ""; }; - 0EB03E142290D22A006D03A0 /* NWTCPConnectionState+Description.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NWTCPConnectionState+Description.swift"; sourceTree = ""; }; - 0EB03E152290D22A006D03A0 /* NEUDPInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NEUDPInterface.swift; sourceTree = ""; }; - 0EB03E162290D22A006D03A0 /* NWUDPSessionState+Description.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NWUDPSessionState+Description.swift"; sourceTree = ""; }; - 0EB03E172290D22A006D03A0 /* DNSResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = ""; }; - 0EB03E182290D22A006D03A0 /* TunnelKitProvider+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TunnelKitProvider+Configuration.swift"; sourceTree = ""; }; - 0EB03E192290D22A006D03A0 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; - 0EB03E1A2290D22A006D03A0 /* MemoryDestination.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryDestination.swift; sourceTree = ""; }; - 0EB03E1B2290D22A006D03A0 /* GenericSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericSocket.swift; sourceTree = ""; }; - 0EB03E1C2290D22A006D03A0 /* TunnelKitProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelKitProvider.swift; sourceTree = ""; }; - 0EB03E1D2290D22A006D03A0 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; - 0EB03E1E2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TunnelKitProvider+Interaction.swift"; sourceTree = ""; }; - 0EB03E1F2290D22A006D03A0 /* InterfaceObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterfaceObserver.swift; sourceTree = ""; }; 0EB03E3E2290D310006D03A0 /* CoreConfiguration+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreConfiguration+OpenVPN.swift"; sourceTree = ""; }; 0EB03E412291542C006D03A0 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = ""; }; - 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManipulationTests.swift; sourceTree = ""; }; - 0EB2B45620F0BD16004233D7 /* RandomTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomTests.swift; sourceTree = ""; }; - 0EB2B45820F0BD9A004233D7 /* LinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTests.swift; sourceTree = ""; }; - 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; - 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawPerformanceTests.swift; sourceTree = ""; }; - 0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionPerformanceTests.swift; sourceTree = ""; }; - 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPathPerformanceTests.swift; sourceTree = ""; }; 0ECC60D72254981A0020BEAC /* ConfigurationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationError.swift; sourceTree = ""; }; 0ECE3527212EB7770040F253 /* CryptoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoContainer.swift; sourceTree = ""; }; 0ECEB1132252C8E900E9E551 /* tunnelbear.enc.8.ovpn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.8.ovpn; sourceTree = ""; }; 0ECEB1142252C8E900E9E551 /* tunnelbear.enc.8.key */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.8.key; sourceTree = ""; }; 0ECEB11A2252CDAD00E9E551 /* tunnelbear.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.crt; sourceTree = ""; }; - 0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+CompressionFraming.swift"; sourceTree = ""; }; + 0ED9C8632138139000621BA3 /* CompressionFraming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompressionFraming.swift; sourceTree = ""; }; 0EE2F96D2291636B00F56F49 /* IPv4Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv4Settings.swift; sourceTree = ""; }; 0EE2F9702291638600F56F49 /* IPv6Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv6Settings.swift; sourceTree = ""; }; 0EE2F973229163C900F56F49 /* Proxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Proxy.swift; sourceTree = ""; }; + 0EE2F97622916A5D00F56F49 /* OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenVPN.swift; sourceTree = ""; }; 0EE3B3E321471C3A0027AB17 /* StaticKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticKey.swift; sourceTree = ""; }; 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketMacros.h; sourceTree = ""; }; 0EE7A79720F6296F00B42E6A /* PacketMacros.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PacketMacros.m; sourceTree = ""; }; 0EE7A79D20F6488400B42E6A /* DataPathCrypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataPathCrypto.h; sourceTree = ""; }; - 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataPathEncryptionTests.swift; sourceTree = ""; }; 0EFB902722788511006405E4 /* RoutingTable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutingTable.h; sourceTree = ""; }; 0EFB902822788511006405E4 /* RoutingTable.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoutingTable.m; sourceTree = ""; }; - 0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+EncryptionBridge.swift"; sourceTree = ""; }; - 0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+SessionKey.swift"; sourceTree = ""; }; + 0EFEB42A2006D3C800F81029 /* EncryptionBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionBridge.swift; sourceTree = ""; }; + 0EFEB42B2006D3C800F81029 /* SessionKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionKey.swift; sourceTree = ""; }; 0EFEB42D2006D3C800F81029 /* MSS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSS.h; sourceTree = ""; }; 0EFEB42E2006D3C800F81029 /* Allocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Allocation.h; sourceTree = ""; }; 0EFEB42F2006D3C800F81029 /* TunnelInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelInterface.swift; sourceTree = ""; }; @@ -420,7 +324,7 @@ 0EFEB4372006D3C800F81029 /* SecureRandom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureRandom.swift; sourceTree = ""; }; 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtocolMacros.swift; sourceTree = ""; }; 0EFEB4392006D3C800F81029 /* ReplayProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReplayProtector.h; sourceTree = ""; }; - 0EFEB43A2006D3C800F81029 /* SessionProxy+Authenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+Authenticator.swift"; sourceTree = ""; }; + 0EFEB43A2006D3C800F81029 /* Authenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authenticator.swift; sourceTree = ""; }; 0EFEB43B2006D3C800F81029 /* ZeroingData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZeroingData.swift; sourceTree = ""; }; 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionProxy.swift; sourceTree = ""; }; 0EFEB43D2006D3C800F81029 /* MSS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSS.m; sourceTree = ""; }; @@ -502,24 +406,6 @@ 0E11089B1F77B9E800A92462 /* TunnelKitTests */ = { isa = PBXGroup; children = ( - 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */, - 0E58BF3B2240547F006FB157 /* CompressionTests.swift */, - 0E011F7F2196E20300BA59EE /* ConfigurationParserTests.swift */, - 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */, - 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */, - 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */, - 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */, - 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */, - 0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */, - 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */, - 0EB2B45820F0BD9A004233D7 /* LinkTests.swift */, - 0E12B2A22145341B00B4BAE9 /* PacketTests.swift */, - 0E245D682135972800B012A2 /* PushTests.swift */, - 0EB2B45620F0BD16004233D7 /* RandomTests.swift */, - 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */, - 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */, - 0E041D0B2152E80A0025FE3C /* StaticKeyTests.swift */, - 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */, 0E749F612178911C00BB2701 /* pia-2048.pem */, 0E011F832196E25400BA59EE /* pia-hungary.ovpn */, 0ECEB11A2252CDAD00E9E551 /* tunnelbear.crt */, @@ -585,7 +471,6 @@ 0E17D8041F730DDD009EE129 /* Sources */ = { isa = PBXGroup; children = ( - 0EB03E0F2290D22A006D03A0 /* AppExtension */, 0EFEB4292006D3C800F81029 /* Core */, 0E58BEDC2240521F006FB157 /* LZO */, 0EB03E0A2290A338006D03A0 /* OpenVPN */, @@ -648,8 +533,12 @@ 0EB03E0A2290A338006D03A0 /* OpenVPN */ = { isa = PBXGroup; children = ( + 0EFEB43A2006D3C800F81029 /* Authenticator.swift */, + 0E58BF522240FAA6006FB157 /* CompressionAlgorithm.swift */, 0E58BF4F2240F98E006FB157 /* CompressionAlgorithmNative.h */, + 0ED9C8632138139000621BA3 /* CompressionFraming.swift */, 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */, + 0E0C2124212ED29D008AB282 /* Configuration.swift */, 0E011F872196E2AB00BA59EE /* ConfigurationParser.swift */, 0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */, 0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.swift */, @@ -670,9 +559,11 @@ 0EFEB4352006D3C800F81029 /* DataPath.h */, 0EFEB44C2006D3C800F81029 /* DataPath.m */, 0EE7A79D20F6488400B42E6A /* DataPathCrypto.h */, + 0EFEB42A2006D3C800F81029 /* EncryptionBridge.swift */, 0EB03E412291542C006D03A0 /* module.modulemap */, 0EFEB42D2006D3C800F81029 /* MSS.h */, 0EFEB43D2006D3C800F81029 /* MSS.m */, + 0EE2F97622916A5D00F56F49 /* OpenVPN.swift */, 0EFEB43E2006D3C800F81029 /* Packet.swift */, 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */, 0EE7A79720F6296F00B42E6A /* PacketMacros.m */, @@ -682,53 +573,18 @@ 0EFEB4392006D3C800F81029 /* ReplayProtector.h */, 0EFEB4482006D3C800F81029 /* ReplayProtector.m */, 0E0C2123212ED29D008AB282 /* SessionError.swift */, + 0EFEB42B2006D3C800F81029 /* SessionKey.swift */, 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */, - 0EFEB43A2006D3C800F81029 /* SessionProxy+Authenticator.swift */, - 0E58BF522240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift */, - 0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */, - 0E0C2124212ED29D008AB282 /* SessionProxy+Configuration.swift */, - 0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */, 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */, - 0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */, - 0E3E0F202108A8CC00B371C1 /* SessionProxy+SessionReply.swift */, - 0E041D082152E6FE0025FE3C /* SessionProxy+TLSWrap.swift */, + 0E3E0F202108A8CC00B371C1 /* SessionReply.swift */, 0EE3B3E321471C3A0027AB17 /* StaticKey.swift */, 0EFEB4442006D3C800F81029 /* TLSBox.h */, 0EFEB4302006D3C800F81029 /* TLSBox.m */, + 0E041D082152E6FE0025FE3C /* TLSWrap.swift */, ); path = OpenVPN; sourceTree = ""; }; - 0EB03E0F2290D22A006D03A0 /* AppExtension */ = { - isa = PBXGroup; - children = ( - 0EB03E112290D22A006D03A0 /* Transport */, - 0EB03E102290D22A006D03A0 /* ConnectionStrategy.swift */, - 0EB03E172290D22A006D03A0 /* DNSResolver.swift */, - 0EB03E1B2290D22A006D03A0 /* GenericSocket.swift */, - 0EB03E1F2290D22A006D03A0 /* InterfaceObserver.swift */, - 0EB03E192290D22A006D03A0 /* Keychain.swift */, - 0EB03E1A2290D22A006D03A0 /* MemoryDestination.swift */, - 0EB03E1C2290D22A006D03A0 /* TunnelKitProvider.swift */, - 0EB03E182290D22A006D03A0 /* TunnelKitProvider+Configuration.swift */, - 0EB03E1E2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift */, - 0EB03E1D2290D22A006D03A0 /* Utils.swift */, - ); - path = AppExtension; - sourceTree = ""; - }; - 0EB03E112290D22A006D03A0 /* Transport */ = { - isa = PBXGroup; - children = ( - 0EB03E122290D22A006D03A0 /* NETCPInterface.swift */, - 0EB03E132290D22A006D03A0 /* NETunnelInterface.swift */, - 0EB03E142290D22A006D03A0 /* NWTCPConnectionState+Description.swift */, - 0EB03E152290D22A006D03A0 /* NEUDPInterface.swift */, - 0EB03E162290D22A006D03A0 /* NWUDPSessionState+Description.swift */, - ); - path = Transport; - sourceTree = ""; - }; 0EFEB4292006D3C800F81029 /* Core */ = { isa = PBXGroup; children = ( @@ -1223,24 +1079,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EB2B45720F0BD16004233D7 /* RandomTests.swift in Sources */, - 0E011F812196E23700BA59EE /* ConfigurationParserTests.swift in Sources */, - 0EB2B45920F0BD9A004233D7 /* LinkTests.swift in Sources */, - 0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */, - 0E50D57521634E0A00FC87A8 /* ControlChannelTests.swift in Sources */, - 0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */, - 0EB2B45B20F0BE4C004233D7 /* TestUtils.swift in Sources */, - 0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */, - 0E12B2A32145341B00B4BAE9 /* PacketTests.swift in Sources */, - 0E041D0C2152E80A0025FE3C /* StaticKeyTests.swift in Sources */, - 0E245D692135972800B012A2 /* PushTests.swift in Sources */, - 0EB2B46120F0C0A4004233D7 /* DataPathPerformanceTests.swift in Sources */, - 0E0B203C227886AD007A3CB9 /* RoutingTests.swift in Sources */, - 0EB2B45F20F0C098004233D7 /* EncryptionPerformanceTests.swift in Sources */, - 0EE7A7A120F664AC00B42E6A /* DataPathEncryptionTests.swift in Sources */, - 0EB2B45D20F0BF41004233D7 /* RawPerformanceTests.swift in Sources */, - 0E85A25A202CC5AF0059E9F9 /* AppExtensionTests.swift in Sources */, - 0E58BF3C2240547F006FB157 /* CompressionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1257,17 +1095,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EB03E202290D22A006D03A0 /* ConnectionStrategy.swift in Sources */, - 0EB03E322290D22A006D03A0 /* MemoryDestination.swift in Sources */, 0E58BF4C22405C2F006FB157 /* StandardLZO.m in Sources */, - 0EB03E222290D22A006D03A0 /* NETCPInterface.swift in Sources */, 0EE2F974229163C900F56F49 /* Proxy.swift in Sources */, 0EFEB4732006D3C800F81029 /* LinkInterface.swift in Sources */, - 0EFEB4652006D3C800F81029 /* SessionProxy+Authenticator.swift in Sources */, + 0EFEB4652006D3C800F81029 /* Authenticator.swift in Sources */, 0EE7A79820F6296F00B42E6A /* PacketMacros.m in Sources */, - 0EB03E282290D22A006D03A0 /* NEUDPInterface.swift in Sources */, - 0EB03E2C2290D22A006D03A0 /* DNSResolver.swift in Sources */, - 0EFEB4562006D3C800F81029 /* SessionProxy+SessionKey.swift in Sources */, + 0EFEB4562006D3C800F81029 /* SessionKey.swift in Sources */, 0E0B20422278A85C007A3CB9 /* RoutingTableEntry.m in Sources */, 0E58BF5922411FEF006FB157 /* LZO.m in Sources */, 0E12B29E21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */, @@ -1279,13 +1112,12 @@ 0E011F7D2196D97200BA59EE /* EndpointProtocol.swift in Sources */, 0E0C2125212ED29D008AB282 /* SessionError.swift in Sources */, 0E12B2A821456C0200B4BAE9 /* ControlChannel.swift in Sources */, - 0EFEB4552006D3C800F81029 /* SessionProxy+EncryptionBridge.swift in Sources */, + 0EFEB4552006D3C800F81029 /* EncryptionBridge.swift in Sources */, 0EFEB45C2006D3C800F81029 /* ZeroingData.m in Sources */, 0EE2F9712291638600F56F49 /* IPv6Settings.swift in Sources */, 0EFEB4632006D3C800F81029 /* ProtocolMacros.swift in Sources */, 0EFB902B22788512006405E4 /* RoutingTable.m in Sources */, 0EFEB46D2006D3C800F81029 /* Data+Manipulation.swift in Sources */, - 0EB03E2A2290D22A006D03A0 /* NWUDPSessionState+Description.swift in Sources */, 0ECE3528212EB7770040F253 /* CryptoContainer.swift in Sources */, 0EFEB4742006D3C800F81029 /* CoreConfiguration.swift in Sources */, 0E07595F20EF6D1400F38FD8 /* CryptoCBC.m in Sources */, @@ -1293,36 +1125,28 @@ 0E07598020F0060E00F38FD8 /* CryptoAEAD.m in Sources */, 0E011F882196E2AB00BA59EE /* ConfigurationParser.swift in Sources */, 0E39BCEA214B2AB60035E9DE /* ControlPacket.m in Sources */, - 0EB03E3A2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift in Sources */, 0EE2F96E2291636B00F56F49 /* IPv4Settings.swift in Sources */, 0E12B2AB2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */, 0EFEB4662006D3C800F81029 /* ZeroingData.swift in Sources */, 0EFEB4682006D3C800F81029 /* MSS.m in Sources */, 0E48AC662271ADA9009B1A98 /* PacketStream.m in Sources */, - 0EB03E382290D22A006D03A0 /* Utils.swift in Sources */, + 0EE2F97722916A5D00F56F49 /* OpenVPN.swift in Sources */, 0E411B9D2271F90700E0852C /* DNS.m in Sources */, 0EFEB45B2006D3C800F81029 /* TLSBox.m in Sources */, 0E58BF3922405410006FB157 /* minilzo.c in Sources */, - 0EB03E362290D22A006D03A0 /* TunnelKitProvider.swift in Sources */, - 0EB03E2E2290D22A006D03A0 /* TunnelKitProvider+Configuration.swift in Sources */, 0EFEB4702006D3C800F81029 /* Allocation.m in Sources */, - 0E041D092152E6FE0025FE3C /* SessionProxy+TLSWrap.swift in Sources */, + 0E041D092152E6FE0025FE3C /* TLSWrap.swift in Sources */, 0EFEB4672006D3C800F81029 /* SessionProxy.swift in Sources */, - 0EB03E3C2290D22A006D03A0 /* InterfaceObserver.swift in Sources */, - 0ED9C8642138139000621BA3 /* SessionProxy+CompressionFraming.swift in Sources */, - 0EB03E262290D22A006D03A0 /* NWTCPConnectionState+Description.swift in Sources */, + 0ED9C8642138139000621BA3 /* CompressionFraming.swift in Sources */, 0EFEB4722006D3C800F81029 /* ReplayProtector.m in Sources */, - 0E3E0F212108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */, + 0E3E0F212108A8CC00B371C1 /* SessionReply.swift in Sources */, 0ECC60D82254981A0020BEAC /* ConfigurationError.swift in Sources */, 0EFEB4752006D3C800F81029 /* Errors.m in Sources */, - 0E58BF532240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */, + 0E58BF532240FAA6006FB157 /* CompressionAlgorithm.swift in Sources */, 0EB03E3F2290D310006D03A0 /* CoreConfiguration+OpenVPN.swift in Sources */, - 0EB03E342290D22A006D03A0 /* GenericSocket.swift in Sources */, - 0EB03E302290D22A006D03A0 /* Keychain.swift in Sources */, 0E12B2A521454F7F00B4BAE9 /* BidirectionalState.swift in Sources */, - 0EB03E242290D22A006D03A0 /* NETunnelInterface.swift in Sources */, 0EFEB4762006D3C800F81029 /* DataPath.m in Sources */, - 0E0C2127212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */, + 0E0C2127212ED29D008AB282 /* Configuration.swift in Sources */, 0EFEB4692006D3C800F81029 /* Packet.swift in Sources */, 0E011F7A2196D93600BA59EE /* SocketType.swift in Sources */, 0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */, @@ -1333,16 +1157,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EB03E212290D22A006D03A0 /* ConnectionStrategy.swift in Sources */, - 0EB03E332290D22A006D03A0 /* MemoryDestination.swift in Sources */, 0E58BF4D22405C2F006FB157 /* StandardLZO.m in Sources */, - 0EB03E232290D22A006D03A0 /* NETCPInterface.swift in Sources */, 0EE2F975229163C900F56F49 /* Proxy.swift in Sources */, 0EFEB4A12006D7F300F81029 /* LinkInterface.swift in Sources */, 0EE7A79920F6296F00B42E6A /* PacketMacros.m in Sources */, 0E0B20432278A85C007A3CB9 /* RoutingTableEntry.m in Sources */, - 0EB03E292290D22A006D03A0 /* NEUDPInterface.swift in Sources */, - 0EB03E2D2290D22A006D03A0 /* DNSResolver.swift in Sources */, 0E58BF5A22411FEF006FB157 /* LZO.m in Sources */, 0E12B29F21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */, 0EFEB4A02006D7F300F81029 /* ReplayProtector.m in Sources */, @@ -1361,45 +1180,36 @@ 0EFB902C22788512006405E4 /* RoutingTable.m in Sources */, 0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */, 0EFEB49A2006D7F300F81029 /* MSS.m in Sources */, - 0EB03E2B2290D22A006D03A0 /* NWUDPSessionState+Description.swift in Sources */, 0ECE352A212EB88E0040F253 /* CryptoContainer.swift in Sources */, - 0EFEB48D2006D7F300F81029 /* SessionProxy+EncryptionBridge.swift in Sources */, + 0EFEB48D2006D7F300F81029 /* EncryptionBridge.swift in Sources */, 0EFEB4922006D7F300F81029 /* ZeroingData.m in Sources */, 0E07596020EF6D1400F38FD8 /* CryptoCBC.m in Sources */, 0EFEB4932006D7F300F81029 /* CryptoBox.m in Sources */, 0E011F892196E2AB00BA59EE /* ConfigurationParser.swift in Sources */, 0E39BCEB214B2AB60035E9DE /* ControlPacket.m in Sources */, - 0EB03E3B2290D22A006D03A0 /* TunnelKitProvider+Interaction.swift in Sources */, 0EE2F96F2291636B00F56F49 /* IPv4Settings.swift in Sources */, 0E12B2AC2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */, 0E07598120F0060E00F38FD8 /* CryptoAEAD.m in Sources */, 0EFEB49C2006D7F300F81029 /* Data+Manipulation.swift in Sources */, 0E48AC672271ADA9009B1A98 /* PacketStream.m in Sources */, - 0EB03E392290D22A006D03A0 /* Utils.swift in Sources */, + 0EE2F97822916A5D00F56F49 /* OpenVPN.swift in Sources */, 0E411B9E2271F90700E0852C /* DNS.m in Sources */, 0EFEB4902006D7F300F81029 /* TunnelInterface.swift in Sources */, 0EFEB49E2006D7F300F81029 /* Allocation.m in Sources */, - 0EB03E372290D22A006D03A0 /* TunnelKitProvider.swift in Sources */, - 0EB03E2F2290D22A006D03A0 /* TunnelKitProvider+Configuration.swift in Sources */, 0E58BF3A22405410006FB157 /* minilzo.c in Sources */, - 0E041D0A2152E6FE0025FE3C /* SessionProxy+TLSWrap.swift in Sources */, - 0EFEB48E2006D7F300F81029 /* SessionProxy+SessionKey.swift in Sources */, - 0EB03E3D2290D22A006D03A0 /* InterfaceObserver.swift in Sources */, - 0ED9C8652138139000621BA3 /* SessionProxy+CompressionFraming.swift in Sources */, - 0EB03E272290D22A006D03A0 /* NWTCPConnectionState+Description.swift in Sources */, + 0E041D0A2152E6FE0025FE3C /* TLSWrap.swift in Sources */, + 0EFEB48E2006D7F300F81029 /* SessionKey.swift in Sources */, + 0ED9C8652138139000621BA3 /* CompressionFraming.swift in Sources */, 0EFEB4A42006D7F300F81029 /* DataPath.m in Sources */, 0ECC60D92254981A0020BEAC /* ConfigurationError.swift in Sources */, - 0E3E0F222108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */, - 0E58BF542240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */, + 0E3E0F222108A8CC00B371C1 /* SessionReply.swift in Sources */, + 0E58BF542240FAA6006FB157 /* CompressionAlgorithm.swift in Sources */, 0E12B2A621454F7F00B4BAE9 /* BidirectionalState.swift in Sources */, 0EB03E402290D310006D03A0 /* CoreConfiguration+OpenVPN.swift in Sources */, - 0EB03E352290D22A006D03A0 /* GenericSocket.swift in Sources */, - 0EB03E312290D22A006D03A0 /* Keychain.swift in Sources */, 0EFEB4912006D7F300F81029 /* TLSBox.m in Sources */, - 0EB03E252290D22A006D03A0 /* NETunnelInterface.swift in Sources */, 0EFEB49D2006D7F300F81029 /* IOInterface.swift in Sources */, - 0E0C2128212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */, - 0EFEB4972006D7F300F81029 /* SessionProxy+Authenticator.swift in Sources */, + 0E0C2128212ED29D008AB282 /* Configuration.swift in Sources */, + 0EFEB4972006D7F300F81029 /* Authenticator.swift in Sources */, 0E011F7B2196D93600BA59EE /* SocketType.swift in Sources */, 0EFEB49B2006D7F300F81029 /* Packet.swift in Sources */, ); @@ -1409,24 +1219,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EA82A3A2190B2B9007960EB /* RandomTests.swift in Sources */, - 0E011F822196E23800BA59EE /* ConfigurationParserTests.swift in Sources */, - 0EA82A332190B2B9007960EB /* DataPathPerformanceTests.swift in Sources */, - 0EA82A372190B2B9007960EB /* LinkTests.swift in Sources */, - 0EA82A352190B2B9007960EB /* EncryptionPerformanceTests.swift in Sources */, - 0EA82A2F2190B2B9007960EB /* AppExtensionTests.swift in Sources */, - 0EA82A322190B2B9007960EB /* DataPathEncryptionTests.swift in Sources */, - 0EA82A392190B2B9007960EB /* PushTests.swift in Sources */, - 0EA82A3C2190B2B9007960EB /* StaticKeyTests.swift in Sources */, - 0EA82A302190B2B9007960EB /* ControlChannelTests.swift in Sources */, - 0EA82A312190B2B9007960EB /* DataManipulationTests.swift in Sources */, - 0EA82A382190B2B9007960EB /* PacketTests.swift in Sources */, - 0E0B203D227886AD007A3CB9 /* RoutingTests.swift in Sources */, - 0EA82A3B2190B2B9007960EB /* RawPerformanceTests.swift in Sources */, - 0EA82A362190B2B9007960EB /* EncryptionTests.swift in Sources */, - 0EA82A3D2190B2B9007960EB /* TestUtils.swift in Sources */, - 0EA82A342190B2B9007960EB /* DNSTests.swift in Sources */, - 0E58BF3D2240547F006FB157 /* CompressionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+Authenticator.swift b/TunnelKit/Sources/OpenVPN/Authenticator.swift similarity index 98% rename from TunnelKit/Sources/OpenVPN/SessionProxy+Authenticator.swift rename to TunnelKit/Sources/OpenVPN/Authenticator.swift index 3bfb681..11cd1a0 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+Authenticator.swift +++ b/TunnelKit/Sources/OpenVPN/Authenticator.swift @@ -1,5 +1,5 @@ // -// SessionProxy+Authenticator.swift +// Authenticator.swift // TunnelKit // // Created by Davide De Rosa on 2/9/17. @@ -48,7 +48,7 @@ fileprivate extension ZeroingData { } } -extension SessionProxy { +extension OpenVPN { class Authenticator { private var controlBuffer: ZeroingData @@ -90,7 +90,7 @@ extension SessionProxy { // MARK: Authentication request // Ruby: on_tls_connect - func putAuth(into: TLSBox, options: SessionProxy.Configuration) throws { + func putAuth(into: TLSBox, options: Configuration) throws { let raw = Z(ProtocolMacros.tlsPrefix) // local keys diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+CompressionAlgorithm.swift b/TunnelKit/Sources/OpenVPN/CompressionAlgorithm.swift similarity index 96% rename from TunnelKit/Sources/OpenVPN/SessionProxy+CompressionAlgorithm.swift rename to TunnelKit/Sources/OpenVPN/CompressionAlgorithm.swift index 89f4a14..76daf17 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+CompressionAlgorithm.swift +++ b/TunnelKit/Sources/OpenVPN/CompressionAlgorithm.swift @@ -1,5 +1,5 @@ // -// SessionProxy+CompressionAlgorithm.swift +// CompressionAlgorithm.swift // TunnelKit // // Created by Davide De Rosa on 3/19/19. @@ -26,7 +26,7 @@ import Foundation import __TunnelKitOpenVPN -extension SessionProxy { +extension OpenVPN { /// Defines the type of compression algorithm. public enum CompressionAlgorithm: Int, Codable, CustomStringConvertible { diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+CompressionFraming.swift b/TunnelKit/Sources/OpenVPN/CompressionFraming.swift similarity index 96% rename from TunnelKit/Sources/OpenVPN/SessionProxy+CompressionFraming.swift rename to TunnelKit/Sources/OpenVPN/CompressionFraming.swift index 39644d6..b4f7c70 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+CompressionFraming.swift +++ b/TunnelKit/Sources/OpenVPN/CompressionFraming.swift @@ -1,5 +1,5 @@ // -// SessionProxy+CompressionFraming.swift +// CompressionFraming.swift // TunnelKit // // Created by Davide De Rosa on 8/30/18. @@ -26,7 +26,7 @@ import Foundation import __TunnelKitOpenVPN -extension SessionProxy { +extension OpenVPN { /// Defines the type of compression framing. public enum CompressionFraming: Int, Codable, CustomStringConvertible { diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+Configuration.swift b/TunnelKit/Sources/OpenVPN/Configuration.swift similarity index 82% rename from TunnelKit/Sources/OpenVPN/SessionProxy+Configuration.swift rename to TunnelKit/Sources/OpenVPN/Configuration.swift index 677f726..a051b70 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+Configuration.swift +++ b/TunnelKit/Sources/OpenVPN/Configuration.swift @@ -1,5 +1,5 @@ // -// SessionProxy+Configuration.swift +// Configuration.swift // TunnelKit // // Created by Davide De Rosa on 8/23/18. @@ -36,7 +36,7 @@ import Foundation -extension SessionProxy { +extension OpenVPN { /// A pair of credentials for authentication. public struct Credentials: Codable, Equatable { @@ -167,22 +167,22 @@ extension SessionProxy { static let compressionFraming: CompressionFraming = .disabled } - /// The way to create a `SessionProxy.Configuration` object for a `SessionProxy`. + /// The way to create a `Configuration` object for a `SessionProxy`. public struct ConfigurationBuilder { // MARK: General /// The cipher algorithm for data encryption. - public var cipher: SessionProxy.Cipher? + public var cipher: Cipher? /// The digest algorithm for HMAC. - public var digest: SessionProxy.Digest? + public var digest: Digest? /// Compression framing, disabled by default. - public var compressionFraming: SessionProxy.CompressionFraming? + public var compressionFraming: CompressionFraming? /// Compression algorithm, disabled by default. - public var compressionAlgorithm: SessionProxy.CompressionAlgorithm? + public var compressionAlgorithm: CompressionAlgorithm? /// The CA for TLS negotiation (PEM format). public var ca: CryptoContainer? @@ -194,7 +194,7 @@ extension SessionProxy { public var clientKey: CryptoContainer? /// The optional TLS wrapping. - public var tlsWrap: SessionProxy.TLSWrap? + public var tlsWrap: TLSWrap? /// If set, overrides TLS security level (0 = lowest). public var tlsSecurityLevel: Int? @@ -261,9 +261,9 @@ extension SessionProxy { } /** - Builds a `SessionProxy.Configuration` object. + Builds a `Configuration` object. - - Returns: A `SessionProxy.Configuration` object with this builder. + - Returns: A `Configuration` object with this builder. */ public func build() -> Configuration { return Configuration( @@ -317,82 +317,82 @@ extension SessionProxy { /// The immutable configuration for `SessionProxy`. public struct Configuration: Codable { - /// - Seealso: `SessionProxy.ConfigurationBuilder.cipher` + /// - Seealso: `ConfigurationBuilder.cipher` public let cipher: Cipher? - /// - Seealso: `SessionProxy.ConfigurationBuilder.digest` + /// - Seealso: `ConfigurationBuilder.digest` public let digest: Digest? - /// - Seealso: `SessionProxy.ConfigurationBuilder.compressionFraming` + /// - Seealso: `ConfigurationBuilder.compressionFraming` public let compressionFraming: CompressionFraming? - /// - Seealso: `SessionProxy.ConfigurationBuilder.compressionAlgorithm` + /// - Seealso: `ConfigurationBuilder.compressionAlgorithm` public let compressionAlgorithm: CompressionAlgorithm? - /// - Seealso: `SessionProxy.ConfigurationBuilder.ca` + /// - Seealso: `ConfigurationBuilder.ca` public let ca: CryptoContainer? - /// - Seealso: `SessionProxy.ConfigurationBuilder.clientCertificate` + /// - Seealso: `ConfigurationBuilder.clientCertificate` public let clientCertificate: CryptoContainer? - /// - Seealso: `SessionProxy.ConfigurationBuilder.clientKey` + /// - Seealso: `ConfigurationBuilder.clientKey` public let clientKey: CryptoContainer? - /// - Seealso: `SessionProxy.ConfigurationBuilder.tlsWrap` + /// - Seealso: `ConfigurationBuilder.tlsWrap` public let tlsWrap: TLSWrap? - /// - Seealso: `SessionProxy.ConfigurationBuilder.tlsSecurityLevel` + /// - Seealso: `ConfigurationBuilder.tlsSecurityLevel` public let tlsSecurityLevel: Int? - /// - Seealso: `SessionProxy.ConfigurationBuilder.keepAliveInterval` + /// - Seealso: `ConfigurationBuilder.keepAliveInterval` public let keepAliveInterval: TimeInterval? - /// - Seealso: `SessionProxy.ConfigurationBuilder.renegotiatesAfter` + /// - Seealso: `ConfigurationBuilder.renegotiatesAfter` public let renegotiatesAfter: TimeInterval? - /// - Seealso: `SessionProxy.ConfigurationBuilder.hostname` + /// - Seealso: `ConfigurationBuilder.hostname` public let hostname: String? - /// - Seealso: `SessionProxy.ConfigurationBuilder.endpointProtocols` + /// - Seealso: `ConfigurationBuilder.endpointProtocols` public let endpointProtocols: [EndpointProtocol]? - /// - Seealso: `SessionProxy.ConfigurationBuilder.checksEKU` + /// - Seealso: `ConfigurationBuilder.checksEKU` public let checksEKU: Bool? - /// - Seealso: `SessionProxy.ConfigurationBuilder.randomizeEndpoint` + /// - Seealso: `ConfigurationBuilder.randomizeEndpoint` public let randomizeEndpoint: Bool? - /// - Seealso: `SessionProxy.ConfigurationBuilder.usesPIAPatches` + /// - Seealso: `ConfigurationBuilder.usesPIAPatches` public let usesPIAPatches: Bool? - /// - Seealso: `SessionProxy.ConfigurationBuilder.authToken` + /// - Seealso: `ConfigurationBuilder.authToken` public let authToken: String? - /// - Seealso: `SessionProxy.ConfigurationBuilder.peerId` + /// - Seealso: `ConfigurationBuilder.peerId` public let peerId: UInt32? - /// - Seealso: `SessionProxy.ConfigurationBuilder.ipv4` + /// - Seealso: `ConfigurationBuilder.ipv4` public let ipv4: IPv4Settings? - /// - Seealso: `SessionProxy.ConfigurationBuilder.ipv6` + /// - Seealso: `ConfigurationBuilder.ipv6` public let ipv6: IPv6Settings? - /// - Seealso: `SessionProxy.ConfigurationBuilder.dnsServers` + /// - Seealso: `ConfigurationBuilder.dnsServers` public let dnsServers: [String]? - /// - Seealso: `SessionProxy.ConfigurationBuilder.searchDomain` + /// - Seealso: `ConfigurationBuilder.searchDomain` public let searchDomain: String? - /// - Seealso: `SessionProxy.ConfigurationBuilder.httpProxy` + /// - Seealso: `ConfigurationBuilder.httpProxy` public let httpProxy: Proxy? - /// - Seealso: `SessionProxy.ConfigurationBuilder.httpsProxy` + /// - Seealso: `ConfigurationBuilder.httpsProxy` public let httpsProxy: Proxy? - /// - Seealso: `SessionProxy.ConfigurationBuilder.proxyBypassDomains` + /// - Seealso: `ConfigurationBuilder.proxyBypassDomains` public let proxyBypassDomains: [String]? - /// - Seealso: `SessionProxy.ConfigurationBuilder.routingPolicies` + /// - Seealso: `ConfigurationBuilder.routingPolicies` public let routingPolicies: [RoutingPolicy]? // MARK: Shortcuts @@ -416,15 +416,15 @@ extension SessionProxy { // MARK: Modification -extension SessionProxy.Configuration { +extension OpenVPN.Configuration { /** - Returns a `SessionProxy.ConfigurationBuilder` to use this configuration as a starting point for a new one. + Returns a `ConfigurationBuilder` to use this configuration as a starting point for a new one. - - Returns: An editable `SessionProxy.ConfigurationBuilder` initialized with this configuration. + - Returns: An editable `ConfigurationBuilder` initialized with this configuration. */ - public func builder() -> SessionProxy.ConfigurationBuilder { - var builder = SessionProxy.ConfigurationBuilder() + public func builder() -> OpenVPN.ConfigurationBuilder { + var builder = OpenVPN.ConfigurationBuilder() builder.cipher = cipher builder.digest = digest builder.compressionFraming = compressionFraming diff --git a/TunnelKit/Sources/OpenVPN/ConfigurationParser.swift b/TunnelKit/Sources/OpenVPN/ConfigurationParser.swift index 6c4c62e..798bafc 100644 --- a/TunnelKit/Sources/OpenVPN/ConfigurationParser.swift +++ b/TunnelKit/Sources/OpenVPN/ConfigurationParser.swift @@ -29,736 +29,739 @@ import __TunnelKitCore private let log = SwiftyBeaver.self -/// Provides methods to parse a `SessionProxy.Configuration` from an .ovpn configuration file. -public class ConfigurationParser { +extension OpenVPN { - // XXX: parsing is very optimistic - - struct Regex { - - // MARK: General - - static let cipher = NSRegularExpression("^cipher +[^,\\s]+") - - static let auth = NSRegularExpression("^auth +[\\w\\-]+") - - static let compLZO = NSRegularExpression("^comp-lzo.*") - - static let compress = NSRegularExpression("^compress.*") - - static let keyDirection = NSRegularExpression("^key-direction +\\d") - - static let ping = NSRegularExpression("^ping +\\d+") - - static let renegSec = NSRegularExpression("^reneg-sec +\\d+") - - static let blockBegin = NSRegularExpression("^<[\\w\\-]+>") - - static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>") - - // MARK: Client - - static let proto = NSRegularExpression("^proto +(udp6?|tcp6?)") - - static let port = NSRegularExpression("^port +\\d+") - - static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp6?|tcp6?))?") - - static let eku = NSRegularExpression("^remote-cert-tls +server") - - static let remoteRandom = NSRegularExpression("^remote-random") - - // MARK: Server - - static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+") - - static let peerId = NSRegularExpression("^peer-id +[0-9]+") - - // MARK: Routing - - static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)") - - static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+") - - static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+") - - static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}") - - static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}") - - static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+") - - static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+") - - static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+") - - static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS?) +[^ ]+ +\\d+") - - static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+") - - static let redirectGateway = NSRegularExpression("^redirect-gateway.*") + /// Provides methods to parse a `Configuration` from an .ovpn configuration file. + public class ConfigurationParser { - // MARK: Unsupported + // XXX: parsing is very optimistic -// static let fragment = NSRegularExpression("^fragment +\\d+") - static let fragment = NSRegularExpression("^fragment") - - static let connectionProxy = NSRegularExpression("^\\w+-proxy") - - static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ") - - static let connection = NSRegularExpression("^") - - // MARK: Continuation - - static let continuation = NSRegularExpression("^push-continuation [12]") - } - - private enum Topology: String { - case net30 - - case p2p - - case subnet - } - - private enum RedirectGateway: String { - case def1 // default - - case noIPv4 = "!ipv4" - - case ipv6 - - case local - - case autolocal - - case blockLocal = "block-local" - - case bypassDHCP = "bypass-dhcp" - - case bypassDNS = "bypass-dns" - } - - /// Result of the parser. - public struct Result { - - /// Original URL of the configuration file, if parsed from an URL. - public let url: URL? - - /// The overall parsed `SessionProxy.Configuration`. - public let configuration: SessionProxy.Configuration - - /// 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(...)` - public let strippedLines: [String]? - - /// Holds an optional `ConfigurationError` that didn't block the parser, but it would be worth taking care of. - public let warning: ConfigurationError? - } - - /** - Parses an .ovpn file from an URL. - - - Parameter url: The URL of the configuration file. - - Parameter passphrase: The optional passphrase for encrypted data. - - Parameter returnsStripped: When `true`, stores the stripped file into `Result.strippedLines`. Defaults to `false`. - - Returns: The `Result` outcome of the parsing. - - Throws: `ConfigurationError` if the configuration file is wrong or incomplete. - */ - public static func parsed(fromURL url: URL, passphrase: String? = nil, returnsStripped: Bool = false) throws -> Result { - let lines = try String(contentsOf: url).trimmedLines() - return try parsed(fromLines: lines, passphrase: passphrase, originalURL: url, returnsStripped: returnsStripped) - } - - /** - Parses a configuration from an array of lines. - - - Parameter lines: The array of lines holding the configuration. - - Parameter passphrase: The optional passphrase for encrypted data. - - Parameter originalURL: The optional original URL of the configuration file. - - Parameter returnsStripped: When `true`, stores the stripped file into `Result.strippedLines`. Defaults to `false`. - - Returns: The `Result` outcome of the parsing. - - Throws: `ConfigurationError` if the configuration file is wrong or incomplete. - */ - public static func parsed(fromLines lines: [String], passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> Result { - var optStrippedLines: [String]? = returnsStripped ? [] : nil - var optWarning: ConfigurationError? - var unsupportedError: ConfigurationError? - var currentBlockName: String? - var currentBlock: [String] = [] - - var optCipher: SessionProxy.Cipher? - var optDigest: SessionProxy.Digest? - var optCompressionFraming: SessionProxy.CompressionFraming? - var optCompressionAlgorithm: SessionProxy.CompressionAlgorithm? - var optCA: CryptoContainer? - var optClientCertificate: CryptoContainer? - var optClientKey: CryptoContainer? - var optKeyDirection: StaticKey.Direction? - var optTLSKeyLines: [Substring]? - var optTLSStrategy: SessionProxy.TLSWrap.Strategy? - var optKeepAliveSeconds: TimeInterval? - var optRenegotiateAfterSeconds: TimeInterval? - // - var optHostname: String? - var optDefaultProto: SocketType? - var optDefaultPort: UInt16? - var optRemotes: [(String, UInt16?, SocketType?)] = [] // address, port, socket - var optChecksEKU: Bool? - var optRandomizeEndpoint: Bool? - // - var optAuthToken: String? - var optPeerId: UInt32? - // - var optTopology: String? - var optIfconfig4Arguments: [String]? - var optIfconfig6Arguments: [String]? - var optGateway4Arguments: [String]? - var optRoutes4: [(String, String, String?)] = [] // address, netmask, gateway - var optRoutes6: [(String, UInt8, String?)] = [] // destination, prefix, gateway - var optDNSServers: [String]? - var optSearchDomain: String? - var optHTTPProxy: Proxy? - var optHTTPSProxy: Proxy? - var optProxyBypass: [String]? - var optRedirectGateway: Set? - - log.verbose("Configuration file:") - for line in lines { - log.verbose(line) + struct Regex { - var isHandled = false - var strippedLine = line - defer { - if isHandled { - optStrippedLines?.append(strippedLine) - } - } + // MARK: General + static let cipher = NSRegularExpression("^cipher +[^,\\s]+") + + static let auth = NSRegularExpression("^auth +[\\w\\-]+") + + static let compLZO = NSRegularExpression("^comp-lzo.*") + + static let compress = NSRegularExpression("^compress.*") + + static let keyDirection = NSRegularExpression("^key-direction +\\d") + + static let ping = NSRegularExpression("^ping +\\d+") + + static let renegSec = NSRegularExpression("^reneg-sec +\\d+") + + static let blockBegin = NSRegularExpression("^<[\\w\\-]+>") + + static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>") + + // MARK: Client + + static let proto = NSRegularExpression("^proto +(udp6?|tcp6?)") + + static let port = NSRegularExpression("^port +\\d+") + + static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp6?|tcp6?))?") + + static let eku = NSRegularExpression("^remote-cert-tls +server") + + static let remoteRandom = NSRegularExpression("^remote-random") + + // MARK: Server + + static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+") + + static let peerId = NSRegularExpression("^peer-id +[0-9]+") + + // MARK: Routing + + static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)") + + static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+") + + static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+") + + static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}") + + static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}") + + static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+") + + static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+") + + static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+") + + static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS?) +[^ ]+ +\\d+") + + static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+") + + static let redirectGateway = NSRegularExpression("^redirect-gateway.*") + // MARK: Unsupported - // check blocks first - Regex.connection.enumerateComponents(in: line) { (_) in - unsupportedError = ConfigurationError.unsupportedConfiguration(option: " blocks") - } - Regex.fragment.enumerateComponents(in: line) { (_) in - unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment") - } - Regex.connectionProxy.enumerateComponents(in: line) { (_) in - unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proxy: \"\(line)\"") - } - Regex.externalFiles.enumerateComponents(in: line) { (_) in - unsupportedError = ConfigurationError.unsupportedConfiguration(option: "external file: \"\(line)\"") - } - if line.contains("mtu") || line.contains("mssfix") { - isHandled = true - } + // static let fragment = NSRegularExpression("^fragment +\\d+") + static let fragment = NSRegularExpression("^fragment") + + static let connectionProxy = NSRegularExpression("^\\w+-proxy") + + static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ") + + static let connection = NSRegularExpression("^") // MARK: Continuation - - var isContinuation = false - Regex.continuation.enumerateArguments(in: line) { - isContinuation = ($0.first == "2") - } - guard !isContinuation else { - throw SessionError.continuationPushReply - } - - // MARK: Inline content - if unsupportedError == nil { - if currentBlockName == nil { - Regex.blockBegin.enumerateComponents(in: line) { + static let continuation = NSRegularExpression("^push-continuation [12]") + } + + private enum Topology: String { + case net30 + + case p2p + + case subnet + } + + private enum RedirectGateway: String { + case def1 // default + + case noIPv4 = "!ipv4" + + case ipv6 + + case local + + case autolocal + + case blockLocal = "block-local" + + case bypassDHCP = "bypass-dhcp" + + case bypassDNS = "bypass-dns" + } + + /// Result of the parser. + public struct Result { + + /// Original URL of the configuration file, if parsed from an URL. + public let url: URL? + + /// The overall parsed `Configuration`. + public let configuration: Configuration + + /// 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(...)` + public let strippedLines: [String]? + + /// Holds an optional `ConfigurationError` that didn't block the parser, but it would be worth taking care of. + public let warning: ConfigurationError? + } + + /** + Parses an .ovpn file from an URL. + + - Parameter url: The URL of the configuration file. + - Parameter passphrase: The optional passphrase for encrypted data. + - Parameter returnsStripped: When `true`, stores the stripped file into `Result.strippedLines`. Defaults to `false`. + - Returns: The `Result` outcome of the parsing. + - Throws: `ConfigurationError` if the configuration file is wrong or incomplete. + */ + public static func parsed(fromURL url: URL, passphrase: String? = nil, returnsStripped: Bool = false) throws -> Result { + let lines = try String(contentsOf: url).trimmedLines() + return try parsed(fromLines: lines, passphrase: passphrase, originalURL: url, returnsStripped: returnsStripped) + } + + /** + Parses a configuration from an array of lines. + + - Parameter lines: The array of lines holding the configuration. + - Parameter passphrase: The optional passphrase for encrypted data. + - Parameter originalURL: The optional original URL of the configuration file. + - Parameter returnsStripped: When `true`, stores the stripped file into `Result.strippedLines`. Defaults to `false`. + - Returns: The `Result` outcome of the parsing. + - Throws: `ConfigurationError` if the configuration file is wrong or incomplete. + */ + public static func parsed(fromLines lines: [String], passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> Result { + var optStrippedLines: [String]? = returnsStripped ? [] : nil + var optWarning: ConfigurationError? + var unsupportedError: ConfigurationError? + var currentBlockName: String? + var currentBlock: [String] = [] + + var optCipher: Cipher? + var optDigest: Digest? + var optCompressionFraming: CompressionFraming? + var optCompressionAlgorithm: CompressionAlgorithm? + var optCA: CryptoContainer? + var optClientCertificate: CryptoContainer? + var optClientKey: CryptoContainer? + var optKeyDirection: StaticKey.Direction? + var optTLSKeyLines: [Substring]? + var optTLSStrategy: TLSWrap.Strategy? + var optKeepAliveSeconds: TimeInterval? + var optRenegotiateAfterSeconds: TimeInterval? + // + var optHostname: String? + var optDefaultProto: SocketType? + var optDefaultPort: UInt16? + var optRemotes: [(String, UInt16?, SocketType?)] = [] // address, port, socket + var optChecksEKU: Bool? + var optRandomizeEndpoint: Bool? + // + var optAuthToken: String? + var optPeerId: UInt32? + // + var optTopology: String? + var optIfconfig4Arguments: [String]? + var optIfconfig6Arguments: [String]? + var optGateway4Arguments: [String]? + var optRoutes4: [(String, String, String?)] = [] // address, netmask, gateway + var optRoutes6: [(String, UInt8, String?)] = [] // destination, prefix, gateway + var optDNSServers: [String]? + var optSearchDomain: String? + var optHTTPProxy: Proxy? + var optHTTPSProxy: Proxy? + var optProxyBypass: [String]? + var optRedirectGateway: Set? + + log.verbose("Configuration file:") + for line in lines { + log.verbose(line) + + var isHandled = false + var strippedLine = line + defer { + if isHandled { + optStrippedLines?.append(strippedLine) + } + } + + // MARK: Unsupported + + // check blocks first + Regex.connection.enumerateComponents(in: line) { (_) in + unsupportedError = ConfigurationError.unsupportedConfiguration(option: " blocks") + } + Regex.fragment.enumerateComponents(in: line) { (_) in + unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment") + } + Regex.connectionProxy.enumerateComponents(in: line) { (_) in + unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proxy: \"\(line)\"") + } + Regex.externalFiles.enumerateComponents(in: line) { (_) in + unsupportedError = ConfigurationError.unsupportedConfiguration(option: "external file: \"\(line)\"") + } + if line.contains("mtu") || line.contains("mssfix") { + isHandled = true + } + + // MARK: Continuation + + var isContinuation = false + Regex.continuation.enumerateArguments(in: line) { + isContinuation = ($0.first == "2") + } + guard !isContinuation else { + throw SessionError.continuationPushReply + } + + // MARK: Inline content + + if unsupportedError == nil { + if currentBlockName == nil { + Regex.blockBegin.enumerateComponents(in: line) { + isHandled = true + let tag = $0.first! + let from = tag.index(after: tag.startIndex) + let to = tag.index(before: tag.endIndex) + + currentBlockName = String(tag[from.."] + if $0.count > 1 { + port = UInt16($0[1]) + strippedComponents.append($0[1]) + } + if $0.count > 2 { + proto = SocketType(protoString: $0[2]) + strippedComponents.append($0[2]) + } + optRemotes.append((hostname, port, proto)) + + // replace private data + strippedLine = strippedComponents.joined(separator: " ") + } + Regex.eku.enumerateComponents(in: line) { (_) in + isHandled = true + optChecksEKU = true + } + Regex.remoteRandom.enumerateComponents(in: line) { (_) in + isHandled = true + optRandomizeEndpoint = true + } + + // MARK: Server + + Regex.authToken.enumerateArguments(in: line) { + optAuthToken = $0[0] + } + Regex.peerId.enumerateArguments(in: line) { + optPeerId = UInt32($0[0]) + } + + // MARK: Routing + + Regex.topology.enumerateArguments(in: line) { + optTopology = $0.first + } + Regex.ifconfig.enumerateArguments(in: line) { + optIfconfig4Arguments = $0 + } + Regex.ifconfig6.enumerateArguments(in: line) { + optIfconfig6Arguments = $0 + } + Regex.route.enumerateArguments(in: line) { + let routeEntryArguments = $0 + + let address = routeEntryArguments[0] + let mask = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : "255.255.255.255" + let gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4 + optRoutes4.append((address, mask, gateway)) + } + Regex.route6.enumerateArguments(in: line) { + let routeEntryArguments = $0 + + let destinationComponents = routeEntryArguments[0].components(separatedBy: "/") + guard destinationComponents.count == 2 else { + return + } + guard let prefix = UInt8(destinationComponents[1]) else { return } - // first is opening tag - currentBlock.removeFirst() - switch blockName { - case "ca": - optCA = CryptoContainer(pem: currentBlock.joined(separator: "\n")) + let destination = destinationComponents[0] + let gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6 + optRoutes6.append((destination, prefix, gateway)) + } + Regex.gateway.enumerateArguments(in: line) { + optGateway4Arguments = $0 + } + Regex.dns.enumerateArguments(in: line) { + guard $0.count == 2 else { + return + } + if optDNSServers == nil { + optDNSServers = [] + } + optDNSServers?.append($0[1]) + } + Regex.domain.enumerateArguments(in: line) { + guard $0.count == 2 else { + return + } + optSearchDomain = $0[1] + } + Regex.proxy.enumerateArguments(in: line) { + guard $0.count == 3, let port = UInt16($0[2]) else { + return + } + switch $0[0] { + case "PROXY_HTTPS": + optHTTPSProxy = Proxy($0[1], port) - case "cert": - optClientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n")) - - case "key": - ConfigurationParser.normalizeEncryptedPEMBlock(block: ¤tBlock) - optClientKey = CryptoContainer(pem: currentBlock.joined(separator: "\n")) - - case "tls-auth": - optTLSKeyLines = currentBlock.map { Substring($0) } - optTLSStrategy = .auth - - case "tls-crypt": - optTLSKeyLines = currentBlock.map { Substring($0) } - optTLSStrategy = .crypt + case "PROXY_HTTP": + optHTTPProxy = Proxy($0[1], port) default: break } - currentBlockName = nil - currentBlock = [] + } + Regex.proxyBypass.enumerateArguments(in: line) { + guard !$0.isEmpty else { + return + } + optProxyBypass = $0 + optProxyBypass?.removeFirst() + } + Regex.redirectGateway.enumerateArguments(in: line) { + + // redirect IPv4 by default + optRedirectGateway = [.def1] + + for arg in $0 { + guard let opt = RedirectGateway(rawValue: arg) else { + continue + } + optRedirectGateway?.insert(opt) + } + } + + // + + if let error = unsupportedError { + throw error } } - if let _ = currentBlockName { - currentBlock.append(line) - continue - } + + // + + var sessionBuilder = ConfigurationBuilder() // MARK: General - Regex.cipher.enumerateArguments(in: line) { - isHandled = true - guard let rawValue = $0.first else { - return + sessionBuilder.cipher = optCipher + sessionBuilder.digest = optDigest + sessionBuilder.compressionFraming = optCompressionFraming + sessionBuilder.compressionAlgorithm = optCompressionAlgorithm + sessionBuilder.ca = optCA + sessionBuilder.clientCertificate = optClientCertificate + + if let clientKey = optClientKey, clientKey.isEncrypted { + guard let passphrase = passphrase else { + throw ConfigurationError.encryptionPassphrase } - optCipher = SessionProxy.Cipher(rawValue: rawValue.uppercased()) - if optCipher == nil { - unsupportedError = ConfigurationError.unsupportedConfiguration(option: "cipher \(rawValue)") + do { + sessionBuilder.clientKey = try clientKey.decrypted(with: passphrase) + } catch let e { + throw ConfigurationError.unableToDecrypt(error: e) + } + } else { + sessionBuilder.clientKey = optClientKey + } + + if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy { + let optKey: StaticKey? + switch strategy { + case .auth: + optKey = StaticKey(lines: keyLines, direction: optKeyDirection) + + case .crypt: + optKey = StaticKey(lines: keyLines, direction: .client) + } + if let key = optKey { + sessionBuilder.tlsWrap = TLSWrap(strategy: strategy, key: key) } } - Regex.auth.enumerateArguments(in: line) { - isHandled = true - guard let rawValue = $0.first else { - return - } - optDigest = SessionProxy.Digest(rawValue: rawValue.uppercased()) - if optDigest == nil { - unsupportedError = ConfigurationError.unsupportedConfiguration(option: "auth \(rawValue)") - } - } - Regex.compLZO.enumerateArguments(in: line) { - isHandled = true - optCompressionFraming = .compLZO - - if !LZOIsSupported() { - guard let arg = $0.first else { - optWarning = optWarning ?? .unsupportedConfiguration(option: line) - return - } - guard arg == "no" else { - unsupportedError = .unsupportedConfiguration(option: line) - return - } - } else { - let arg = $0.first - optCompressionAlgorithm = (arg == "no") ? .disabled : .LZO - } - } - Regex.compress.enumerateArguments(in: line) { - isHandled = true - optCompressionFraming = .compress - - if !LZOIsSupported() { - guard $0.isEmpty else { - unsupportedError = .unsupportedConfiguration(option: line) - return - } - } else { - if let arg = $0.first { - optCompressionAlgorithm = (arg == "lzo") ? .LZO : .other - } else { - optCompressionAlgorithm = .disabled - } - } - } - Regex.keyDirection.enumerateArguments(in: line) { - isHandled = true - guard let arg = $0.first, let value = Int(arg) else { - return - } - optKeyDirection = StaticKey.Direction(rawValue: value) - } - Regex.ping.enumerateArguments(in: line) { - isHandled = true - guard let arg = $0.first else { - return - } - optKeepAliveSeconds = TimeInterval(arg) - } - Regex.renegSec.enumerateArguments(in: line) { - isHandled = true - guard let arg = $0.first else { - return - } - optRenegotiateAfterSeconds = TimeInterval(arg) - } + + sessionBuilder.keepAliveInterval = optKeepAliveSeconds + sessionBuilder.renegotiatesAfter = optRenegotiateAfterSeconds // MARK: Client - Regex.proto.enumerateArguments(in: line) { - isHandled = true - guard let str = $0.first else { - return - } - optDefaultProto = SocketType(protoString: str) - if optDefaultProto == nil { - unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proto \(str)") - } - } - Regex.port.enumerateArguments(in: line) { - isHandled = true - guard let str = $0.first else { - return - } - optDefaultPort = UInt16(str) - } - Regex.remote.enumerateArguments(in: line) { - isHandled = true - guard let hostname = $0.first else { - return - } - var port: UInt16? - var proto: SocketType? - var strippedComponents = ["remote", ""] - if $0.count > 1 { - port = UInt16($0[1]) - strippedComponents.append($0[1]) - } - if $0.count > 2 { - proto = SocketType(protoString: $0[2]) - strippedComponents.append($0[2]) - } - optRemotes.append((hostname, port, proto)) + optDefaultProto = optDefaultProto ?? .udp + optDefaultPort = optDefaultPort ?? 1194 + if !optRemotes.isEmpty { + sessionBuilder.hostname = optRemotes[0].0 - // replace private data - strippedLine = strippedComponents.joined(separator: " ") - } - Regex.eku.enumerateComponents(in: line) { (_) in - isHandled = true - optChecksEKU = true - } - Regex.remoteRandom.enumerateComponents(in: line) { (_) in - isHandled = true - optRandomizeEndpoint = true + var fullRemotes: [(String, UInt16, SocketType)] = [] + let hostname = optRemotes[0].0 + optRemotes.forEach { + guard $0.0 == hostname else { + return + } + guard let port = $0.1 ?? optDefaultPort else { + return + } + guard let socketType = $0.2 ?? optDefaultProto else { + return + } + fullRemotes.append((hostname, port, socketType)) + } + sessionBuilder.endpointProtocols = fullRemotes.map { EndpointProtocol($0.2, $0.1) } + } else { + sessionBuilder.hostname = nil } + sessionBuilder.checksEKU = optChecksEKU + sessionBuilder.randomizeEndpoint = optRandomizeEndpoint + // MARK: Server - Regex.authToken.enumerateArguments(in: line) { - optAuthToken = $0[0] - } - Regex.peerId.enumerateArguments(in: line) { - optPeerId = UInt32($0[0]) - } + sessionBuilder.authToken = optAuthToken + sessionBuilder.peerId = optPeerId // MARK: Routing - Regex.topology.enumerateArguments(in: line) { - optTopology = $0.first - } - Regex.ifconfig.enumerateArguments(in: line) { - optIfconfig4Arguments = $0 - } - Regex.ifconfig6.enumerateArguments(in: line) { - optIfconfig6Arguments = $0 - } - Regex.route.enumerateArguments(in: line) { - let routeEntryArguments = $0 - - let address = routeEntryArguments[0] - let mask = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : "255.255.255.255" - let gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4 - optRoutes4.append((address, mask, gateway)) - } - Regex.route6.enumerateArguments(in: line) { - let routeEntryArguments = $0 - - let destinationComponents = routeEntryArguments[0].components(separatedBy: "/") - guard destinationComponents.count == 2 else { - return - } - guard let prefix = UInt8(destinationComponents[1]) else { - return + // + // excerpts from OpenVPN manpage + // + // "--ifconfig l rn": + // + // Set TUN/TAP adapter parameters. l is the IP address of the local VPN endpoint. For TUN devices in point-to-point mode, rn is the IP address of + // the remote VPN endpoint. For TAP devices, or TUN devices used with --topology subnet, rn is the subnet mask of the virtual network segment which + // is being created or connected to. + // + // "--topology mode": + // + // Note: Using --topology subnet changes the interpretation of the arguments of --ifconfig to mean "address netmask", no longer "local remote". + // + if let ifconfig4Arguments = optIfconfig4Arguments { + guard ifconfig4Arguments.count == 2 else { + throw ConfigurationError.malformed(option: "ifconfig takes 2 arguments") } - let destination = destinationComponents[0] - let gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6 - optRoutes6.append((destination, prefix, gateway)) - } - Regex.gateway.enumerateArguments(in: line) { - optGateway4Arguments = $0 - } - Regex.dns.enumerateArguments(in: line) { - guard $0.count == 2 else { - return - } - if optDNSServers == nil { - optDNSServers = [] - } - optDNSServers?.append($0[1]) - } - Regex.domain.enumerateArguments(in: line) { - guard $0.count == 2 else { - return - } - optSearchDomain = $0[1] - } - Regex.proxy.enumerateArguments(in: line) { - guard $0.count == 3, let port = UInt16($0[2]) else { - return - } - switch $0[0] { - case "PROXY_HTTPS": - optHTTPSProxy = Proxy($0[1], port) + let address4: String + let addressMask4: String + let defaultGateway4: String + + let topology = Topology(rawValue: optTopology ?? "") ?? .net30 + switch topology { + case .subnet: - case "PROXY_HTTP": - optHTTPProxy = Proxy($0[1], port) + // default gateway required when topology is subnet + guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else { + throw ConfigurationError.malformed(option: "route-gateway takes 1 argument") + } + address4 = ifconfig4Arguments[0] + addressMask4 = ifconfig4Arguments[1] + defaultGateway4 = gateway4Arguments[0] default: - break + address4 = ifconfig4Arguments[0] + addressMask4 = "255.255.255.255" + defaultGateway4 = ifconfig4Arguments[1] } + let routes4 = optRoutes4.map { IPv4Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway4) } + + sessionBuilder.ipv4 = IPv4Settings( + address: address4, + addressMask: addressMask4, + defaultGateway: defaultGateway4, + routes: routes4 + ) } - Regex.proxyBypass.enumerateArguments(in: line) { - guard !$0.isEmpty else { - return + + if let ifconfig6Arguments = optIfconfig6Arguments { + guard ifconfig6Arguments.count == 2 else { + throw ConfigurationError.malformed(option: "ifconfig-ipv6 takes 2 arguments") } - optProxyBypass = $0 - optProxyBypass?.removeFirst() + let address6Components = ifconfig6Arguments[0].components(separatedBy: "/") + guard address6Components.count == 2 else { + throw ConfigurationError.malformed(option: "ifconfig-ipv6 address must have a /prefix") + } + guard let addressPrefix6 = UInt8(address6Components[1]) else { + throw ConfigurationError.malformed(option: "ifconfig-ipv6 address prefix must be a 8-bit number") + } + + let address6 = address6Components[0] + let defaultGateway6 = ifconfig6Arguments[1] + let routes6 = optRoutes6.map { IPv6Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway6) } + + sessionBuilder.ipv6 = IPv6Settings( + address: address6, + addressPrefixLength: addressPrefix6, + defaultGateway: defaultGateway6, + routes: routes6 + ) } - Regex.redirectGateway.enumerateArguments(in: line) { + + sessionBuilder.dnsServers = optDNSServers + sessionBuilder.searchDomain = optSearchDomain + sessionBuilder.httpProxy = optHTTPProxy + sessionBuilder.httpsProxy = optHTTPSProxy + sessionBuilder.proxyBypassDomains = optProxyBypass - // redirect IPv4 by default - optRedirectGateway = [.def1] + if let flags = optRedirectGateway { + var policies: Set = [] + for opt in flags { + switch opt { + case .def1: + policies.insert(.IPv4) + + case .ipv6: + policies.insert(.IPv6) + + case .blockLocal: + policies.insert(.blockLocal) - for arg in $0 { - guard let opt = RedirectGateway(rawValue: arg) else { + default: + // TODO: handle [auto]local and block-* continue } - optRedirectGateway?.insert(opt) } + if flags.contains(.noIPv4) { + policies.remove(.IPv4) + } + sessionBuilder.routingPolicies = [RoutingPolicy](policies) } // - if let error = unsupportedError { - throw error - } - } - - // - - var sessionBuilder = SessionProxy.ConfigurationBuilder() - - // MARK: General - - sessionBuilder.cipher = optCipher - sessionBuilder.digest = optDigest - sessionBuilder.compressionFraming = optCompressionFraming - sessionBuilder.compressionAlgorithm = optCompressionAlgorithm - sessionBuilder.ca = optCA - sessionBuilder.clientCertificate = optClientCertificate - - if let clientKey = optClientKey, clientKey.isEncrypted { - guard let passphrase = passphrase else { - throw ConfigurationError.encryptionPassphrase - } - do { - sessionBuilder.clientKey = try clientKey.decrypted(with: passphrase) - } catch let e { - throw ConfigurationError.unableToDecrypt(error: e) - } - } else { - sessionBuilder.clientKey = optClientKey - } - - if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy { - let optKey: StaticKey? - switch strategy { - case .auth: - optKey = StaticKey(lines: keyLines, direction: optKeyDirection) - - case .crypt: - optKey = StaticKey(lines: keyLines, direction: .client) - } - if let key = optKey { - sessionBuilder.tlsWrap = SessionProxy.TLSWrap(strategy: strategy, key: key) - } - } - - sessionBuilder.keepAliveInterval = optKeepAliveSeconds - sessionBuilder.renegotiatesAfter = optRenegotiateAfterSeconds - - // MARK: Client - - optDefaultProto = optDefaultProto ?? .udp - optDefaultPort = optDefaultPort ?? 1194 - if !optRemotes.isEmpty { - sessionBuilder.hostname = optRemotes[0].0 - - var fullRemotes: [(String, UInt16, SocketType)] = [] - let hostname = optRemotes[0].0 - optRemotes.forEach { - guard $0.0 == hostname else { - return - } - guard let port = $0.1 ?? optDefaultPort else { - return - } - guard let socketType = $0.2 ?? optDefaultProto else { - return - } - fullRemotes.append((hostname, port, socketType)) - } - sessionBuilder.endpointProtocols = fullRemotes.map { EndpointProtocol($0.2, $0.1) } - } else { - sessionBuilder.hostname = nil - } - - sessionBuilder.checksEKU = optChecksEKU - sessionBuilder.randomizeEndpoint = optRandomizeEndpoint - - // MARK: Server - - sessionBuilder.authToken = optAuthToken - sessionBuilder.peerId = optPeerId - - // MARK: Routing - - // - // excerpts from OpenVPN manpage - // - // "--ifconfig l rn": - // - // Set TUN/TAP adapter parameters. l is the IP address of the local VPN endpoint. For TUN devices in point-to-point mode, rn is the IP address of - // the remote VPN endpoint. For TAP devices, or TUN devices used with --topology subnet, rn is the subnet mask of the virtual network segment which - // is being created or connected to. - // - // "--topology mode": - // - // Note: Using --topology subnet changes the interpretation of the arguments of --ifconfig to mean "address netmask", no longer "local remote". - // - if let ifconfig4Arguments = optIfconfig4Arguments { - guard ifconfig4Arguments.count == 2 else { - throw ConfigurationError.malformed(option: "ifconfig takes 2 arguments") - } - - let address4: String - let addressMask4: String - let defaultGateway4: String - - let topology = Topology(rawValue: optTopology ?? "") ?? .net30 - switch topology { - case .subnet: - - // default gateway required when topology is subnet - guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else { - throw ConfigurationError.malformed(option: "route-gateway takes 1 argument") - } - address4 = ifconfig4Arguments[0] - addressMask4 = ifconfig4Arguments[1] - defaultGateway4 = gateway4Arguments[0] - - default: - address4 = ifconfig4Arguments[0] - addressMask4 = "255.255.255.255" - defaultGateway4 = ifconfig4Arguments[1] - } - let routes4 = optRoutes4.map { IPv4Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway4) } - - sessionBuilder.ipv4 = IPv4Settings( - address: address4, - addressMask: addressMask4, - defaultGateway: defaultGateway4, - routes: routes4 + return Result( + url: originalURL, + configuration: sessionBuilder.build(), + strippedLines: optStrippedLines, + warning: optWarning ) } - - if let ifconfig6Arguments = optIfconfig6Arguments { - guard ifconfig6Arguments.count == 2 else { - throw ConfigurationError.malformed(option: "ifconfig-ipv6 takes 2 arguments") - } - let address6Components = ifconfig6Arguments[0].components(separatedBy: "/") - guard address6Components.count == 2 else { - throw ConfigurationError.malformed(option: "ifconfig-ipv6 address must have a /prefix") - } - guard let addressPrefix6 = UInt8(address6Components[1]) else { - throw ConfigurationError.malformed(option: "ifconfig-ipv6 address prefix must be a 8-bit number") - } + + private static func normalizeEncryptedPEMBlock(block: inout [String]) { + // if block.count >= 1 && block[0].contains("ENCRYPTED") { + // return true + // } - let address6 = address6Components[0] - let defaultGateway6 = ifconfig6Arguments[1] - let routes6 = optRoutes6.map { IPv6Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway6) } - - sessionBuilder.ipv6 = IPv6Settings( - address: address6, - addressPrefixLength: addressPrefix6, - defaultGateway: defaultGateway6, - routes: routes6 - ) - } - - sessionBuilder.dnsServers = optDNSServers - sessionBuilder.searchDomain = optSearchDomain - sessionBuilder.httpProxy = optHTTPProxy - sessionBuilder.httpsProxy = optHTTPSProxy - sessionBuilder.proxyBypassDomains = optProxyBypass - - if let flags = optRedirectGateway { - var policies: Set = [] - for opt in flags { - switch opt { - case .def1: - policies.insert(.IPv4) - - case .ipv6: - policies.insert(.IPv6) - - case .blockLocal: - policies.insert(.blockLocal) - - default: - // TODO: handle [auto]local and block-* - continue - } + // XXX: restore blank line after encryption header (easier than tweaking trimmedLines) + if block.count >= 3 && block[1].contains("Proc-Type") { + block.insert("", at: 3) + // return true } - if flags.contains(.noIPv4) { - policies.remove(.IPv4) - } - sessionBuilder.routingPolicies = [SessionProxy.RoutingPolicy](policies) + // return false } - - // - - return Result( - url: originalURL, - configuration: sessionBuilder.build(), - strippedLines: optStrippedLines, - warning: optWarning - ) - } - - private static func normalizeEncryptedPEMBlock(block: inout [String]) { -// if block.count >= 1 && block[0].contains("ENCRYPTED") { -// return true -// } - - // XXX: restore blank line after encryption header (easier than tweaking trimmedLines) - if block.count >= 3 && block[1].contains("Proc-Type") { - block.insert("", at: 3) -// return true - } -// return false } } diff --git a/TunnelKit/Sources/OpenVPN/ControlChannel.swift b/TunnelKit/Sources/OpenVPN/ControlChannel.swift index fd9edd6..9067798 100644 --- a/TunnelKit/Sources/OpenVPN/ControlChannel.swift +++ b/TunnelKit/Sources/OpenVPN/ControlChannel.swift @@ -30,210 +30,212 @@ import __TunnelKitOpenVPN private let log = SwiftyBeaver.self -class ControlChannelError: Error, CustomStringConvertible { - let description: String - - init(_ message: String) { - description = "\(String(describing: ControlChannelError.self))(\(message))" - } -} - -class ControlChannel { - private let serializer: ControlChannelSerializer - - private(set) var sessionId: Data? - - var remoteSessionId: Data? { - didSet { - if let id = remoteSessionId { - log.debug("Control: Remote sessionId is \(id.toHex())") - } +extension OpenVPN { + class ControlChannelError: Error, CustomStringConvertible { + let description: String + + init(_ message: String) { + description = "\(String(describing: ControlChannelError.self))(\(message))" } } - private var queue: BidirectionalState<[ControlPacket]> + class ControlChannel { + private let serializer: ControlChannelSerializer + + private(set) var sessionId: Data? + + var remoteSessionId: Data? { + didSet { + if let id = remoteSessionId { + log.debug("Control: Remote sessionId is \(id.toHex())") + } + } + } - private var currentPacketId: BidirectionalState + private var queue: BidirectionalState<[ControlPacket]> - private var pendingAcks: Set + private var currentPacketId: BidirectionalState - private var plainBuffer: ZeroingData + private var pendingAcks: Set - private var dataCount: BidirectionalState - - convenience init() { - self.init(serializer: PlainSerializer()) - } - - convenience init(withAuthKey key: StaticKey, digest: SessionProxy.Digest) throws { - self.init(serializer: try AuthSerializer(withKey: key, digest: digest)) - } + private var plainBuffer: ZeroingData - convenience init(withCryptKey key: StaticKey) throws { - self.init(serializer: try CryptSerializer(withKey: key)) - } - - private init(serializer: ControlChannelSerializer) { - self.serializer = serializer - sessionId = nil - remoteSessionId = nil - queue = BidirectionalState(withResetValue: []) - currentPacketId = BidirectionalState(withResetValue: 0) - pendingAcks = [] - plainBuffer = Z(count: TLSBoxMaxBufferLength) - dataCount = BidirectionalState(withResetValue: 0) - } - - func reset(forNewSession: Bool) throws { - if forNewSession { - try sessionId = SecureRandom.data(length: PacketSessionIdLength) + private var dataCount: BidirectionalState + + convenience init() { + self.init(serializer: PlainSerializer()) + } + + convenience init(withAuthKey key: StaticKey, digest: Digest) throws { + self.init(serializer: try AuthSerializer(withKey: key, digest: digest)) + } + + convenience init(withCryptKey key: StaticKey) throws { + self.init(serializer: try CryptSerializer(withKey: key)) + } + + private init(serializer: ControlChannelSerializer) { + self.serializer = serializer + sessionId = nil remoteSessionId = nil + queue = BidirectionalState(withResetValue: []) + currentPacketId = BidirectionalState(withResetValue: 0) + pendingAcks = [] + plainBuffer = Z(count: TLSBoxMaxBufferLength) + dataCount = BidirectionalState(withResetValue: 0) } - queue.reset() - currentPacketId.reset() - pendingAcks.removeAll() - plainBuffer.zero() - dataCount.reset() - serializer.reset() - } - - func readInboundPacket(withData data: Data, offset: Int) throws -> ControlPacket { - let packet = try serializer.deserialize(data: data, start: offset, end: nil) - log.debug("Control: Read packet \(packet)") - if let ackIds = packet.ackIds as? [UInt32], let ackRemoteSessionId = packet.ackRemoteSessionId { - try readAcks(ackIds, acksRemoteSessionId: ackRemoteSessionId) - } - return packet - } - - func enqueueInboundPacket(packet: ControlPacket) -> [ControlPacket] { - queue.inbound.append(packet) - queue.inbound.sort { $0.packetId < $1.packetId } - var toHandle: [ControlPacket] = [] - for queuedPacket in queue.inbound { - if queuedPacket.packetId < currentPacketId.inbound { - queue.inbound.removeFirst() - continue + func reset(forNewSession: Bool) throws { + if forNewSession { + try sessionId = SecureRandom.data(length: PacketSessionIdLength) + remoteSessionId = nil } - if queuedPacket.packetId != currentPacketId.inbound { - continue - } - - toHandle.append(queuedPacket) - - currentPacketId.inbound += 1 - queue.inbound.removeFirst() - } - return toHandle - } - - func enqueueOutboundPackets(withCode code: PacketCode, key: UInt8, payload: Data, maxPacketSize: Int) { - guard let sessionId = sessionId else { - fatalError("Missing sessionId, do reset(forNewSession: true) first") + queue.reset() + currentPacketId.reset() + pendingAcks.removeAll() + plainBuffer.zero() + dataCount.reset() + serializer.reset() } - let oldIdOut = currentPacketId.outbound - var queuedCount = 0 - var offset = 0 - - repeat { - let subPayloadLength = min(maxPacketSize, payload.count - offset) - let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength) - let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: currentPacketId.outbound, payload: subPayloadData) - - queue.outbound.append(packet) - currentPacketId.outbound += 1 - offset += maxPacketSize - queuedCount += subPayloadLength - } while (offset < payload.count) - - assert(queuedCount == payload.count) - - // packet count - let packetCount = currentPacketId.outbound - oldIdOut - if (packetCount > 1) { - log.debug("Control: Enqueued \(packetCount) packets [\(oldIdOut)-\(currentPacketId.outbound - 1)]") - } else { - log.debug("Control: Enqueued 1 packet [\(oldIdOut)]") + func readInboundPacket(withData data: Data, offset: Int) throws -> ControlPacket { + let packet = try serializer.deserialize(data: data, start: offset, end: nil) + log.debug("Control: Read packet \(packet)") + if let ackIds = packet.ackIds as? [UInt32], let ackRemoteSessionId = packet.ackRemoteSessionId { + try readAcks(ackIds, acksRemoteSessionId: ackRemoteSessionId) + } + return packet } - } - - func writeOutboundPackets() throws -> [Data] { - var rawList: [Data] = [] - for packet in queue.outbound { - if let sentDate = packet.sentDate { - let timeAgo = -sentDate.timeIntervalSinceNow - guard (timeAgo >= CoreConfiguration.OpenVPN.retransmissionLimit) else { - log.debug("Control: Skip writing packet with packetId \(packet.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)") + + func enqueueInboundPacket(packet: ControlPacket) -> [ControlPacket] { + queue.inbound.append(packet) + queue.inbound.sort { $0.packetId < $1.packetId } + + var toHandle: [ControlPacket] = [] + for queuedPacket in queue.inbound { + if queuedPacket.packetId < currentPacketId.inbound { + queue.inbound.removeFirst() continue } + if queuedPacket.packetId != currentPacketId.inbound { + continue + } + + toHandle.append(queuedPacket) + + currentPacketId.inbound += 1 + queue.inbound.removeFirst() + } + return toHandle + } + + func enqueueOutboundPackets(withCode code: PacketCode, key: UInt8, payload: Data, maxPacketSize: Int) { + guard let sessionId = sessionId else { + fatalError("Missing sessionId, do reset(forNewSession: true) first") + } + + let oldIdOut = currentPacketId.outbound + var queuedCount = 0 + var offset = 0 + + repeat { + let subPayloadLength = min(maxPacketSize, payload.count - offset) + let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength) + let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: currentPacketId.outbound, payload: subPayloadData) + + queue.outbound.append(packet) + currentPacketId.outbound += 1 + offset += maxPacketSize + queuedCount += subPayloadLength + } while (offset < payload.count) + + assert(queuedCount == payload.count) + + // packet count + let packetCount = currentPacketId.outbound - oldIdOut + if (packetCount > 1) { + log.debug("Control: Enqueued \(packetCount) packets [\(oldIdOut)-\(currentPacketId.outbound - 1)]") + } else { + log.debug("Control: Enqueued 1 packet [\(oldIdOut)]") + } + } + + func writeOutboundPackets() throws -> [Data] { + var rawList: [Data] = [] + for packet in queue.outbound { + if let sentDate = packet.sentDate { + let timeAgo = -sentDate.timeIntervalSinceNow + guard (timeAgo >= CoreConfiguration.OpenVPN.retransmissionLimit) else { + log.debug("Control: Skip writing packet with packetId \(packet.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)") + continue + } + } + + log.debug("Control: Write control packet \(packet)") + + let raw = try serializer.serialize(packet: packet) + rawList.append(raw) + packet.sentDate = Date() + + // track pending acks for sent packets + pendingAcks.insert(packet.packetId) + } + // log.verbose("Packets now pending ack: \(pendingAcks)") + return rawList + } + + func hasPendingAcks() -> Bool { + return !pendingAcks.isEmpty + } + + // Ruby: handle_acks + private func readAcks(_ packetIds: [UInt32], acksRemoteSessionId: Data) throws { + guard let sessionId = sessionId else { + throw SessionError.missingSessionId + } + guard acksRemoteSessionId == sessionId else { + log.error("Control: Ack session mismatch (\(acksRemoteSessionId.toHex()) != \(sessionId.toHex()))") + throw SessionError.sessionMismatch + } + + // drop queued out packets if ack-ed + for (i, packet) in queue.outbound.enumerated() { + if packetIds.contains(packet.packetId) { + queue.outbound.remove(at: i) + } } - log.debug("Control: Write control packet \(packet)") - - let raw = try serializer.serialize(packet: packet) - rawList.append(raw) - packet.sentDate = Date() - - // track pending acks for sent packets - pendingAcks.insert(packet.packetId) - } -// log.verbose("Packets now pending ack: \(pendingAcks)") - return rawList - } - - func hasPendingAcks() -> Bool { - return !pendingAcks.isEmpty - } - - // Ruby: handle_acks - private func readAcks(_ packetIds: [UInt32], acksRemoteSessionId: Data) throws { - guard let sessionId = sessionId else { - throw SessionError.missingSessionId - } - guard acksRemoteSessionId == sessionId else { - log.error("Control: Ack session mismatch (\(acksRemoteSessionId.toHex()) != \(sessionId.toHex()))") - throw SessionError.sessionMismatch + // remove ack-ed packets from pending + pendingAcks.subtract(packetIds) + + // log.verbose("Packets still pending ack: \(pendingAcks)") } - // drop queued out packets if ack-ed - for (i, packet) in queue.outbound.enumerated() { - if packetIds.contains(packet.packetId) { - queue.outbound.remove(at: i) + func writeAcks(withKey key: UInt8, ackPacketIds: [UInt32], ackRemoteSessionId: Data) throws -> Data { + guard let sessionId = sessionId else { + throw SessionError.missingSessionId } + let packet = ControlPacket(key: key, sessionId: sessionId, ackIds: ackPacketIds as [NSNumber], ackRemoteSessionId: ackRemoteSessionId) + log.debug("Control: Write ack packet \(packet)") + return try serializer.serialize(packet: packet) } - // remove ack-ed packets from pending - pendingAcks.subtract(packetIds) - -// log.verbose("Packets still pending ack: \(pendingAcks)") - } - - func writeAcks(withKey key: UInt8, ackPacketIds: [UInt32], ackRemoteSessionId: Data) throws -> Data { - guard let sessionId = sessionId else { - throw SessionError.missingSessionId + func currentControlData(withTLS tls: TLSBox) throws -> ZeroingData { + var length = 0 + try tls.pullRawPlainText(plainBuffer.mutableBytes, length: &length) + return plainBuffer.withOffset(0, count: length) + } + + func addReceivedDataCount(_ count: Int) { + dataCount.inbound += count } - let packet = ControlPacket(key: key, sessionId: sessionId, ackIds: ackPacketIds as [NSNumber], ackRemoteSessionId: ackRemoteSessionId) - log.debug("Control: Write ack packet \(packet)") - return try serializer.serialize(packet: packet) - } - - func currentControlData(withTLS tls: TLSBox) throws -> ZeroingData { - var length = 0 - try tls.pullRawPlainText(plainBuffer.mutableBytes, length: &length) - return plainBuffer.withOffset(0, count: length) - } - - func addReceivedDataCount(_ count: Int) { - dataCount.inbound += count - } - func addSentDataCount(_ count: Int) { - dataCount.outbound += count - } - - func currentDataCount() -> (Int, Int) { - return dataCount.pair + func addSentDataCount(_ count: Int) { + dataCount.outbound += count + } + + func currentDataCount() -> (Int, Int) { + return dataCount.pair + } } } diff --git a/TunnelKit/Sources/OpenVPN/ControlChannelSerializer.swift b/TunnelKit/Sources/OpenVPN/ControlChannelSerializer.swift index bd117a2..d56fe99 100644 --- a/TunnelKit/Sources/OpenVPN/ControlChannelSerializer.swift +++ b/TunnelKit/Sources/OpenVPN/ControlChannelSerializer.swift @@ -38,7 +38,7 @@ protocol ControlChannelSerializer { func deserialize(data: Data, start: Int, end: Int?) throws -> ControlPacket } -extension ControlChannel { +extension OpenVPN.ControlChannel { class PlainSerializer: ControlChannelSerializer { func reset() { } @@ -52,11 +52,11 @@ extension ControlChannel { let end = end ?? packet.count guard end >= offset + PacketOpcodeLength else { - throw ControlChannelError("Missing opcode") + throw OpenVPN.ControlChannelError("Missing opcode") } let codeValue = packet[offset] >> 3 guard let code = PacketCode(rawValue: codeValue) else { - throw ControlChannelError("Unknown code: \(codeValue))") + throw OpenVPN.ControlChannelError("Unknown code: \(codeValue))") } let key = packet[offset] & 0b111 offset += PacketOpcodeLength @@ -64,13 +64,13 @@ extension ControlChannel { log.debug("Control: Try read packet with code \(code) and key \(key)") guard end >= offset + PacketSessionIdLength else { - throw ControlChannelError("Missing sessionId") + throw OpenVPN.ControlChannelError("Missing sessionId") } let sessionId = packet.subdata(offset: offset, count: PacketSessionIdLength) offset += PacketSessionIdLength guard end >= offset + 1 else { - throw ControlChannelError("Missing ackSize") + throw OpenVPN.ControlChannelError("Missing ackSize") } let ackSize = packet[offset] offset += 1 @@ -79,7 +79,7 @@ extension ControlChannel { var ackRemoteSessionId: Data? if ackSize > 0 { guard end >= (offset + Int(ackSize) * PacketIdLength) else { - throw ControlChannelError("Missing acks") + throw OpenVPN.ControlChannelError("Missing acks") } var ids: [UInt32] = [] for _ in 0..= offset + PacketSessionIdLength else { - throw ControlChannelError("Missing remoteSessionId") + throw OpenVPN.ControlChannelError("Missing remoteSessionId") } let remoteSessionId = packet.subdata(offset: offset, count: PacketSessionIdLength) offset += PacketSessionIdLength @@ -100,16 +100,16 @@ extension ControlChannel { if code == .ackV1 { guard let ackIds = ackIds else { - throw ControlChannelError("Ack packet without ids") + throw OpenVPN.ControlChannelError("Ack packet without ids") } guard let ackRemoteSessionId = ackRemoteSessionId else { - throw ControlChannelError("Ack packet without remoteSessionId") + throw OpenVPN.ControlChannelError("Ack packet without remoteSessionId") } return ControlPacket(key: key, sessionId: sessionId, ackIds: ackIds as [NSNumber], ackRemoteSessionId: ackRemoteSessionId) } guard end >= offset + PacketIdLength else { - throw ControlChannelError("Missing packetId") + throw OpenVPN.ControlChannelError("Missing packetId") } let packetId = packet.networkUInt32Value(from: offset) offset += PacketIdLength @@ -129,7 +129,7 @@ extension ControlChannel { } } -extension ControlChannel { +extension OpenVPN.ControlChannel { class AuthSerializer: ControlChannelSerializer { private let encrypter: Encrypter @@ -149,7 +149,7 @@ extension ControlChannel { private let plain: PlainSerializer - init(withKey key: StaticKey, digest: SessionProxy.Digest) throws { + init(withKey key: OpenVPN.StaticKey, digest: OpenVPN.Digest) throws { let crypto = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: digest.rawValue) try crypto.configure( withCipherEncKey: nil, @@ -190,7 +190,7 @@ extension ControlChannel { // data starts with (prefix=(header + sessionId) + auth=(hmac + replayId)) guard end >= preambleLength else { - throw ControlChannelError("Missing HMAC") + throw OpenVPN.ControlChannelError("Missing HMAC") } // needs a copy for swapping @@ -209,7 +209,7 @@ extension ControlChannel { } } -extension ControlChannel { +extension OpenVPN.ControlChannel { class CryptSerializer: ControlChannelSerializer { private let encrypter: Encrypter @@ -227,7 +227,7 @@ extension ControlChannel { private let plain: PlainSerializer - init(withKey key: StaticKey) throws { + init(withKey key: OpenVPN.StaticKey) throws { let crypto = CryptoBox(cipherAlgorithm: "AES-256-CTR", digestAlgorithm: "SHA256") try crypto.configure( withCipherEncKey: key.cipherEncryptKey, @@ -267,7 +267,7 @@ extension ControlChannel { // data starts with (ad=(header + sessionId + replayId) + tag) guard end >= start + adLength + tagLength else { - throw ControlChannelError("Missing AD+TAG") + throw OpenVPN.ControlChannelError("Missing AD+TAG") } let encryptedCount = packet.count - adLength diff --git a/TunnelKit/Sources/OpenVPN/CryptoContainer.swift b/TunnelKit/Sources/OpenVPN/CryptoContainer.swift index 44a14af..4e87963 100644 --- a/TunnelKit/Sources/OpenVPN/CryptoContainer.swift +++ b/TunnelKit/Sources/OpenVPN/CryptoContainer.swift @@ -37,58 +37,59 @@ import Foundation import __TunnelKitOpenVPN -/// Represents a cryptographic container in PEM format. -public struct CryptoContainer: Equatable { - private static let begin = "-----BEGIN " +extension OpenVPN { - private static let end = "-----END " - - /// The content in PEM format (ASCII). - public let pem: String - - /// :nodoc: - public init(pem: String) { - guard let beginRange = pem.range(of: CryptoContainer.begin) else { - self.pem = "" - return + /// Represents a cryptographic container in PEM format. + public struct CryptoContainer: Codable, Equatable { + private static let begin = "-----BEGIN " + + private static let end = "-----END " + + /// The content in PEM format (ASCII). + public let pem: String + + var isEncrypted: Bool { + return pem.contains("ENCRYPTED") + } + + /// :nodoc: + public init(pem: String) { + guard let beginRange = pem.range(of: CryptoContainer.begin) else { + self.pem = "" + return + } + self.pem = String(pem[beginRange.lowerBound...]) + } + + func write(to url: URL) throws { + try pem.write(to: url, atomically: true, encoding: .ascii) } - self.pem = String(pem[beginRange.lowerBound...]) - } - - func write(to url: URL) throws { - try pem.write(to: url, atomically: true, encoding: .ascii) - } - // MARK: Equatable - - /// :nodoc: - public static func ==(lhs: CryptoContainer, rhs: CryptoContainer) -> Bool { - return lhs.pem == rhs.pem - } -} - -/// :nodoc: -extension CryptoContainer: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let pem = try container.decode(String.self) - self.init(pem: pem) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(pem) - } -} - -/// :nodoc: -public extension CryptoContainer { - var isEncrypted: Bool { - return pem.contains("ENCRYPTED") - } - - func decrypted(with passphrase: String) throws -> CryptoContainer { - let decryptedPEM = try TLSBox.decryptedPrivateKey(fromPEM: pem, passphrase: passphrase) - return CryptoContainer(pem: decryptedPEM) + func decrypted(with passphrase: String) throws -> CryptoContainer { + let decryptedPEM = try TLSBox.decryptedPrivateKey(fromPEM: pem, passphrase: passphrase) + return CryptoContainer(pem: decryptedPEM) + } + + // MARK: Equatable + + /// :nodoc: + public static func ==(lhs: CryptoContainer, rhs: CryptoContainer) -> Bool { + return lhs.pem == rhs.pem + } + + // MARK: Codable + + /// :nodoc: + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let pem = try container.decode(String.self) + self.init(pem: pem) + } + + /// :nodoc: + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(pem) + } } } diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+EncryptionBridge.swift b/TunnelKit/Sources/OpenVPN/EncryptionBridge.swift similarity index 95% rename from TunnelKit/Sources/OpenVPN/SessionProxy+EncryptionBridge.swift rename to TunnelKit/Sources/OpenVPN/EncryptionBridge.swift index c0dd781..e122ddf 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+EncryptionBridge.swift +++ b/TunnelKit/Sources/OpenVPN/EncryptionBridge.swift @@ -1,5 +1,5 @@ // -// SessionProxy+EncryptionBridge.swift +// EncryptionBridge.swift // TunnelKit // // Created by Davide De Rosa on 2/8/17. @@ -38,7 +38,7 @@ import Foundation import __TunnelKitCore import __TunnelKitOpenVPN -extension SessionProxy { +extension OpenVPN { /// Bridges native encryption for high-level operations. public class EncryptionBridge { @@ -127,7 +127,7 @@ extension SessionProxy { return buffer.withOffset(0, count: length) } - convenience init(_ cipher: SessionProxy.Cipher, _ digest: SessionProxy.Digest, _ auth: SessionProxy.Authenticator, + convenience init(_ cipher: Cipher, _ digest: Digest, _ auth: Authenticator, _ sessionId: Data, _ remoteSessionId: Data) throws { guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else { @@ -161,7 +161,7 @@ extension SessionProxy { try self.init(cipher, digest, cipherEncKey, cipherDecKey, hmacEncKey, hmacDecKey) } - init(_ cipher: SessionProxy.Cipher, _ digest: SessionProxy.Digest, _ cipherEncKey: ZeroingData, _ cipherDecKey: ZeroingData, _ hmacEncKey: ZeroingData, _ hmacDecKey: ZeroingData) throws { + init(_ cipher: Cipher, _ digest: Digest, _ cipherEncKey: ZeroingData, _ cipherDecKey: ZeroingData, _ hmacEncKey: ZeroingData, _ hmacDecKey: ZeroingData) throws { box = CryptoBox(cipherAlgorithm: cipher.rawValue, digestAlgorithm: digest.rawValue) try box.configure( withCipherEncKey: cipherEncKey, diff --git a/TunnelKit/Sources/OpenVPN/OpenVPN.swift b/TunnelKit/Sources/OpenVPN/OpenVPN.swift new file mode 100644 index 0000000..500d042 --- /dev/null +++ b/TunnelKit/Sources/OpenVPN/OpenVPN.swift @@ -0,0 +1,30 @@ +// +// OpenVPN.swift +// TunnelKit +// +// Created by Davide De Rosa on 5/19/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// 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 . +// + +import Foundation + +/// Container for OpenVPN classes. +public class OpenVPN { +} diff --git a/TunnelKit/Sources/OpenVPN/Packet.swift b/TunnelKit/Sources/OpenVPN/Packet.swift index 24264b0..cf205e7 100644 --- a/TunnelKit/Sources/OpenVPN/Packet.swift +++ b/TunnelKit/Sources/OpenVPN/Packet.swift @@ -37,66 +37,6 @@ import Foundation import __TunnelKitOpenVPN -///// Reads and writes packets as a stream. Useful for stream-oriented links (e.g TCP/IP). -//public class PacketStream { -// -// /** -// Parses packets from a stream. -// -// - Parameter stream: The data stream. -// - Returns: A pair where the first value is the `Int` offset up to which -// the stream could be parsed, and the second value is an array containing -// the parsed packets up to such offset. -// */ -// public static func packets(from stream: Data) -> (Int, [Data]) { -// var ni = 0 -// var parsed: [Data] = [] -// while (ni + 2 <= stream.count) { -// let packlen = Int(stream.networkUInt16Value(from: ni)) -// let start = ni + 2 -// let end = start + packlen -// guard (end <= stream.count) else { -// break -// } -// let packet = stream.subdata(offset: start, count: end - start) -// parsed.append(packet) -// ni = end -// } -// return (ni, parsed) -// } -// -// /** -// Creates a contiguous stream of packets. -// -// - Parameter packet: The packet. -// - Returns: A stream made of the packet. -// */ -// public static func stream(from packet: Data) -> Data { -// var raw = Data(capacity: 2 + packet.count) -// raw.append(UInt16(packet.count).bigEndian) -// raw.append(contentsOf: packet) -// return raw -// } -// -// /** -// Creates a contiguous stream of packets. -// -// - Parameter packets: The array of packets. -// - Returns: A stream made of the array of packets. -// */ -// public static func stream(from packets: [Data]) -> Data { -// var raw = Data() -// for payload in packets { -// raw.append(UInt16(payload.count).bigEndian) -// raw.append(payload) -// } -// return raw -// } -// -// private init() { -// } -//} - /// :nodoc: extension ControlPacket { @@ -121,23 +61,25 @@ extension ControlPacket { } } -class DataPacket { - static let pingString = Data(hex: "2a187bf3641eb4cb07ed2d0a981fc748") -} +extension OpenVPN { + class DataPacket { + static let pingString = Data(hex: "2a187bf3641eb4cb07ed2d0a981fc748") + } -enum OCCPacket: UInt8 { - case exit = 0x06 - - private static let magicString = Data(hex: "287f346bd4ef7a812d56b8d3afc5459c") + enum OCCPacket: UInt8 { + case exit = 0x06 + + private static let magicString = Data(hex: "287f346bd4ef7a812d56b8d3afc5459c") - func serialized(_ info: Any? = nil) -> Data { - var data = OCCPacket.magicString - data.append(rawValue) - switch self { - case .exit: - break // nothing more + func serialized(_ info: Any? = nil) -> Data { + var data = OCCPacket.magicString + data.append(rawValue) + switch self { + case .exit: + break // nothing more + } + return data } - return data } } diff --git a/TunnelKit/Sources/OpenVPN/ProtocolMacros.swift b/TunnelKit/Sources/OpenVPN/ProtocolMacros.swift index 457904f..3ab6850 100644 --- a/TunnelKit/Sources/OpenVPN/ProtocolMacros.swift +++ b/TunnelKit/Sources/OpenVPN/ProtocolMacros.swift @@ -36,10 +36,12 @@ import Foundation -class ProtocolMacros { - - // UInt32(0) + UInt8(KeyMethod = 2) - static let tlsPrefix = Data(hex: "0000000002") +extension OpenVPN { + class ProtocolMacros { + + // UInt32(0) + UInt8(KeyMethod = 2) + static let tlsPrefix = Data(hex: "0000000002") - static let numberOfKeys = UInt8(8) // 3-bit + static let numberOfKeys = UInt8(8) // 3-bit + } } diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+SessionKey.swift b/TunnelKit/Sources/OpenVPN/SessionKey.swift similarity index 98% rename from TunnelKit/Sources/OpenVPN/SessionProxy+SessionKey.swift rename to TunnelKit/Sources/OpenVPN/SessionKey.swift index 2091bee..76e9626 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+SessionKey.swift +++ b/TunnelKit/Sources/OpenVPN/SessionKey.swift @@ -1,5 +1,5 @@ // -// SessionProxy+SessionKey.swift +// SessionKey.swift // TunnelKit // // Created by Davide De Rosa on 4/12/17. @@ -41,7 +41,7 @@ import __TunnelKitOpenVPN private let log = SwiftyBeaver.self -extension SessionProxy { +extension OpenVPN { class SessionKey { enum State { case invalid, hardReset, softReset, tls diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+PIA.swift b/TunnelKit/Sources/OpenVPN/SessionProxy+PIA.swift index eab24ae..cb79e51 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+PIA.swift +++ b/TunnelKit/Sources/OpenVPN/SessionProxy+PIA.swift @@ -36,7 +36,7 @@ import Foundation -extension SessionProxy { +extension OpenVPN.SessionProxy { struct PIAHardReset { private static let obfuscationKeyLength = 3 @@ -50,7 +50,7 @@ extension SessionProxy { private let digestName: String - init(caMd5Digest: String, cipher: Cipher, digest: Digest) { + init(caMd5Digest: String, cipher: OpenVPN.Cipher, digest: OpenVPN.Digest) { self.caMd5Digest = caMd5Digest cipherName = cipher.rawValue.lowercased() digestName = digest.rawValue.lowercased() diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy.swift b/TunnelKit/Sources/OpenVPN/SessionProxy.swift index ff5359c..5618386 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy.swift +++ b/TunnelKit/Sources/OpenVPN/SessionProxy.swift @@ -43,14 +43,14 @@ private let log = SwiftyBeaver.self /// Observes major events notified by a `SessionProxy`. public protocol SessionProxyDelegate: class { - + /** Called after starting a session. - + - Parameter remoteAddress: The address of the VPN server. - Parameter reply: The compound `SessionReply` containing tunnel settings. */ - func sessionDidStart(_: SessionProxy, remoteAddress: String, reply: SessionReply) + func sessionDidStart(_: OpenVPN.SessionProxy, remoteAddress: String, reply: SessionReply) /** Called after stopping a session. @@ -58,1222 +58,1225 @@ public protocol SessionProxyDelegate: class { - Parameter shouldReconnect: When `true`, the session can/should be restarted. Usually because the stop reason was recoverable. - Seealso: `SessionProxy.reconnect(...)` */ - func sessionDidStop(_: SessionProxy, shouldReconnect: Bool) + func sessionDidStop(_: OpenVPN.SessionProxy, shouldReconnect: Bool) } -/// Provides methods to set up and maintain an OpenVPN session. -public class SessionProxy { - private enum StopMethod { - case shutdown - - case reconnect - } - - private struct Caches { - static let ca = "ca.pem" +extension OpenVPN { - static let clientCertificate = "cert.pem" - - static let clientKey = "key.pem" - } - - // MARK: Configuration - - /// The session base configuration. - public let configuration: Configuration - - /// The optional credentials. - public var credentials: Credentials? - - private var keepAliveInterval: TimeInterval? { - let interval: TimeInterval? - if let negInterval = pushReply?.options.keepAliveInterval, negInterval > 0 { - interval = TimeInterval(negInterval) - } else if let cfgInterval = configuration.keepAliveInterval, cfgInterval > 0.0 { - interval = cfgInterval - } else { - return nil - } - return interval - } - - /// An optional `SessionProxyDelegate` for receiving session events. - public weak var delegate: SessionProxyDelegate? - - // MARK: State - - private let queue: DispatchQueue - - private var tlsObserver: NSObjectProtocol? - - private var withLocalOptions: Bool - - private var keys: [UInt8: SessionKey] - - private var oldKeys: [SessionKey] - - private var negotiationKeyIdx: UInt8 - - private var currentKeyIdx: UInt8? - - private var negotiationKey: SessionKey { - guard let key = keys[negotiationKeyIdx] else { - fatalError("Keys are empty or index \(negotiationKeyIdx) not found in \(keys.keys)") - } - return key - } - - private var currentKey: SessionKey? { - guard let i = currentKeyIdx else { - return nil - } - return keys[i] - } - - private var link: LinkInterface? - - private var tunnel: TunnelInterface? - - private var isReliableLink: Bool { - return link?.isReliable ?? false - } - - private var continuatedPushReplyMessage: String? - - private var pushReply: SessionReply? - - private var nextPushRequestDate: Date? - - private var connectedDate: Date? - - private var lastPing: BidirectionalState - - private var isStopping: Bool - - /// The optional reason why the session stopped. - public private(set) var stopError: Error? - - // MARK: Control - - private var controlChannel: ControlChannel - - private var authenticator: Authenticator? - - // MARK: Caching - - private let cachesURL: URL - - private var caURL: URL { - return cachesURL.appendingPathComponent(Caches.ca) - } - - private var clientCertificateURL: URL { - return cachesURL.appendingPathComponent(Caches.clientCertificate) - } - - private var clientKeyURL: URL { - return cachesURL.appendingPathComponent(Caches.clientKey) - } - - // MARK: Init - - /** - Creates a VPN session. - - - Parameter queue: The `DispatchQueue` where to run the session loop. - - Parameter configuration: The `SessionProxy.Configuration` to use for this session. - */ - public init(queue: DispatchQueue, configuration: Configuration, cachesURL: URL) throws { - guard let ca = configuration.ca else { - throw ConfigurationError.missingConfiguration(option: "ca") - } - - self.queue = queue - self.configuration = configuration - self.cachesURL = cachesURL - - withLocalOptions = true - keys = [:] - oldKeys = [] - negotiationKeyIdx = 0 - lastPing = BidirectionalState(withResetValue: Date.distantPast) - isStopping = false - - if let tlsWrap = configuration.tlsWrap { - switch tlsWrap.strategy { - case .auth: - controlChannel = try ControlChannel(withAuthKey: tlsWrap.key, digest: configuration.fallbackDigest) - - case .crypt: - controlChannel = try ControlChannel(withCryptKey: tlsWrap.key) - } - } else { - controlChannel = ControlChannel() - } - - // cache PEMs locally (mandatory for OpenSSL) - let fm = FileManager.default - try ca.pem.write(to: caURL, atomically: true, encoding: .ascii) - if let container = configuration.clientCertificate { - try container.pem.write(to: clientCertificateURL, atomically: true, encoding: .ascii) - } else { - try? fm.removeItem(at: clientCertificateURL) - } - if let container = configuration.clientKey { - try container.pem.write(to: clientKeyURL, atomically: true, encoding: .ascii) - } else { - try? fm.removeItem(at: clientKeyURL) - } - } - - /// :nodoc: - deinit { - cleanup() - - let fm = FileManager.default - for url in [caURL, clientCertificateURL, clientKeyURL] { - try? fm.removeItem(at: url) - } - } - - // MARK: Public interface - - /** - Establishes the link interface for this session. The interface must be up and running for sending and receiving packets. - - - Precondition: `link` is an active network interface. - - Postcondition: The VPN negotiation is started. - - Parameter link: The `LinkInterface` on which to establish the VPN session. - */ - public func setLink(_ link: LinkInterface) { - guard (self.link == nil) else { - log.warning("Link interface already set!") - return - } - - log.debug("Starting VPN session") - - // WARNING: runs in notification source queue (we know it's "queue", but better be safe than sorry) - tlsObserver = NotificationCenter.default.addObserver(forName: .TLSBoxPeerVerificationError, object: nil, queue: nil) { (notification) in - let error = notification.userInfo?[TunnelKitErrorKey] as? Error - self.queue.async { - self.deferStop(.shutdown, error) - } - } - - self.link = link - start() - } - - /** - Returns `true` if the current session can rebind to a new link with `rebindLink(...)`. - - - Returns: `true` if supports link rebinding. - */ - public func canRebindLink() -> Bool { -// return (pushReply?.peerId != nil) - - // FIXME: floating is currently unreliable - return false - } - - /** - Rebinds the session to a new link if supported. - - - Precondition: `link` is an active network interface. - - Postcondition: The VPN session is active. - - Parameter link: The `LinkInterface` on which to establish the VPN session. - - Seealso: `canRebindLink()`. - */ - public func rebindLink(_ link: LinkInterface) { - guard let _ = pushReply?.options.peerId else { - log.warning("Session doesn't support link rebinding!") - return - } - - isStopping = false - stopError = nil - - log.debug("Rebinding VPN session to a new link") - self.link = link - loopLink() - } - - /** - Establishes the tunnel interface for this session. The interface must be up and running for sending and receiving packets. - - - Precondition: `tunnel` is an active network interface. - - Postcondition: The VPN data channel is open. - - Parameter tunnel: The `TunnelInterface` on which to exchange the VPN data traffic. - */ - public func setTunnel(tunnel: TunnelInterface) { - guard (self.tunnel == nil) else { - log.warning("Tunnel interface already set!") - return - } - self.tunnel = tunnel - loopTunnel() - } - - /** - Returns the current data bytes count. - - - Returns: The current data bytes count as a pair, inbound first. - */ - public func dataCount() -> (Int, Int)? { - guard let _ = link else { - return nil - } - return controlChannel.currentDataCount() - } - - /** - Shuts down the session with an optional `Error` reason. Does nothing if the session is already stopped or about to stop. - - - Parameter error: An optional `Error` being the reason of the shutdown. - */ - public func shutdown(error: Error?) { - guard !isStopping else { - log.warning("Ignore stop request, already stopping!") - return - } - deferStop(.shutdown, error) - } - - /** - Shuts down the session with an optional `Error` reason and signals a reconnect flag to `SessionProxyDelegate.sessionDidStop(...)`. Does nothing if the session is already stopped or about to stop. - - - Parameter error: An optional `Error` being the reason of the shutdown. - - Seealso: `SessionProxyDelegate.sessionDidStop(...)` - */ - public func reconnect(error: Error?) { - guard !isStopping else { - log.warning("Ignore stop request, already stopping!") - return - } - deferStop(.reconnect, error) - } - - // Ruby: cleanup - /** - Cleans up the session resources. - */ - public func cleanup() { - log.info("Cleaning up...") - - if let observer = tlsObserver { - NotificationCenter.default.removeObserver(observer) - tlsObserver = nil - } - - keys.removeAll() - oldKeys.removeAll() - negotiationKeyIdx = 0 - currentKeyIdx = nil - - nextPushRequestDate = nil - connectedDate = nil - authenticator = nil - continuatedPushReplyMessage = nil - pushReply = nil - link = nil - if !(tunnel?.isPersistent ?? false) { - tunnel = nil - } - - isStopping = false - stopError = nil - } - - // MARK: Loop - - // Ruby: start - private func start() { - loopLink() - hardReset() - } - - private func loopNegotiation() { - guard let link = link else { - return - } - guard !keys.isEmpty else { - return - } - - guard !negotiationKey.didHardResetTimeOut(link: link) else { - doReconnect(error: SessionError.negotiationTimeout) - return - } - guard !negotiationKey.didNegotiationTimeOut(link: link) else { - doShutdown(error: SessionError.negotiationTimeout) - return - } + /// Provides methods to set up and maintain an OpenVPN session. + public class SessionProxy { + private enum StopMethod { + case shutdown - pushRequest() - if !isReliableLink { + case reconnect + } + + private struct Caches { + static let ca = "ca.pem" + + static let clientCertificate = "cert.pem" + + static let clientKey = "key.pem" + } + + // MARK: Configuration + + /// The session base configuration. + public let configuration: Configuration + + /// The optional credentials. + public var credentials: Credentials? + + private var keepAliveInterval: TimeInterval? { + let interval: TimeInterval? + if let negInterval = pushReply?.options.keepAliveInterval, negInterval > 0 { + interval = TimeInterval(negInterval) + } else if let cfgInterval = configuration.keepAliveInterval, cfgInterval > 0.0 { + interval = cfgInterval + } else { + return nil + } + return interval + } + + /// An optional `SessionProxyDelegate` for receiving session events. + public weak var delegate: SessionProxyDelegate? + + // MARK: State + + private let queue: DispatchQueue + + private var tlsObserver: NSObjectProtocol? + + private var withLocalOptions: Bool + + private var keys: [UInt8: SessionKey] + + private var oldKeys: [SessionKey] + + private var negotiationKeyIdx: UInt8 + + private var currentKeyIdx: UInt8? + + private var negotiationKey: SessionKey { + guard let key = keys[negotiationKeyIdx] else { + fatalError("Keys are empty or index \(negotiationKeyIdx) not found in \(keys.keys)") + } + return key + } + + private var currentKey: SessionKey? { + guard let i = currentKeyIdx else { + return nil + } + return keys[i] + } + + private var link: LinkInterface? + + private var tunnel: TunnelInterface? + + private var isReliableLink: Bool { + return link?.isReliable ?? false + } + + private var continuatedPushReplyMessage: String? + + private var pushReply: SessionReply? + + private var nextPushRequestDate: Date? + + private var connectedDate: Date? + + private var lastPing: BidirectionalState + + private var isStopping: Bool + + /// The optional reason why the session stopped. + public private(set) var stopError: Error? + + // MARK: Control + + private var controlChannel: ControlChannel + + private var authenticator: Authenticator? + + // MARK: Caching + + private let cachesURL: URL + + private var caURL: URL { + return cachesURL.appendingPathComponent(Caches.ca) + } + + private var clientCertificateURL: URL { + return cachesURL.appendingPathComponent(Caches.clientCertificate) + } + + private var clientKeyURL: URL { + return cachesURL.appendingPathComponent(Caches.clientKey) + } + + // MARK: Init + + /** + Creates a VPN session. + + - Parameter queue: The `DispatchQueue` where to run the session loop. + - Parameter configuration: The `Configuration` to use for this session. + */ + public init(queue: DispatchQueue, configuration: Configuration, cachesURL: URL) throws { + guard let ca = configuration.ca else { + throw ConfigurationError.missingConfiguration(option: "ca") + } + + self.queue = queue + self.configuration = configuration + self.cachesURL = cachesURL + + withLocalOptions = true + keys = [:] + oldKeys = [] + negotiationKeyIdx = 0 + lastPing = BidirectionalState(withResetValue: Date.distantPast) + isStopping = false + + if let tlsWrap = configuration.tlsWrap { + switch tlsWrap.strategy { + case .auth: + controlChannel = try ControlChannel(withAuthKey: tlsWrap.key, digest: configuration.fallbackDigest) + + case .crypt: + controlChannel = try ControlChannel(withCryptKey: tlsWrap.key) + } + } else { + controlChannel = ControlChannel() + } + + // cache PEMs locally (mandatory for OpenSSL) + let fm = FileManager.default + try ca.pem.write(to: caURL, atomically: true, encoding: .ascii) + if let container = configuration.clientCertificate { + try container.pem.write(to: clientCertificateURL, atomically: true, encoding: .ascii) + } else { + try? fm.removeItem(at: clientCertificateURL) + } + if let container = configuration.clientKey { + try container.pem.write(to: clientKeyURL, atomically: true, encoding: .ascii) + } else { + try? fm.removeItem(at: clientKeyURL) + } + } + + /// :nodoc: + deinit { + cleanup() + + let fm = FileManager.default + for url in [caURL, clientCertificateURL, clientKeyURL] { + try? fm.removeItem(at: url) + } + } + + // MARK: Public interface + + /** + Establishes the link interface for this session. The interface must be up and running for sending and receiving packets. + + - Precondition: `link` is an active network interface. + - Postcondition: The VPN negotiation is started. + - Parameter link: The `LinkInterface` on which to establish the VPN session. + */ + public func setLink(_ link: LinkInterface) { + guard (self.link == nil) else { + log.warning("Link interface already set!") + return + } + + log.debug("Starting VPN session") + + // WARNING: runs in notification source queue (we know it's "queue", but better be safe than sorry) + tlsObserver = NotificationCenter.default.addObserver(forName: .TLSBoxPeerVerificationError, object: nil, queue: nil) { (notification) in + let error = notification.userInfo?[TunnelKitErrorKey] as? Error + self.queue.async { + self.deferStop(.shutdown, error) + } + } + + self.link = link + start() + } + + /** + Returns `true` if the current session can rebind to a new link with `rebindLink(...)`. + + - Returns: `true` if supports link rebinding. + */ + public func canRebindLink() -> Bool { + // return (pushReply?.peerId != nil) + + // FIXME: floating is currently unreliable + return false + } + + /** + Rebinds the session to a new link if supported. + + - Precondition: `link` is an active network interface. + - Postcondition: The VPN session is active. + - Parameter link: The `LinkInterface` on which to establish the VPN session. + - Seealso: `canRebindLink()`. + */ + public func rebindLink(_ link: LinkInterface) { + guard let _ = pushReply?.options.peerId else { + log.warning("Session doesn't support link rebinding!") + return + } + + isStopping = false + stopError = nil + + log.debug("Rebinding VPN session to a new link") + self.link = link + loopLink() + } + + /** + Establishes the tunnel interface for this session. The interface must be up and running for sending and receiving packets. + + - Precondition: `tunnel` is an active network interface. + - Postcondition: The VPN data channel is open. + - Parameter tunnel: The `TunnelInterface` on which to exchange the VPN data traffic. + */ + public func setTunnel(tunnel: TunnelInterface) { + guard (self.tunnel == nil) else { + log.warning("Tunnel interface already set!") + return + } + self.tunnel = tunnel + loopTunnel() + } + + /** + Returns the current data bytes count. + + - Returns: The current data bytes count as a pair, inbound first. + */ + public func dataCount() -> (Int, Int)? { + guard let _ = link else { + return nil + } + return controlChannel.currentDataCount() + } + + /** + Shuts down the session with an optional `Error` reason. Does nothing if the session is already stopped or about to stop. + + - Parameter error: An optional `Error` being the reason of the shutdown. + */ + public func shutdown(error: Error?) { + guard !isStopping else { + log.warning("Ignore stop request, already stopping!") + return + } + deferStop(.shutdown, error) + } + + /** + Shuts down the session with an optional `Error` reason and signals a reconnect flag to `SessionProxyDelegate.sessionDidStop(...)`. Does nothing if the session is already stopped or about to stop. + + - Parameter error: An optional `Error` being the reason of the shutdown. + - Seealso: `SessionProxyDelegate.sessionDidStop(...)` + */ + public func reconnect(error: Error?) { + guard !isStopping else { + log.warning("Ignore stop request, already stopping!") + return + } + deferStop(.reconnect, error) + } + + // Ruby: cleanup + /** + Cleans up the session resources. + */ + public func cleanup() { + log.info("Cleaning up...") + + if let observer = tlsObserver { + NotificationCenter.default.removeObserver(observer) + tlsObserver = nil + } + + keys.removeAll() + oldKeys.removeAll() + negotiationKeyIdx = 0 + currentKeyIdx = nil + + nextPushRequestDate = nil + connectedDate = nil + authenticator = nil + continuatedPushReplyMessage = nil + pushReply = nil + link = nil + if !(tunnel?.isPersistent ?? false) { + tunnel = nil + } + + isStopping = false + stopError = nil + } + + // MARK: Loop + + // Ruby: start + private func start() { + loopLink() + hardReset() + } + + private func loopNegotiation() { + guard let link = link else { + return + } + guard !keys.isEmpty else { + return + } + + guard !negotiationKey.didHardResetTimeOut(link: link) else { + doReconnect(error: SessionError.negotiationTimeout) + return + } + guard !negotiationKey.didNegotiationTimeOut(link: link) else { + doShutdown(error: SessionError.negotiationTimeout) + return + } + + pushRequest() + if !isReliableLink { + flushControlQueue() + } + + guard negotiationKey.controlState == .connected else { + queue.asyncAfter(deadline: .now() + CoreConfiguration.OpenVPN.tickInterval) { [weak self] in + self?.loopNegotiation() + } + return + } + + // let loop die when negotiation is complete + } + + // Ruby: udp_loop + private func loopLink() { + let loopedLink = link + loopedLink?.setReadHandler(queue: queue) { [weak self] (newPackets, error) in + guard loopedLink === self?.link else { + log.warning("Ignoring read from outdated LINK") + return + } + if let error = error { + log.error("Failed LINK read: \(error)") + return + } + + if let packets = newPackets, !packets.isEmpty { + self?.maybeRenegotiate() + + // log.verbose("Received \(packets.count) packets from LINK") + self?.receiveLink(packets: packets) + } + } + } + + // Ruby: tun_loop + private func loopTunnel() { + tunnel?.setReadHandler(queue: queue) { [weak self] (newPackets, error) in + if let error = error { + log.error("Failed TUN read: \(error)") + return + } + + if let packets = newPackets, !packets.isEmpty { + // log.verbose("Received \(packets.count) packets from \(self.tunnelName)") + self?.receiveTunnel(packets: packets) + } + } + } + + // Ruby: recv_link + private func receiveLink(packets: [Data]) { + guard shouldHandlePackets() else { + return + } + + lastPing.inbound = Date() + + var dataPacketsByKey = [UInt8: [Data]]() + + for packet in packets { + // log.verbose("Received data from LINK (\(packet.count) bytes): \(packet.toHex())") + + guard let firstByte = packet.first else { + log.warning("Dropped malformed packet (missing opcode)") + continue + } + let codeValue = firstByte >> 3 + guard let code = PacketCode(rawValue: codeValue) else { + log.warning("Dropped malformed packet (unknown code: \(codeValue))") + continue + } + // log.verbose("Parsed packet with code \(code)") + + var offset = 1 + if (code == .dataV2) { + guard packet.count >= offset + PacketPeerIdLength else { + log.warning("Dropped malformed packet (missing peerId)") + continue + } + offset += PacketPeerIdLength + } + + if (code == .dataV1) || (code == .dataV2) { + let key = firstByte & 0b111 + guard let _ = keys[key] else { + log.error("Key with id \(key) not found") + deferStop(.shutdown, SessionError.badKey) + return + } + + // XXX: improve with array reference + var dataPackets = dataPacketsByKey[key] ?? [Data]() + dataPackets.append(packet) + dataPacketsByKey[key] = dataPackets + + continue + } + + let controlPacket: ControlPacket + do { + let parsedPacket = try controlChannel.readInboundPacket(withData: packet, offset: 0) + handleAcks() + if parsedPacket.code == .ackV1 { + continue + } + controlPacket = parsedPacket + } catch let e { + log.warning("Dropped malformed packet: \(e)") + continue + // deferStop(.shutdown, e) + // return + } + if (code == .hardResetServerV2) && (negotiationKey.controlState == .connected) { + deferStop(.shutdown, SessionError.staleSession) + return + } else if (code == .softResetV1) && !negotiationKey.softReset { + softReset(isServerInitiated: true) + } + + sendAck(for: controlPacket) + + let pendingInboundQueue = controlChannel.enqueueInboundPacket(packet: controlPacket) + for inboundPacket in pendingInboundQueue { + handleControlPacket(inboundPacket) + } + } + + // send decrypted packets to tunnel all at once + for (keyId, dataPackets) in dataPacketsByKey { + guard let sessionKey = keys[keyId] else { + log.warning("Accounted a data packet for which the cryptographic key hadn't been found") + continue + } + handleDataPackets(dataPackets, key: sessionKey) + } + } + + // Ruby: recv_tun + private func receiveTunnel(packets: [Data]) { + guard shouldHandlePackets() else { + return + } + sendDataPackets(packets) + lastPing.outbound = Date() + } + + // Ruby: ping + private func ping() { + guard (currentKey?.controlState == .connected) else { + return + } + + let now = Date() + guard (now.timeIntervalSince(lastPing.inbound) <= CoreConfiguration.OpenVPN.pingTimeout) else { + deferStop(.shutdown, SessionError.pingTimeout) + return + } + + // postpone ping if elapsed less than keep-alive + if let interval = keepAliveInterval { + let elapsed = now.timeIntervalSince(lastPing.outbound) + guard (elapsed >= interval) else { + scheduleNextPing(elapsed: elapsed) + return + } + } + + log.debug("Send ping") + sendDataPackets([DataPacket.pingString]) + lastPing.outbound = Date() + + scheduleNextPing() + } + + private func scheduleNextPing(elapsed: TimeInterval = 0.0) { + guard let interval = keepAliveInterval else { + return + } + let remaining = min(interval, interval - elapsed) + queue.asyncAfter(deadline: .now() + remaining) { [weak self] in + self?.ping() + } + } + + // MARK: Handshake + + // Ruby: reset_ctrl + private func resetControlChannel(forNewSession: Bool) { + authenticator = nil + do { + try controlChannel.reset(forNewSession: forNewSession) + } catch let e { + deferStop(.shutdown, e) + } + } + + // Ruby: hard_reset + private func hardReset() { + log.debug("Send hard reset") + + resetControlChannel(forNewSession: true) + continuatedPushReplyMessage = nil + pushReply = nil + negotiationKeyIdx = 0 + let newKey = SessionKey(id: UInt8(negotiationKeyIdx)) + keys[negotiationKeyIdx] = newKey + log.debug("Negotiation key index is \(negotiationKeyIdx)") + + let payload = hardResetPayload() ?? Data() + negotiationKey.state = .hardReset + guard !keys.isEmpty else { + fatalError("Main loop must follow hard reset, keys are empty!") + } + loopNegotiation() + enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload) + } + + private func hardResetPayload() -> Data? { + guard !(configuration.usesPIAPatches ?? false) else { + let caMD5: String + do { + caMD5 = try TLSBox.md5(forCertificatePath: caURL.path) + } catch { + log.error("CA MD5 could not be computed, skipping custom HARD_RESET") + return nil + } + log.debug("CA MD5 is: \(caMD5)") + return try? PIAHardReset( + caMd5Digest: caMD5, + cipher: configuration.fallbackCipher, + digest: configuration.fallbackDigest + ).encodedData() + } + return nil + } + + // Ruby: soft_reset + private func softReset(isServerInitiated: Bool) { + if isServerInitiated { + log.debug("Handle soft reset") + } else { + log.debug("Send soft reset") + } + + resetControlChannel(forNewSession: false) + negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % ProtocolMacros.numberOfKeys) + let newKey = SessionKey(id: UInt8(negotiationKeyIdx)) + keys[negotiationKeyIdx] = newKey + log.debug("Negotiation key index is \(negotiationKeyIdx)") + + negotiationKey.state = .softReset + negotiationKey.softReset = true + loopNegotiation() + if !isServerInitiated { + enqueueControlPackets(code: .softResetV1, key: UInt8(negotiationKeyIdx), payload: Data()) + } + } + + // Ruby: on_tls_connect + private func onTLSConnect() { + log.debug("TLS.connect: Handshake is complete") + + negotiationKey.controlState = .preAuth + + do { + authenticator = try Authenticator(credentials?.username, pushReply?.options.authToken ?? credentials?.password) + authenticator?.withLocalOptions = withLocalOptions + try authenticator?.putAuth(into: negotiationKey.tls, options: configuration) + } catch let e { + deferStop(.shutdown, e) + return + } + + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.auth: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } + log.verbose("TLS.auth: Still can't pull ciphertext") + return + } + + log.debug("TLS.auth: Pulled ciphertext (\(cipherTextOut.count) bytes)") + enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) + } + + // Ruby: push_request + private func pushRequest() { + guard negotiationKey.controlState == .preIfConfig else { + return + } + guard let targetDate = nextPushRequestDate, Date() > targetDate else { + return + } + + log.debug("TLS.ifconfig: Put plaintext (PUSH_REQUEST)") + try? negotiationKey.tls.putPlainText("PUSH_REQUEST\0") + + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.auth: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } + log.verbose("TLS.ifconfig: Still can't pull ciphertext") + return + } + + log.debug("TLS.ifconfig: Send pulled ciphertext (\(cipherTextOut.count) bytes)") + enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) + + if negotiationKey.softReset { + completeConnection() + } + nextPushRequestDate = Date().addingTimeInterval(CoreConfiguration.OpenVPN.pushRequestInterval) + } + + private func maybeRenegotiate() { + guard let renegotiatesAfter = configuration.renegotiatesAfter, renegotiatesAfter > 0 else { + return + } + guard (negotiationKeyIdx == currentKeyIdx) else { + return + } + + let elapsed = -negotiationKey.startTime.timeIntervalSinceNow + if (elapsed > renegotiatesAfter) { + log.debug("Renegotiating after \(elapsed) seconds") + softReset(isServerInitiated: false) + } + } + + private func completeConnection() { + setupEncryption() + authenticator = nil + negotiationKey.controlState = .connected + connectedDate = Date() + transitionKeys() + } + + // MARK: Control + + // Ruby: handle_ctrl_pkt + private func handleControlPacket(_ packet: ControlPacket) { + guard packet.key == negotiationKey.id else { + log.error("Bad key in control packet (\(packet.key) != \(negotiationKey.id))") + // deferStop(.shutdown, SessionError.badKey) + return + } + + // start new TLS handshake + if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) || + ((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) { + + if negotiationKey.state == .hardReset { + controlChannel.remoteSessionId = packet.sessionId + } + guard let remoteSessionId = controlChannel.remoteSessionId else { + log.error("No remote sessionId (never set)") + deferStop(.shutdown, SessionError.missingSessionId) + return + } + guard packet.sessionId == remoteSessionId else { + log.error("Packet session mismatch (\(packet.sessionId.toHex()) != \(remoteSessionId.toHex()))") + deferStop(.shutdown, SessionError.sessionMismatch) + return + } + + negotiationKey.state = .tls + + log.debug("Start TLS handshake") + + let tls = TLSBox( + caPath: caURL.path, + clientCertificatePath: (configuration.clientCertificate != nil) ? clientCertificateURL.path : nil, + clientKeyPath: (configuration.clientKey != nil) ? clientKeyURL.path : nil, + checksEKU: configuration.checksEKU ?? false + ) + if let tlsSecurityLevel = configuration.tlsSecurityLevel { + tls.securityLevel = tlsSecurityLevel + } + negotiationKey.tlsOptional = tls + do { + try negotiationKey.tls.start() + } catch let e { + deferStop(.shutdown, e) + return + } + + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.connect: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } + deferStop(.shutdown, e) + return + } + + log.debug("TLS.connect: Pulled ciphertext (\(cipherTextOut.count) bytes)") + enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) + } + // exchange TLS ciphertext + else if ((packet.code == .controlV1) && (negotiationKey.state == .tls)) { + guard let remoteSessionId = controlChannel.remoteSessionId else { + log.error("No remote sessionId found in packet (control packets before server HARD_RESET)") + deferStop(.shutdown, SessionError.missingSessionId) + return + } + guard packet.sessionId == remoteSessionId else { + log.error("Packet session mismatch (\(packet.sessionId.toHex()) != \(remoteSessionId.toHex()))") + deferStop(.shutdown, SessionError.sessionMismatch) + return + } + + guard let cipherTextIn = packet.payload else { + log.warning("TLS.connect: Control packet with empty payload?") + return + } + + log.debug("TLS.connect: Put received ciphertext (\(cipherTextIn.count) bytes)") + try? negotiationKey.tls.putCipherText(cipherTextIn) + + let cipherTextOut: Data + do { + cipherTextOut = try negotiationKey.tls.pullCipherText() + log.debug("TLS.connect: Send pulled ciphertext (\(cipherTextOut.count) bytes)") + enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) + } catch let e { + if let _ = e.tunnelKitErrorCode() { + log.error("TLS.connect: Failed pulling ciphertext (error: \(e))") + shutdown(error: e) + return + } + log.verbose("TLS.connect: No available ciphertext to pull") + } + + if negotiationKey.shouldOnTLSConnect() { + onTLSConnect() + } + + do { + while true { + let controlData = try controlChannel.currentControlData(withTLS: negotiationKey.tls) + handleControlData(controlData) + } + } catch _ { + } + } + } + + // Ruby: handle_ctrl_data + private func handleControlData(_ data: ZeroingData) { + guard let auth = authenticator else { + return + } + + if CoreConfiguration.logsSensitiveData { + log.debug("Pulled plain control data (\(data.count) bytes): \(data.toHex())") + } else { + log.debug("Pulled plain control data (\(data.count) bytes)") + } + + auth.appendControlData(data) + + if (negotiationKey.controlState == .preAuth) { + do { + guard try auth.parseAuthReply() else { + return + } + } catch let e { + deferStop(.shutdown, e) + return + } + + negotiationKey.controlState = .preIfConfig + nextPushRequestDate = Date().addingTimeInterval(negotiationKey.softReset ? CoreConfiguration.OpenVPN.softResetDelay : CoreConfiguration.OpenVPN.retransmissionLimit) + pushRequest() + } + + for message in auth.parseMessages() { + if CoreConfiguration.logsSensitiveData { + log.debug("Parsed control message (\(message.count) bytes): \"\(message)\"") + } else { + log.debug("Parsed control message (\(message.count) bytes)") + } + handleControlMessage(message) + } + } + + // Ruby: handle_ctrl_msg + private func handleControlMessage(_ message: String) { + guard !message.hasPrefix("AUTH_FAILED") else { + + // XXX: retry without client options + if authenticator?.withLocalOptions ?? false { + log.warning("Authentication failure, retrying without local options") + withLocalOptions = false + deferStop(.reconnect, SessionError.badCredentials) + return + } + + deferStop(.shutdown, SessionError.badCredentials) + return + } + + guard (negotiationKey.controlState == .preIfConfig) else { + return + } + + if CoreConfiguration.logsSensitiveData { + log.debug("Received control message: \"\(message)\"") + } + + let completeMessage: String + if let continuated = continuatedPushReplyMessage { + completeMessage = "\(continuated),\(message)" + } else { + completeMessage = message + } + let reply: PushReply + do { + guard let optionalReply = try PushReply(message: completeMessage) else { + return + } + reply = optionalReply + log.debug("Received PUSH_REPLY: \"\(reply.maskedDescription)\"") + + if let framing = reply.options.compressionFraming, let compression = reply.options.compressionAlgorithm { + switch compression { + case .disabled: + break + + case .LZO: + if !LZOIsSupported() { + log.error("Server has LZO compression enabled and this was not built into the library (framing=\(framing))") + throw SessionError.serverCompression + } + + case .other: + log.error("Server has non-LZO compression enabled and this is currently unsupported (framing=\(framing))") + throw SessionError.serverCompression + } + } + } catch SessionError.continuationPushReply { + continuatedPushReplyMessage = completeMessage.replacingOccurrences(of: "push-continuation", with: "") + // FIXME: strip "PUSH_REPLY" and "push-continuation 2" + return + } catch let e { + deferStop(.shutdown, e) + return + } + + pushReply = reply + guard reply.options.ipv4 != nil || reply.options.ipv6 != nil else { + deferStop(.shutdown, SessionError.noRouting) + return + } + + completeConnection() + + guard let remoteAddress = link?.remoteAddress else { + fatalError("Could not resolve link remote address") + } + delegate?.sessionDidStart(self, remoteAddress: remoteAddress, reply: reply) + + scheduleNextPing() + } + + // Ruby: transition_keys + private func transitionKeys() { + if let key = currentKey { + oldKeys.append(key) + } + currentKeyIdx = negotiationKeyIdx + cleanKeys() + } + + // Ruby: clean_keys + private func cleanKeys() { + while (oldKeys.count > 1) { + let key = oldKeys.removeFirst() + keys.removeValue(forKey: key.id) + } + } + + // Ruby: q_ctrl + private func enqueueControlPackets(code: PacketCode, key: UInt8, payload: Data) { + guard let link = link else { + log.warning("Not writing to LINK, interface is down") + return + } + + controlChannel.enqueueOutboundPackets(withCode: code, key: key, payload: payload, maxPacketSize: link.mtu) flushControlQueue() } - guard negotiationKey.controlState == .connected else { - queue.asyncAfter(deadline: .now() + CoreConfiguration.OpenVPN.tickInterval) { [weak self] in - self?.loopNegotiation() - } - return - } - - // let loop die when negotiation is complete - } - - // Ruby: udp_loop - private func loopLink() { - let loopedLink = link - loopedLink?.setReadHandler(queue: queue) { [weak self] (newPackets, error) in - guard loopedLink === self?.link else { - log.warning("Ignoring read from outdated LINK") - return - } - if let error = error { - log.error("Failed LINK read: \(error)") - return - } - - if let packets = newPackets, !packets.isEmpty { - self?.maybeRenegotiate() - -// log.verbose("Received \(packets.count) packets from LINK") - self?.receiveLink(packets: packets) - } - } - } - - // Ruby: tun_loop - private func loopTunnel() { - tunnel?.setReadHandler(queue: queue) { [weak self] (newPackets, error) in - if let error = error { - log.error("Failed TUN read: \(error)") - return - } - - if let packets = newPackets, !packets.isEmpty { -// log.verbose("Received \(packets.count) packets from \(self.tunnelName)") - self?.receiveTunnel(packets: packets) - } - } - } - - // Ruby: recv_link - private func receiveLink(packets: [Data]) { - guard shouldHandlePackets() else { - return - } - - lastPing.inbound = Date() - - var dataPacketsByKey = [UInt8: [Data]]() - - for packet in packets { -// log.verbose("Received data from LINK (\(packet.count) bytes): \(packet.toHex())") - - guard let firstByte = packet.first else { - log.warning("Dropped malformed packet (missing opcode)") - continue - } - let codeValue = firstByte >> 3 - guard let code = PacketCode(rawValue: codeValue) else { - log.warning("Dropped malformed packet (unknown code: \(codeValue))") - continue - } -// log.verbose("Parsed packet with code \(code)") - - var offset = 1 - if (code == .dataV2) { - guard packet.count >= offset + PacketPeerIdLength else { - log.warning("Dropped malformed packet (missing peerId)") - continue - } - offset += PacketPeerIdLength - } - - if (code == .dataV1) || (code == .dataV2) { - let key = firstByte & 0b111 - guard let _ = keys[key] else { - log.error("Key with id \(key) not found") - deferStop(.shutdown, SessionError.badKey) - return - } - - // XXX: improve with array reference - var dataPackets = dataPacketsByKey[key] ?? [Data]() - dataPackets.append(packet) - dataPacketsByKey[key] = dataPackets - - continue - } - - let controlPacket: ControlPacket + // Ruby: flush_ctrl_q_out + private func flushControlQueue() { + let rawList: [Data] do { - let parsedPacket = try controlChannel.readInboundPacket(withData: packet, offset: 0) - handleAcks() - if parsedPacket.code == .ackV1 { - continue - } - controlPacket = parsedPacket - } catch let e { - log.warning("Dropped malformed packet: \(e)") - continue -// deferStop(.shutdown, e) -// return - } - if (code == .hardResetServerV2) && (negotiationKey.controlState == .connected) { - deferStop(.shutdown, SessionError.staleSession) - return - } else if (code == .softResetV1) && !negotiationKey.softReset { - softReset(isServerInitiated: true) - } - - sendAck(for: controlPacket) - - let pendingInboundQueue = controlChannel.enqueueInboundPacket(packet: controlPacket) - for inboundPacket in pendingInboundQueue { - handleControlPacket(inboundPacket) - } - } - - // send decrypted packets to tunnel all at once - for (keyId, dataPackets) in dataPacketsByKey { - guard let sessionKey = keys[keyId] else { - log.warning("Accounted a data packet for which the cryptographic key hadn't been found") - continue - } - handleDataPackets(dataPackets, key: sessionKey) - } - } - - // Ruby: recv_tun - private func receiveTunnel(packets: [Data]) { - guard shouldHandlePackets() else { - return - } - sendDataPackets(packets) - lastPing.outbound = Date() - } - - // Ruby: ping - private func ping() { - guard (currentKey?.controlState == .connected) else { - return - } - - let now = Date() - guard (now.timeIntervalSince(lastPing.inbound) <= CoreConfiguration.OpenVPN.pingTimeout) else { - deferStop(.shutdown, SessionError.pingTimeout) - return - } - - // postpone ping if elapsed less than keep-alive - if let interval = keepAliveInterval { - let elapsed = now.timeIntervalSince(lastPing.outbound) - guard (elapsed >= interval) else { - scheduleNextPing(elapsed: elapsed) - return - } - } - - log.debug("Send ping") - sendDataPackets([DataPacket.pingString]) - lastPing.outbound = Date() - - scheduleNextPing() - } - - private func scheduleNextPing(elapsed: TimeInterval = 0.0) { - guard let interval = keepAliveInterval else { - return - } - let remaining = min(interval, interval - elapsed) - queue.asyncAfter(deadline: .now() + remaining) { [weak self] in - self?.ping() - } - } - - // MARK: Handshake - - // Ruby: reset_ctrl - private func resetControlChannel(forNewSession: Bool) { - authenticator = nil - do { - try controlChannel.reset(forNewSession: forNewSession) - } catch let e { - deferStop(.shutdown, e) - } - } - - // Ruby: hard_reset - private func hardReset() { - log.debug("Send hard reset") - - resetControlChannel(forNewSession: true) - continuatedPushReplyMessage = nil - pushReply = nil - negotiationKeyIdx = 0 - let newKey = SessionKey(id: UInt8(negotiationKeyIdx)) - keys[negotiationKeyIdx] = newKey - log.debug("Negotiation key index is \(negotiationKeyIdx)") - - let payload = hardResetPayload() ?? Data() - negotiationKey.state = .hardReset - guard !keys.isEmpty else { - fatalError("Main loop must follow hard reset, keys are empty!") - } - loopNegotiation() - enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload) - } - - private func hardResetPayload() -> Data? { - guard !(configuration.usesPIAPatches ?? false) else { - let caMD5: String - do { - caMD5 = try TLSBox.md5(forCertificatePath: caURL.path) - } catch { - log.error("CA MD5 could not be computed, skipping custom HARD_RESET") - return nil - } - log.debug("CA MD5 is: \(caMD5)") - return try? PIAHardReset( - caMd5Digest: caMD5, - cipher: configuration.fallbackCipher, - digest: configuration.fallbackDigest - ).encodedData() - } - return nil - } - - // Ruby: soft_reset - private func softReset(isServerInitiated: Bool) { - if isServerInitiated { - log.debug("Handle soft reset") - } else { - log.debug("Send soft reset") - } - - resetControlChannel(forNewSession: false) - negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % ProtocolMacros.numberOfKeys) - let newKey = SessionKey(id: UInt8(negotiationKeyIdx)) - keys[negotiationKeyIdx] = newKey - log.debug("Negotiation key index is \(negotiationKeyIdx)") - - negotiationKey.state = .softReset - negotiationKey.softReset = true - loopNegotiation() - if !isServerInitiated { - enqueueControlPackets(code: .softResetV1, key: UInt8(negotiationKeyIdx), payload: Data()) - } - } - - // Ruby: on_tls_connect - private func onTLSConnect() { - log.debug("TLS.connect: Handshake is complete") - - negotiationKey.controlState = .preAuth - - do { - authenticator = try Authenticator(credentials?.username, pushReply?.options.authToken ?? credentials?.password) - authenticator?.withLocalOptions = withLocalOptions - try authenticator?.putAuth(into: negotiationKey.tls, options: configuration) - } catch let e { - deferStop(.shutdown, e) - return - } - - let cipherTextOut: Data - do { - cipherTextOut = try negotiationKey.tls.pullCipherText() - } catch let e { - if let _ = e.tunnelKitErrorCode() { - log.error("TLS.auth: Failed pulling ciphertext (error: \(e))") - shutdown(error: e) - return - } - log.verbose("TLS.auth: Still can't pull ciphertext") - return - } - - log.debug("TLS.auth: Pulled ciphertext (\(cipherTextOut.count) bytes)") - enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) - } - - // Ruby: push_request - private func pushRequest() { - guard negotiationKey.controlState == .preIfConfig else { - return - } - guard let targetDate = nextPushRequestDate, Date() > targetDate else { - return - } - - log.debug("TLS.ifconfig: Put plaintext (PUSH_REQUEST)") - try? negotiationKey.tls.putPlainText("PUSH_REQUEST\0") - - let cipherTextOut: Data - do { - cipherTextOut = try negotiationKey.tls.pullCipherText() - } catch let e { - if let _ = e.tunnelKitErrorCode() { - log.error("TLS.auth: Failed pulling ciphertext (error: \(e))") - shutdown(error: e) - return - } - log.verbose("TLS.ifconfig: Still can't pull ciphertext") - return - } - - log.debug("TLS.ifconfig: Send pulled ciphertext (\(cipherTextOut.count) bytes)") - enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) - - if negotiationKey.softReset { - completeConnection() - } - nextPushRequestDate = Date().addingTimeInterval(CoreConfiguration.OpenVPN.pushRequestInterval) - } - - private func maybeRenegotiate() { - guard let renegotiatesAfter = configuration.renegotiatesAfter, renegotiatesAfter > 0 else { - return - } - guard (negotiationKeyIdx == currentKeyIdx) else { - return - } - - let elapsed = -negotiationKey.startTime.timeIntervalSinceNow - if (elapsed > renegotiatesAfter) { - log.debug("Renegotiating after \(elapsed) seconds") - softReset(isServerInitiated: false) - } - } - - private func completeConnection() { - setupEncryption() - authenticator = nil - negotiationKey.controlState = .connected - connectedDate = Date() - transitionKeys() - } - - // MARK: Control - - // Ruby: handle_ctrl_pkt - private func handleControlPacket(_ packet: ControlPacket) { - guard packet.key == negotiationKey.id else { - log.error("Bad key in control packet (\(packet.key) != \(negotiationKey.id))") -// deferStop(.shutdown, SessionError.badKey) - return - } - - // start new TLS handshake - if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) || - ((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) { - - if negotiationKey.state == .hardReset { - controlChannel.remoteSessionId = packet.sessionId - } - guard let remoteSessionId = controlChannel.remoteSessionId else { - log.error("No remote sessionId (never set)") - deferStop(.shutdown, SessionError.missingSessionId) - return - } - guard packet.sessionId == remoteSessionId else { - log.error("Packet session mismatch (\(packet.sessionId.toHex()) != \(remoteSessionId.toHex()))") - deferStop(.shutdown, SessionError.sessionMismatch) - return - } - - negotiationKey.state = .tls - - log.debug("Start TLS handshake") - - let tls = TLSBox( - caPath: caURL.path, - clientCertificatePath: (configuration.clientCertificate != nil) ? clientCertificateURL.path : nil, - clientKeyPath: (configuration.clientKey != nil) ? clientKeyURL.path : nil, - checksEKU: configuration.checksEKU ?? false - ) - if let tlsSecurityLevel = configuration.tlsSecurityLevel { - tls.securityLevel = tlsSecurityLevel - } - negotiationKey.tlsOptional = tls - do { - try negotiationKey.tls.start() + rawList = try controlChannel.writeOutboundPackets() } catch let e { + log.warning("Failed control packet serialization: \(e)") deferStop(.shutdown, e) return } - - let cipherTextOut: Data - do { - cipherTextOut = try negotiationKey.tls.pullCipherText() - } catch let e { - if let _ = e.tunnelKitErrorCode() { - log.error("TLS.connect: Failed pulling ciphertext (error: \(e))") - shutdown(error: e) - return - } - deferStop(.shutdown, e) - return - } - - log.debug("TLS.connect: Pulled ciphertext (\(cipherTextOut.count) bytes)") - enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) - } - // exchange TLS ciphertext - else if ((packet.code == .controlV1) && (negotiationKey.state == .tls)) { - guard let remoteSessionId = controlChannel.remoteSessionId else { - log.error("No remote sessionId found in packet (control packets before server HARD_RESET)") - deferStop(.shutdown, SessionError.missingSessionId) - return - } - guard packet.sessionId == remoteSessionId else { - log.error("Packet session mismatch (\(packet.sessionId.toHex()) != \(remoteSessionId.toHex()))") - deferStop(.shutdown, SessionError.sessionMismatch) - return - } - - guard let cipherTextIn = packet.payload else { - log.warning("TLS.connect: Control packet with empty payload?") - return - } - - log.debug("TLS.connect: Put received ciphertext (\(cipherTextIn.count) bytes)") - try? negotiationKey.tls.putCipherText(cipherTextIn) - - let cipherTextOut: Data - do { - cipherTextOut = try negotiationKey.tls.pullCipherText() - log.debug("TLS.connect: Send pulled ciphertext (\(cipherTextOut.count) bytes)") - enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) - } catch let e { - if let _ = e.tunnelKitErrorCode() { - log.error("TLS.connect: Failed pulling ciphertext (error: \(e))") - shutdown(error: e) - return - } - log.verbose("TLS.connect: No available ciphertext to pull") - } - - if negotiationKey.shouldOnTLSConnect() { - onTLSConnect() - } - - do { - while true { - let controlData = try controlChannel.currentControlData(withTLS: negotiationKey.tls) - handleControlData(controlData) - } - } catch _ { - } - } - } - - // Ruby: handle_ctrl_data - private func handleControlData(_ data: ZeroingData) { - guard let auth = authenticator else { - return - } - - if CoreConfiguration.logsSensitiveData { - log.debug("Pulled plain control data (\(data.count) bytes): \(data.toHex())") - } else { - log.debug("Pulled plain control data (\(data.count) bytes)") - } - - auth.appendControlData(data) - - if (negotiationKey.controlState == .preAuth) { - do { - guard try auth.parseAuthReply() else { - return - } - } catch let e { - deferStop(.shutdown, e) - return - } - - negotiationKey.controlState = .preIfConfig - nextPushRequestDate = Date().addingTimeInterval(negotiationKey.softReset ? CoreConfiguration.OpenVPN.softResetDelay : CoreConfiguration.OpenVPN.retransmissionLimit) - pushRequest() - } - - for message in auth.parseMessages() { - if CoreConfiguration.logsSensitiveData { - log.debug("Parsed control message (\(message.count) bytes): \"\(message)\"") - } else { - log.debug("Parsed control message (\(message.count) bytes)") - } - handleControlMessage(message) - } - } - - // Ruby: handle_ctrl_msg - private func handleControlMessage(_ message: String) { - guard !message.hasPrefix("AUTH_FAILED") else { - - // XXX: retry without client options - if authenticator?.withLocalOptions ?? false { - log.warning("Authentication failure, retrying without local options") - withLocalOptions = false - deferStop(.reconnect, SessionError.badCredentials) - return - } - - deferStop(.shutdown, SessionError.badCredentials) - return - } - - guard (negotiationKey.controlState == .preIfConfig) else { - return - } - - if CoreConfiguration.logsSensitiveData { - log.debug("Received control message: \"\(message)\"") - } - - let completeMessage: String - if let continuated = continuatedPushReplyMessage { - completeMessage = "\(continuated),\(message)" - } else { - completeMessage = message - } - let reply: PushReply - do { - guard let optionalReply = try PushReply(message: completeMessage) else { - return - } - reply = optionalReply - log.debug("Received PUSH_REPLY: \"\(reply.maskedDescription)\"") - - if let framing = reply.options.compressionFraming, let compression = reply.options.compressionAlgorithm { - switch compression { - case .disabled: - break - - case .LZO: - if !LZOIsSupported() { - log.error("Server has LZO compression enabled and this was not built into the library (framing=\(framing))") - throw SessionError.serverCompression - } - - case .other: - log.error("Server has non-LZO compression enabled and this is currently unsupported (framing=\(framing))") - throw SessionError.serverCompression - } - } - } catch SessionError.continuationPushReply { - continuatedPushReplyMessage = completeMessage.replacingOccurrences(of: "push-continuation", with: "") - // FIXME: strip "PUSH_REPLY" and "push-continuation 2" - return - } catch let e { - deferStop(.shutdown, e) - return - } - - pushReply = reply - guard reply.options.ipv4 != nil || reply.options.ipv6 != nil else { - deferStop(.shutdown, SessionError.noRouting) - return - } - - completeConnection() - - guard let remoteAddress = link?.remoteAddress else { - fatalError("Could not resolve link remote address") - } - delegate?.sessionDidStart(self, remoteAddress: remoteAddress, reply: reply) - - scheduleNextPing() - } - - // Ruby: transition_keys - private func transitionKeys() { - if let key = currentKey { - oldKeys.append(key) - } - currentKeyIdx = negotiationKeyIdx - cleanKeys() - } - - // Ruby: clean_keys - private func cleanKeys() { - while (oldKeys.count > 1) { - let key = oldKeys.removeFirst() - keys.removeValue(forKey: key.id) - } - } - - // Ruby: q_ctrl - private func enqueueControlPackets(code: PacketCode, key: UInt8, payload: Data) { - guard let link = link else { - log.warning("Not writing to LINK, interface is down") - return - } - - controlChannel.enqueueOutboundPackets(withCode: code, key: key, payload: payload, maxPacketSize: link.mtu) - flushControlQueue() - } - - // Ruby: flush_ctrl_q_out - private func flushControlQueue() { - let rawList: [Data] - do { - rawList = try controlChannel.writeOutboundPackets() - } catch let e { - log.warning("Failed control packet serialization: \(e)") - deferStop(.shutdown, e) - return - } - for raw in rawList { - log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())") - } - - // WARNING: runs in Network.framework queue - link?.writePackets(rawList) { [weak self] (error) in - if let error = error { - self?.queue.sync { - log.error("Failed LINK write during control flush: \(error)") - self?.deferStop(.shutdown, SessionError.failedLinkWrite) - } - return - } - } - } - - // Ruby: setup_keys - private func setupEncryption() { - guard let auth = authenticator else { - fatalError("Setting up encryption without having authenticated") - } - guard let sessionId = controlChannel.sessionId else { - fatalError("Setting up encryption without a local sessionId") - } - guard let remoteSessionId = controlChannel.remoteSessionId else { - fatalError("Setting up encryption without a remote sessionId") - } - guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else { - fatalError("Setting up encryption without server randoms") - } - guard let pushReply = pushReply else { - fatalError("Setting up encryption without a former PUSH_REPLY") - } - - if CoreConfiguration.logsSensitiveData { - log.debug("Set up encryption from the following components:") - log.debug("\tpreMaster: \(auth.preMaster.toHex())") - log.debug("\trandom1: \(auth.random1.toHex())") - log.debug("\trandom2: \(auth.random2.toHex())") - log.debug("\tserverRandom1: \(serverRandom1.toHex())") - log.debug("\tserverRandom2: \(serverRandom2.toHex())") - log.debug("\tsessionId: \(sessionId.toHex())") - log.debug("\tremoteSessionId: \(remoteSessionId.toHex())") - } else { - log.debug("Set up encryption") - } - - let pushedCipher = pushReply.options.cipher - if let negCipher = pushedCipher { - log.info("\tNegotiated cipher: \(negCipher.rawValue)") - } - let pushedFraming = pushReply.options.compressionFraming - if let negFraming = pushedFraming { - log.info("\tNegotiated compression framing: \(negFraming)") - } - let pushedCompression = pushReply.options.compressionAlgorithm - if let negCompression = pushedCompression { - log.info("\tNegotiated compression algorithm: \(negCompression)") - } - if let negPing = pushReply.options.keepAliveInterval { - log.info("\tNegotiated keep-alive: \(negPing) seconds") - } - - let bridge: EncryptionBridge - do { - bridge = try EncryptionBridge( - pushedCipher ?? configuration.fallbackCipher, - configuration.fallbackDigest, - auth, - sessionId, - remoteSessionId - ) - } catch let e { - deferStop(.shutdown, e) - return - } - - negotiationKey.dataPath = DataPath( - encrypter: bridge.encrypter(), - decrypter: bridge.decrypter(), - peerId: pushReply.options.peerId ?? PacketPeerIdDisabled, - compressionFraming: (pushedFraming ?? configuration.fallbackCompressionFraming).native, - compressionAlgorithm: (pushedCompression ?? configuration.compressionAlgorithm ?? .disabled).native, - maxPackets: link?.packetBufferSize ?? 200, - usesReplayProtection: CoreConfiguration.OpenVPN.usesReplayProtection - ) - } - - // MARK: Data - - // Ruby: handle_data_pkt - private func handleDataPackets(_ packets: [Data], key: SessionKey) { - controlChannel.addReceivedDataCount(packets.flatCount) - do { - guard let decryptedPackets = try key.decrypt(packets: packets) else { - log.warning("Could not decrypt packets, is SessionKey properly configured (dataPath, peerId)?") - return - } - guard !decryptedPackets.isEmpty else { - return - } - - tunnel?.writePackets(decryptedPackets, completionHandler: nil) - } catch let e { - guard !e.isTunnelKitError() else { - deferStop(.shutdown, e) - return - } - deferStop(.reconnect, e) - } - } - - // Ruby: send_data_pkt - private func sendDataPackets(_ packets: [Data]) { - guard let key = currentKey else { - return - } - do { - guard let encryptedPackets = try key.encrypt(packets: packets) else { - log.warning("Could not encrypt packets, is SessionKey properly configured (dataPath, peerId)?") - return - } - guard !encryptedPackets.isEmpty else { - return + for raw in rawList { + log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())") } // WARNING: runs in Network.framework queue - controlChannel.addSentDataCount(encryptedPackets.flatCount) - link?.writePackets(encryptedPackets) { [weak self] (error) in + link?.writePackets(rawList) { [weak self] (error) in if let error = error { - - // try mitigating "No buffer space available" - if let posixError = error as? POSIXError, posixError.code == POSIXErrorCode.ENOBUFS { - log.warning("Data: Packets dropped, no buffer space available") - return - } - self?.queue.sync { - log.error("Data: Failed LINK write during send data: \(error)") + log.error("Failed LINK write during control flush: \(error)") self?.deferStop(.shutdown, SessionError.failedLinkWrite) } return } -// log.verbose("Data: \(encryptedPackets.count) packets successfully written to LINK") } - } catch let e { - guard !e.isTunnelKitError() else { + } + + // Ruby: setup_keys + private func setupEncryption() { + guard let auth = authenticator else { + fatalError("Setting up encryption without having authenticated") + } + guard let sessionId = controlChannel.sessionId else { + fatalError("Setting up encryption without a local sessionId") + } + guard let remoteSessionId = controlChannel.remoteSessionId else { + fatalError("Setting up encryption without a remote sessionId") + } + guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else { + fatalError("Setting up encryption without server randoms") + } + guard let pushReply = pushReply else { + fatalError("Setting up encryption without a former PUSH_REPLY") + } + + if CoreConfiguration.logsSensitiveData { + log.debug("Set up encryption from the following components:") + log.debug("\tpreMaster: \(auth.preMaster.toHex())") + log.debug("\trandom1: \(auth.random1.toHex())") + log.debug("\trandom2: \(auth.random2.toHex())") + log.debug("\tserverRandom1: \(serverRandom1.toHex())") + log.debug("\tserverRandom2: \(serverRandom2.toHex())") + log.debug("\tsessionId: \(sessionId.toHex())") + log.debug("\tremoteSessionId: \(remoteSessionId.toHex())") + } else { + log.debug("Set up encryption") + } + + let pushedCipher = pushReply.options.cipher + if let negCipher = pushedCipher { + log.info("\tNegotiated cipher: \(negCipher.rawValue)") + } + let pushedFraming = pushReply.options.compressionFraming + if let negFraming = pushedFraming { + log.info("\tNegotiated compression framing: \(negFraming)") + } + let pushedCompression = pushReply.options.compressionAlgorithm + if let negCompression = pushedCompression { + log.info("\tNegotiated compression algorithm: \(negCompression)") + } + if let negPing = pushReply.options.keepAliveInterval { + log.info("\tNegotiated keep-alive: \(negPing) seconds") + } + + let bridge: EncryptionBridge + do { + bridge = try EncryptionBridge( + pushedCipher ?? configuration.fallbackCipher, + configuration.fallbackDigest, + auth, + sessionId, + remoteSessionId + ) + } catch let e { deferStop(.shutdown, e) return } - deferStop(.reconnect, e) - } - } - - // MARK: Acks - - private func handleAcks() { - } - - // Ruby: send_ack - private func sendAck(for controlPacket: ControlPacket) { - log.debug("Send ack for received packetId \(controlPacket.packetId)") - let raw: Data - do { - raw = try controlChannel.writeAcks( - withKey: controlPacket.key, - ackPacketIds: [controlPacket.packetId], - ackRemoteSessionId: controlPacket.sessionId + negotiationKey.dataPath = DataPath( + encrypter: bridge.encrypter(), + decrypter: bridge.decrypter(), + peerId: pushReply.options.peerId ?? PacketPeerIdDisabled, + compressionFraming: (pushedFraming ?? configuration.fallbackCompressionFraming).native, + compressionAlgorithm: (pushedCompression ?? configuration.compressionAlgorithm ?? .disabled).native, + maxPackets: link?.packetBufferSize ?? 200, + usesReplayProtection: CoreConfiguration.OpenVPN.usesReplayProtection ) - } catch let e { - deferStop(.shutdown, e) - return } - // WARNING: runs in Network.framework queue - link?.writePacket(raw) { [weak self] (error) in - if let error = error { - self?.queue.sync { - log.error("Failed LINK write during send ack for packetId \(controlPacket.packetId): \(error)") - self?.deferStop(.shutdown, SessionError.failedLinkWrite) - } - return - } - log.debug("Ack successfully written to LINK for packetId \(controlPacket.packetId)") - } - } - - // MARK: Stop - - private func shouldHandlePackets() -> Bool { - return (!isStopping && !keys.isEmpty) - } - - private func deferStop(_ method: StopMethod, _ error: Error?) { - isStopping = true + // MARK: Data - let completion = { [weak self] in - switch method { - case .shutdown: - self?.doShutdown(error: error) - - case .reconnect: - self?.doReconnect(error: error) - } - } - - // shut down after sending exit notification if socket is unreliable (normally UDP) - if let link = link, !link.isReliable { + // Ruby: handle_data_pkt + private func handleDataPackets(_ packets: [Data], key: SessionKey) { + controlChannel.addReceivedDataCount(packets.flatCount) do { - guard let packets = try currentKey?.encrypt(packets: [OCCPacket.exit.serialized()]) else { - completion() + guard let decryptedPackets = try key.decrypt(packets: packets) else { + log.warning("Could not decrypt packets, is SessionKey properly configured (dataPath, peerId)?") return } - link.writePackets(packets) { [weak self] (error) in - self?.queue.sync { - completion() - } + guard !decryptedPackets.isEmpty else { + return } - } catch { + + tunnel?.writePackets(decryptedPackets, completionHandler: nil) + } catch let e { + guard !e.isTunnelKitError() else { + deferStop(.shutdown, e) + return + } + deferStop(.reconnect, e) + } + } + + // Ruby: send_data_pkt + private func sendDataPackets(_ packets: [Data]) { + guard let key = currentKey else { + return + } + do { + guard let encryptedPackets = try key.encrypt(packets: packets) else { + log.warning("Could not encrypt packets, is SessionKey properly configured (dataPath, peerId)?") + return + } + guard !encryptedPackets.isEmpty else { + return + } + + // WARNING: runs in Network.framework queue + controlChannel.addSentDataCount(encryptedPackets.flatCount) + link?.writePackets(encryptedPackets) { [weak self] (error) in + if let error = error { + + // try mitigating "No buffer space available" + if let posixError = error as? POSIXError, posixError.code == POSIXErrorCode.ENOBUFS { + log.warning("Data: Packets dropped, no buffer space available") + return + } + + self?.queue.sync { + log.error("Data: Failed LINK write during send data: \(error)") + self?.deferStop(.shutdown, SessionError.failedLinkWrite) + } + return + } + // log.verbose("Data: \(encryptedPackets.count) packets successfully written to LINK") + } + } catch let e { + guard !e.isTunnelKitError() else { + deferStop(.shutdown, e) + return + } + deferStop(.reconnect, e) + } + } + + // MARK: Acks + + private func handleAcks() { + } + + // Ruby: send_ack + private func sendAck(for controlPacket: ControlPacket) { + log.debug("Send ack for received packetId \(controlPacket.packetId)") + + let raw: Data + do { + raw = try controlChannel.writeAcks( + withKey: controlPacket.key, + ackPacketIds: [controlPacket.packetId], + ackRemoteSessionId: controlPacket.sessionId + ) + } catch let e { + deferStop(.shutdown, e) + return + } + + // WARNING: runs in Network.framework queue + link?.writePacket(raw) { [weak self] (error) in + if let error = error { + self?.queue.sync { + log.error("Failed LINK write during send ack for packetId \(controlPacket.packetId): \(error)") + self?.deferStop(.shutdown, SessionError.failedLinkWrite) + } + return + } + log.debug("Ack successfully written to LINK for packetId \(controlPacket.packetId)") + } + } + + // MARK: Stop + + private func shouldHandlePackets() -> Bool { + return (!isStopping && !keys.isEmpty) + } + + private func deferStop(_ method: StopMethod, _ error: Error?) { + isStopping = true + + let completion = { [weak self] in + switch method { + case .shutdown: + self?.doShutdown(error: error) + + case .reconnect: + self?.doReconnect(error: error) + } + } + + // shut down after sending exit notification if socket is unreliable (normally UDP) + if let link = link, !link.isReliable { + do { + guard let packets = try currentKey?.encrypt(packets: [OCCPacket.exit.serialized()]) else { + completion() + return + } + link.writePackets(packets) { [weak self] (error) in + self?.queue.sync { + completion() + } + } + } catch { + completion() + } + } else { completion() } - } else { - completion() } - } - - private func doShutdown(error: Error?) { - if let error = error { - log.error("Trigger shutdown (error: \(error))") - } else { - log.info("Trigger shutdown on request") + + private func doShutdown(error: Error?) { + if let error = error { + log.error("Trigger shutdown (error: \(error))") + } else { + log.info("Trigger shutdown on request") + } + stopError = error + delegate?.sessionDidStop(self, shouldReconnect: false) } - stopError = error - delegate?.sessionDidStop(self, shouldReconnect: false) - } - - private func doReconnect(error: Error?) { - if let error = error { - log.error("Trigger reconnection (error: \(error))") - } else { - log.info("Trigger reconnection on request") + + private func doReconnect(error: Error?) { + if let error = error { + log.error("Trigger reconnection (error: \(error))") + } else { + log.info("Trigger reconnection on request") + } + stopError = error + delegate?.sessionDidStop(self, shouldReconnect: true) } - stopError = error - delegate?.sessionDidStop(self, shouldReconnect: true) } } diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+SessionReply.swift b/TunnelKit/Sources/OpenVPN/SessionReply.swift similarity index 95% rename from TunnelKit/Sources/OpenVPN/SessionProxy+SessionReply.swift rename to TunnelKit/Sources/OpenVPN/SessionReply.swift index 9772e61..c2e51f3 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+SessionReply.swift +++ b/TunnelKit/Sources/OpenVPN/SessionReply.swift @@ -1,5 +1,5 @@ // -// SessionProxy+SessionReply.swift +// SessionReply.swift // TunnelKit // // Created by Davide De Rosa on 7/25/18. @@ -40,16 +40,16 @@ import Foundation public protocol SessionReply { /// The returned options. - var options: SessionProxy.Configuration { get } + var options: OpenVPN.Configuration { get } } -extension SessionProxy { +extension OpenVPN { struct PushReply: SessionReply, CustomStringConvertible { private static let prefix = "PUSH_REPLY," private let original: String - let options: SessionProxy.Configuration + let options: Configuration init?(message: String) throws { guard message.hasPrefix(PushReply.prefix) else { diff --git a/TunnelKit/Sources/OpenVPN/StaticKey.swift b/TunnelKit/Sources/OpenVPN/StaticKey.swift index 25ef60a..f1f8aa8 100644 --- a/TunnelKit/Sources/OpenVPN/StaticKey.swift +++ b/TunnelKit/Sources/OpenVPN/StaticKey.swift @@ -26,207 +26,210 @@ import Foundation import __TunnelKitCore -/// Represents an OpenVPN static key file (as generated with --genkey) -public class StaticKey: Codable { - enum CodingKeys: CodingKey { - case data +extension OpenVPN { + + /// Represents an OpenVPN static key file (as generated with --genkey) + public class StaticKey: Codable { + enum CodingKeys: CodingKey { + case data + + case dir + } + + /// The key-direction field, usually 0 on servers and 1 on clients. + public enum Direction: Int, Codable { + + /// Conventional server direction (implicit for tls-crypt). + case server = 0 + + /// Conventional client direction (implicit for tls-crypt). + case client = 1 + } - case dir - } - - /// The key-direction field, usually 0 on servers and 1 on clients. - public enum Direction: Int, Codable { - - /// Conventional server direction (implicit for tls-crypt). - case server = 0 + private static let contentLength = 256 // 2048-bit - /// Conventional client direction (implicit for tls-crypt). - case client = 1 - } - - private static let contentLength = 256 // 2048-bit - - private static let keyCount = 4 - - private static let keyLength = StaticKey.contentLength / StaticKey.keyCount + private static let keyCount = 4 + + private static let keyLength = StaticKey.contentLength / StaticKey.keyCount - private static let fileHead = "-----BEGIN OpenVPN Static key V1-----" - - private static let fileFoot = "-----END OpenVPN Static key V1-----" - - private static let nonHexCharset = CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted - - private let secureData: ZeroingData + private static let fileHead = "-----BEGIN OpenVPN Static key V1-----" + + private static let fileFoot = "-----END OpenVPN Static key V1-----" + + private static let nonHexCharset = CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted + + private let secureData: ZeroingData - let direction: Direction? - - /// Returns the encryption key. - /// - /// - Precondition: `direction` must be non-nil. - /// - Seealso: `SessionProxy.ConfigurationBuilder.tlsWrap` - public var cipherEncryptKey: ZeroingData { - guard let direction = direction else { - preconditionFailure() + let direction: Direction? + + /// Returns the encryption key. + /// + /// - Precondition: `direction` must be non-nil. + /// - Seealso: `ConfigurationBuilder.tlsWrap` + public var cipherEncryptKey: ZeroingData { + guard let direction = direction else { + preconditionFailure() + } + switch direction { + case .server: + return key(at: 0) + + case .client: + return key(at: 2) + } } - switch direction { - case .server: - return key(at: 0) - - case .client: - return key(at: 2) - } - } - /// Returns the decryption key. - /// - /// - Precondition: `direction` must be non-nil. - /// - Seealso: `SessionProxy.ConfigurationBuilder.tlsWrap` - public var cipherDecryptKey: ZeroingData { - guard let direction = direction else { - preconditionFailure() + /// Returns the decryption key. + /// + /// - Precondition: `direction` must be non-nil. + /// - Seealso: `ConfigurationBuilder.tlsWrap` + public var cipherDecryptKey: ZeroingData { + guard let direction = direction else { + preconditionFailure() + } + switch direction { + case .server: + return key(at: 2) + + case .client: + return key(at: 0) + } } - switch direction { - case .server: - return key(at: 2) - - case .client: - return key(at: 0) + + /// Returns the HMAC sending key. + /// + /// - Seealso: `ConfigurationBuilder.tlsWrap` + public var hmacSendKey: ZeroingData { + guard let direction = direction else { + return key(at: 1) + } + switch direction { + case .server: + return key(at: 1) + + case .client: + return key(at: 3) + } } - } - - /// Returns the HMAC sending key. - /// - /// - Seealso: `SessionProxy.ConfigurationBuilder.tlsWrap` - public var hmacSendKey: ZeroingData { - guard let direction = direction else { - return key(at: 1) + + /// Returns the HMAC receiving key. + /// + /// - Seealso: `ConfigurationBuilder.tlsWrap` + public var hmacReceiveKey: ZeroingData { + guard let direction = direction else { + return key(at: 1) + } + switch direction { + case .server: + return key(at: 3) + + case .client: + return key(at: 1) + } } - switch direction { - case .server: - return key(at: 1) - - case .client: - return key(at: 3) + + /** + Initializes with data and direction. + + - Parameter data: The key data. + - Parameter direction: The key direction, or bidirectional if nil. For tls-crypt behavior, must not be nil. + */ + public init(data: Data, direction: Direction?) { + precondition(data.count == StaticKey.contentLength) + secureData = Z(data) + self.direction = direction } - } - - /// Returns the HMAC receiving key. - /// - /// - Seealso: `SessionProxy.ConfigurationBuilder.tlsWrap` - public var hmacReceiveKey: ZeroingData { - guard let direction = direction else { - return key(at: 1) + + /** + Initializes with file content and direction. + + - Parameter file: The text file containing the key. + - Parameter direction: The key direction, or bidirectional if nil. + */ + public convenience init?(file: String, direction: Direction?) { + let lines = file.split(separator: "\n") + self.init(lines: lines, direction: direction) } - switch direction { - case .server: - return key(at: 3) - - case .client: - return key(at: 1) - } - } - - /** - Initializes with data and direction. - - - Parameter data: The key data. - - Parameter direction: The key direction, or bidirectional if nil. For tls-crypt behavior, must not be nil. - */ - public init(data: Data, direction: Direction?) { - precondition(data.count == StaticKey.contentLength) - secureData = Z(data) - self.direction = direction - } - - /** - Initializes with file content and direction. - - - Parameter file: The text file containing the key. - - Parameter direction: The key direction, or bidirectional if nil. - */ - public convenience init?(file: String, direction: Direction?) { - let lines = file.split(separator: "\n") - self.init(lines: lines, direction: direction) - } - - /// :nodoc: - public convenience init?(lines: [Substring], direction: Direction?) { - var isHead = true - var hexLines: [Substring] = [] + + /// :nodoc: + public convenience init?(lines: [Substring], direction: Direction?) { + var isHead = true + var hexLines: [Substring] = [] - for l in lines { - if isHead { - guard !l.hasPrefix("#") else { + for l in lines { + if isHead { + guard !l.hasPrefix("#") else { + continue + } + guard l == StaticKey.fileHead else { + return nil + } + isHead = false continue } - guard l == StaticKey.fileHead else { + guard let first = l.first else { return nil } - isHead = false - continue + if first == "-" { + guard l == StaticKey.fileFoot else { + return nil + } + break + } + hexLines.append(l) } - guard let first = l.first else { + + let hex = String(hexLines.joined()) + guard hex.count == 2 * StaticKey.contentLength else { return nil } - if first == "-" { - guard l == StaticKey.fileFoot else { - return nil - } - break + if let _ = hex.rangeOfCharacter(from: StaticKey.nonHexCharset) { + return nil } - hexLines.append(l) + let data = Data(hex: hex) + + self.init(data: data, direction: direction) } - - let hex = String(hexLines.joined()) - guard hex.count == 2 * StaticKey.contentLength else { - return nil - } - if let _ = hex.rangeOfCharacter(from: StaticKey.nonHexCharset) { - return nil - } - let data = Data(hex: hex) - self.init(data: data, direction: direction) - } - - /** - Initializes as bidirectional. - - - Parameter biData: The key data. - */ - public convenience init(biData data: Data) { - self.init(data: data, direction: nil) - } - - private func key(at: Int) -> ZeroingData { - let size = secureData.count / StaticKey.keyCount // 64 bytes each - assert(size == StaticKey.keyLength) - return secureData.withOffset(at * size, count: size) - } - - /// :nodoc: - public static func deserialized(_ data: Data) throws -> StaticKey { - return try JSONDecoder().decode(StaticKey.self, from: data) - } - - /// :nodoc: - public func serialized() -> Data? { - return try? JSONEncoder().encode(self) - } - - // MARK: Codable - - /// :nodoc: - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - secureData = Z(try container.decode(Data.self, forKey: .data)) - direction = try container.decodeIfPresent(Direction.self, forKey: .dir) - } - - /// :nodoc: - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(secureData.toData(), forKey: .data) - try container.encodeIfPresent(direction, forKey: .dir) + /** + Initializes as bidirectional. + + - Parameter biData: The key data. + */ + public convenience init(biData data: Data) { + self.init(data: data, direction: nil) + } + + private func key(at: Int) -> ZeroingData { + let size = secureData.count / StaticKey.keyCount // 64 bytes each + assert(size == StaticKey.keyLength) + return secureData.withOffset(at * size, count: size) + } + + /// :nodoc: + public static func deserialized(_ data: Data) throws -> StaticKey { + return try JSONDecoder().decode(StaticKey.self, from: data) + } + + /// :nodoc: + public func serialized() -> Data? { + return try? JSONEncoder().encode(self) + } + + // MARK: Codable + + /// :nodoc: + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + secureData = Z(try container.decode(Data.self, forKey: .data)) + direction = try container.decodeIfPresent(Direction.self, forKey: .dir) + } + + /// :nodoc: + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(secureData.toData(), forKey: .data) + try container.encodeIfPresent(direction, forKey: .dir) + } } } diff --git a/TunnelKit/Sources/OpenVPN/SessionProxy+TLSWrap.swift b/TunnelKit/Sources/OpenVPN/TLSWrap.swift similarity index 91% rename from TunnelKit/Sources/OpenVPN/SessionProxy+TLSWrap.swift rename to TunnelKit/Sources/OpenVPN/TLSWrap.swift index 955e2e4..7c75880 100644 --- a/TunnelKit/Sources/OpenVPN/SessionProxy+TLSWrap.swift +++ b/TunnelKit/Sources/OpenVPN/TLSWrap.swift @@ -1,5 +1,5 @@ // -// SessionProxy+TLSWrap.swift +// TLSWrap.swift // TunnelKit // // Created by Davide De Rosa on 9/11/18. @@ -25,7 +25,7 @@ import Foundation -extension SessionProxy { +extension OpenVPN { /// Holds parameters for TLS wrapping. public class TLSWrap: Codable { @@ -53,8 +53,8 @@ extension SessionProxy { } /// :nodoc: - public static func deserialized(_ data: Data) throws -> SessionProxy.TLSWrap { - return try JSONDecoder().decode(SessionProxy.TLSWrap.self, from: data) + public static func deserialized(_ data: Data) throws -> TLSWrap { + return try JSONDecoder().decode(TLSWrap.self, from: data) } /// :nodoc: diff --git a/TunnelKitTests/AppExtensionTests.swift b/TunnelKitTests/AppExtensionTests.swift index 78fc9f2..ddebf22 100644 --- a/TunnelKitTests/AppExtensionTests.swift +++ b/TunnelKitTests/AppExtensionTests.swift @@ -57,9 +57,9 @@ class AppExtensionTests: XCTestCase { let identifier = "com.example.Provider" let appGroup = "group.com.algoritmico.TunnelKit" let hostname = "example.com" - let credentials = SessionProxy.Credentials("foo", "bar") + let credentials = Credentials("foo", "bar") - var sessionBuilder = SessionProxy.ConfigurationBuilder() + var sessionBuilder = ConfigurationBuilder() sessionBuilder.ca = CryptoContainer(pem: "abcdef") sessionBuilder.cipher = .aes128cbc sessionBuilder.digest = .sha256 diff --git a/TunnelKitTests/ConfigurationParserTests.swift b/TunnelKitTests/ConfigurationParserTests.swift index a33a67d..e7e1b3d 100644 --- a/TunnelKitTests/ConfigurationParserTests.swift +++ b/TunnelKitTests/ConfigurationParserTests.swift @@ -40,14 +40,13 @@ class ConfigurationParserTests: XCTestCase { // from lines func testCompression() throws { -// XCTAssertNotNil(try OptionsBundle.parsed(fromLines: ["comp-lzo"]).warning) - XCTAssertNil(try ConfigurationParser.parsed(fromLines: ["comp-lzo"]).warning) - XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["comp-lzo no"])) - XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["comp-lzo yes"])) -// XCTAssertThrowsError(try ConfigurationParser.parsed(fromLines: ["comp-lzo yes"])) + XCTAssertNil(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo"]).warning) + XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo no"])) + XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo yes"])) +// XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo yes"])) - XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["compress"])) - XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["compress lzo"])) + XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["compress"])) + XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["compress lzo"])) } func testDHCPOption() throws { @@ -72,7 +71,7 @@ class ConfigurationParserTests: XCTestCase { } func testRedirectGateway() throws { - var parsed: SessionProxy.Configuration + var parsed: Configuration parsed = try! ConfigurationParser.parsed(fromLines: []).configuration XCTAssertEqual(parsed.routingPolicies, nil) diff --git a/TunnelKitTests/ControlChannelTests.swift b/TunnelKitTests/ControlChannelTests.swift index ff067a7..e24a258 100644 --- a/TunnelKitTests/ControlChannelTests.swift +++ b/TunnelKitTests/ControlChannelTests.swift @@ -50,7 +50,7 @@ class ControlChannelTests: XCTestCase { // 00000000 // message packet_id (HARD_RESET -> UInt32(0)) func testHMAC() { let key = StaticKey(biData: Data(hex: hex)) - let server = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: SessionProxy.Digest.sha1.rawValue) + let server = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: Digest.sha1.rawValue) XCTAssertNoThrow(try server.configure(withCipherEncKey: nil, cipherDecKey: nil, hmacEncKey: key.hmacReceiveKey, hmacDecKey: key.hmacSendKey)) // let original = Data(hex: "38858fe14742fdae40e67c9137933a412a711c0d0514aca6db6476d17d000000015b96c9470000000000") diff --git a/TunnelKitTests/DataPathPerformanceTests.swift b/TunnelKitTests/DataPathPerformanceTests.swift index 5cef12f..23b08c8 100644 --- a/TunnelKitTests/DataPathPerformanceTests.swift +++ b/TunnelKitTests/DataPathPerformanceTests.swift @@ -49,7 +49,7 @@ class DataPathPerformanceTests: XCTestCase { let ck = try! SecureRandom.safeData(length: 32) let hk = try! SecureRandom.safeData(length: 32) - let crypto = try! SessionProxy.EncryptionBridge(.aes128cbc, .sha1, ck, ck, hk, hk) + let crypto = try! EncryptionBridge(.aes128cbc, .sha1, ck, ck, hk, hk) encrypter = crypto.encrypter() decrypter = crypto.decrypter() diff --git a/TunnelKitTests/PushTests.swift b/TunnelKitTests/PushTests.swift index 5f675fe..5376fc1 100644 --- a/TunnelKitTests/PushTests.swift +++ b/TunnelKitTests/PushTests.swift @@ -48,7 +48,7 @@ class PushTests: XCTestCase { func testNet30() { let msg = "PUSH_REPLY,redirect-gateway def1,dhcp-option DNS 209.222.18.222,dhcp-option DNS 209.222.18.218,ping 10,comp-lzo no,route 10.5.10.1,topology net30,ifconfig 10.5.10.6 10.5.10.5,auth-token AUkQf/b3nj3L+CH4RJPP0Vuq8/gpntr7uPqzjQhncig=" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() XCTAssertEqual(reply.options.ipv4?.address, "10.5.10.6") @@ -59,7 +59,7 @@ class PushTests: XCTestCase { func testSubnet() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2") @@ -70,7 +70,7 @@ class PushTests: XCTestCase { func testRoute() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,route 192.168.0.0 255.255.255.0 10.8.0.12,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() let route = reply.options.ipv4!.routes.first! @@ -82,7 +82,7 @@ class PushTests: XCTestCase { func testIPv6() { let msg = "PUSH_REPLY,dhcp-option DNS6 2001:4860:4860::8888,dhcp-option DNS6 2001:4860:4860::8844,tun-ipv6,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig-ipv6 fe80::601:30ff:feb7:ec01/64 fe80::601:30ff:feb7:dc02,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2") @@ -96,7 +96,7 @@ class PushTests: XCTestCase { func testCompressionFraming() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() XCTAssertEqual(reply.options.compressionFraming, .compLZO) @@ -106,27 +106,27 @@ class PushTests: XCTestCase { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC" var reply: SessionReply - reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo no"))! + reply = try! PushReply(message: msg.appending(",comp-lzo no"))! reply.debug() XCTAssertEqual(reply.options.compressionFraming, .compLZO) XCTAssertEqual(reply.options.compressionAlgorithm, .disabled) - reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo"))! + reply = try! PushReply(message: msg.appending(",comp-lzo"))! reply.debug() XCTAssertEqual(reply.options.compressionFraming, .compLZO) XCTAssertEqual(reply.options.compressionAlgorithm, .LZO) - reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo yes"))! + reply = try! PushReply(message: msg.appending(",comp-lzo yes"))! reply.debug() XCTAssertEqual(reply.options.compressionFraming, .compLZO) XCTAssertEqual(reply.options.compressionAlgorithm, .LZO) - reply = try! SessionProxy.PushReply(message: msg.appending(",compress"))! + reply = try! PushReply(message: msg.appending(",compress"))! reply.debug() XCTAssertEqual(reply.options.compressionFraming, .compress) XCTAssertEqual(reply.options.compressionAlgorithm, .disabled) - reply = try! SessionProxy.PushReply(message: msg.appending(",compress lz4"))! + reply = try! PushReply(message: msg.appending(",compress lz4"))! reply.debug() XCTAssertEqual(reply.options.compressionFraming, .compress) XCTAssertEqual(reply.options.compressionAlgorithm, .other) @@ -134,7 +134,7 @@ class PushTests: XCTestCase { func testNCP() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() XCTAssertEqual(reply.options.cipher, .aes256gcm) @@ -142,7 +142,7 @@ class PushTests: XCTestCase { func testNCPTrailing() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.18 10.8.0.17,peer-id 3,cipher AES-256-GCM,auth-token" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() XCTAssertEqual(reply.options.cipher, .aes256gcm) @@ -150,7 +150,7 @@ class PushTests: XCTestCase { func testPing() { let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13" - let reply = try! SessionProxy.PushReply(message: msg)! + let reply = try! PushReply(message: msg)! reply.debug() XCTAssertEqual(reply.options.keepAliveInterval, 10) @@ -158,7 +158,7 @@ class PushTests: XCTestCase { func testProvost() { let msg = "PUSH_REPLY,route 87.233.192.218,route 87.233.192.219,route 87.233.192.220,route 87.248.186.252,route 92.241.171.245,route 103.246.200.0 255.255.252.0,route 109.239.140.0 255.255.255.0,route 128.199.0.0 255.255.0.0,route 13.125.0.0 255.255.0.0,route 13.230.0.0 255.254.0.0,route 13.56.0.0 255.252.0.0,route 149.154.160.0 255.255.252.0,route 149.154.164.0 255.255.252.0,route 149.154.168.0 255.255.252.0,route 149.154.172.0 255.255.252.0,route 159.122.128.0 255.255.192.0,route 159.203.0.0 255.255.0.0,route 159.65.0.0 255.255.0.0,route 159.89.0.0 255.255.0.0,route 165.227.0.0 255.255.0.0,route 167.99.0.0 255.255.0.0,route 174.138.0.0 255.255.128.0,route 176.67.169.0 255.255.255.0,route 178.239.88.0 255.255.248.0,route 178.63.0.0 255.255.0.0,route 18.130.0.0 255.255.0.0,route 18.144.0.0 255.255.0.0,route 18.184.0.0 255.254.0.0,route 18.194.0.0 255.254.0.0,route 18.196.0.0 255.254.0.0,route 18.204.0.0 255.252.0.0,push-continuation 2" - let reply = try? SessionProxy.PushReply(message: msg)! + let reply = try? PushReply(message: msg)! reply?.debug() } }