Browse Source

Merge pull request #375 from Psiphon-Labs/ios-beta

Merge ios-beta into master
Adam Pritchard 8 years ago
parent
commit
4a80be17f9

+ 18 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel.xcodeproj/project.pbxproj

@@ -18,6 +18,8 @@
 		6685BDD41E2EBB1000F0E414 /* GoPsi.objc.h in Headers */ = {isa = PBXBuildFile; fileRef = 6685BDD21E2EBB1000F0E414 /* GoPsi.objc.h */; };
 		6685BDD51E2EBB1000F0E414 /* Universe.objc.h in Headers */ = {isa = PBXBuildFile; fileRef = 6685BDD31E2EBB1000F0E414 /* Universe.objc.h */; };
 		6685BDD91E300AC200F0E414 /* strip-frameworks.sh in Resources */ = {isa = PBXBuildFile; fileRef = 6685BDD81E300AC200F0E414 /* strip-frameworks.sh */; };
+		66BAD3351E525FBC00CD06DE /* JailbreakCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BAD3331E525FBC00CD06DE /* JailbreakCheck.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		66BAD3361E525FBC00CD06DE /* JailbreakCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BAD3341E525FBC00CD06DE /* JailbreakCheck.m */; };
 		66BDB02A1DA6BFCC0079384C /* PsiphonTunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66BDB0201DA6BFCC0079384C /* PsiphonTunnel.framework */; };
 		66BDB02F1DA6BFCC0079384C /* PsiphonTunnelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BDB02E1DA6BFCC0079384C /* PsiphonTunnelTests.m */; };
 		66BDB0311DA6BFCC0079384C /* PsiphonTunnel.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BDB0231DA6BFCC0079384C /* PsiphonTunnel.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -74,6 +76,8 @@
 		6685BDD21E2EBB1000F0E414 /* GoPsi.objc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GoPsi.objc.h; path = PsiphonTunnel/Psi.framework/Versions/A/Headers/GoPsi.objc.h; sourceTree = "<group>"; };
 		6685BDD31E2EBB1000F0E414 /* Universe.objc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Universe.objc.h; path = PsiphonTunnel/Psi.framework/Versions/A/Headers/Universe.objc.h; sourceTree = "<group>"; };
 		6685BDD81E300AC200F0E414 /* strip-frameworks.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "strip-frameworks.sh"; path = "scripts/strip-frameworks.sh"; sourceTree = "<group>"; };
+		66BAD3331E525FBC00CD06DE /* JailbreakCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JailbreakCheck.h; sourceTree = "<group>"; };
+		66BAD3341E525FBC00CD06DE /* JailbreakCheck.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JailbreakCheck.m; sourceTree = "<group>"; };
 		66BDB0201DA6BFCC0079384C /* PsiphonTunnel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PsiphonTunnel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		66BDB0231DA6BFCC0079384C /* PsiphonTunnel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PsiphonTunnel.h; sourceTree = "<group>"; };
 		66BDB0241DA6BFCC0079384C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -143,6 +147,15 @@
 			name = Psi;
 			sourceTree = "<group>";
 		};
+		66BAD3321E525FBC00CD06DE /* JailbreakCheck */ = {
+			isa = PBXGroup;
+			children = (
+				66BAD3331E525FBC00CD06DE /* JailbreakCheck.h */,
+				66BAD3341E525FBC00CD06DE /* JailbreakCheck.m */,
+			);
+			path = JailbreakCheck;
+			sourceTree = "<group>";
+		};
 		66BDB0161DA6BFCC0079384C = {
 			isa = PBXGroup;
 			children = (
@@ -166,6 +179,7 @@
 		66BDB0221DA6BFCC0079384C /* PsiphonTunnel */ = {
 			isa = PBXGroup;
 			children = (
+				66BAD3321E525FBC00CD06DE /* JailbreakCheck */,
 				662659241DD270E900872F6C /* Reachability */,
 				66BDB04A1DC26CCC0079384C /* json-framework */,
 				66BDB0231DA6BFCC0079384C /* PsiphonTunnel.h */,
@@ -223,6 +237,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				6685BDCB1E2E882800F0E414 /* ref.h in Headers */,
+				66BAD3351E525FBC00CD06DE /* JailbreakCheck.h in Headers */,
 				4E89F7FF1E2ED3CE00005F4C /* LookupIPv6.h in Headers */,
 				662659271DD270E900872F6C /* Reachability.h in Headers */,
 				66BDB05D1DC26CCC0079384C /* SBJson4StreamParser.h in Headers */,
@@ -349,6 +364,7 @@
 				66BDB0661DC26CCC0079384C /* SBJson4StreamWriterState.m in Sources */,
 				66BDB05C1DC26CCC0079384C /* SBJson4Parser.m in Sources */,
 				4E89F7FE1E2ED3CE00005F4C /* LookupIPv6.c in Sources */,
+				66BAD3361E525FBC00CD06DE /* JailbreakCheck.m in Sources */,
 				66BDB0681DC26CCC0079384C /* SBJson4Writer.m in Sources */,
 				66BDB0621DC26CCC0079384C /* SBJson4StreamTokeniser.m in Sources */,
 				66BDB0441DA6C7DD0079384C /* PsiphonTunnel.m in Sources */,
@@ -477,6 +493,7 @@
 		66BDB0351DA6BFCC0079384C /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
+				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_IDENTITY = "";
 				DEFINES_MODULE = YES;
 				DEVELOPMENT_TEAM = Q6HLNEX92A;
@@ -501,6 +518,7 @@
 		66BDB0361DA6BFCC0079384C /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
+				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_IDENTITY = "";
 				DEFINES_MODULE = YES;
 				DEVELOPMENT_TEAM = Q6HLNEX92A;

+ 29 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/JailbreakCheck/JailbreakCheck.h

@@ -0,0 +1,29 @@
+//
+//  JailbreakCheck.h
+//  JailbreakCheck
+//
+
+/*
+ * Copyright (c) 2017, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program 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.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+@interface JailbreakCheck : NSObject
+
++ (BOOL)isDeviceJailbroken;
+
+@end

+ 136 - 0
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/JailbreakCheck/JailbreakCheck.m

@@ -0,0 +1,136 @@
+// Adapted from https://github.com/olxios/JailbreakCheck
+/*
+ MIT License
+
+ Copyright (c) 2016 olxios
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+
+#import <Foundation/Foundation.h>
+#import "UIKit/UIKit.h"
+#import <sys/stat.h>
+#import "JailbreakCheck.h"
+
+
+@implementation JailbreakCheck
+
+
+BOOL checkReadWritePermissions()
+{
+    // UIApplication:sharedApplication is disallowed in an application exetension
+    // (such as would be used by a whole-device Psiphon VPN). We may re-enable
+    // this code later, but leave it out for now to avoid confusion.
+    /*
+    if([[UIApplication sharedApplication] canOpenURL:
+        [NSURL URLWithString:@"cydia://package/com.com.com"]])
+    {
+        return TRUE;
+    }
+
+    NSError *error;
+    NSString *stringToBeWritten = @"0";
+    [stringToBeWritten writeToFile:@"/private/jailbreak.test"
+                        atomically:YES
+                          encoding:NSUTF8StringEncoding error:&error];
+    if (error == nil)
+    {
+        return TRUE;
+    }
+     */
+    
+    return FALSE;
+}
+
+BOOL checkJailbreakSymLink(NSString *checkPath)
+{
+    struct stat s;
+
+    if (lstat([checkPath UTF8String], &s) == 0)
+    {
+        if (S_ISLNK(s.st_mode) == 1)
+        {
+            return TRUE;
+        }
+    }
+    
+    return FALSE;
+}
+
+BOOL checkJailbreakSymlinks()
+{
+    NSArray *linksChecks = @[@"/Applications",
+                             @"/usr/libexec",
+                             @"/usr/share",
+                             @"/Library/Wallpaper",
+                             @"/usr/include"];
+    
+    for (NSString *checkPath in linksChecks)
+    {
+        if (checkJailbreakSymLink(checkPath)) {
+            return TRUE;
+        }
+    }
+    
+    return FALSE;
+}
+
+BOOL checkJailbreakFile(NSString *checkPath)
+{
+    struct stat s;
+
+    if (stat([checkPath UTF8String], &s) == 0)
+    {
+        return TRUE;
+    }
+    
+    return FALSE;
+}
+
+BOOL checkJailbreakFiles()
+{
+    NSArray *fileChecks = @[@"/bin/bash",
+                            @"/etc/apt",
+                            @"/usr/sbin/sshd",
+                            @"/Library/MobileSubstrate/MobileSubstrate.dylib",
+                            @"/Applications/Cydia.app",
+                            @"/bin/sh",
+                            @"/var/cache/apt",
+                            @"/var/tmp/cydia.log"];
+    
+    for (NSString *checkPath in fileChecks)
+    {
+        if (checkJailbreakFile(checkPath)) {
+            return TRUE;
+        }
+    }
+    
+    return FALSE;
+}
+
++ (BOOL)isDeviceJailbroken
+{
+    return
+        checkJailbreakSymlinks()
+        || checkJailbreakFiles()
+        || checkReadWritePermissions();
+}
+
+@end

+ 6 - 6
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.h

@@ -24,6 +24,7 @@
 
 #import <UIKit/UIKit.h>
 #import "Reachability.h"
+#import "JailbreakCheck.h"
 
 
 //! Project version number for PsiphonTunnel.
@@ -52,24 +53,24 @@ FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
  - `PropagationChannelId`
  - `SponsorId`
  - Remote server list functionality is not strictly required, but absence greatly undermines circumvention ability.
-   - `RemoteServerListUrl`
+   - `RemoteServerListURLs`
    - `RemoteServerListSignaturePublicKey`
  - Obfuscated server list functionality is also not strictly required, but aids circumvention ability.
-   - `ObfuscatedServerListRootURL`
+   - `ObfuscatedServerListRootURLs`
+   - `RemoteServerListSignaturePublicKey`: This is the same field as above. It is required if either `RemoteServerListURLs` or `ObfuscatedServerListRootURLs` is supplied.
 
  Optional fields (if you don't need them, don't set them):
  - `DataStoreDirectory`: If not set, the library will use a sane location. Override if the client wants to restrict where operational data is kept. If overridden, the directory must already exist and be writable.
  - `RemoteServerListDownloadFilename`: If not set, the library will use a sane location. Override if the client wants to restrict where operational data is kept.
  - `ObfuscatedServerListDownloadDirectory`: If not set, the library will use a sane location. Override if the client wants to restrict where operational data is kept. If overridden, the directory must already exist and be writable.
- - `ClientPlatform`: Should not be set by most library consumers.
  - `UpstreamProxyUrl`
  - `EmitDiagnosticNotices`
  - `EgressRegion`
  - `EstablishTunnelTimeoutSeconds`
  - Should only be set if the Psiphon library is handling upgrade downloading (which it usually is _not_):
-   - `UpgradeDownloadUrl`
+   - `UpgradeDownloadURLs`
    - `UpgradeDownloadClientVersionHeader`
-   - `UpgradeDownloadFilename`
+   - `UpgradeDownloadFilename`: Will be set to a sane default if not supplied.
  - Only set if disabling timeouts (for very slow network connections):
    - `TunnelConnectTimeoutSeconds`
    - `TunnelPortForwardDialTimeoutSeconds`
@@ -83,7 +84,6 @@ FOUNDATION_EXPORT const unsigned char PsiphonTunnelVersionString[];
    - `LocalHttpProxyPort`
    - `LocalSocksProxyPort`
    - `TunnelWholeDevice`: For stats purposes, but must be accurate. Defaults to 0 (false).
-
  @endcode
 
  @note All other config fields must not be set.

+ 73 - 20
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m

@@ -17,12 +17,14 @@
  *
  */
 
+#import <net/if.h>
 #import <CoreTelephony/CTTelephonyNetworkInfo.h>
 #import <CoreTelephony/CTCarrier.h>
 #import "LookupIPv6.h"
 #import "Psi-meta.h"
 #import "PsiphonTunnel.h"
 #import "json-framework/SBJson4.h"
+#import "JailbreakCheck/JailbreakCheck.h"
 
 
 @interface PsiphonTunnel () <GoPsiPsiphonProvider>
@@ -232,9 +234,9 @@
         [self logMessage:[NSString stringWithFormat: @"RemoteServerListDownloadFilename overridden from '%@' to '%@'", defaultRemoteServerListFilename, config[@"RemoteServerListDownloadFilename"]]];
     }
     
-    // If RemoteServerListUrl and RemoteServerListSignaturePublicKey are absent,
-    // we'll just leave them out, but we'll log about it.
-    if (config[@"RemoteServerListUrl"] == nil ||
+    // If RemoteServerListUrl/RemoteServerListURLs and RemoteServerListSignaturePublicKey
+    // are absent, we'll just leave them out, but we'll log about it.
+    if ((config[@"RemoteServerListUrl"] == nil && config[@"RemoteServerListURLs"] == nil) ||
         config[@"RemoteServerListSignaturePublicKey"] == nil) {
         [self logMessage:@"Remote server list functionality will be disabled"];
     }
@@ -262,11 +264,29 @@
         [self logMessage:[NSString stringWithFormat: @"ObfuscatedServerListDownloadDirectory overridden from '%@' to '%@'", [defaultOSLDirectoryURL path], config[@"ObfuscatedServerListDownloadDirectory"]]];
     }
     
-    // If ObfuscatedServerListRootURL is absent, we'll leave it out, but log the absence.
-    if (config[@"ObfuscatedServerListRootURL"] == nil) {
+    // If ObfuscatedServerListRootURL/ObfuscatedServerListRootURLs is absent,
+    // we'll leave it out, but log the absence.
+    if (config[@"ObfuscatedServerListRootURL"] == nil && config[@"ObfuscatedServerListRootURLs"] == nil) {
         [self logMessage:@"Obfuscated server list functionality will be disabled"];
     }
 
+    //
+    // Upgrade Download Filename
+    //
+
+    NSString *defaultUpgradeDownloadFilename = [[libraryURL URLByAppendingPathComponent:@"upgrade_download_file" isDirectory:NO] path];
+    if (defaultUpgradeDownloadFilename == nil) {
+        [self logMessage:@"Unable to create defaultUpgradeDownloadFilename"];
+        return nil;
+    }
+
+    if (config[@"UpgradeDownloadFilename"] == nil) {
+        config[@"UpgradeDownloadFilename"] = defaultUpgradeDownloadFilename;
+    }
+    else {
+        [self logMessage:[NSString stringWithFormat: @"UpgradeDownloadFilename overridden from '%@' to '%@'", defaultUpgradeDownloadFilename, config[@"UpgradeDownloadFilename"]]];
+    }
+
     // Other optional fields not being altered. If not set, their defaults will be used:
     // * EstablishTunnelTimeoutSeconds
     // * TunnelWholeDevice
@@ -275,15 +295,48 @@
     // * UpstreamProxyUrl
     // * EmitDiagnosticNotices
     // * EgressRegion
-    // * UpgradeDownloadUrl
+    // * UpgradeDownloadUrl/UpgradeDownloadURLs
     // * UpgradeDownloadClientVersionHeader
-    // * UpgradeDownloadFilename
     // * timeout fields
     
     //
     // Fill in the rest of the values.
     //
     
+    // ClientPlatform must not contain:
+    //   - underscores, which are used by us to separate the constituent parts
+    //   - spaces, which are considered invalid by the server
+    // Like "iOS". Older iOS reports "iPhone OS", which we will convert.
+    NSString *systemName = [[UIDevice currentDevice] systemName];
+    if ([systemName isEqual: @"iPhone OS"]) {
+        systemName = @"iOS";
+    }
+    systemName = [[systemName
+                   stringByReplacingOccurrencesOfString:@"_" withString:@"-"]
+                  stringByReplacingOccurrencesOfString:@" " withString:@"-"];
+    // Like "10.2.1"
+    NSString *systemVersion = [[[[UIDevice currentDevice]systemVersion]
+                                stringByReplacingOccurrencesOfString:@"_" withString:@"-"]
+                               stringByReplacingOccurrencesOfString:@" " withString:@"-"];
+    
+    // "unjailbroken"/"jailbroken"
+    NSString *jailbroken = @"unjailbroken";
+    if ([JailbreakCheck isDeviceJailbroken]) {
+        jailbroken = @"jailbroken";
+    }
+    // Like "com.psiphon3.browser"
+    NSString *bundleIdentifier = [[[[NSBundle mainBundle] bundleIdentifier]
+                                   stringByReplacingOccurrencesOfString:@"_" withString:@"-"]
+                                  stringByReplacingOccurrencesOfString:@" " withString:@"-"];
+    
+    NSString *clientPlatform = [NSString stringWithFormat:@"%@_%@_%@_%@",
+                                systemName,
+                                systemVersion,
+                                jailbroken,
+                                bundleIdentifier];
+    
+    config[@"ClientPlatform"] = clientPlatform;
+        
     config[@"EmitBytesTransferred"] = [NSNumber numberWithBool:TRUE];
 
     config[@"DeviceRegion"] = [PsiphonTunnel getDeviceRegion];
@@ -301,18 +354,6 @@
     }
     config[@"TrustedCACertificatesFilename"] = bundledTrustedCAPath;
     
-    //
-    // Many other fields must *only* be modified by official Psiphon clients.
-    // Some of them require default values.
-    //
-    
-    if (config[@"ClientPlatform"] == nil) {
-        config[@"ClientPlatform"] = @"iOS-Library";
-    }
-    else {
-        [self logMessage:[NSString stringWithFormat: @"ClientPlatform overridden from 'iOS-Library' to '%@'", config[@"ClientPlatform"]]];
-    }
-
     NSString *finalConfigStr = [[[SBJson4Writer alloc] init] stringWithObject:config];
     
     if (finalConfigStr == nil) {
@@ -537,7 +578,19 @@
 #pragma mark - GoPsiPsiphonProvider protocol implementation (private)
 
 - (BOOL)bindToDevice:(long)fileDescriptor error:(NSError **)error {
-    // This PsiphonProvider function is only called in TunnelWholeDevice mode
+    // This function is only called in TunnelWholeDevice mode
+    
+    // TODO: Does this function ever get called?
+    
+    // TODO: Determine if this is robust.
+    unsigned int interfaceIndex = if_nametoindex("ap1");
+    
+    int ret = setsockopt(fileDescriptor, IPPROTO_TCP, IP_BOUND_IF, &interfaceIndex, sizeof(interfaceIndex));
+    if (ret != 0) {
+        [self logMessage:[NSString stringWithFormat: @"bindToDevice: setsockopt failed; errno: %d", errno]];
+        return FALSE;
+    }
+
     return TRUE;
 }
 

+ 4 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest.xcodeproj/project.pbxproj

@@ -16,6 +16,7 @@
 		6626590E1DCB8CF400872F6C /* TunneledWebRequestUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6626590D1DCB8CF400872F6C /* TunneledWebRequestUITests.swift */; };
 		662659211DCBC7C300872F6C /* PsiphonTunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 662659201DCBC7C300872F6C /* PsiphonTunnel.framework */; };
 		662659231DCBC8D800872F6C /* PsiphonTunnel.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 662659201DCBC7C300872F6C /* PsiphonTunnel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		6682D90E1EB1334000329958 /* psiphon-embedded-server-entries.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6682D90D1EB1334000329958 /* psiphon-embedded-server-entries.txt */; };
 		6688DBB61DCD684B00721A9E /* psiphon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = 6688DBB51DCD684B00721A9E /* psiphon-config.json */; };
 /* End PBXBuildFile section */
 
@@ -64,6 +65,7 @@
 		6626590D1DCB8CF400872F6C /* TunneledWebRequestUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunneledWebRequestUITests.swift; sourceTree = "<group>"; };
 		6626590F1DCB8CF400872F6C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		662659201DCBC7C300872F6C /* PsiphonTunnel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = PsiphonTunnel.framework; sourceTree = "<group>"; };
+		6682D90D1EB1334000329958 /* psiphon-embedded-server-entries.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "psiphon-embedded-server-entries.txt"; sourceTree = "<group>"; };
 		6688DBB51DCD684B00721A9E /* psiphon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "psiphon-config.json"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -123,6 +125,7 @@
 				662658F61DCB8CF300872F6C /* LaunchScreen.storyboard */,
 				662658F91DCB8CF300872F6C /* Info.plist */,
 				6688DBB51DCD684B00721A9E /* psiphon-config.json */,
+				6682D90D1EB1334000329958 /* psiphon-embedded-server-entries.txt */,
 				662659201DCBC7C300872F6C /* PsiphonTunnel.framework */,
 			);
 			path = TunneledWebRequest;
@@ -260,6 +263,7 @@
 			files = (
 				662658F81DCB8CF300872F6C /* LaunchScreen.storyboard in Resources */,
 				662658F51DCB8CF300872F6C /* Assets.xcassets in Resources */,
+				6682D90E1EB1334000329958 /* psiphon-embedded-server-entries.txt in Resources */,
 				662658F31DCB8CF300872F6C /* Main.storyboard in Resources */,
 				6688DBB61DCD684B00721A9E /* psiphon-config.json in Resources */,
 			);

+ 36 - 6
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/ViewController.swift

@@ -37,11 +37,25 @@ class ViewController: UIViewController {
         // Start up the tunnel and begin connecting.
         // This could be started elsewhere or earlier.
         NSLog("Starting tunnel")
-        let embeddedServerEntries = ""
+
+        guard let embeddedServerEntries = getEmbeddedServerEntries() else {
+            NSLog("getEmbeddedServerEntries failed!")
+            return
+        }
+
         guard let success = self.psiphonTunnel?.start(embeddedServerEntries), success else {
             NSLog("psiphonTunnel.start returned false")
             return
         }
+        
+        // The Psiphon Library exposes reachability functions, which can be used for detecting internet status.
+        let reachability = Reachability.forInternetConnection()
+        let networkStatus = reachability?.currentReachabilityStatus()
+        NSLog("Internet is reachable? \(networkStatus != NotReachable)")
+        
+        // The Psiphon Library exposes a function to test if the device is jailbroken. 
+        let jailbroken = JailbreakCheck.isDeviceJailbroken()
+        NSLog("Device is jailbroken? \(jailbroken)")
     }
     
     override func didReceiveMemoryWarning() {
@@ -54,6 +68,22 @@ class ViewController: UIViewController {
                               .replacingOccurrences(of: "\r", with: "")
         self.webView.stringByEvaluatingJavaScript(from: String.init(format: "document.body.innerHTML+='<br><pre>%@</pre><br>'", arguments: [escapedText]))
     }
+
+    /// Read the Psiphon embedded server entries resource file and return the contents.
+    /// * returns: The string of the contents of the file.
+    func getEmbeddedServerEntries() -> String? {
+        guard let psiphonEmbeddedServerEntriesUrl = Bundle.main.url(forResource: "psiphon-embedded-server-entries", withExtension: "txt") else {
+            NSLog("Error getting Psiphon embedded server entries resource file URL!")
+            return nil
+        }
+
+        do {
+            return try String.init(contentsOf: psiphonEmbeddedServerEntriesUrl)
+        } catch {
+            NSLog("Error reading Psiphon embedded server entries resource file!")
+            return nil
+        }
+    }
     
     /// Request URL using URLSession configured to use the current proxy.
     /// * parameters:
@@ -89,7 +119,7 @@ class ViewController: UIViewController {
         let task = session.dataTask(with: request) {
             (data: Data?, response: URLResponse?, error: Error?) in
             if error != nil {
-                NSLog("Client-side error in request to \(url): \(error)")
+                NSLog("Client-side error in request to \(url): \(String(describing: error))")
                 // Invoke the callback indicating error.
                 completion(nil)
                 return
@@ -104,7 +134,7 @@ class ViewController: UIViewController {
             
             let httpResponse = response as? HTTPURLResponse
             if httpResponse?.statusCode != 200 {
-                NSLog("Server-side error in request to \(url): \(httpResponse)")
+                NSLog("Server-side error in request to \(url): \(String(describing: httpResponse))")
                 // Invoke the callback indicating error.
                 completion(nil)
                 return
@@ -148,7 +178,7 @@ class ViewController: UIViewController {
         let task = URLSession.shared.dataTask(with: URL(string: proxiedURL)!) {
             (data: Data?, response: URLResponse?, error: Error?) in
             if error != nil {
-                NSLog("Client-side error in request to \(url): \(error)")
+                NSLog("Client-side error in request to \(url): \(String(describing: error))")
                 // Invoke the callback indicating error.
                 completion(nil)
                 return
@@ -163,7 +193,7 @@ class ViewController: UIViewController {
             
             let httpResponse = response as? HTTPURLResponse
             if httpResponse?.statusCode != 200 {
-                NSLog("Server-side error in request to \(url): \(httpResponse)")
+                NSLog("Server-side error in request to \(url): \(String(describing: httpResponse))")
                 // Invoke the callback indicating error.
                 completion(nil)
                 return
@@ -200,7 +230,7 @@ extension ViewController: TunneledAppDelegate {
         do {
             return try String.init(contentsOf: psiphonConfigUrl)
         } catch {
-            NSLog("Error getting Psiphon config resource file URL!")
+            NSLog("Error reading Psiphon config resource file!")
             return nil
         }
     }

+ 3 - 1
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/psiphon-config.json.stub

@@ -9,5 +9,7 @@ All other values will be provided to you by Psiphon Inc.
   "PropagationChannelId": "...",
   "SponsorId": "...",
   "RemoteServerListSignaturePublicKey": "...",
-  "RemoteServerListUrl": "..."
+  "RemoteServerListURLs": "[...]",
+  "ObfuscatedServerListRootURLs": "[...]",
+  "EmitDiagnosticNotices": true /* Useful when testing */
 }

+ 2 - 0
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/psiphon-embedded-server-entries.txt.stub

@@ -0,0 +1,2 @@
+Embedded server entries supplied by Psiphon Inc. go here.
+This file should be empty if embedded server entries are not being used. (But they should be used.)

+ 4 - 7
MobileLibrary/iOS/build-psiphon-framework.sh

@@ -38,11 +38,9 @@ INTERMEDIATE_OUPUT_DIR="${BASE_DIR}/PsiphonTunnel/PsiphonTunnel"
 INTERMEDIATE_OUPUT_FILE="${FRAMEWORK}.framework"
 FRAMEWORK_BINARY="${INTERMEDIATE_OUPUT_DIR}/${INTERMEDIATE_OUPUT_FILE}/Versions/A/${FRAMEWORK}"
 
-# The "OPENSSL" tag enables support of OpenSSL for use by IndistinguishableTLS.
-
 PRIVATE_PLUGINS_TAG=""
 if [[ ${FORCE_PRIVATE_PLUGINS} == true ]]; then PRIVATE_PLUGINS_TAG="PRIVATE_PLUGINS"; fi
-BUILD_TAGS="OPENSSL IOS ${PRIVATE_PLUGINS_TAG}"
+BUILD_TAGS="IOS ${PRIVATE_PLUGINS_TAG}"
 
 LIBSSL=${BASE_DIR}/OpenSSL-for-iPhone/lib/libssl.a
 LIBCRYPTO=${BASE_DIR}/OpenSSL-for-iPhone/lib/libcrypto.a
@@ -65,8 +63,7 @@ export PATH=${GOPATH}/bin:${PATH}
 # The GOPATH we're using is temporary, so make sure there isn't one from a previous run.
 rm -rf ${GOPATH}
 
-# When updating the pinned rev, you will have to manually delete go-ios-build
-GOMOBILE_PINNED_REV=a0f998b2d8c7ee81ddbead9202dd5e0184a998ad
+GOMOBILE_PINNED_REV=eb9032959f05f108b05721914dfe09cfa0c5131d
 GOMOBILE_PATH=${GOPATH}/src/golang.org/x/mobile/cmd/gomobile
 
 TUNNEL_CORE_SRC_DIR=${GOPATH}/src/github.com/Psiphon-Labs/psiphon-tunnel-core
@@ -144,9 +141,9 @@ git checkout master
 git checkout -b pinned ${GOMOBILE_PINNED_REV}
 
 go install
-${GOPATH}/bin/gomobile init -v
+${GOPATH}/bin/gomobile init -v -x
 if [[ $? != 0 ]]; then
-  echo "FAILURE: ${GOPATH}/bin/gomobile init -v"
+  echo "FAILURE: ${GOPATH}/bin/gomobile init"
   exit 1
 fi