Repository: iCepa/Tor.framework Branch: pure_pod Commit: 19ac105f1a64 Files: 69 Total size: 319.8 KB Directory structure: gitextract_5cjxzza8/ ├── .gitignore ├── .gitmodules ├── Arti.podspec ├── Brewfile ├── Example/ │ ├── Example-Mac/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── Tor-Example-Mac-Bridging-Header.h │ │ ├── ViewController.swift │ │ └── main.m │ ├── Podfile │ ├── Tests/ │ │ ├── TORConfigurationTests.m │ │ ├── TORControllerTests.m │ │ ├── Tests-Info.plist │ │ └── Tests-Prefix.pch │ ├── Tor/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── Launch Screen.storyboard │ │ ├── Tor-Example-Bridging-Header.h │ │ ├── Tor-Info.plist │ │ ├── Tor-Prefix.pch │ │ ├── ViewController.swift │ │ └── main.m │ ├── Tor.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ ├── Tor-Example-Mac.xcscheme │ │ └── Tor-Example.xcscheme │ └── Tor.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── README.md ├── Tor/ │ ├── Assets/ │ │ └── .gitkeep │ ├── Classes/ │ │ ├── Arti/ │ │ │ ├── TORArti.h │ │ │ └── TORArti.m │ │ ├── CTor/ │ │ │ ├── TORLogging.h │ │ │ ├── TORLogging.m │ │ │ ├── TORThread.h │ │ │ ├── TORThread.m │ │ │ ├── TORX25519KeyPair.h │ │ │ └── TORX25519KeyPair.m │ │ ├── Core/ │ │ │ ├── NSBundle+GeoIP.h │ │ │ ├── NSBundle+GeoIP.m │ │ │ ├── NSCharacterSet+PredefinedSets.h │ │ │ ├── NSCharacterSet+PredefinedSets.m │ │ │ ├── TORAuthKey.h │ │ │ ├── TORAuthKey.m │ │ │ ├── TORCircuit.h │ │ │ ├── TORCircuit.m │ │ │ ├── TORConfiguration.h │ │ │ ├── TORConfiguration.m │ │ │ ├── TORControlCommand.h │ │ │ ├── TORControlReplyCode.h │ │ │ ├── TORController.h │ │ │ ├── TORController.m │ │ │ ├── TORNode.h │ │ │ ├── TORNode.m │ │ │ ├── TOROnionAuth.h │ │ │ └── TOROnionAuth.m │ │ └── Onionmasq/ │ │ ├── Onionmasq.h │ │ └── Onionmasq.m │ ├── download.sh │ ├── mmap-cache.patch │ └── onionmasq.sh ├── Tor.podspec ├── build-xcframework.sh └── docs/ ├── Tor.json └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ geoip geoip6 tor.xcframework tor-nolzma.xcframework tor.xcframework.zip tor-nolzma.xcframework.zip arti.xcframework arti.xcframework.zip # Created by https://www.toptal.com/developers/gitignore/api/macos,xcode,swift,objective-c # Edit at https://www.toptal.com/developers/gitignore?templates=macos,xcode,swift,objective-c ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Objective-C ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. Carthage/Checkouts Carthage/Build/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ### Objective-C Patch ### ### Swift ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts # Accio dependency management Dependencies/ .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control # Code Injection # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode ### Xcode ### ## Xcode 8 and earlier ### Xcode Patch ### *.xcodeproj/* !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ !*.xcworkspace/contents.xcworkspacedata /*.gcno **/xcshareddata/WorkspaceSettings.xcsettings # End of https://www.toptal.com/developers/gitignore/api/macos,xcode,swift,objective-c ================================================ FILE: .gitmodules ================================================ [submodule "Tor/onionmasq"] path = Tor/onionmasq url = https://gitlab.torproject.org/tpo/core/onionmasq.git ================================================ FILE: Arti.podspec ================================================ Pod::Spec.new do |m| m.name = 'Tor' m.version = '408.22.1' m.summary = 'Tor.framework is the easiest way to embed Tor in your iOS application.' m.description = 'Tor.framework is the easiest way to embed Tor in your iOS application.' m.homepage = 'https://github.com/iCepa/Tor.framework' m.license = { :type => 'MIT', :file => 'LICENSE' } m.authors = { 'Conrad Kramer' => 'conrad@conradkramer.com', 'Chris Ballinger' => 'chris@chatsecure.org', 'Mike Tigas' => 'mike@tig.as', 'Benjamin Erhart' => 'berhart@netzarchitekten.com', } m.source = { :git => 'https://github.com/iCepa/Tor.framework.git', :branch => 'pure_pod', :tag => "v#{m.version}", :submodules => true } m.social_media_url = 'https://chaos.social/@tla' m.ios.deployment_target = '15.0' m.macos.deployment_target = '11.0' m.prepare_command = "Tor/download.sh v#{m.version} arti 16a8f88e8b4f09b39462733cefb8b1c5f715b36c03c4745a6a5c6a8eb370c48f" script = <<-ENDSCRIPT cd "${PODS_TARGET_SRCROOT}/Tor/%1$s" ../%1$s.sh ENDSCRIPT m.subspec 'Core' do |s| s.requires_arc = true s.source_files = 'Tor/Classes/Core/**/*' end m.subspec 'Arti' do |s| s.dependency 'Tor/Core' s.source_files = 'Tor/Classes/Arti/**/*' s.vendored_frameworks = 'arti.xcframework' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/arti.xcframework/ios-arm64/arti.framework/Headers"', } s.preserve_paths = 'arti.xcframework', 'download.sh' s.user_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => 'USE_ARTI=1', 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) USE_ARTI', } end m.subspec 'Onionmasq' do |s| s.dependency 'Tor/Core' s.source_files = 'Tor/Classes/Onionmasq/**/*' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/Tor/onionmasq"', 'OTHER_LDFLAGS' => '$(inherited) -L"${BUILT_PRODUCTS_DIR}/Tor" -l"onionmasq_apple"', } s.user_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => 'USE_ONIONMASQ=1', 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) USE_ONIONMASQ', } s.script_phases = [ { :name => 'Build Onionmasq', :execution_position => :before_compile, :output_files => ['onionmasq-always-execute-this-but-supress-warning'], :script => sprintf(script, "onionmasq") }, ] s.preserve_paths = 'Tor/onionmasq', 'Tor/onionmasq.sh' end m.default_subspecs = 'Arti' end ================================================ FILE: Brewfile ================================================ brew "automake" brew "autoconf" brew "libtool" brew "gettext" brew "po4a" brew "rustup" ================================================ FILE: Example/Example-Mac/AppDelegate.h ================================================ // // AppDelegate.h // Tor_Example_Mac // // Created by Benjamin Erhart on 13.01.22. // Copyright © 2022 Benjamin Erhart. All rights reserved. // #import @interface AppDelegate : NSObject @end ================================================ FILE: Example/Example-Mac/AppDelegate.m ================================================ // // AppDelegate.m // Tor_Example_Mac // // Created by Benjamin Erhart on 13.01.22. // Copyright © 2022 Benjamin Erhart. All rights reserved. // #import "AppDelegate.h" #import #import #import #ifdef USE_ARTI #import #else #ifdef USE_ONIONMASQ #import #else #import #endif #endif @interface AppDelegate () @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSFileManager *fm = NSFileManager.defaultManager; NSURL *appSuppDir = [fm URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask].firstObject; TORConfiguration *configuration = [TORConfiguration new]; configuration.ignoreMissingTorrc = YES; configuration.avoidDiskWrites = YES; configuration.clientOnly = YES; configuration.cookieAuthentication = YES; configuration.autoControlPort = YES; configuration.dataDirectory = [appSuppDir URLByAppendingPathComponent:@"tor"]; configuration.geoipFile = NSBundle.geoIpBundle.geoipFile; configuration.geoip6File = NSBundle.geoIpBundle.geoip6File; NSURL *cacheDir = [fm URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].firstObject; #ifdef USE_ARTI configuration.socksPort = 9150; configuration.dnsPort = 1951; configuration.dataDirectory = [appSuppDir URLByAppendingPathComponent:@"org.torproject.Arti"]; configuration.logfile = [cacheDir URLByAppendingPathComponent:@"arti.log"]; configuration.cacheDirectory = [cacheDir URLByAppendingPathComponent:@"org.torproject.Arti"]; NSLog(@"Configuration:\n%@", [configuration compile]); [TORArti startWithConfiguration:configuration completed:^{ NSLog(@"established"); [NSNotificationCenter.defaultCenter postNotificationName:@"tor-ready" object:[[NSNumber alloc] initWithUnsignedInteger:configuration.socksPort]]; }]; #else #ifdef USE_ONIONMASQ [Onionmasq startWithReader:^{ NSLog(@"[Read]"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [Onionmasq receive:@[]]; }); } writer:^_Bool(NSData * _Nonnull packet, NSNumber * _Nonnull version) { NSLog(@"[Write] version=%@, packet=%@", version, packet); return false; } stateDir:appSuppDir cacheDir:cacheDir pcapFile:nil onEvent:^(id event) { NSLog(@"[Event] %@", event); } onLog:^(NSString * _Nonnull message) { NSLog(@"[Log] %@", message); }]; #else TORThread *thread = [[TORThread alloc] initWithConfiguration:configuration]; [thread start]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ NSData *cookie = configuration.cookie; TORController *controller = [[TORController alloc] initWithControlPortFile:configuration.controlPortFile]; [controller authenticateWithData:cookie completion:^(BOOL success, NSError *error) { __weak TORController *c = controller; NSLog(@"authenticated success=%d", success); if (!success) { return; } [c addObserverForCircuitEstablished:^(BOOL established) { NSLog(@"established=%d", established); if (!established) { return; } CFTimeInterval startTime = CACurrentMediaTime(); [c getInfoForKeys:@[@"net/listeners/socks"] completion:^(NSArray * _Nonnull values) { if (values.count > 0) { NSArray *parts = [values[0] componentsSeparatedByString:@":"]; if (parts.count > 1) { dispatch_async(dispatch_get_main_queue(), ^{ [NSNotificationCenter.defaultCenter postNotificationName:@"tor-ready" object:[[NSNumber alloc] initWithInteger:parts[1].integerValue]]; }); } } [c getCircuits:^(NSArray * _Nonnull circuits) { NSLog(@"Circuits: %@", circuits); NSLog(@"Elapsed Time: %f", CACurrentMediaTime() - startTime); }]; }]; }]; }]; }); #endif #endif } - (void)applicationWillTerminate:(NSNotification *)aNotification { // Insert code here to tear down your application } - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { return YES; } @end ================================================ FILE: Example/Example-Mac/Base.lproj/Main.storyboard ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: Example/Example-Mac/Tor-Example-Mac-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // ================================================ FILE: Example/Example-Mac/ViewController.swift ================================================ // // ViewController.swift // Tor-Example-Mac // // Created by Benjamin Erhart on 28.11.25. // Copyright © 2025 Benjamin Erhart. All rights reserved. // import Cocoa import WebKit class ViewController: NSViewController { private var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(load), name: .init("tor-ready"), object: nil) } @objc func load(_ notification: Notification) { let port = notification.object as? UInt16 Task { await MainActor.run { let conf = WKWebViewConfiguration() if #available(iOS 17.0, macOS 14.0, *), let port = port { let endpoint = NWEndpoint.hostPort(host: .ipv4(.init("127.0.0.1")!), port: .init(integerLiteral: port)) conf.websiteDataStore.proxyConfigurations.removeAll() conf.websiteDataStore.proxyConfigurations.append(ProxyConfiguration(socksv5Proxy: endpoint)) } webView = WKWebView(frame: .zero, configuration: conf) webView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(webView) webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true webView.load(.init(url: .init(string: "https://check.torproject.org")!)) } } } } ================================================ FILE: Example/Example-Mac/main.m ================================================ // // main.m // Tor_Example_Mac // // Created by Benjamin Erhart on 13.01.22. // Copyright © 2022 Benjamin Erhart. All rights reserved. // #import int main(int argc, const char * argv[]) { @autoreleasepool { // Setup code that might create autoreleased objects goes here. } return NSApplicationMain(argc, argv); } ================================================ FILE: Example/Podfile ================================================ use_frameworks! def tor # pod 'Tor/Arti', # :path => '../' # :podspec => '../Arti.podspec' pod 'Tor/GeoIP', # -NoLZMA', :path => '../' end target 'Tor-Example' do platform :ios, '15.0' tor target 'Tor-Tests' do inherit! :search_paths end end target 'Tor-Example-Mac' do platform :macos, '11.0' tor end ================================================ FILE: Example/Tests/TORConfigurationTests.m ================================================ // // TORConfigurationTests.m // Tor // // Created by Conrad Kramer on 8/11/15. // #import @interface TORConfigurationTests : XCTestCase @end @implementation TORConfigurationTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } @end ================================================ FILE: Example/Tests/TORControllerTests.m ================================================ // // Tests.m // Tests // // Created by Conrad Kramer on 8/10/15. // #import #import @interface TORControllerTests : XCTestCase @property (nonatomic, strong) TORController *controller; @property (readonly) NSData *cookie; @end @implementation TORControllerTests + (TORConfiguration *)configuration { #if TARGET_IPHONE_SIMULATOR NSString *homeDirectory = nil; for (NSString *variable in @[@"IPHONE_SIMULATOR_HOST_HOME", @"SIMULATOR_HOST_HOME"]) { char *value = getenv(variable.UTF8String); if (value) { homeDirectory = @(value); break; } } #else NSString *homeDirectory = NSHomeDirectory(); #endif TORConfiguration *configuration = [TORConfiguration new]; configuration.cookieAuthentication = @YES; configuration.dataDirectory = [NSURL fileURLWithPath:NSTemporaryDirectory()]; configuration.controlSocket = [[NSURL fileURLWithPath:homeDirectory] URLByAppendingPathComponent:@".Trash/control_port"]; configuration.arguments = @[ @"--ignore-missing-torrc", @"--GeoIPFile", [NSBundle.mainBundle pathForResource:@"geoip" ofType:nil], @"--GeoIPv6File", [NSBundle.mainBundle pathForResource:@"geoip6" ofType:nil], ]; return configuration; } + (void)setUp { [super setUp]; TORThread *thread = [[TORThread alloc] initWithConfiguration:self.configuration]; [thread start]; [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; } - (void)setUp { [super setUp]; self.controller = [[TORController alloc] initWithSocketURL:[[[self class] configuration] controlSocket]]; } - (void)testCookieAuthenticationFailure { XCTestExpectation *expectation = [self expectationWithDescription:@"authenticate callback"]; [self.controller authenticateWithData:[@"invalid" dataUsingEncoding:NSUTF8StringEncoding] completion:^(BOOL success, NSError *error) { XCTAssertFalse(success); XCTAssertEqualObjects(error.domain, TORControllerErrorDomain); XCTAssertNotEqual(error.code, TORControlReplyCodeOK); XCTAssertGreaterThan(error.localizedDescription, @"Authentication failed: Wrong length on authentication cookie."); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:1.0f handler:nil]; } - (void)testCookieAuthenticationSuccess { XCTestExpectation *expectation = [self expectationWithDescription:@"authenticate callback"]; [self.controller authenticateWithData:self.cookie completion:^(BOOL success, NSError *error) { XCTAssertTrue(success); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:1.0f handler:nil]; } - (void)testSessionConfiguration { XCTestExpectation *expectation = [self expectationWithDescription:@"tor callback"]; [self exec:^{ [self.controller getSessionConfiguration:^(NSURLSessionConfiguration *configuration) { NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration]; [[session dataTaskWithURL:[NSURL URLWithString:@"https://facebookcorewwwi.onion/"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { XCTAssertEqual([(NSHTTPURLResponse *)response statusCode], 200); XCTAssertNil(error); [expectation fulfill]; }] resume]; }]; }]; [self waitForExpectationsWithTimeout:120.0f handler:nil]; } - (void)testGetAndCloseCircuits { XCTestExpectation *expectation = [self expectationWithDescription:@"resolution callback"]; [self exec:^{ [self.controller getCircuits:^(NSArray * _Nonnull circuits) { NSLog(@"circuits=%@", circuits); for (TORCircuit *circuit in circuits) { for (TORNode *node in circuit.nodes) { XCTAssert(node.fingerprint.length > 0, @"A circuit should have a fingerprint."); XCTAssert(node.ipv4Address.length > 0 || node.ipv6Address.length > 0, @"A circuit should have an IPv4 or IPv6 address."); } } [self.controller closeCircuits:circuits completion:^(BOOL success) { XCTAssertTrue(success, @"Circuits were closed successfully."); [expectation fulfill]; }]; }]; }]; [self waitForExpectationsWithTimeout:120 handler:nil]; } - (void)testReset { XCTestExpectation *expectation = [self expectationWithDescription:@"reset callback"]; [self exec:^{ [self.controller resetConnection:^(BOOL success) { NSLog(@"success=%d", success); XCTAssertTrue(success, @"Reset should work correctly."); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:120 handler:nil]; } // MARK: Helper Properties and Methods - (NSData *)cookie { return [NSData dataWithContentsOfURL: [[self.class configuration].dataDirectory URLByAppendingPathComponent:@"control_auth_cookie"]]; } - (void)exec:(void (^)(void))callback { TORController *controller = self.controller; [controller authenticateWithData:self.cookie completion:^(BOOL success, NSError * _Nullable error) { XCTAssertTrue(success); XCTAssertNil(error); [controller addObserverForCircuitEstablished:^(BOOL established) { // May be called multiple times. We wait until circuit is established. if (!established) { return; } callback(); }]; }]; } @end ================================================ FILE: Example/Tests/Tests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Example/Tests/Tests-Prefix.pch ================================================ // The contents of this file are implicitly included at the beginning of every test case source file. #ifdef __OBJC__ #endif ================================================ FILE: Example/Tor/AppDelegate.h ================================================ // // AppDelegate.h // Tor // // Created by Benjamin Erhart on 01/13/2022. // Copyright (c) 2022 Benjamin Erhart. All rights reserved. // @import UIKit; @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: Example/Tor/AppDelegate.m ================================================ // // AppDelegate.m // Tor // // Created by Benjamin Erhart on 01/13/2022. // Copyright (c) 2022 Benjamin Erhart. All rights reserved. // #import "AppDelegate.h" #import #import #import #ifdef USE_ARTI #import #else #ifdef USE_ONIONMASQ #import #else #import #endif #endif @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSFileManager *fm = NSFileManager.defaultManager; NSURL *docDir = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject; TORConfiguration *configuration = [TORConfiguration new]; configuration.ignoreMissingTorrc = YES; configuration.avoidDiskWrites = YES; configuration.clientOnly = YES; configuration.cookieAuthentication = YES; configuration.autoControlPort = YES; configuration.dataDirectory = [docDir URLByAppendingPathComponent:@"tor"]; configuration.geoipFile = NSBundle.geoIpBundle.geoipFile; configuration.geoip6File = NSBundle.geoIpBundle.geoip6File; NSURL *cacheDir = [fm URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].firstObject; #ifdef USE_ARTI configuration.socksPort = 9150; configuration.dnsPort = 1951; configuration.dataDirectory = [docDir URLByAppendingPathComponent:@"org.torproject.Arti"]; configuration.logfile = [docDir URLByAppendingPathComponent:@"arti.log"]; configuration.cacheDirectory = [cacheDir URLByAppendingPathComponent:@"org.torproject.Arti"]; NSLog(@"Configuration:\n%@", [configuration compile]); [TORArti startWithConfiguration:configuration completed:^{ NSLog(@"established"); [NSNotificationCenter.defaultCenter postNotificationName:@"tor-ready" object:[[NSNumber alloc] initWithUnsignedInteger:configuration.socksPort]]; }]; #else #ifdef USE_ONIONMASQ [Onionmasq startWithReader:^{ NSLog(@"[Read]"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [Onionmasq receive:@[]]; }); } writer:^_Bool(NSData * _Nonnull packet, NSNumber * _Nonnull version) { NSLog(@"[Write] version=%@, packet=%@", version, packet); return false; } stateDir:docDir cacheDir:cacheDir pcapFile:nil onEvent:^(id event) { NSLog(@"[Event] %@", event); } onLog:^(NSString * _Nonnull message) { NSLog(@"[Log] %@", message); }]; #else TORThread *thread = [[TORThread alloc] initWithConfiguration:configuration]; [thread start]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ NSData *cookie = configuration.cookie; TORController *controller = [[TORController alloc] initWithControlPortFile:configuration.controlPortFile]; [controller authenticateWithData:cookie completion:^(BOOL success, NSError *error) { __weak TORController *c = controller; NSLog(@"authenticated success=%d", success); if (!success) { return; } [c addObserverForCircuitEstablished:^(BOOL established) { NSLog(@"established=%d", established); if (!established) { return; } CFTimeInterval startTime = CACurrentMediaTime(); [c getInfoForKeys:@[@"net/listeners/socks"] completion:^(NSArray * _Nonnull values) { if (values.count > 0) { NSArray *parts = [values[0] componentsSeparatedByString:@":"]; if (parts.count > 1) { dispatch_async(dispatch_get_main_queue(), ^{ [NSNotificationCenter.defaultCenter postNotificationName:@"tor-ready" object:[[NSNumber alloc] initWithInteger:parts[1].integerValue]]; }); } } [c getCircuits:^(NSArray * _Nonnull circuits) { NSLog(@"Circuits: %@", circuits); NSLog(@"Elapsed Time: %f", CACurrentMediaTime() - startTime); }]; }]; // [c getInfoForKeys:@[@"ns/all"] completion:^(NSArray * _Nonnull values) { // NSLog(@"Line count: %lu", values.count); // NSLog(@"Elapsed Time: %f", CACurrentMediaTime() - startTime); // // // NSArray *exitNodes = [TORNode parseFromNsString:values.firstObject exitOnly:YES]; // // NSLog(@"#Exit Nodes: %lu", exitNodes.count); // NSLog(@"Elapsed Time: %f", CACurrentMediaTime() - startTime); // // [c resolveCountriesOfNodes:exitNodes testCapabilities:NO completion:^{ // for (TORNode *node in exitNodes) { // NSLog(@"Node: %@", node); // } // // NSLog(@"Elapsed Time: %f", CACurrentMediaTime() - startTime); // }]; // }]; }]; }]; }); #endif #endif return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end ================================================ FILE: Example/Tor/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Example/Tor/Launch Screen.storyboard ================================================ ================================================ FILE: Example/Tor/Tor-Example-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // ================================================ FILE: Example/Tor/Tor-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSApplicationCategoryType LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIMainStoryboardFile Main UILaunchStoryboardName Launch Screen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Example/Tor/Tor-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #import #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __OBJC__ @import UIKit; @import Foundation; #endif ================================================ FILE: Example/Tor/ViewController.swift ================================================ // // ViewController.swift // Tor-Example // // Created by Benjamin Erhart on 01.12.25. // Copyright © 2025 Benjamin Erhart. All rights reserved. // import UIKit import WebKit class ViewController: UIViewController { private var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(load), name: .init("tor-ready"), object: nil) } @objc func load(_ notification: Notification) { let port = notification.object as? UInt16 Task { await MainActor.run { let conf = WKWebViewConfiguration() if #available(iOS 17.0, macOS 14.0, *), let port = port { let endpoint = NWEndpoint.hostPort(host: .ipv4(.init("127.0.0.1")!), port: .init(integerLiteral: port)) conf.websiteDataStore.proxyConfigurations.removeAll() conf.websiteDataStore.proxyConfigurations.append(ProxyConfiguration(socksv5Proxy: endpoint)) } webView = WKWebView(frame: .zero, configuration: conf) webView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(webView) webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true webView.load(.init(url: .init(string: "https://check.torproject.org")!)) } } } } ================================================ FILE: Example/Tor/main.m ================================================ // // main.m // Tor // // Created by Benjamin Erhart on 01/10/2022. // Copyright (c) 2022 Benjamin Erhart. All rights reserved. // @import UIKit; #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: Example/Tor.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 0314CBC366B59AACC3BD77D9 /* Pods_Tor_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B3520587D654F10671E7745 /* Pods_Tor_Tests.framework */; }; 2A7611A3AF4442FB65EAC417 /* Pods_Tor_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01163AA30AD557595178CC35 /* Pods_Tor_Example.framework */; }; 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 6003F59E195388D20070C39A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* AppDelegate.m */; }; 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 7913583D7355D84D17A5203C /* Pods_Tor_Example_Mac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BEC16FF0E4EF90AB4524CF2 /* Pods_Tor_Example_Mac.framework */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; A057DD272ED9F2800044B431 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A057DD262ED9F2800044B431 /* ViewController.swift */; }; A057DDA52EDDE7800044B431 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A057DDA42EDDE7800044B431 /* ViewController.swift */; }; A057DDA72EDDEEC80044B431 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A057DDA62EDDEEC80044B431 /* Launch Screen.storyboard */; }; A0F008FC27906DBA0073D36D /* TORConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F008FA27906DBA0073D36D /* TORConfigurationTests.m */; }; A0F008FD27906DBA0073D36D /* TORControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F008FB27906DBA0073D36D /* TORControllerTests.m */; }; A0F0090D279070B40073D36D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F0090C279070B40073D36D /* AppDelegate.m */; }; A0F00915279070B40073D36D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A0F00913279070B40073D36D /* Main.storyboard */; }; A0F00917279070B40073D36D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F00916279070B40073D36D /* main.m */; }; A0F0091D279072D60073D36D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F0091C279072D60073D36D /* main.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 6003F582195388D10070C39A /* Project object */; proxyType = 1; remoteGlobalIDString = 6003F589195388D20070C39A; remoteInfo = Tor; }; A009806F2F165ECB00FE3A57 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A009E83F2F1653D800FE3A57 /* ios.xcodeproj */; proxyType = 2; remoteGlobalIDString = 376EC1C52794D174000D74A9; remoteInfo = ios; }; A04C21312EE0736C00A8AFFA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A04C1E332EE0736B00A8AFFA /* ios.xcodeproj */; proxyType = 2; remoteGlobalIDString = 376EC1C52794D174000D74A9; remoteInfo = ios; }; A04CF0EE2EE0632200A8AFFA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A04CEDDB2EE0632200A8AFFA /* ios.xcodeproj */; proxyType = 2; remoteGlobalIDString = 376EC1C52794D174000D74A9; remoteInfo = ios; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 01163AA30AD557595178CC35 /* Pods_Tor_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tor_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0B3520587D654F10671E7745 /* Pods_Tor_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tor_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1D7391AAD3752903BDA8A2CC /* Pods-Tor_Example_Mac.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor_Example_Mac.debug.xcconfig"; path = "Target Support Files/Pods-Tor_Example_Mac/Pods-Tor_Example_Mac.debug.xcconfig"; sourceTree = ""; }; 3369E201AA8D44290EFDB264 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 4BD4F2E4E0599631250E97FC /* Pods-Tor_Example_Mac.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor_Example_Mac.release.xcconfig"; path = "Target Support Files/Pods-Tor_Example_Mac/Pods-Tor_Example_Mac.release.xcconfig"; sourceTree = ""; }; 4BEC16FF0E4EF90AB4524CF2 /* Pods_Tor_Example_Mac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tor_Example_Mac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4DFC6FCDC4F8056024E28F5B /* Pods-Tor-Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor-Tests.release.xcconfig"; path = "Target Support Files/Pods-Tor-Tests/Pods-Tor-Tests.release.xcconfig"; sourceTree = ""; }; 4F31AB271AA16E836CDC5F8D /* Pods-Tor-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor-Example.debug.xcconfig"; path = "Target Support Files/Pods-Tor-Example/Pods-Tor-Example.debug.xcconfig"; sourceTree = ""; }; 591053441A46E47D764CDDAB /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 6003F58A195388D20070C39A /* Tor-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Tor-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 6003F595195388D20070C39A /* Tor-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tor-Info.plist"; sourceTree = ""; }; 6003F59C195388D20070C39A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 6003F59D195388D20070C39A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 6003F5AE195388D20070C39A /* Tor-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tor-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; 6CF2DE60BF09B91BB81D2E6F /* Pods-Tor_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor_Example.release.xcconfig"; path = "Target Support Files/Pods-Tor_Example/Pods-Tor_Example.release.xcconfig"; sourceTree = ""; }; 74CA0FB740D38C49003C1E1A /* Pods-Tor-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor-Example.release.xcconfig"; path = "Target Support Files/Pods-Tor-Example/Pods-Tor-Example.release.xcconfig"; sourceTree = ""; }; 80D47871C3901193EE623A3E /* Pods-Tor_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Tor_Tests/Pods-Tor_Tests.debug.xcconfig"; sourceTree = ""; }; 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 8AA26438ED97EA21E1B6572D /* Pods-Tor-Example-Mac.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor-Example-Mac.release.xcconfig"; path = "Target Support Files/Pods-Tor-Example-Mac/Pods-Tor-Example-Mac.release.xcconfig"; sourceTree = ""; }; 90700F7B59D5C0641E3250C1 /* Pods-Tor_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor_Example.debug.xcconfig"; path = "Target Support Files/Pods-Tor_Example/Pods-Tor_Example.debug.xcconfig"; sourceTree = ""; }; 93A4EA262BB0B20934ED644E /* Tor.podspec */ = {isa = PBXFileReference; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Tor.podspec; path = ../Tor.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; A009E83F2F1653D800FE3A57 /* ios.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = ios.xcodeproj; sourceTree = ""; }; A0463DB82AA0DB0700AC9925 /* onionmasq.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = onionmasq.sh; path = ../Tor/onionmasq.sh; sourceTree = ""; }; A04C1E332EE0736B00A8AFFA /* ios.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = ios.xcodeproj; sourceTree = ""; }; A04CEDDB2EE0632200A8AFFA /* ios.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = ios.xcodeproj; sourceTree = ""; }; A057DD252ED9F2800044B431 /* Tor-Example-Mac-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tor-Example-Mac-Bridging-Header.h"; sourceTree = ""; }; A057DD262ED9F2800044B431 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; A057DDA32EDDE7800044B431 /* Tor-Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tor-Example-Bridging-Header.h"; sourceTree = ""; }; A057DDA42EDDE7800044B431 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; A057DDA62EDDEEC80044B431 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; A05D84CE2E180D4200D15D76 /* download.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = download.sh; path = ../Tor/download.sh; sourceTree = SOURCE_ROOT; }; A07DA5B42791AED000D62827 /* .gitmodules */ = {isa = PBXFileReference; lastKnownFileType = text; name = .gitmodules; path = ../.gitmodules; sourceTree = ""; }; A0912FD12DEE026600071F53 /* Brewfile */ = {isa = PBXFileReference; lastKnownFileType = text; name = Brewfile; path = ../Brewfile; sourceTree = SOURCE_ROOT; }; A091B5002DEDF93000071F53 /* build-xcframework.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "build-xcframework.sh"; path = "../build-xcframework.sh"; sourceTree = SOURCE_ROOT; }; A0C582562AE8FCA000A81084 /* Arti.podspec */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = text; name = Arti.podspec; path = ../Arti.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; A0C981E32A9F52D100E265EE /* mmap-cache.patch */ = {isa = PBXFileReference; lastKnownFileType = text; name = "mmap-cache.patch"; path = "../Tor/mmap-cache.patch"; sourceTree = ""; }; A0F008F727906CA30073D36D /* Podfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Podfile.lock; sourceTree = ""; }; A0F008F827906CA30073D36D /* Podfile */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = text; path = Podfile; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; A0F008FA27906DBA0073D36D /* TORConfigurationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TORConfigurationTests.m; sourceTree = ""; }; A0F008FB27906DBA0073D36D /* TORControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TORControllerTests.m; sourceTree = ""; }; A0F008FE27906F620073D36D /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; name = .gitignore; path = ../.gitignore; sourceTree = ""; }; A0F00909279070B40073D36D /* Tor-Example-Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Tor-Example-Mac.app"; sourceTree = BUILT_PRODUCTS_DIR; }; A0F0090B279070B40073D36D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; A0F0090C279070B40073D36D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; A0F00916279070B40073D36D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; A0F0091C279072D60073D36D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; A0F0091E279073DA0073D36D /* Tor-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tor-Prefix.pch"; sourceTree = ""; }; A0F0092027907A8A0073D36D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C2C4B3C67BC4AA29D918DFE0 /* Pods-Tor-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor-Tests.debug.xcconfig"; path = "Target Support Files/Pods-Tor-Tests/Pods-Tor-Tests.debug.xcconfig"; sourceTree = ""; }; CCD44E911F9C4625A303B626 /* Pods-Tor-Example-Mac.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor-Example-Mac.debug.xcconfig"; path = "Target Support Files/Pods-Tor-Example-Mac/Pods-Tor-Example-Mac.debug.xcconfig"; sourceTree = ""; }; FF35A366A1B1BD8B0C89AE24 /* Pods-Tor_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tor_Tests.release.xcconfig"; path = "Target Support Files/Pods-Tor_Tests/Pods-Tor_Tests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 6003F587195388D20070C39A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */, 6003F592195388D20070C39A /* UIKit.framework in Frameworks */, 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */, 2A7611A3AF4442FB65EAC417 /* Pods_Tor_Example.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 6003F5AB195388D20070C39A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */, 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */, 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */, 0314CBC366B59AACC3BD77D9 /* Pods_Tor_Tests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; A0F00906279070B40073D36D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 7913583D7355D84D17A5203C /* Pods_Tor_Example_Mac.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 6003F581195388D10070C39A = { isa = PBXGroup; children = ( 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */, 6003F593195388D20070C39A /* Example for Tor */, 6003F5B5195388D20070C39A /* Tests */, A0F0090A279070B40073D36D /* Example-Mac */, 6003F58C195388D20070C39A /* Frameworks */, 6003F58B195388D20070C39A /* Products */, 8C50D6DACFD9A136564A1BEC /* Pods */, ); sourceTree = ""; }; 6003F58B195388D20070C39A /* Products */ = { isa = PBXGroup; children = ( 6003F58A195388D20070C39A /* Tor-Example.app */, 6003F5AE195388D20070C39A /* Tor-Tests.xctest */, A0F00909279070B40073D36D /* Tor-Example-Mac.app */, ); name = Products; sourceTree = ""; }; 6003F58C195388D20070C39A /* Frameworks */ = { isa = PBXGroup; children = ( 6003F58D195388D20070C39A /* Foundation.framework */, 6003F58F195388D20070C39A /* CoreGraphics.framework */, 6003F591195388D20070C39A /* UIKit.framework */, 6003F5AF195388D20070C39A /* XCTest.framework */, 4BEC16FF0E4EF90AB4524CF2 /* Pods_Tor_Example_Mac.framework */, 01163AA30AD557595178CC35 /* Pods_Tor_Example.framework */, 0B3520587D654F10671E7745 /* Pods_Tor_Tests.framework */, ); name = Frameworks; sourceTree = ""; }; 6003F593195388D20070C39A /* Example for Tor */ = { isa = PBXGroup; children = ( 6003F59C195388D20070C39A /* AppDelegate.h */, 6003F59D195388D20070C39A /* AppDelegate.m */, 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */, A0F0091C279072D60073D36D /* main.m */, 6003F595195388D20070C39A /* Tor-Info.plist */, A0F0091E279073DA0073D36D /* Tor-Prefix.pch */, A057DDA42EDDE7800044B431 /* ViewController.swift */, A057DDA32EDDE7800044B431 /* Tor-Example-Bridging-Header.h */, A057DDA62EDDEEC80044B431 /* Launch Screen.storyboard */, ); name = "Example for Tor"; path = Tor; sourceTree = ""; }; 6003F5B5195388D20070C39A /* Tests */ = { isa = PBXGroup; children = ( A0F008FA27906DBA0073D36D /* TORConfigurationTests.m */, A0F008FB27906DBA0073D36D /* TORControllerTests.m */, 6003F5B7195388D20070C39A /* Tests-Info.plist */, 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */, ); path = Tests; sourceTree = ""; }; 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = { isa = PBXGroup; children = ( A0912FD12DEE026600071F53 /* Brewfile */, 93A4EA262BB0B20934ED644E /* Tor.podspec */, A0C582562AE8FCA000A81084 /* Arti.podspec */, 3369E201AA8D44290EFDB264 /* README.md */, 591053441A46E47D764CDDAB /* LICENSE */, A0F008F827906CA30073D36D /* Podfile */, A0F008F727906CA30073D36D /* Podfile.lock */, A0F008FE27906F620073D36D /* .gitignore */, A07DA5B42791AED000D62827 /* .gitmodules */, A05D84CE2E180D4200D15D76 /* download.sh */, A0463DB82AA0DB0700AC9925 /* onionmasq.sh */, A0C981E32A9F52D100E265EE /* mmap-cache.patch */, A091B5002DEDF93000071F53 /* build-xcframework.sh */, ); name = "Podspec Metadata"; sourceTree = ""; }; 8C50D6DACFD9A136564A1BEC /* Pods */ = { isa = PBXGroup; children = ( 90700F7B59D5C0641E3250C1 /* Pods-Tor_Example.debug.xcconfig */, 6CF2DE60BF09B91BB81D2E6F /* Pods-Tor_Example.release.xcconfig */, 80D47871C3901193EE623A3E /* Pods-Tor_Tests.debug.xcconfig */, FF35A366A1B1BD8B0C89AE24 /* Pods-Tor_Tests.release.xcconfig */, 1D7391AAD3752903BDA8A2CC /* Pods-Tor_Example_Mac.debug.xcconfig */, 4BD4F2E4E0599631250E97FC /* Pods-Tor_Example_Mac.release.xcconfig */, CCD44E911F9C4625A303B626 /* Pods-Tor-Example-Mac.debug.xcconfig */, 8AA26438ED97EA21E1B6572D /* Pods-Tor-Example-Mac.release.xcconfig */, 4F31AB271AA16E836CDC5F8D /* Pods-Tor-Example.debug.xcconfig */, 74CA0FB740D38C49003C1E1A /* Pods-Tor-Example.release.xcconfig */, C2C4B3C67BC4AA29D918DFE0 /* Pods-Tor-Tests.debug.xcconfig */, 4DFC6FCDC4F8056024E28F5B /* Pods-Tor-Tests.release.xcconfig */, ); path = Pods; sourceTree = ""; }; A009806C2F1653D900FE3A57 /* Products */ = { isa = PBXGroup; children = ( A00980702F165ECB00FE3A57 /* ios.app */, ); name = Products; sourceTree = ""; }; A04C1E392EE0736B00A8AFFA /* Products */ = { isa = PBXGroup; children = ( A04C21322EE0736C00A8AFFA /* ios.app */, ); name = Products; sourceTree = ""; }; A04CEDE12EE0632200A8AFFA /* Products */ = { isa = PBXGroup; children = ( A04CF0EF2EE0632200A8AFFA /* ios.app */, ); name = Products; sourceTree = ""; }; A0F0090A279070B40073D36D /* Example-Mac */ = { isa = PBXGroup; children = ( A0F0090B279070B40073D36D /* AppDelegate.h */, A0F0090C279070B40073D36D /* AppDelegate.m */, A0F00913279070B40073D36D /* Main.storyboard */, A0F00916279070B40073D36D /* main.m */, A057DD262ED9F2800044B431 /* ViewController.swift */, A057DD252ED9F2800044B431 /* Tor-Example-Mac-Bridging-Header.h */, ); path = "Example-Mac"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 6003F589195388D20070C39A /* Tor-Example */ = { isa = PBXNativeTarget; buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Tor-Example" */; buildPhases = ( 92C23A76A3C3CB7B0EE26734 /* [CP] Check Pods Manifest.lock */, 6003F586195388D20070C39A /* Sources */, 6003F587195388D20070C39A /* Frameworks */, 6003F588195388D20070C39A /* Resources */, 90B5FFAA88FED781C1B58A99 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "Tor-Example"; productName = Tor; productReference = 6003F58A195388D20070C39A /* Tor-Example.app */; productType = "com.apple.product-type.application"; }; 6003F5AD195388D20070C39A /* Tor-Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Tor-Tests" */; buildPhases = ( 90D8C2DC582D8E6FC6716412 /* [CP] Check Pods Manifest.lock */, 6003F5AA195388D20070C39A /* Sources */, 6003F5AB195388D20070C39A /* Frameworks */, 6003F5AC195388D20070C39A /* Resources */, ); buildRules = ( ); dependencies = ( 6003F5B4195388D20070C39A /* PBXTargetDependency */, ); name = "Tor-Tests"; productName = TorTests; productReference = 6003F5AE195388D20070C39A /* Tor-Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; A0F00908279070B40073D36D /* Tor-Example-Mac */ = { isa = PBXNativeTarget; buildConfigurationList = A0F00919279070B40073D36D /* Build configuration list for PBXNativeTarget "Tor-Example-Mac" */; buildPhases = ( 9A118950F57DB5601BC45C3C /* [CP] Check Pods Manifest.lock */, A0F00905279070B40073D36D /* Sources */, A0F00906279070B40073D36D /* Frameworks */, A0F00907279070B40073D36D /* Resources */, C24010A627399EEA7C17DDED /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "Tor-Example-Mac"; productName = Tor_Example_Mac; productReference = A0F00909279070B40073D36D /* Tor-Example-Mac.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 6003F582195388D10070C39A /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; CLASSPREFIX = TOR; LastUpgradeCheck = 2640; ORGANIZATIONNAME = "Benjamin Erhart"; TargetAttributes = { 6003F589195388D20070C39A = { LastSwiftMigration = 2610; }; 6003F5AD195388D20070C39A = { TestTargetID = 6003F589195388D20070C39A; }; A0F00908279070B40073D36D = { CreatedOnToolsVersion = 13.2.1; LastSwiftMigration = 2610; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "Tor" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 6003F581195388D10070C39A; preferredProjectObjectVersion = 77; productRefGroup = 6003F58B195388D20070C39A /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = A009806C2F1653D900FE3A57 /* Products */; ProjectRef = A009E83F2F1653D800FE3A57 /* ios.xcodeproj */; }, { ProductGroup = A04C1E392EE0736B00A8AFFA /* Products */; ProjectRef = A04C1E332EE0736B00A8AFFA /* ios.xcodeproj */; }, { ProductGroup = A04CEDE12EE0632200A8AFFA /* Products */; ProjectRef = A04CEDDB2EE0632200A8AFFA /* ios.xcodeproj */; }, ); projectRoot = ""; targets = ( 6003F589195388D20070C39A /* Tor-Example */, 6003F5AD195388D20070C39A /* Tor-Tests */, A0F00908279070B40073D36D /* Tor-Example-Mac */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ A00980702F165ECB00FE3A57 /* ios.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; path = ios.app; remoteRef = A009806F2F165ECB00FE3A57 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; A04C21322EE0736C00A8AFFA /* ios.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; path = ios.app; remoteRef = A04C21312EE0736C00A8AFFA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; A04CF0EF2EE0632200A8AFFA /* ios.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; path = ios.app; remoteRef = A04CF0EE2EE0632200A8AFFA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 6003F588195388D20070C39A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( A057DDA72EDDEEC80044B431 /* Launch Screen.storyboard in Resources */, 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6003F5AC195388D20070C39A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; A0F00907279070B40073D36D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( A0F00915279070B40073D36D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 90B5FFAA88FED781C1B58A99 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Tor-Example/Pods-Tor-Example-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Tor-Example/Pods-Tor-Example-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tor-Example/Pods-Tor-Example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 90D8C2DC582D8E6FC6716412 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Tor-Tests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 92C23A76A3C3CB7B0EE26734 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Tor-Example-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9A118950F57DB5601BC45C3C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Tor-Example-Mac-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; C24010A627399EEA7C17DDED /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Tor-Example-Mac/Pods-Tor-Example-Mac-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Tor-Example-Mac/Pods-Tor-Example-Mac-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tor-Example-Mac/Pods-Tor-Example-Mac-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 6003F586195388D20070C39A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A057DDA52EDDE7800044B431 /* ViewController.swift in Sources */, 6003F59E195388D20070C39A /* AppDelegate.m in Sources */, A0F0091D279072D60073D36D /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6003F5AA195388D20070C39A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A0F008FC27906DBA0073D36D /* TORConfigurationTests.m in Sources */, A0F008FD27906DBA0073D36D /* TORControllerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; A0F00905279070B40073D36D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A057DD272ED9F2800044B431 /* ViewController.swift in Sources */, A0F00917279070B40073D36D /* main.m in Sources */, A0F0090D279070B40073D36D /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 6003F5B4195388D20070C39A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 6003F589195388D20070C39A /* Tor-Example */; targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ A0F00913279070B40073D36D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( A0F0092027907A8A0073D36D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 6003F5BD195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; STRING_CATALOG_GENERATE_SYMBOLS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 6003F5BE195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; SDKROOT = iphoneos; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 6003F5C0195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4F31AB271AA16E836CDC5F8D /* Pods-Tor-Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tor/Tor-Prefix.pch"; INFOPLIST_FILE = "Tor/Tor-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Tor/Tor-Example-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 6.0; WRAPPER_EXTENSION = app; }; name = Debug; }; 6003F5C1195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 74CA0FB740D38C49003C1E1A /* Pods-Tor-Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tor/Tor-Prefix.pch"; INFOPLIST_FILE = "Tor/Tor-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Tor/Tor-Example-Bridging-Header.h"; SWIFT_VERSION = 6.0; WRAPPER_EXTENSION = app; }; name = Release; }; 6003F5C3195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C2C4B3C67BC4AA29D918DFE0 /* Pods-Tor-Tests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tor_Example.app/Tor_Example"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; 6003F5C4195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4DFC6FCDC4F8056024E28F5B /* Pods-Tor-Tests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; INFOPLIST_FILE = "Tests/Tests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tor_Example.app/Tor_Example"; WRAPPER_EXTENSION = xctest; }; name = Release; }; A0F0091A279070B40073D36D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = CCD44E911F9C4625A303B626 /* Pods-Tor-Example-Mac.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_APP_SANDBOX = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_USER_SELECTED_FILES = readonly; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Benjamin Erhart. All rights reserved."; INFOPLIST_KEY_NSMainStoryboardFile = Main; INFOPLIST_KEY_NSPrincipalClass = NSApplication; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.Tor-Example-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "Example-Mac/Tor-Example-Mac-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 6.0; }; name = Debug; }; A0F0091B279070B40073D36D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8AA26438ED97EA21E1B6572D /* Pods-Tor-Example-Mac.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_APP_SANDBOX = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; ENABLE_USER_SELECTED_FILES = readonly; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Benjamin Erhart. All rights reserved."; INFOPLIST_KEY_NSMainStoryboardFile = Main; INFOPLIST_KEY_NSPrincipalClass = NSApplication; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.Tor-Example-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "Example-Mac/Tor-Example-Mac-Bridging-Header.h"; SWIFT_VERSION = 6.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 6003F585195388D10070C39A /* Build configuration list for PBXProject "Tor" */ = { isa = XCConfigurationList; buildConfigurations = ( 6003F5BD195388D20070C39A /* Debug */, 6003F5BE195388D20070C39A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Tor-Example" */ = { isa = XCConfigurationList; buildConfigurations = ( 6003F5C0195388D20070C39A /* Debug */, 6003F5C1195388D20070C39A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Tor-Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 6003F5C3195388D20070C39A /* Debug */, 6003F5C4195388D20070C39A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A0F00919279070B40073D36D /* Build configuration list for PBXNativeTarget "Tor-Example-Mac" */ = { isa = XCConfigurationList; buildConfigurations = ( A0F0091A279070B40073D36D /* Debug */, A0F0091B279070B40073D36D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 6003F582195388D10070C39A /* Project object */; } ================================================ FILE: Example/Tor.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/Tor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Example/Tor.xcodeproj/xcshareddata/xcschemes/Tor-Example-Mac.xcscheme ================================================ ================================================ FILE: Example/Tor.xcodeproj/xcshareddata/xcschemes/Tor-Example.xcscheme ================================================ ================================================ FILE: Example/Tor.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/Tor.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: LICENSE ================================================ Copyright (c) 2015-2026 The iCepa Contributors: - Conrad Kramer (https://conradkramer.com) - Chris Ballinger (http://chatsecure.org) - Mike Tigas (https://mike.tig.as) - Benjamin Erhart (https://die.netzarchitekten.com) 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. ================================================ FILE: README.md ================================================ # Tor.framework [![Version](https://img.shields.io/cocoapods/v/Tor.svg?style=flat)](https://cocoapods.org/pods/Tor) [![License](https://img.shields.io/cocoapods/l/Tor.svg?style=flat)](https://cocoapods.org/pods/Tor) [![Platform](https://img.shields.io/cocoapods/p/Tor.svg?style=flat)](https://cocoapods.org/pods/Tor) Tor.framework is the easiest way to embed Tor in your iOS application. The API is *not* stable yet, and subject to change. Currently, the framework compiles in the following versions of `tor`, `libevent`, `openssl`, and `liblzma`: | Component | Version | |:--------- | --------:| | tor | 0.4.9.6 | | libevent | 2.1.12 | | OpenSSL | 3.6.1 | | liblzma | 5.8.2 | | Arti | 1.7.0 | | Onionmasq | 0.6.2 | ## LATEST CHANGES - No inline compilation necessary anymore: Now uses precompiled `tor.xcframework`, `tor-nolzma.xcframework` and `arti.xcframework`which will be downloaded from https://github.com/iCepa/Tor.framework/releases on install/update. - Finally removed `TorStatic.podspec` as there was no feedback about it and it started to be in the way. ## Example To run the example project, clone the repo, and run `pod install` from the Example directory first. ## Requirements - iOS 15.0 or later - MacOS 11.0 or later - Xcode 26.0 or later ## Installation C-Tor is available through [CocoaPods](https://cocoapods.org). To install it, simply add the following line to your Podfile: ```ruby use_frameworks! pod 'Tor', '~> 409' ``` (or `Tor/GeoIP` - see below.) Arti is available through it's own Podspec. To install it, simply add the following line to your Podfile: ```ruby use_frameworks! pod 'Tor/Arti', :podspec => 'https://raw.githubusercontent.com/iCepa/Tor.framework/refs/heads/pure_pod/Arti.podspec' ``` ## Compiling yourself Prerequesite: - [Homebrew](https://brew.sh) ```sh git clone https://github.com/iCepa/Tor.framework.git cd Tor.framework brew bundle rustup default stable rustup target add aarch64-apple-darwin rustup target add x86_64-apple-darwin rustup target add aarch64-apple-ios rustup target add aarch64-apple-ios-sim rustup target add x86_64-apple-ios cargo install cbindgen ./build-xcframework.sh -ac ``` *NOTE*: Builds are not reproducible. ## Preparing a new release For maintainers/contributors of Tor.framework, a new release should be prepared by doing the following: - Update the version numbers of the libraries used in [`build-xcframework.sh`](build-xcframework.sh). - Follow the instructions in [Compiling yourself](#compiling-yourself) - Check the logs and test the created `tor.xcframework`, `tor-nolzma.xcframework` and `arti.xcframework` with the contained example apps. - Update info, version numbers and checksums in `README.md`, `Tor.podspec` and `Arti.podspec`! - Commit, tag and push new release. - Create a pre-release on https://github.com/iCepa/Tor.framework/releases with the latest info as per older releases and upload the created `tor.xcframework.zip`, `tor-nolzma.framework.zip` and `arti.framework.zip` files. - Then lint like this: ```sh pod lib lint --allow-warnings Tor.podspec ``` - If the linting went well, publish to CocoaPods: ```sh pod trunk push --allow-warnings Tor.podspec ``` - Then update the [release](https://github.com/iCepa/Tor.framework/releases) in GitHub, setting it as the latest release. ## Usage ### All-in-one `TorManager` For a headache-free start into the world of Tor on iOS and macOS, check out the new [`TorManager` project](https://github.com/tladesignz/TorManager)! ### Do-it-yourself Starting an instance of Tor involves using three classes: `TORThread`, `TORConfiguration` and `TORController`. Here is an example of integrating Tor with `NSURLSession`: ```objc TORConfiguration *configuration = [TORConfiguration new]; configuration.ignoreMissingTorrc = YES; configuration.cookieAuthentication = YES; configuration.dataDirectory = [NSURL fileURLWithPath:NSTemporaryDirectory()]; configuration.controlSocket = [configuration.dataDirectory URLByAppendingPathComponent:@"control_port"]; TORThread *thread = [[TORThread alloc] initWithConfiguration:configuration]; [thread start]; NSData *cookie = configuration.cookie; TORController *controller = [[TORController alloc] initWithSocketURL:configuration.controlSocket]; NSError *error; [controller connect:&error]; if (error) { NSLog(@"Error: %@", error); return; } [controller authenticateWithData:cookie completion:^(BOOL success, NSError *error) { if (!success) return; [controller addObserverForCircuitEstablished:^(BOOL established) { if (!established) return; [controller getSessionConfiguration:^(NSURLSessionConfiguration *configuration) { NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration]; ... }]; }]; }]; ``` ### GeoIP In your `Podfile` use the subspec `GeoIP` instead of the root spec: ```ruby use_frameworks! pod 'Tor/GeoIP' ``` The subspec will create a "GeoIP" bundle with the appropriate GeoIP files. To use it with Tor, add this to your configuration: ```objc TORConfiguration *configuration = [TORConfiguration new]; configuration.geoipFile = NSBundle.geoIpBundle.geoipFile; configuration.geoip6File = NSBundle.geoIpBundle.geoip6File; ``` ### Experimental Arti and Onionmasq podspec Since I while, this project also contains a podspec, which uses Arti (A Rust Tor Implementation) or Onionmasq (Arti with a wrapper taking in IP packets, useful for VPN-style apps.) ```ruby pod 'Tor/Arti', :podspec => 'https://raw.githubusercontent.com/iCepa/Tor.framework/pure_pod/Arti.podspec' ``` or ```ruby pod 'Tor/Onionmasq', :podspec => 'https://raw.githubusercontent.com/iCepa/Tor.framework/pure_pod/Arti.podspec' ``` There's currently a known issue: Onionmasq won't compile if you build for iOS or an iOS simulator right away, since some Rust dependencies use custom build scripts which need to get compiled for MacOS, but will try to use the wrong platform (iOS) in this case. This can be fixed, if you compile for your machine first: ```sh cd Pods/Tor/Tor/onionmasq make macos-debug-aarch64-apple-darwin # If you run on Apple Silicon make macos-debug-x86_64-apple-darwin # If you're still on Intel ``` Then, the Rust dependency build scripts will be compiled correctly and the Xcode build will run correctly. You can also precompile your debug and release targets on the command line, if you like: ```sh make macos-release-universal-macos # Release build for MacOS as universal binary make ios-release-aarch64-apple-ios # Release build for iOS make ios-debug-aarch64-apple-ios # Debug build for iOS device make ios-debug-aarch64-apple-ios-sim # Debug build for iOS simulator running on Apple Silicon make ios-debug-x86_64-apple-ios # Debug build for iOS simulator running on Intel ``` ## Further reading https://tordev.guardianproject.info ## Authors - Conrad Kramer, conrad@conradkramer.com - Chris Ballinger, chris@chatsecure.org - Mike Tigas, mike@tig.as - Benjamin Erhart, berhart@netzarchitekten.com ## License Tor.framework is available under the MIT license. See the [`LICENSE`](https://github.com/iCepa/Tor.framework/blob/master/LICENSE) file for more info. ================================================ FILE: Tor/Assets/.gitkeep ================================================ ================================================ FILE: Tor/Classes/Arti/TORArti.h ================================================ // // TORArti.h // Tor // // Created by Benjamin Erhart on 02.02.23. // #import #import NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(TorArti) @interface TORArti : NSObject /** Start Arti. @param socksPort The port to use for accepting SOCKS5 requests. @param dnsPort The port to use for accepting DNS requests. @param logfile A logfile to write to. OPTIONAL @param stateDir Directory, where Arti can store its state. OPTIONAL. If not provided, will use \c Library/Application \c Support/org.torproject.Arti. @param cacheDir Directory, where Arti can store its caching data. OPTIONAL. If not providied, will use \c Library/Cache/org.torproject.Arti. @param obfs4proxyPath The path to the Obfs4proxy binary. OPTIONAL. Only for MacOS! iOS apps are not allowed to start other processes! @param bridge A bridge configuration line needed for the provided Obfs4proxy. OPTIONAL. @param completed Callback when Arti is ready to connect. */ + (void)startWithSocksPort:(NSUInteger)socksPort dnsPort:(NSUInteger)dnsPort obfs4Port:(NSUInteger)obfs4Port snowflakePort:(NSUInteger)snowflakePort logfile:(NSURL * _Nullable)logfile stateDir:(NSURL * _Nullable)stateDir cacheDir:(NSURL * _Nullable)cacheDir obfs4proxyPath:(NSURL * _Nullable)obfs4proxyPath bridge:(NSString * _Nullable)bridge completed:(nullable void (^)(void))completed; + (void)startWithConfiguration:(TORConfiguration * _Nonnull)configuration completed:(nullable void (^)(void))completed; + (void)stop; //+ (NSError *)status; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Arti/TORArti.m ================================================ // // TORArti.m // Tor // // Created by Benjamin Erhart on 02.02.23. // #import "TORArti.h" #import "arti-mobile.h" //#import "arti-rpc-client-core.h" @implementation TORArti NSString *logfilePath; NSRegularExpression *regex; typedef void (^Completed)(void); Completed completedBlock; + (void)startWithSocksPort:(NSUInteger)socksPort dnsPort:(NSUInteger)dnsPort obfs4Port:(NSUInteger)obfs4Port snowflakePort:(NSUInteger)snowflakePort logfile:(NSURL * _Nullable)logfile stateDir:(NSURL * _Nullable)stateDir cacheDir:(NSURL * _Nullable)cacheDir obfs4proxyPath:(NSURL * _Nullable)obfs4proxyPath bridge:(NSString * _Nullable)bridge completed:(nullable void (^)(void))completed { logfilePath = logfile.path; completedBlock = completed; NSFileManager *fm = NSFileManager.defaultManager; if (![fm fileExistsAtPath:logfilePath]) { [fm createFileAtPath:logfilePath contents:nil attributes:nil]; } if (!stateDir) { stateDir = [[[fm URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] firstObject] URLByAppendingPathComponent:@"org.torproject.Arti"]; } if (!cacheDir) { cacheDir = [[[fm URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] firstObject] URLByAppendingPathComponent:@"org.torproject.Arti"]; } // Remove ANSI colors. regex = [[NSRegularExpression alloc] initWithPattern:@"\\x1b\\[[0-9;]*m" options:NSRegularExpressionDotMatchesLineSeparators error:nil]; start_arti([stateDir.path cStringUsingEncoding:NSUTF8StringEncoding], [cacheDir.path cStringUsingEncoding:NSUTF8StringEncoding], (int)obfs4Port, (int)snowflakePort, [obfs4proxyPath.path cStringUsingEncoding:NSUTF8StringEncoding], [bridge cStringUsingEncoding:NSUTF8StringEncoding], (int)socksPort, (int)dnsPort, &loggingCb); } + (void)startWithConfiguration:(TORConfiguration * _Nonnull)configuration completed:(nullable void (^)(void))completed { NSUInteger obfs4Port = 0; NSUInteger snowflakePort = 0; NSURL *obfs4proxyPath = nil; NSString *bridge = nil; if ([configuration valueOf:@"UseBridges"]) { NSString *ctp = [configuration valueOf:@"ClientTransportPlugin"]; NSArray *pieces = [ctp componentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceCharacterSet]; NSRegularExpression *socksRegex = [[NSRegularExpression alloc] initWithPattern:@"^socks[45]$" options:NSRegularExpressionCaseInsensitive error:nil]; if (pieces.count > 0) { if (pieces.count > 2 && [socksRegex numberOfMatchesInString:pieces[1] options:0 range:NSMakeRange(0, pieces[1].length)] > 0 ) { NSRegularExpression *addrRegex = [[NSRegularExpression alloc] initWithPattern:@"^.+:(\\d{1,5})$" options:0 error:nil]; NSArray *matches = [addrRegex matchesInString:pieces[2] options:0 range:NSMakeRange(0, pieces[2].length)]; if (matches.count > 0 && matches[0].numberOfRanges > 1) { NSRange range = [matches[0] rangeAtIndex:1]; NSString *port = [pieces[2] substringWithRange:range]; if ([pieces[0] caseInsensitiveCompare:@"snowflake"] == NSOrderedSame) { snowflakePort = [port integerValue]; } else if ([pieces[0] caseInsensitiveCompare:@"obfs4"] == NSOrderedSame) { obfs4Port = [port integerValue]; } } } else if (pieces.count > 1 && [pieces[0] caseInsensitiveCompare:@"obfs4"] == NSOrderedSame ) { obfs4proxyPath = [[NSURL alloc] initFileURLWithPath:pieces[1] isDirectory:NO]; } } bridge = [configuration valueOf:@"Bridge"]; } [self startWithSocksPort:configuration.socksPort dnsPort:configuration.dnsPort obfs4Port:obfs4Port snowflakePort:snowflakePort logfile:configuration.logfile stateDir:configuration.dataDirectory cacheDir:configuration.cacheDirectory obfs4proxyPath:obfs4proxyPath bridge:bridge completed:completed]; } + (void)stop { stop_arti(); } // Experimental! Not working. //+ (NSError *)status //{ // ArtiRpcConnBuilder *builder; // ArtiRpcError *error; // // if (arti_rpc_conn_builder_new(&builder, &error) != ARTI_RPC_STATUS_SUCCESS) // { // return [self nsErrorFromArti:error]; // } // // ArtiRpcConn *conn; // // if (arti_rpc_conn_builder_connect(builder, &conn, &error) != ARTI_RPC_STATUS_SUCCESS) // { // arti_rpc_conn_builder_free(builder); // // return [self nsErrorFromArti:error]; // } // // arti_rpc_conn_builder_free(builder); // // const char *sessionId = arti_rpc_conn_get_session_id(conn); // // NSLog(@"sessionId=%s", sessionId); // // ArtiRpcStr *response; // // if (arti_rpc_conn_execute(conn, [@"arti:get_client_status" cStringUsingEncoding:NSUTF8StringEncoding], &response, &error) != ARTI_RPC_STATUS_SUCCESS) // { // arti_rpc_conn_free(conn); // // return [self nsErrorFromArti:error]; // } // // NSLog(@"response=%s", arti_rpc_str_get(response)); // // arti_rpc_str_free(response); // // arti_rpc_conn_free(conn); // // return nil; //} // //+ (NSError *)nsErrorFromArti:(ArtiRpcError *)error //{ // NSString *msg = [NSString stringWithCString:arti_rpc_err_message(error) encoding:NSUTF8StringEncoding]; // ArtiRpcStatus code = arti_rpc_err_status(error); // // NSError *err = [[NSError alloc] initWithDomain:@"Arti" code:code userInfo:@{NSLocalizedDescriptionKey: msg}]; // // arti_rpc_err_free(error); // // NSLog(@"Arti Error=%@", err); // // return err; //} void loggingCb(const char * message) { NSMutableString *msg = [[NSMutableString alloc] initWithUTF8String:message]; if (completedBlock && [msg.lowercaseString containsString:@"directory is complete"]) { dispatch_async(dispatch_get_main_queue(), ^{ completedBlock(); completedBlock = nil; }); } if (logfilePath.length < 1) return; [regex replaceMatchesInString:msg options:0 range:NSMakeRange(0, msg.length) withTemplate:@""]; NSFileHandle *fh = [NSFileHandle fileHandleForUpdatingAtPath: logfilePath]; [fh seekToEndOfFile]; [fh writeData:[msg dataUsingEncoding:NSUTF8StringEncoding]]; [fh closeFile]; } @end ================================================ FILE: Tor/Classes/CTor/TORLogging.h ================================================ // // TORLogging.h // Tor // // Created by Benjamin Erhart on 9/9/17. // #import #import NS_ASSUME_NONNULL_BEGIN typedef void(* tor_log_cb)(os_log_type_t severity, const char* msg); extern void TORInstallEventLogging(void); extern void TORInstallEventLoggingCallback(tor_log_cb cb); extern void TORInstallTorLogging(void); extern void TORInstallTorLoggingCallback(tor_log_cb cb); NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/CTor/TORLogging.m ================================================ // // TORLogging.m // Tor // // Created by Benjamin Erhart on 9/9/17. // #import "TORLogging.h" #import #import // XXXX This is not an exposed or supported Tor API. // XXXX If Tor changes this header, then this code might break. #import tor_log_cb tor_log_callback; tor_log_cb event_log_callback; NS_ASSUME_NONNULL_BEGIN static char *subsystem = "org.torproject.Tor"; static inline const char *TORLegacyLevelFromOSLogType(os_log_type_t type) { switch (type) { case OS_LOG_TYPE_ERROR: return "3"; case OS_LOG_TYPE_INFO: return "4"; case OS_LOG_TYPE_DEBUG: default: return "5"; } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" static void TORLegacyLog(os_log_type_t type, const char *msg) { static dispatch_once_t onceToken; static aslclient log = NULL; dispatch_once(&onceToken, ^{ log = asl_open(NULL, "com.apple.console", 0); }); char read_uid[16]; snprintf(read_uid, sizeof(read_uid), "%d", geteuid()); aslmsg message = asl_new(ASL_TYPE_MSG); if (message != NULL) { if (asl_set(message, ASL_KEY_LEVEL, TORLegacyLevelFromOSLogType(type)) == 0 && asl_set(message, ASL_KEY_MSG, msg) == 0 && asl_set(message, ASL_KEY_READ_UID, read_uid) == 0) { asl_send(log, message); } asl_free(message); } } #pragma clang diagnostic pop static inline os_log_type_t TORLogTypeFromEventSeverity(int severity) { switch (severity) { case EVENT_LOG_ERR: case EVENT_LOG_WARN: return OS_LOG_TYPE_ERROR; case EVENT_LOG_MSG: return OS_LOG_TYPE_INFO; case EVENT_LOG_DEBUG: return OS_LOG_TYPE_DEBUG; default: return OS_LOG_TYPE_DEFAULT; } } static void TOREventLogCallback(int severity, const char *msg) { os_log_type_t type = TORLogTypeFromEventSeverity(severity); if (event_log_callback) { event_log_callback(type, msg); } else if (@available(iOS 10.0, macOS 10.12, *)) { static dispatch_once_t onceToken; static os_log_t log = NULL; dispatch_once(&onceToken, ^{ log = os_log_create(subsystem, "libevent"); }); os_log_with_type(log, type, "%{public}s", msg); } else { TORLegacyLog(type, msg); } } static const char * __nullable TORCategoryForDomain(uint32_t domain) { switch (domain) { case LD_GENERAL: return "general"; case LD_CRYPTO: return "crypto"; case LD_NET: return "net"; case LD_CONFIG: return "config"; case LD_FS: return "fs"; case LD_PROTOCOL: return "protocol"; case LD_MM: return "mm"; case LD_HTTP: return "http"; case LD_APP: return "app"; case LD_CONTROL: return "control"; case LD_CIRC: return "circ"; case LD_REND: return "rend"; case LD_BUG: return "bug"; case LD_DIR: return "dir"; case LD_DIRSERV: return "dirserv"; case LD_OR: return "or"; case LD_EDGE: return "edge"; case LD_ACCT: return "acct"; case LD_HIST: return "hist"; case LD_HANDSHAKE: return "handshake"; case LD_HEARTBEAT: return "heartbeat"; case LD_CHANNEL: return "channel"; case LD_SCHED: return "sched"; case LD_GUARD: return "guard"; default: return NULL; } } static inline os_log_type_t TORLogTypeFromSeverity(int severity) { switch (severity) { case LOG_ERR: return OS_LOG_TYPE_FAULT; case LOG_WARN: return OS_LOG_TYPE_ERROR; case LOG_NOTICE: case LOG_INFO: return OS_LOG_TYPE_INFO; case LOG_DEBUG: return OS_LOG_TYPE_DEBUG; default: return OS_LOG_TYPE_DEFAULT; } } static void TORLogCallback(int severity, uint64_t domain, const char *msg) { if (domain & LD_NOCB) { return; } os_log_type_t type = TORLogTypeFromSeverity(severity); if (tor_log_callback) { tor_log_callback(type, msg); } else if (@available(iOS 10.0, macOS 10.12, *)) { int index = 0; while (domain >>= 1) { ++index; } if (index >= N_LOGGING_DOMAINS) { return; } static os_log_t logs[N_LOGGING_DOMAINS] = { NULL }; os_log_t log = logs[index]; if (log == NULL) { log = os_log_create(subsystem, TORCategoryForDomain(1u << index)); logs[index] = log; } os_log_with_type(log, type, "%{public}s", msg); } else { TORLegacyLog(type, msg); } } void TORInstallEventLogging(void) { event_log_callback = NULL; event_set_log_callback(TOREventLogCallback); event_enable_debug_logging(EVENT_DBG_ALL); } void TORInstallEventLoggingCallback(tor_log_cb cb) { event_log_callback = cb; event_set_log_callback(TOREventLogCallback); event_enable_debug_logging(EVENT_DBG_ALL); } void TORInstallTorLogging(void) { tor_log_callback = NULL; log_severity_list_t list; set_log_severity_config(LOG_DEBUG, LOG_ERR, &list); add_callback_log(&list, TORLogCallback); } extern void TORInstallTorLoggingCallback(tor_log_cb cb) { tor_log_callback = cb; log_severity_list_t list; set_log_severity_config(LOG_DEBUG, LOG_ERR, &list); add_callback_log(&list, TORLogCallback); } NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/CTor/TORThread.h ================================================ // // TORThread.h // Tor // // Created by Conrad Kramer on 7/19/15. // #import NS_ASSUME_NONNULL_BEGIN @class TORConfiguration; NS_SWIFT_NAME(TorThread) @interface TORThread : NSThread #if __has_feature(objc_class_property) @property (class, readonly, nullable) TORThread *activeThread; #else + (nullable TORThread *)activeThread; #endif - (instancetype)initWithConfiguration:(nullable TORConfiguration *)configuration; - (instancetype)initWithArguments:(nullable NSArray *)arguments NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/CTor/TORThread.m ================================================ // // TORThread.m // Tor // // Created by Conrad Kramer on 7/19/15. // #import #import "TORThread.h" #import "TORLogging.h" #import "TORConfiguration.h" NS_ASSUME_NONNULL_BEGIN static __weak TORThread *_thread = nil; @interface TORThread () @property (nonatomic, readonly, copy, nullable) NSArray *arguments; @end @implementation TORThread + (nullable TORThread *)activeThread { return _thread; } - (instancetype)init { return [self initWithArguments:nil]; } - (instancetype)initWithConfiguration:(nullable TORConfiguration *)configuration { return [self initWithArguments:[configuration compile]]; } - (instancetype)initWithArguments:(nullable NSArray *)arguments { NSAssert(_thread == nil, @"There can only be one TORThread per process"); self = [super init]; if (!self) return nil; _thread = self; _arguments = [arguments copy]; self.name = @"Tor"; return self; } - (void)main { NSArray *arguments = self.arguments; int argc = (int)(arguments.count + 1); char *argv[argc]; argv[0] = "tor"; for (NSUInteger idx = 0; idx < arguments.count; idx++) argv[idx + 1] = (char *)[arguments[idx] UTF8String]; argv[argc] = NULL; //#if DEBUG // event_enable_debug_mode(); //#endif tor_main_configuration_t *cfg = tor_main_configuration_new(); tor_main_configuration_set_command_line(cfg, argc, argv); tor_run_main(cfg); tor_main_configuration_free(cfg); } @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/CTor/TORX25519KeyPair.h ================================================ // // TORX25519KeyPair.h // Tor // // Created by Benjamin Erhart on 11.10.21. // #import #import "TORAuthKey.h" NS_ASSUME_NONNULL_BEGIN /** Class to generate or hold a X25519 public/private key pair encoded in BASE32. */ NS_SWIFT_NAME(TorX25519KeyPair) @interface TORX25519KeyPair : NSObject /** The BASE32 encoded private key. Should be exactly 32 bytes long, resp. 52 characters in BASE32 encoding. */ @property (nonatomic, nullable, readonly) NSString *privateKey; /** The BASE32 encoded public key. Should be exactly 32 bytes long, resp. 52 characters in BASE32 encoding. */ @property (nonatomic, nullable, readonly) NSString *publicKey; /** Generate a new X25519 key pair using Tor's implementation. On iOS 13 and up, another option is also available: CryptoKit's \c Curve25519.KeyAggreement.PrivateKey */ - (instancetype)init; /** Initialize with a pre-generated, BASE32-encoded X25519 key pair. A valid key pair is exactly 32 bytes long, resp. 52 characters in BASE32 encoding. No validity checks are made! It's your responsibility to provide valid key material. @param privateKey The private key, BASE32 encoded. @param publicKey The public key, BASE32 encoded. */ - (instancetype)initWithBase32PrivateKey:(NSString *)privateKey andPublicKey:(NSString *)publicKey; /** Initialize with a pre-generated X25519 key pair. A valid key pair is exactly 32 bytes long. No validity checks are made! It's your responsibility to provide valid key material. @param privateKey The private key. @param publicKey The public key. */ - (instancetype)initWithPrivateKey:(NSData *)privateKey andPublicKey:(NSData *)publicKey; /** Create a private \c TORAuthKey from this key material using the provided domain. @param domain The domain name, this private key is for. Must include the \c .onion TLD! @returns the private \c TORAuthKey of this key pair's private key or \c nil if the \c domain is empty or this class doesn't contain a private key. */ - (nullable TORAuthKey *)getPrivateAuthKeyForDomain:(nonnull NSString *)domain; /** Create a private \c TORAuthKey from this key material using the provided domain. @param url The domain, this private key is for. @returns the private \c TORAuthKey of this key pair's private key or \c nil if this class doesn't contain a private key. */ - (nullable TORAuthKey *)getPrivateAuthKeyForUrl:(nonnull NSURL *)url; /** Create a public \c TORAuthKey from this key material using the provided name. @param name The name used to store that \c TORAuthKey, without the extension! @returns the public \c TORAuthKey of this key pair's public key or \c nil if the \c name is empty or this class doesn't contain a public key. */ - (nullable TORAuthKey *)getPublicAuthKeyWithName:(nonnull NSString *)name; /** Helper method to BASE32 encode raw binary \c NSData into a \c NSString. @param raw The raw binary \c NSData to encode. @returns a BASE32 encoded representation of that binary data. */ + (nullable NSString *)base32Encode:(NSData *)raw; /** Helper method to decode raw binary \c NSData contained in a BASE32 encoded \c NSString. @param encoded The BASE32 encoded data to decode. @returns binary data. */ + (nullable NSData *)base32Decode:(NSString *)encoded; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/CTor/TORX25519KeyPair.m ================================================ // // TORX25519KeyPair.m // Tor // // Created by Benjamin Erhart on 11.10.21. // #import "TORX25519KeyPair.h" #import #import #import @implementation TORX25519KeyPair - (instancetype)init { if ((self = [super init])) { curve25519_keypair_t *keypair = tor_malloc_zero(sizeof(curve25519_keypair_t)); curve25519_init(); curve25519_keypair_generate(keypair, 0); _privateKey = [TORX25519KeyPair base32Encode:[ NSData dataWithBytes:keypair->seckey.secret_key length:sizeof(keypair->seckey.secret_key)]]; _publicKey = [TORX25519KeyPair base32Encode:[ NSData dataWithBytes:keypair->pubkey.public_key length:sizeof(keypair->pubkey.public_key)]]; tor_free(keypair); } return self; } - (instancetype)initWithBase32PrivateKey:(NSString *)privateKey andPublicKey:(NSString *)publicKey { if ((self = [super init])) { _privateKey = privateKey; _publicKey = publicKey; } return self; } - (instancetype)initWithPrivateKey:(NSData *)privateKey andPublicKey:(NSData *)publicKey { if ((self = [super init])) { _privateKey = [TORX25519KeyPair base32Encode:privateKey]; _publicKey = [TORX25519KeyPair base32Encode:publicKey]; } return self; } // MARK: Public Methods - (nullable TORAuthKey *)getPrivateAuthKeyForDomain:(nonnull NSString *)domain { if (domain.length < 1) return nil; NSURLComponents *urlc = [NSURLComponents new]; urlc.scheme = @"http"; urlc.host = domain; NSURL *url = urlc.URL; if (!url) return nil; return [self getPrivateAuthKeyForUrl:url]; } - (nullable TORAuthKey *)getPrivateAuthKeyForUrl:(nonnull NSURL *)url { NSString *privateKey = _privateKey; if (!privateKey) return nil; return [[TORAuthKey alloc] initPrivate:privateKey forDomain:url]; } - (nullable TORAuthKey *)getPublicAuthKeyWithName:(nonnull NSString *)name { NSString *publicKey = _publicKey; if (!publicKey) return nil; if (name.length < 1) return nil; return [[TORAuthKey alloc] initPublic:publicKey withName:name]; } // MARK: Public Class Methods + (nullable NSString *)base32Encode:(NSData *)raw { char dest[base32_encoded_size(raw.length)]; base32_encode(dest, sizeof(dest), raw.bytes, raw.length); return [NSString stringWithUTF8String:dest]; } + (nullable NSData *)base32Decode:(NSString *)encoded { const char *src = [encoded cStringUsingEncoding:kCFStringEncodingUTF8]; char dest[sizeof(src) * 5 / 8]; base32_decode(dest, sizeof(dest), src, sizeof(src)); return [NSData dataWithBytes:dest length:sizeof(dest)]; } @end ================================================ FILE: Tor/Classes/Core/NSBundle+GeoIP.h ================================================ // // NSBundle+GeoIP.h // Tor // // Created by Benjamin Erhart on 02.12.21. // #import NS_ASSUME_NONNULL_BEGIN @interface NSBundle (GeoIP) @property (class, readonly, nullable) NSBundle *geoIpBundle; @property (readonly, nullable) NSURL *geoipFile; @property (readonly, nullable) NSURL *geoip6File; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/NSBundle+GeoIP.m ================================================ // // NSBundle+GeoIP.m // Tor // // Created by Benjamin Erhart on 02.12.21. // #import "NSBundle+GeoIP.h" #import "TORConfiguration.h" @implementation NSBundle (GeoIP) + (NSBundle *)geoIpBundle { NSURL *url = [[NSBundle bundleForClass:TORConfiguration.class] URLForResource:@"GeoIP" withExtension:@"bundle"]; if (!url) return nil; return [NSBundle bundleWithURL:url]; } - (NSURL *)geoipFile { return [self URLForResource:@"geoip" withExtension:nil]; } - (NSURL *)geoip6File { return [self URLForResource:@"geoip6" withExtension:nil]; } @end ================================================ FILE: Tor/Classes/Core/NSCharacterSet+PredefinedSets.h ================================================ // // NSCharacterSet+PredefinedSets.h // Tor // // Created by Benjamin Erhart on 12.12.19. // #import NS_ASSUME_NONNULL_BEGIN @interface NSCharacterSet (PredefinedSets) @property (class, readonly) NSCharacterSet *doubleQuote; @property (class, readonly) NSCharacterSet *longNameDivider; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/NSCharacterSet+PredefinedSets.m ================================================ // // NSCharacterSet+Quotes.m // Tor // // Created by Benjamin Erhart on 12.12.19. // #import "NSCharacterSet+PredefinedSets.h" @implementation NSCharacterSet (PredefinedSets) static NSCharacterSet *_doubleQuote; static NSCharacterSet *_longNameDivider; + (NSCharacterSet *)doubleQuote { if (!_doubleQuote) { _doubleQuote = [NSCharacterSet characterSetWithCharactersInString:@"\""]; } return _doubleQuote; } + (NSCharacterSet *)longNameDivider { if (!_longNameDivider) { _longNameDivider = [NSCharacterSet characterSetWithCharactersInString:@"~="]; } return _longNameDivider; } @end ================================================ FILE: Tor/Classes/Core/TORAuthKey.h ================================================ // // TORAuthKey.h // Tor // // Created by Benjamin Erhart on 29.09.21. // #import NS_ASSUME_NONNULL_BEGIN /** The representation of one private or public v3 onion service authentication key. */ NS_SWIFT_NAME(TorAuthKey) @interface TORAuthKey : NSObject /** The location on disk, where this data was read from/will be written to. */ @property (nonatomic, readonly, nonnull) NSURL *file; /** Flag, if this is a private (\c YES) or a public (\c NO) key. */ @property (atomic, readonly) BOOL isPrivate; /** The full onion service URL. */ @property (nonatomic, readonly, nullable) NSURL *onionAddress; /** The authentication type. Currently only the \c descriptor type is supported. This class will set this value hard for you. */ @property (nonatomic, readonly, nonnull) NSString *authType; /** The key type. Currently only \c x25519 is supported. This class will set this value hard for you. Make sure, that the key you provide actually is of that type! */ @property (nonatomic, readonly, nonnull) NSString *keyType; /** The actual public OR private key. This class doesn't enforce it, but Tor wants this to be in a BASE32 encoded format. Make sure, it is of the type \c x25519, as this is currently the only supported type by Tor and by this class! */ @property (nonatomic, readonly, nonnull) NSString *key; /** Load from a key file on disk. See https://2019.www.torproject.org/docs/tor-manual.html.en#ClientOnionAuthDir and https://2019.www.torproject.org/docs/tor-manual.html.en#_client_authorization for the expected format. @param url The URL to the file containing the key. Expected to be in the correct format. */ - (instancetype)initFromUrl:(NSURL *)url; /** Load from a key file on disk. See https://2019.www.torproject.org/docs/tor-manual.html.en#ClientOnionAuthDir and https://2019.www.torproject.org/docs/tor-manual.html.en#_client_authorization for the expected format. @param path The path to the file containing the key. Expected to be in the correct format. */ - (instancetype)initFromFile:(NSString *)path; /** Create a new private key for a given domain. Normally, the domain will be used as file name, but this method will generate a UUID, if no domain can be found. @param key A \c BASE32 encoded \c x25519 private key. @param url A URL containing the v3 onion service domain for which this key is. */ - (instancetype)initPrivate:(NSString * _Nonnull)key forDomain:(NSURL * _Nonnull)url; /** Create a new public key with the given name. The name wil be used as the file name. @param key A \c BASE32 encoded \c x25519 public key. @param name A name to identify this key to be used as the file name. */ - (instancetype)initPublic:(NSString * _Nonnull)key withName: (NSString *)name; /** Set the base directory for the key. This needs to be called \b before \c persist, if you created a fresh key. @param directory The base directory where this key should be persisted. @see -persist */ - (void)setDirectory:(NSURL *)directory; /** Persists this key to the file system. Call \c setDirectory:, if this instance wasn't created by reading a key from a file! @returns \c YES on success, \c NO on failure. @see -setDirectory: */ - (BOOL)persist; /** Keys are considered equal, if their file URLs match. */ - (BOOL)isEqualToAuthKey:(TORAuthKey *)authKey; /** Checks, if the given file name has the correct extension for either a private or public key. @param url A file URL to a key file. @returns \c YES, if this URL contains a key file extension. */ + (BOOL)isAuthFile:(NSURL *)url; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TORAuthKey.m ================================================ // // TORAuthKey.m // Tor // // Created by Benjamin Erhart on 29.09.21. // #import "TORAuthKey.h" @implementation TORAuthKey - (instancetype)initFromUrl:(NSURL *)url { if ((self = [super init])) { NSError *error; NSString *raw = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error]; if (error) { NSLog(@"[%@] Error while setting key: %@", NSStringFromClass(self.class), error.localizedDescription); return nil; } _file = url; NSMutableArray *parts = [raw componentsSeparatedByString:@":"].mutableCopy; NSString *piece = [TORAuthKey getNextPieceOf:parts]; if (!piece) return nil; if (self.isPrivate) { _onionAddress = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"http://%@.onion", piece]]; piece = [TORAuthKey getNextPieceOf:parts]; if (!piece) return nil; } _authType = piece; piece = [TORAuthKey getNextPieceOf:parts]; if (!piece) return nil; _keyType = piece; piece = [TORAuthKey getNextPieceOf:parts]; if (!piece) return nil; _key = piece; } return self; } - (instancetype)initFromFile:(NSString *)path { return [self initFromUrl:[NSURL fileURLWithPath:path]]; } - (instancetype)initPrivate:(NSString * _Nonnull)key forDomain:(NSURL * _Nonnull)url { if ((self = [super init])) { NSString *name = url.host.stringByDeletingPathExtension; if (!name || [name isEqualToString:@""]) name = [NSUUID UUID].UUIDString; _file = [[NSURL alloc] initFileURLWithPath:[NSString stringWithFormat:@"%@.auth_private", name]]; _onionAddress = url; _authType = @"descriptor"; // Currently the only allowed value. _keyType = @"x25519"; // Currently the only allowed value. _key = key; } return self; } - (instancetype)initPublic:(NSString * _Nonnull)key withName: (NSString * _Nonnull)name { if ((self = [super init])) { if (!name || [name isEqualToString:@""]) name = [NSUUID UUID].UUIDString; _file = [[NSURL alloc] initFileURLWithPath:[NSString stringWithFormat:@"%@.auth", name]]; _onionAddress = nil; _authType = @"descriptor"; // Currently the only allowed value. _keyType = @"x25519"; // Currently the only allowed value. _key = key; } return self; } // MARK: Public Methods - (BOOL)isPrivate { return [_file.pathExtension isEqualToString:@"auth_private"]; } - (void)setDirectory:(NSURL *)directory { NSString *filename = _file.lastPathComponent; if (filename) { NSURL *file = [directory URLByAppendingPathComponent:filename]; if (file) _file = file; } } - (BOOL)persist { NSError *error; [self.description writeToURL:_file atomically:YES encoding:NSUTF8StringEncoding error:&error]; if (error) { NSLog(@"[%@] Error while persisting key: %@", NSStringFromClass(self.class), error.localizedDescription); NSLog(@"%@", self.debugDescription); return NO; } return YES; } - (NSString *)description { if (self.isPrivate) { // Spec says: "MUST NOT have the ".onion" suffix. return [NSString stringWithFormat:@"%@:%@:%@:%@", _onionAddress.host.stringByDeletingPathExtension, _authType, _keyType, _key]; } return [NSString stringWithFormat:@"%@:%@:%@", _authType, _keyType, _key]; } - (NSString *)debugDescription { return [NSString stringWithFormat:@"%@: file: %@, isPrivate: %d, onionAddress: %@, authType: %@, keyType: %@, key: %@", NSStringFromClass(self.class), _file, self.isPrivate, _onionAddress, _authType, _keyType, _key]; } // MARK: Equality - (BOOL)isEqualToAuthKey:(TORAuthKey *)authKey { NSString *url2 = authKey.file.absoluteString; if (!url2) { return NO; } return [_file.absoluteString isEqualToString:url2]; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[TORAuthKey class]]) { return NO; } return [self isEqualToAuthKey:object]; } - (NSUInteger)hash { return _file.hash; } // MARK: Class Methods + (BOOL)isAuthFile:(NSURL *)url { return [url.pathExtension isEqualToString:@"auth"] || [url.pathExtension isEqualToString:@"auth_private"]; } // MARK: Private Methods + (NSString *)getNextPieceOf:(NSMutableArray *)parts { NSString *piece = parts.firstObject; if (parts.count > 0) [parts removeObjectAtIndex: 0]; if (!piece) NSLog(@"Invalid format of auth key file!"); return piece; } @end ================================================ FILE: Tor/Classes/Core/TORCircuit.h ================================================ // // TORCircuit.h // Tor // // Created by Benjamin Erhart on 11.12.19. // // Documentation this class is modelled after: // https://gitlab.torproject.org/tpo/core/torspec/-/raw/main/control-spec.txt // Chapter 4.1.1 Circuit status changed #import #import "TORNode.h" NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(TorCircuit) @interface TORCircuit : NSObject /** Regular expression to identify and extract ID, status and circuit path consisting of "LongNames". Syntax of node "LongNames": https://torproject.gitlab.io/torspec/control-spec.html#general-use-tokens */ @property (class, readonly) NSRegularExpression *mainInfoRegex; /** - Tag: statusLaunched Circuit ID assigned to new circuit. */ @property (class, readonly) NSString *statusLaunched; /** All hops finished, can now accept streams. */ @property (class, readonly) NSString *statusBuilt; /** All hops finished, waiting to see if a circuit with a better guard will be usable. */ @property (class, readonly) NSString *statusGuardWait; /** One more hop has been completed. */ @property (class, readonly) NSString *statusExtended; /** Circuit closed (was not built). */ @property (class, readonly) NSString *statusFailed; /** Circuit closed (was built). */ @property (class, readonly) NSString *statusClosed; /** One-hop circuit, used for tunneled directory conns. */ @property (class, readonly) NSString *buildFlagOneHopTunnel; /** Internal circuit, not to be used for exiting streams. */ @property (class, readonly) NSString *buildFlagIsInternal; /** This circuit must use only high-capacity nodes. */ @property (class, readonly) NSString *buildFlagNeedCapacity; /** This circuit must use only high-uptime nodes. */ @property (class, readonly) NSString *buildFlagNeedUptime; /** Circuit for AP and/or directory request streams. */ @property (class, readonly) NSString *purposeGeneral; /** HS client-side introduction-point circuit. */ @property (class, readonly) NSString *purposeHsClientIntro; /** HS client-side rendezvous circuit; carries AP streams. */ @property (class, readonly) NSString *purposeHsClientRend; /** HS service-side introduction-point circuit. */ @property (class, readonly) NSString *purposeHsServiceIntro; /** HS service-side rendezvous circuit. */ @property (class, readonly) NSString *purposeHsServiceRend; /** Circuit created ahead of time when using HS vanguards, and later repurposed as needed. */ @property (class, readonly) NSString *purposeHsVanguards; /** Reachability-testing circuit; carries no traffic. */ @property (class, readonly) NSString *purposeTesting; /** Circuit built by a controller. */ @property (class, readonly) NSString *purposeController; /** Circuit being kept around to see how long it takes. */ @property (class, readonly) NSString *purposeMeasureTimeout; /** Circuit is part of a conflux multi-path circuit set. https://tpo.pages.torproject.net/core/doc/tor/structcircuit__t.html#a7826adee26af5def133c43d2fe5f83fa */ @property (class, readonly) NSString *purposeConfluxLinked; /** Circuit is in the pending pool usable in future circuit sets. https://tpo.pages.torproject.net/core/doc/tor/structcircuit__t.html#acabcbac5225591a5b7731a6994f438e4 */ @property (class, readonly) NSString *purposeConfluxUnlinked; /** Client-side introduction-point circuit state: Connecting to intro point. */ @property (class, readonly) NSString *hsStateHsciConnecting; /** Client-side introduction-point circuit state: Sent INTRODUCE1; waiting for reply from IP. */ @property (class, readonly) NSString *hsStateHsciIntroSent; /** Client-side introduction-point circuit state: Received reply from IP relay; closing. */ @property (class, readonly) NSString *hsStateHsciDone; /** Client-side rendezvous-point circuit state: Connecting to or waiting for reply from RP. */ @property (class, readonly) NSString *hsStateHscrConnecting; /** Client-side rendezvous-point circuit state: Established RP; waiting for introduction. */ @property (class, readonly) NSString *hsStateHscrEstablishedIdle; /** Client-side rendezvous-point circuit state: Introduction sent to HS; waiting for rend. */ @property (class, readonly) NSString *hsStateHscrEstablishedWaiting; /** Client-side rendezvous-point circuit state: Connected to HS. */ @property (class, readonly) NSString *hsStateHscrJoined; /** Service-side introduction-point circuit state: Connecting to intro point. */ @property (class, readonly) NSString *hsStateHssiConnecting; /** Service-side introduction-point circuit state: Established intro point. */ @property (class, readonly) NSString *hsStateHssiEstablished; /** Service-side rendezvous-point circuit state: Connecting to client's rend point. */ @property (class, readonly) NSString *hsStateHssrConnecting; /** Service-side rendezvous-point circuit state: Connected to client's RP circuit. */ @property (class, readonly) NSString *hsStateHssrJoined; /** No reason given. */ @property (class, readonly) NSString *reasonNone; /** Tor protocol violation. */ @property (class, readonly) NSString *reasonTorProtocol; /** Internal error. */ @property (class, readonly) NSString *reasonInternal; /** A client sent a TRUNCATE command. */ @property (class, readonly) NSString *reasonRequested; /** Not currently operating; trying to save bandwidth. */ @property (class, readonly) NSString *reasonHibernating; /** Out of memory, sockets, or circuit IDs. */ @property (class, readonly) NSString *reasonResourceLimit; /** Unable to reach relay. */ @property (class, readonly) NSString *reasonConnectFailed; /** Connected to relay, but its OR identity was not as expected. */ @property (class, readonly) NSString *reasonOrIdentity; /** The OR connection that was carrying this circuit died. */ @property (class, readonly) NSString *reasonOrConnClosed; /** Circuit construction took too long. */ @property (class, readonly) NSString *reasonTimeout; /** The circuit has expired for being dirty or old. */ @property (class, readonly) NSString *reasonFinished; /** The circuit was destroyed w/o client TRUNCATE. */ @property (class, readonly) NSString *reasonDestroyed; /** Not enough nodes to make circuit. */ @property (class, readonly) NSString *reasonNoPath; /** Request for unknown hidden service. */ @property (class, readonly) NSString *reasonNoSuchService; /** As @c reasonTimeout, except that we had left the circuit open for measurement purposes to see how long it would take to finish. */ @property (class, readonly) NSString *reasonMeasurementExpired; /** The raw data this object is constructed from. The unchanged argument from @c initFromString:. */ @property (readonly, nullable) NSString *raw; /** The circuit ID. Currently only numbers beginning with "1" but Tor spec says, that could change. */ @property (readonly, nullable) NSString *circuitId; /** The circuit status. Should be one of @c statusLaunched, @c statusBuilt, @c statusGuardWait, @c statusExtended, @c statusFailed or @c statusClosed . */ @property (readonly, nullable) NSString *status; /** The circuit path as a list of @c TORNode objects. */ @property (readonly, nullable) NSArray *nodes; /** Build flags of the circuit. Can be any of @c buildFlagOneHopTunnel, @c buildFlagIsInternal, @c buildFlagNeedCapacity, @c buildFlagNeedUptime or a flag which was unknown at the time of writing of this class. */ @property (readonly, nullable) NSArray *buildFlags; /** Circuit purpose. May be one of @c purposeGeneral, @c purposeHsClientIntro, @c purposeHsClientRend, @c purposeHsServiceIntro, @c purposeHsServiceRend, @c purposeTesting, @c purposeController or, @c purposeMeasureTimeout. */ @property (readonly, nullable) NSString *purpose; /** Circuit hidden service state. May be one of @c hsStateHsciConnecting, @c hsStateHsciIntroSent, @c hsStateHsciDone, @c hsStateHscrConnecting, @c hsStateHscrEstablishedIdle, @c hsStateHscrEstablishedWaiting, @c hsStateHscrJoined, @c hsStateHssiConnecting, @c hsStateHssiEstablished, @c hsStateHssrConnecting, @c hsStateHssrJoined or a state which was unknown at the time of writing of this class. */ @property (readonly, nullable) NSString *hsState; /** The rendevouz query. Should be equal the onion address this circuit was used for minus the @c .onion postfix. */ @property (readonly, nullable) NSString *rendQuery; /** The circuit's timestamp at which the circuit was created or cannibalized. */ @property (readonly, nullable) NSDate *timeCreated; /** The @c reason field is provided only for @c FAILED and @c CLOSED events, and only if extended events are enabled. May be any one of @c reasonNone, @c reasonTorProtocol, @c reasonInternal, @c reasonRequested, @c reasonHibernating, @c reasonResourceLimit, @c reasonConnectFailed, @c reasonOrIdentity, @c reasonOrConnClosed, @c reasonTimeout, @c reasonFinished, @c reasonDestroyed, @c reasonNoPath, @c reasonNoSuchService, @c reasonMeasurementExpired or a reason which was unknown at the time of writing of this class. */ @property (readonly, nullable) NSString *reason; /** The @c remoteReason field is provided only when we receive a @c DESTROY or @c TRUNCATE cell, and only if extended events are enabled. It contains the actual reason given by the remote OR for closing the circuit. May be any one of @c reasonNone, @c reasonTorProtocol, @c reasonInternal, @c reasonRequested, @c reasonHibernating, @c reasonResourceLimit, @c reasonConnectFailed, @c reasonOrIdentity, @c reasonOrConnClosed, @c reasonTimeout, @c reasonFinished, @c reasonDestroyed, @c reasonNoPath, @c reasonNoSuchService, @c reasonMeasurementExpired or a reason which was unknown at the time of writing of this class. */ @property (readonly, nullable) NSString *remoteReason; /** The @c socksUsername and @c socksPassword fields indicate the credentials that were used by a SOCKS client to connect to Tor’s SOCKS port and initiate this circuit. */ @property (readonly, nullable) NSString *socksUsername; /** The @c socksUsername and @c socksPassword fields indicate the credentials that were used by a SOCKS client to connect to Tor’s SOCKS port and initiate this circuit. */ @property (readonly, nullable) NSString *socksPassword; /** Extracts all circuit info from a string which should be the response to a "GETINFO circuit-status". See https://torproject.gitlab.io/torspec/control-spec.html#getinfo @param circuitsString A string as returned by "GETINFO circuit-status". */ + (NSArray *)circuitsFromString:(NSString *)circuitsString; - (instancetype)initFromString:(NSString *)circuitString; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TORCircuit.m ================================================ // // TORCircuit.m // Tor // // Created by Benjamin Erhart on 11.12.19. // #import "TORCircuit.h" #import "NSCharacterSet+PredefinedSets.h" @implementation TORCircuit // MARK: Class Properties static NSRegularExpression *_mainInfoRegex; static NSMutableDictionary *_optionsRegexes; static NSDateFormatter *_timestampFormatter; + (NSRegularExpression *)mainInfoRegex { if (!_mainInfoRegex) { _mainInfoRegex = [NSRegularExpression regularExpressionWithPattern: @"(\\w+)\\s+(LAUNCHED|BUILT|GUARD_WAIT|EXTENDED|FAILED|CLOSED)\\s+((?:\\$[\\da-f]+[=~]\\w+(?:,|\\s|\\Z))+)?" options:NSRegularExpressionCaseInsensitive error:nil]; } return _mainInfoRegex; } + (NSString *)statusLaunched { return @"LAUNCHED"; } + (NSString *)statusBuilt { return @"BUILT"; } + (NSString *)statusGuardWait { return @"GUARD_WAIT"; } + (NSString *)statusExtended { return @"EXTENDED"; } + (NSString *)statusFailed { return @"FAILED"; } + (NSString *)statusClosed { return @"CLOSED"; } + (NSString *)buildFlagOneHopTunnel { return @"ONEHOP_TUNNEL"; } + (NSString *)buildFlagIsInternal { return @"IS_INTERNAL"; } + (NSString *)buildFlagNeedCapacity { return @"NEED_CAPACITY"; } + (NSString *)buildFlagNeedUptime { return @"NEED_UPTIME"; } + (NSString *)purposeGeneral { return @"GENERAL"; } + (NSString *)purposeHsClientIntro { return @"HS_CLIENT_INTRO"; } + (NSString *)purposeHsClientRend { return @"HS_CLIENT_REND"; } + (NSString *)purposeHsServiceIntro { return @"HS_SERVICE_INTRO"; } + (NSString *)purposeHsServiceRend { return @"HS_SERVICE_REND"; } + (NSString *)purposeHsVanguards { return @"HS_VANGUARDS"; } + (NSString *)purposeTesting { return @"TESTING"; } + (NSString *)purposeController { return @"CONTROLLER"; } + (NSString *)purposeMeasureTimeout { return @"MEASURE_TIMEOUT"; } + (NSString *)purposeConfluxLinked { return @"CONFLUX_LINKED"; } + (NSString *)purposeConfluxUnlinked { return @"CONFLUX_UNLINKED"; } + (NSString *)hsStateHsciConnecting { return @"HSCI_CONNECTING"; } + (NSString *)hsStateHsciIntroSent { return @"HSCI_INTRO_SENT"; } + (NSString *)hsStateHsciDone { return @"HSCI_DONE"; } + (NSString *)hsStateHscrConnecting { return @"HSCR_CONNECTING"; } + (NSString *)hsStateHscrEstablishedIdle { return @"HSCR_ESTABLISHED_IDLE"; } + (NSString *)hsStateHscrEstablishedWaiting { return @"HSCR_ESTABLISHED_WAITING"; } + (NSString *)hsStateHscrJoined { return @"HSCR_JOINED"; } + (NSString *)hsStateHssiConnecting { return @"HSSI_CONNECTING"; } + (NSString *)hsStateHssiEstablished { return @"HSSI_ESTABLISHED"; } + (NSString *)hsStateHssrConnecting { return @"HSSR_CONNECTING"; } + (NSString *)hsStateHssrJoined { return @"HSSR_JOINED"; } + (NSString *)reasonNone { return @"NONE"; } + (NSString *)reasonTorProtocol { return @"TORPROTOCOL"; } + (NSString *)reasonInternal { return @"INTERNAL"; } + (NSString *)reasonRequested { return @"REQUESTED"; } + (NSString *)reasonHibernating { return @"HIBERNATING"; } + (NSString *)reasonResourceLimit { return @"RESOURCELIMIT"; } + (NSString *)reasonConnectFailed { return @"CONNECTFAILED"; } + (NSString *)reasonOrIdentity { return @"OR_IDENTITY"; } + (NSString *)reasonOrConnClosed { return @"OR_CONN_CLOSED"; } + (NSString *)reasonTimeout { return @"TIMEOUT"; } + (NSString *)reasonFinished { return @"FINISHED"; } + (NSString *)reasonDestroyed { return @"DESTROYED"; } + (NSString *)reasonNoPath { return @"NOPATH"; } + (NSString *)reasonNoSuchService { return @"NOSUCHSERVICE"; } + (NSString *)reasonMeasurementExpired { return @"MEASUREMENT_EXPIRED"; } // MARK: Class Methods + (NSRegularExpression *)regexForOption:(NSString *)option { if (!_optionsRegexes) { _optionsRegexes = [NSMutableDictionary new]; } if (!_optionsRegexes[option]) { _optionsRegexes[option] = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"(?:%@=(.+?)(?:\\s|\\Z))", option] options:NSRegularExpressionCaseInsensitive error:nil]; } return _optionsRegexes[option]; } + (NSDateFormatter *)timestampFormatter { if (!_timestampFormatter) { _timestampFormatter = [NSDateFormatter new]; _timestampFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSSSS"; _timestampFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; _timestampFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; } return _timestampFormatter; } + (NSArray *)circuitsFromString:(NSString *)circuitsString { NSMutableArray *circuits = [NSMutableArray new]; for (NSString *circuitString in [circuitsString componentsSeparatedByString:@"\r\n"]) { if (circuitString.length > 0) { [circuits addObject: [[TORCircuit alloc] initFromString:circuitString]]; } } return circuits; } // MARK: Initializers - (instancetype)initFromString:(NSString *)circuitString { self = [super init]; if (self) { _raw = circuitString; NSRange range = NSMakeRange(0, circuitString.length); NSTextCheckingResult *match = [TORCircuit.mainInfoRegex matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _circuitId = [circuitString substringWithRange:[match rangeAtIndex:1]]; } if (match && [match rangeAtIndex:2].location != NSNotFound) { _status = [circuitString substringWithRange:[match rangeAtIndex:2]]; } if (match && [match rangeAtIndex:3].location != NSNotFound) { NSMutableArray *nodes = [NSMutableArray new]; NSString *path = [circuitString substringWithRange:[match rangeAtIndex:3]]; NSArray *nodesStrings = [path componentsSeparatedByString:@","]; for (NSString *nodeString in nodesStrings) { [nodes addObject: [[TORNode alloc] initFromString: [nodeString stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]]]; } _nodes = nodes; } match = [[TORCircuit regexForOption:@"BUILD_FLAGS"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _buildFlags = [[circuitString substringWithRange:[match rangeAtIndex:1]] componentsSeparatedByString:@","]; } match = [[TORCircuit regexForOption:@"PURPOSE"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _purpose = [circuitString substringWithRange:[match rangeAtIndex:1]]; } match = [[TORCircuit regexForOption:@"HS_STATE"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _hsState = [circuitString substringWithRange:[match rangeAtIndex:1]]; } match = [[TORCircuit regexForOption:@"REND_QUERY"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _rendQuery = [circuitString substringWithRange:[match rangeAtIndex:1]]; } match = [[TORCircuit regexForOption:@"TIME_CREATED"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _timeCreated = [TORCircuit.timestampFormatter dateFromString: [circuitString substringWithRange:[match rangeAtIndex:1]]]; } match = [[TORCircuit regexForOption:@"REASON"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _reason = [circuitString substringWithRange:[match rangeAtIndex:1]]; } match = [[TORCircuit regexForOption:@"REMOTE_REASON"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _remoteReason = [circuitString substringWithRange:[match rangeAtIndex:1]]; } match = [[TORCircuit regexForOption:@"SOCKS_USERNAME"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _socksUsername = [[circuitString substringWithRange:[match rangeAtIndex:1]] stringByTrimmingCharactersInSet:NSCharacterSet.doubleQuote]; } match = [[TORCircuit regexForOption:@"SOCKS_PASSWORD"] matchesInString:circuitString options:0 range:range].firstObject; if (match && [match rangeAtIndex:1].location != NSNotFound) { _socksPassword = [[circuitString substringWithRange:[match rangeAtIndex:1]] stringByTrimmingCharactersInSet:NSCharacterSet.doubleQuote]; } } return self; } // MARK: NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)coder { if ((self = [super init])) { _raw = [coder decodeObjectOfClass:NSString.class forKey:@"raw"]; _circuitId = [coder decodeObjectOfClass:NSString.class forKey:@"circuitId"]; _status = [coder decodeObjectOfClass:NSString.class forKey:@"status"]; _nodes = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSArray.class, TORNode.class]] forKey:@"nodes"]; _buildFlags = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSArray.class, NSString.class]] forKey:@"buildFlags"]; _purpose = [coder decodeObjectOfClass:NSString.class forKey:@"purpose"]; _hsState = [coder decodeObjectOfClass:NSString.class forKey:@"hsState"]; _rendQuery = [coder decodeObjectOfClass:NSString.class forKey:@"rendQuery"]; _timeCreated = [coder decodeObjectOfClass:NSDate.class forKey:@"timeCreated"]; _reason = [coder decodeObjectOfClass:NSString.class forKey:@"reason"]; _remoteReason = [coder decodeObjectOfClass:NSString.class forKey:@"remoteReason"]; _socksUsername = [coder decodeObjectOfClass:NSString.class forKey:@"socksUsername"]; _socksPassword = [coder decodeObjectOfClass:NSString.class forKey:@"socksPassword"]; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.raw forKey:@"raw"]; [coder encodeObject:self.circuitId forKey:@"circuitId"]; [coder encodeObject:self.status forKey:@"status"]; [coder encodeObject:self.nodes forKey:@"nodes"]; [coder encodeObject:self.buildFlags forKey:@"buildFlags"]; [coder encodeObject:self.purpose forKey:@"purpose"]; [coder encodeObject:self.hsState forKey:@"hsState"]; [coder encodeObject:self.rendQuery forKey:@"rendQuery"]; [coder encodeObject:self.timeCreated forKey:@"timeCreated"]; [coder encodeObject:self.reason forKey:@"reason"]; [coder encodeObject:self.remoteReason forKey:@"remoteReason"]; [coder encodeObject:self.socksUsername forKey:@"socksUsername"]; [coder encodeObject:self.socksPassword forKey:@"socksPassword"]; } // MARK: NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p> circuitId=%@, status=%@, nodes=%@, buildFlags=%@, purpose=%@, hsState=%@, rendQuery=%@, timeCreated=%@, reason=%@, remoteReason=%@, socksUsername=%@, socksPassword=%@, raw=%@]", self.class, self, self.circuitId, self.status, self.nodes, self.buildFlags, self.purpose, self.hsState, self.rendQuery, self.timeCreated, self.reason, self.remoteReason, self.socksUsername, self.socksPassword, self.raw]; } @end ================================================ FILE: Tor/Classes/Core/TORConfiguration.h ================================================ // // TORConfiguration.h // Tor // // Created by Conrad Kramer on 8/10/15. // #import NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(TorConfiguration) @interface TORConfiguration : NSObject @property (nonatomic, copy, nullable) NSURL *dataDirectory; @property (nonatomic, copy, nullable) NSURL *cacheDirectory; @property (nonatomic, copy, nullable, readonly) NSURL *controlPortFile; @property (nonatomic, copy, nullable) NSURL *controlSocket; @property (nonatomic, copy, nullable) NSURL *socksURL; @property (nonatomic) NSUInteger socksPort; @property (nonatomic) NSUInteger dnsPort; @property (nonatomic, copy, nullable) NSURL *clientAuthDirectory; @property (nonatomic, copy, nullable) NSURL *hiddenServiceDirectory; @property (nonatomic, copy, nullable, readonly) NSURL *serviceAuthDirectory; @property (nonatomic, copy, nullable) NSURL *geoipFile; @property (nonatomic, copy, nullable) NSURL *geoip6File; @property (nonatomic, copy, nullable) NSURL *logfile; @property (nonatomic) BOOL ignoreMissingTorrc; @property (nonatomic) BOOL cookieAuthentication; @property (nonatomic) BOOL autoControlPort; @property (nonatomic) BOOL avoidDiskWrites; @property (nonatomic) BOOL clientOnly; @property (nonatomic, readonly) BOOL isLocked; @property (nonatomic, copy, nullable, readonly) NSData *cookie; @property (nonatomic, copy, null_resettable) NSMutableDictionary *options; @property (nonatomic, copy, null_resettable) NSMutableArray *arguments; - (NSString *)valueOf:(NSString *)key; - (NSArray *)compile; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TORConfiguration.m ================================================ // // TORConfiguration.m // Tor // // Created by Conrad Kramer on 8/10/15. // #import "TORConfiguration.h" NS_ASSUME_NONNULL_BEGIN @implementation TORConfiguration - (NSMutableDictionary *)options { if (!_options) _options = [NSMutableDictionary new]; return _options; } - (NSMutableArray *)arguments { if (!_arguments) _arguments = [NSMutableArray new]; return _arguments; } - (nullable NSURL *)controlPortFile { return [self.dataDirectory URLByAppendingPathComponent:@"controlport"]; } - (nullable NSURL *)serviceAuthDirectory { return [self.hiddenServiceDirectory URLByAppendingPathComponent:@"authorized_clients"]; } - (BOOL)isLocked { NSURL *url = [self.dataDirectory URLByAppendingPathComponent:@"lock"]; NSString *path = url.path; if (!path || !url.isFileURL) return false; return [NSFileManager.defaultManager fileExistsAtPath:path]; } - (nullable NSData *)cookie { NSURL *url = [self.dataDirectory URLByAppendingPathComponent:@"control_auth_cookie"]; if (!url || !url.isFileURL) return nil; return [[NSData alloc] initWithContentsOfURL:url]; } - (NSString *)valueOf:(NSString *)key { for (NSString *dictKey in self.options.allKeys) { if ([dictKey caseInsensitiveCompare:key] == NSOrderedSame) { return self.options[dictKey]; } } key = [[NSString alloc] initWithFormat:@"--%@", key]; for (NSString *arg in self.arguments) { if ([arg caseInsensitiveCompare:key] == NSOrderedSame) { NSUInteger i = [self.arguments indexOfObject:arg]; if (i + 1 < self.arguments.count) { return self.arguments[i + 1]; } } } return nil; } - (NSArray *)compile { NSMutableArray *arguments = [NSMutableArray new]; if (self.ignoreMissingTorrc) { [arguments addObjectsFromArray:@[@"--allow-missing-torrc", @"--ignore-missing-torrc"]]; } if (self.avoidDiskWrites) { [arguments addObjectsFromArray:@[@"--AvoidDiskWrites", @"1"]]; } if (self.clientOnly) { [arguments addObjectsFromArray:@[@"--ClientOnly", @"1"]]; } NSString *dataDir = self.dataDirectory.path; if (self.dataDirectory.isFileURL && dataDir) { [arguments addObjectsFromArray:@[@"--DataDirectory", dataDir]]; } NSString *cacheDir = self.cacheDirectory.path; if (self.cacheDirectory.isFileURL && cacheDir) { [arguments addObjectsFromArray:@[@"--CacheDirectory", cacheDir]]; } if (self.cookieAuthentication) { [arguments addObjectsFromArray:@[@"--CookieAuthentication", @"1"]]; } NSString *controlPortFile = self.controlPortFile.path; if (self.autoControlPort && self.controlPortFile.isFileURL && controlPortFile) { [arguments addObjectsFromArray:@[@"--ControlPort", @"auto", @"--ControlPortWriteToFile", controlPortFile]]; } NSString *controlSocket = self.controlSocket.path; if (self.controlSocket.isFileURL && controlSocket) { [arguments addObjectsFromArray:@[@"--ControlSocket", controlSocket]]; } NSString *socksPath = self.socksURL.path; if (self.socksURL.isFileURL && socksPath) { [arguments addObjectsFromArray:@[@"--SocksPort", [NSString stringWithFormat:@"unix:%@", socksPath]]]; } if (self.socksPort > 0) { [arguments addObjectsFromArray:@[@"--SocksPort", [NSString stringWithFormat:@"%lu", (unsigned long)self.socksPort]]]; } if (self.dnsPort > 0) { [arguments addObjectsFromArray:@[@"--DnsPort", [NSString stringWithFormat:@"%lu", (unsigned long)self.dnsPort]]]; } NSString *clientAuthDir = self.clientAuthDirectory.path; if (self.clientAuthDirectory.isFileURL && clientAuthDir) { [arguments addObjectsFromArray:@[@"--ClientOnionAuthDir", clientAuthDir]]; } NSString *hiddenServiceDir = self.hiddenServiceDirectory.path; if (!self.clientOnly && self.hiddenServiceDirectory.isFileURL && hiddenServiceDir) { [arguments addObjectsFromArray:@[@"--HiddenServiceDir", hiddenServiceDir]]; } NSString *geoipFile = self.geoipFile.path; if (self.geoipFile.isFileURL && geoipFile) { [arguments addObjectsFromArray:@[@"--GeoIPFile", geoipFile]]; } NSString *geoip6File = self.geoip6File.path; if (self.geoip6File.isFileURL && geoip6File) { [arguments addObjectsFromArray:@[@"--GeoIPv6File", geoip6File]]; } NSString *logfile = self.logfile.path; if (self.logfile.isFileURL && logfile) { [arguments addObjectsFromArray:@[@"--Log", [NSString stringWithFormat:@"notice file %@", logfile]]]; } [arguments addObjectsFromArray:self.arguments]; for (NSString *key in self.options.allKeys) { [arguments addObject:[NSString stringWithFormat:@"--%@", key]]; NSString *value = self.options[key]; if (value) [arguments addObject:value]; } return arguments; } @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TORControlCommand.h ================================================ // // TORControlCommand.h // Tor // // Created by Denis Kutlubaev on 30.03.2021. // #ifndef TORControlCommand_h #define TORControlCommand_h /** TOR control commands https://github.com/torproject/torspec/blob/master/control-spec.txt */ static NSString * const TORCommandAuthenticate = @"AUTHENTICATE"; static NSString * const TORCommandSignalShutdown = @"SIGNAL SHUTDOWN"; static NSString * const TORCommandResetConf = @"RESETCONF"; static NSString * const TORCommandSetConf = @"SETCONF"; static NSString * const TORCommandSetEvents = @"SETEVENTS"; static NSString * const TORCommandGetInfo = @"GETINFO"; static NSString * const TORCommandSignalReload = @"SIGNAL RELOAD"; static NSString * const TORCommandSignalNewnym = @"SIGNAL NEWNYM"; static NSString * const TORCommandCloseCircuit = @"CLOSECIRCUIT"; #endif /* TORControlCommand_h */ ================================================ FILE: Tor/Classes/Core/TORControlReplyCode.h ================================================ // // TORControlReplyCode.h // Tor // // Created by Denis Kutlubaev on 30.03.2021. // #ifndef TORControlReplyCode_h #define TORControlReplyCode_h /** TOR control reply codes https://github.com/torproject/torspec/blob/master/control-spec.txt The following codes are defined: 250 OK 251 Operation was unnecessary [Tor has declined to perform the operation, but no harm was done.] 451 Resource exhausted 500 Syntax error: protocol 510 Unrecognized command 511 Unimplemented command 512 Syntax error in command argument 513 Unrecognized command argument 514 Authentication required 515 Bad authentication 550 Unspecified Tor error 551 Internal error [Something went wrong inside Tor, so that the client's request couldn't be fulfilled.] 552 Unrecognized entity [A configuration key, a stream ID, circuit ID, event, mentioned in the command did not actually exist.] 553 Invalid configuration value [The client tried to set a configuration option to an incorrect, ill-formed, or impossible value.] 554 Invalid descriptor 555 Unmanaged entity 650 Asynchronous event notification */ typedef NS_ENUM(NSInteger, TORControlReplyCode) { TORControlReplyCodeOK = 250, TORControlReplyCodeOperationWasUnnecessary = 251, TORControlReplyCodeResourceExhaused = 451, TORControlReplyCodeSyntaxErrorProtocol = 500, TORControlReplyCodeUnrecognizedCommand = 510, TORControlReplyCodeUnimplementedCommand = 511, TORControlReplyCodeSyntaxErrorInCommandArgument = 512, TORControlReplyCodeUnrecognizedCommandArgument = 513, TORControlReplyCodeAuthenticationRequired = 514, TORControlReplyCodeBadAuthentication = 515, TORControlReplyCodeUnspecifiedTorError = 550, TORControlReplyCodeInternalError = 551, TORControlReplyCodeUnrecognizedEntity = 552, TORControlReplyCodeInvalidConfigurationValue = 553, TORControlReplyCodeInvalidDescriptor = 554, TORControlReplyCodeUnmanagedEntity = 555, TORControlReplyCodeAsynchronousEventNotification = 650 }; #endif /* TORControlReplyCode_h */ ================================================ FILE: Tor/Classes/Core/TORController.h ================================================ // // TORController.h // Tor // // Created by Conrad Kramer on 5/10/14. // #import #import "TORCircuit.h" #ifdef __cplusplus #define TOR_EXTERN extern "C" __attribute__((visibility ("default"))) #else #define TOR_EXTERN extern __attribute__((visibility ("default"))) #endif NS_ASSUME_NONNULL_BEGIN typedef BOOL (^TORObserverBlock)(NSArray *codes, NSArray *lines, BOOL *stop); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 TOR_EXTERN NSErrorDomain const TORControllerErrorDomain; #else TOR_EXTERN NSString * const TORControllerErrorDomain; #endif NS_SWIFT_NAME(TorController) @interface TORController : NSObject @property (nonatomic, readonly, copy) NSOrderedSet *events; @property (nonatomic, readonly, getter=isConnected) BOOL connected; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithSocketURL:(NSURL *)url NS_DESIGNATED_INITIALIZER; - (instancetype)initWithSocketHost:(NSString *)host port:(in_port_t)port NS_DESIGNATED_INITIALIZER; - (instancetype)initWithControlPortFile:(NSURL *)file; - (BOOL)connect:(out NSError **)error; - (void)disconnect; // Commands - (void)authenticateWithData:(NSData *)data completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion; - (void)resetConfForKey:(NSString *)key completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion; - (void)setConfForKey:(NSString *)key withValue:(NSString *)value completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion; - (void)setConfs:(NSArray *)configs completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion; - (void)listenForEvents:(NSArray *)events completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion; - (void)getInfoForKeys:(NSArray *)keys completion:(void (^)(NSArray *values))completion; // TODO: Provide errors - (void)getSessionConfiguration:(void (^)(NSURLSessionConfiguration * __nullable configuration))completion; - (void)sendCommand:(NSString *)command arguments:(nullable NSArray *)arguments data:(nullable NSData *)data observer:(TORObserverBlock)observer; /** Get a list of all currently available circuits with detailed information about their nodes. @note There's no clear way to determine, which circuit actually was used by a specific request. @param completion The callback upon completion of the task. Will return A list of `TORCircuit`s . Empty if no circuit could be found. */ - (void)getCircuits:(void (^)(NSArray * _Nonnull circuits))completion; /** Resets the Tor connection: Sends "SIGNAL RELOAD" and "SIGNAL NEWNYM" to the Tor thread. See https://torproject.gitlab.io/torspec/control-spec.html#signal @param completion Completion callback. Will return true, if signal calls where successful, false if not. */ - (void)resetConnection:(void (^__nullable)(BOOL success))completion; /** Try to close a list of circuits identified by their IDs. If some closings weren't successful, the most obvious reason would be, that the circuit with the given ID doesn't exist (anymore). So in many circumstances, you can still consider that an ok outcome. @param circuitIds List of circuit IDs. @param completion Completion callback. Will return true, if *all* closings were successful, false, if *at least one* closing failed. */ - (void)closeCircuitsByIds:(NSArray *)circuitIds completion:(void (^__nullable)(BOOL success))completion; /** Try to close a list of given circuits. The given circuits are invalid afterwards, as you just closed them. You should throw them away on completion. @param circuits List of circuits to close. @param completion Completion callback. Will return true, if *all* closings were successful, false, if *at least one* closing failed. */ - (void)closeCircuits:(NSArray *)circuits completion:(void (^__nullable)(BOOL success))completion; /** Resolve countries of given `TORNode`s and updates their `countryCode` property on success. Nodes which already contain a `countryCode` will be ignored. IPv4 addresses will be preferred, if Tor is able to resolve IPv4 addresses (if it has loaded the IPv4 geoip database), and if the node has a `ipv4Address` property of non-zero length. @param nodes List of `TORNode`s to resolve countries for. @param testCapabilities Ask Tor first, if it is actually able to resolve. (If GeoDB databases are loaded.) Pass NO, if you're sure that Tor is able to to save on queries. @param completion Completion callback. */ - (void)resolveCountriesOfNodes:(NSArray * _Nullable)nodes testCapabilities:(BOOL)testCapabilities completion:(void (^__nullable)(void))completion; // Observers - (id)addObserverForCircuitEstablished:(void (^)(BOOL established))block; - (id)addObserverForStatusEvents:(BOOL (^)(NSString *type, NSString *severity, NSString *action, NSDictionary * __nullable arguments))block; - (void)removeObserver:(nullable id)observer; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TORController.m ================================================ // // TORController.m // Tor // // Created by Conrad Kramer on 5/10/14. // #import "TORController.h" #import #import #import #import #import "TORControlReplyCode.h" #import "TORControlCommand.h" #import "NSCharacterSet+PredefinedSets.h" NS_ASSUME_NONNULL_BEGIN #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 NSErrorDomain const TORControllerErrorDomain = @"TORControllerErrorDomain"; #else NSString * const TORControllerErrorDomain = @"TORControllerErrorDomain"; #endif static NSString * const TORControllerMidReplyLineSeparator = @"-"; static NSString * const TORControllerDataReplyLineSeparator = @"+"; static NSString * const TORControllerEndReplyLineSeparator = @" "; @implementation TORController { NSURL *_url; NSString *_host; in_port_t _port; dispatch_io_t _channel; NSMutableArray *_blocks; int sock; } + (dispatch_queue_t)controlQueue { static dispatch_queue_t controlQueue = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ controlQueue = dispatch_queue_create("org.torproject.ios.control", DISPATCH_QUEUE_SERIAL); }); return controlQueue; } - (instancetype)initWithSocketURL:(NSURL *)url { NSParameterAssert(url.fileURL); self = [super init]; if (!self) return nil; _url = [url copy]; _blocks = [NSMutableArray new]; [self connect:nil]; return self; } - (instancetype)initWithSocketHost:(NSString *)host port:(in_port_t)port { NSParameterAssert(host && port); self = [super init]; if (!self) return nil; _host = [host copy]; _port = port; _blocks = [NSMutableArray new]; [self connect:nil]; return self; } - (instancetype)initWithControlPortFile:(NSURL *)file { NSParameterAssert(file.fileURL); // Expected example content: // PORT=127.0.0.1:49651 NSError *error; NSString *content = [[NSString alloc] initWithContentsOfURL:file encoding:NSUTF8StringEncoding error:&error]; NSAssert(!error, error.localizedDescription); NSArray *address = [[[content componentsSeparatedByString:@"="] .lastObject stringByTrimmingCharactersInSet: NSCharacterSet.whitespaceAndNewlineCharacterSet] componentsSeparatedByString:@":"]; NSString *host = address.firstObject; NSAssert(host, @"Provided file doesn't seem to be a valid control port file as written by Tor!"); NSInteger port = address.lastObject.integerValue; return [self initWithSocketHost:host port:(in_port_t) port]; } - (void)dealloc { if (_channel) dispatch_io_close(_channel, DISPATCH_IO_STOP); } #pragma mark - Connecting - (BOOL)isConnected { return (_channel != nil); } - (BOOL)connect:(out NSError **)error { if (_channel) { return NO; } self->sock = -1; _events = [NSOrderedSet new]; if (_url) { struct sockaddr_un control_addr = {}; control_addr.sun_family = AF_UNIX; strncpy(control_addr.sun_path, _url.fileSystemRepresentation, sizeof(control_addr.sun_path) - 1); control_addr.sun_len = (unsigned char)SUN_LEN(&control_addr); self->sock = socket(AF_UNIX, SOCK_STREAM, 0); if (connect(self->sock, (struct sockaddr *)&control_addr, control_addr.sun_len) == -1) { if (error) { *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; } return NO; } } else if (_host && _port) { struct in_addr addr; if (inet_aton(_host.UTF8String, &addr) == 0) { if (error) { *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; } return NO; } struct sockaddr_in control_addr = {}; control_addr.sin_family = AF_INET; control_addr.sin_port = htons(_port); control_addr.sin_addr = addr; control_addr.sin_len = (__uint8_t)sizeof(control_addr); self->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (connect(self->sock, (struct sockaddr *)&control_addr, control_addr.sin_len) == -1) { if (error) { *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; } return NO; } } else { return NO; } __weak TORController *weakSelf = self; _channel = dispatch_io_create(DISPATCH_IO_STREAM, self->sock, [self.class controlQueue], ^(int __unused error) { close(self->sock); TORController *strongSelf = weakSelf; if (strongSelf) { strongSelf->_channel = nil; } }); if (!_channel) { return NO; } NSData *separator = [NSData dataWithBytes:"\x0d\x0a" length:2]; // also known as CR-LF or "\r\n" NSData *period = [NSData dataWithBytes:"." length:1]; NSSet *lineSeparators = [NSSet setWithObjects:TORControllerMidReplyLineSeparator, TORControllerDataReplyLineSeparator, TORControllerEndReplyLineSeparator, nil]; __block NSMutableData *buffer = [NSMutableData new]; __block NSMutableArray *codes = [NSMutableArray new]; __block NSMutableArray *lines = [NSMutableArray new]; __block BOOL dataBlock = NO; dispatch_io_set_low_water(_channel, 1); dispatch_io_read(_channel, 0, SIZE_MAX, [self.class controlQueue], ^(bool __unused done, dispatch_data_t data, int __unused error) { [buffer appendData:(NSData *)data]; NSRange separatorRange; NSRange remainingRange = NSMakeRange(0, buffer.length); while ((separatorRange = [buffer rangeOfData:separator options:0 range:remainingRange]).location != NSNotFound) { NSUInteger lineLength = separatorRange.location - remainingRange.location; NSRange lineRange = NSMakeRange(remainingRange.location, lineLength); remainingRange = NSMakeRange(remainingRange.location + lineLength + separator.length, remainingRange.length - lineLength - separator.length); if (dataBlock) { NSData *lineData = [buffer subdataWithRange:lineRange]; if ([lineData isEqualToData:period]) { dataBlock = NO; } else { if (lines.count > 0) { if (lines.lastObject.length > 0) { NSMutableData *lastData = lines.lastObject.mutableCopy; // BUGFIX: Add in separator again. It is needed to pick apart multi-line results later! [lastData appendData:separator]; [lastData appendData:lineData]; [lines replaceObjectAtIndex:(lines.count - 1) withObject:lastData]; } else { [lines replaceObjectAtIndex:(lines.count - 1) withObject:lineData]; } } else { [lines addObject:lineData]; } } continue; } if (lineRange.length < 4) { continue; } NSString *statusCodeString = [[NSString alloc] initWithData:[buffer subdataWithRange:NSMakeRange(lineRange.location, 3)] encoding:NSUTF8StringEncoding]; if ([statusCodeString rangeOfCharacterFromSet:NSCharacterSet.decimalDigitCharacterSet.invertedSet].location != NSNotFound) { continue; } NSString *lineTypeString = [[NSString alloc] initWithData:[buffer subdataWithRange:NSMakeRange(lineRange.location + 3, 1)] encoding:NSUTF8StringEncoding]; if (![lineSeparators containsObject:lineTypeString]) { continue; } [codes addObject:@(statusCodeString.integerValue)]; [lines addObject:[buffer subdataWithRange:NSMakeRange(lineRange.location + 4, lineRange.length - 4)]]; [buffer replaceBytesInRange:NSMakeRange(0, remainingRange.location) withBytes:NULL length:0]; remainingRange.location = 0; if ([lineTypeString isEqualToString:TORControllerDataReplyLineSeparator]) { dataBlock = YES; } if ([lineTypeString isEqualToString:TORControllerEndReplyLineSeparator]) { NSArray *commandCodes = codes; NSArray *commandLines = lines; codes = [NSMutableArray new]; lines = [NSMutableArray new]; TORController *strongSelf = weakSelf; if (!strongSelf) { continue; } for (TORObserverBlock observer in [strongSelf->_blocks copy]) { BOOL stop = NO; BOOL handled = observer(commandCodes, commandLines, &stop); if (stop) { [strongSelf->_blocks removeObject:observer]; } if (handled) { break; } } } } }); return YES; } - (void)disconnect { [self sendCommand:TORCommandSignalShutdown arguments:nil data:nil observer:^BOOL(NSArray * __unused codes, NSArray * __unused lines, BOOL * __unused stop) { shutdown(self->sock, SHUT_RDWR); self->_channel = nil; return YES; }]; } #pragma mark - Receiving Responses - (id)addObserverForCircuitEstablished:(void (^)(BOOL established))block { NSParameterAssert(block); NSString *event = @"STATUS_CLIENT"; id observer = [self addObserverForStatusEvents:^(NSString *type, NSString * __unused severity, NSString *action, NSDictionary * __unused arguments) { if ([type isEqualToString:event]) { if ([action isEqualToString:@"CIRCUIT_ESTABLISHED"]) { block(YES); return YES; } else if ([action isEqualToString:@"CIRCUIT_NOT_ESTABLISHED"]) { block(NO); return YES; } } return NO; }]; void (^completion)(BOOL, NSError *) = ^(BOOL success, NSError * __unused error) { if (!success) [self removeObserver:observer]; [self getInfoForKeys:@[@"status/circuit-established"] completion:^(NSArray *values) { if (values.count != 1) return [self removeObserver:observer]; if (block) block([values[0] boolValue]); }]; }; dispatch_async([self.class controlQueue], ^{ if ([self->_events containsObject:event]) { completion(YES, nil); } else { NSMutableOrderedSet *events = [self->_events mutableCopy]; [events addObject:event]; [self listenForEvents:events.array completion:completion]; } }); return observer; } - (id)addObserverForStatusEvents:(BOOL (^)(NSString *type, NSString *severity, NSString *action, NSDictionary *arguments))block { NSParameterAssert(block); return [self addObserver:^(NSArray *codes, NSArray *lines, BOOL * __unused stop) { if (codes.firstObject.integerValue != 650) return NO; NSString *replyString = lines.firstObject ? [[NSString alloc] initWithData:(NSData * _Nonnull)lines.firstObject encoding:NSUTF8StringEncoding] : @""; if (![replyString hasPrefix:@"STATUS_"]) return NO; NSArray *components = [TORController parseStatus:replyString]; if (components.count < 3) return NO; NSMutableDictionary *arguments = nil; if (components.count > 3) { arguments = [NSMutableDictionary new]; for (NSString *argument in [components subarrayWithRange:NSMakeRange(3, components.count - 3)]) { NSArray *keyValuePair = [argument componentsSeparatedByString:@"="]; if (keyValuePair.count == 2) { NSString *value = [keyValuePair[1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; [arguments setObject:value forKey:keyValuePair[0]]; } } } NSString *type = (NSString * _Nonnull)components.firstObject; return block(type, components[1], components[2], arguments); }]; } - (id)addObserver:(TORObserverBlock)observer { NSParameterAssert(observer); dispatch_async([self.class controlQueue], ^{ [self->_blocks addObject:observer]; }); return observer; } - (void)removeObserver:(nullable id)observer { if (!observer) return; dispatch_async([self.class controlQueue], ^{ [self->_blocks removeObject:(id _Nonnull)observer]; }); } #pragma mark - Sending Commands - (void)authenticateWithData:(NSData *)data completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion { NSMutableString *hexString = [NSMutableString new]; for (NSUInteger idx = 0; idx < data.length; idx++) [hexString appendFormat:@"%02x", ((const unsigned char *)data.bytes)[idx]]; [self sendCommand:TORCommandAuthenticate arguments:(hexString.length ? @[hexString] : nil) data:nil observer:^BOOL(NSArray *codes, NSArray *lines, BOOL *stop) { NSUInteger code = codes.firstObject.unsignedIntegerValue; if (code != TORControlReplyCodeOK && code != TORControlReplyCodeBadAuthentication) return NO; NSString *message = lines.firstObject ? [[NSString alloc] initWithData:(NSData * _Nonnull)lines.firstObject encoding:NSUTF8StringEncoding] : @""; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey, nil]; BOOL success = (code == TORControlReplyCodeOK && [message isEqualToString:@"OK"]); if (completion) completion(success, success ? nil : [NSError errorWithDomain:TORControllerErrorDomain code:code userInfo:userInfo]); *stop = YES; return YES; }]; } - (void)resetConfForKey:(NSString *)key completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion { [self sendCommand:TORCommandResetConf arguments:@[key] data:nil observer:^BOOL(NSArray *codes, NSArray *lines, BOOL *stop) { NSUInteger code = codes.firstObject.unsignedIntegerValue; if (code != TORControlReplyCodeOK && code != TORControlReplyCodeBadAuthentication) return NO; NSString *message = lines.firstObject ? [[NSString alloc] initWithData:(NSData * _Nonnull)lines.firstObject encoding:NSUTF8StringEncoding] : @""; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey, nil]; BOOL success = (code == TORControlReplyCodeOK && [message isEqualToString:@"OK"]); if (completion) completion(success, success ? nil : [NSError errorWithDomain:TORControllerErrorDomain code:code userInfo:userInfo]); *stop = YES; return YES; }]; } - (void)setConfForKey:(NSString *)key withValue:(NSString *)value completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion { NSString *arg = [NSString stringWithFormat:@"%@=%@", key, value]; [self sendCommand:TORCommandSetConf arguments:@[arg] data:nil observer:^BOOL(NSArray *codes, NSArray *lines, BOOL *stop) { NSUInteger code = codes.firstObject.unsignedIntegerValue; if (code != TORControlReplyCodeOK && code != TORControlReplyCodeBadAuthentication) return NO; NSString *message = lines.firstObject ? [[NSString alloc] initWithData:(NSData * _Nonnull)lines.firstObject encoding:NSUTF8StringEncoding] : @""; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey, nil]; BOOL success = (code == TORControlReplyCodeOK && [message isEqualToString:@"OK"]); if (completion) completion(success, success ? nil : [NSError errorWithDomain:TORControllerErrorDomain code:code userInfo:userInfo]); *stop = YES; return YES; }]; } - (void)setConfs:(NSArray *)configs completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion { NSMutableArray *conf_arg = [[NSMutableArray alloc] init]; for (NSDictionary *config in configs) { NSString *key = [config objectForKey:@"key"]; NSString *value = [config objectForKey:@"value"]; NSString *arg = [NSString stringWithFormat:@"%@=%@", key, value]; [conf_arg addObject:arg]; } [self sendCommand:TORCommandSetConf arguments:conf_arg data:nil observer:^BOOL(NSArray *codes, NSArray *lines, BOOL *stop) { NSUInteger code = codes.firstObject.unsignedIntegerValue; if (code != TORControlReplyCodeOK && code != TORControlReplyCodeBadAuthentication) return NO; NSString *message = lines.firstObject ? [[NSString alloc] initWithData:(NSData * _Nonnull)lines.firstObject encoding:NSUTF8StringEncoding] : @""; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey, nil]; BOOL success = (code == TORControlReplyCodeOK && [message isEqualToString:@"OK"]); if (completion) completion(success, success ? nil : [NSError errorWithDomain:TORControllerErrorDomain code:code userInfo:userInfo]); *stop = YES; return YES; }]; } - (void)listenForEvents:(NSArray *)events completion:(void (^__nullable)(BOOL success, NSError * __nullable error))completion { [self sendCommand:TORCommandSetEvents arguments:events data:nil observer:^BOOL(NSArray *codes, NSArray *lines, BOOL *stop) { NSUInteger code = codes.firstObject.unsignedIntegerValue; if (code != TORControlReplyCodeOK && code != TORControlReplyCodeUnrecognizedEntity) return NO; NSString *message = lines.firstObject ? [[NSString alloc] initWithData:(NSData * _Nonnull)lines.firstObject encoding:NSUTF8StringEncoding] : @""; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey, nil]; BOOL success = (code == TORControlReplyCodeOK && [message isEqualToString:@"OK"]); if (success) self->_events = [NSOrderedSet orderedSetWithArray:events]; if (completion) completion(success, success ? nil : [NSError errorWithDomain:TORControllerErrorDomain code:code userInfo:userInfo]); *stop = YES; return YES; }]; } - (void)getInfoForKeys:(NSArray *)keys completion:(void (^)(NSArray *values))completion { NSData *ok = [NSData dataWithBytes:"OK" length:2]; [self sendCommand:TORCommandGetInfo arguments:keys data:nil observer:^BOOL(NSArray *codes, NSArray *lines, BOOL *stop) { *stop = YES; if (lines.count - 1 != keys.count || codes.count != lines.count) { if (completion) { completion(@[]); } return NO; } if (codes.lastObject.integerValue != TORControlReplyCodeOK || ![lines.lastObject isEqual:ok]) { if (completion) { completion(@[]); } return NO; } NSMutableDictionary *info = [NSMutableDictionary new]; for (NSUInteger i = 0; i < codes.count - 1; i++) { if (codes[i].integerValue == TORControlReplyCodeOK) { NSString *string = [[NSString alloc] initWithData:lines[i] encoding:NSUTF8StringEncoding]; if (!string) { completion(@[]); return NO; } NSRange pos = [string rangeOfString:@"="]; if (pos.location != NSNotFound) { NSString *key = [[string substringToIndex:pos.location] stringByTrimmingCharactersInSet: NSCharacterSet.doubleQuote]; if ([keys containsObject:key]) { info[key] = [[string substringFromIndex:pos.location + pos.length] stringByTrimmingCharactersInSet:NSCharacterSet.doubleQuote]; } else { if (completion) { completion(@[]); return NO; } } } } } NSMutableArray *values = [NSMutableArray new]; for (NSString *key in keys) { [values addObject:(info[key] ?: [NSNull null])]; } if (completion) { completion(values); } return YES; }]; } - (void)getSessionConfiguration:(void (^)(NSURLSessionConfiguration * __nullable configuration))completion { [self getInfoForKeys:@[@"net/listeners/socks"] completion:^(NSArray *values) { if (values.count != 1) return completion(nil); NSArray *components = [values.firstObject componentsSeparatedByString:@":"]; if (components.count != 2) return completion(nil); if ([components[0] isEqualToString:@"unix"]) return completion(nil); // TODO: Provide error if ([components[1] rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound) return completion(nil); // TODO: Provide error NSString *host = components[0]; // Replace 127.0.0.1 with localhost, as without this, there's a strange bug // triggered: It won't resolve .onion addresses, but *only on real devices*. // So, on a real device, there's probably the wrong DNS resolver used, which // would mean, DNS queries were leaking, too. if ([host isEqualToString:@"127.0.0.1"]) { host = @"localhost"; } NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; configuration.connectionProxyDictionary = @{(id)kCFProxyTypeKey: (id)kCFProxyTypeSOCKS, (id)kCFStreamPropertySOCKSProxyHost: host, (id)kCFStreamPropertySOCKSProxyPort: @([components[1] integerValue])}; completion(configuration); }]; } - (void)sendCommand:(NSString *)command arguments:(nullable NSArray *)arguments data:(nullable NSData *)data observer:(TORObserverBlock)observer { NSParameterAssert(command.length); if (!_channel) { return; } if (arguments == nil) { arguments = @[]; } NSString *argumentsString = [[@[command] arrayByAddingObjectsFromArray:(NSArray * _Nonnull)arguments] componentsJoinedByString:@" "]; NSMutableData *commandData = [NSMutableData new]; if (data.length) { [commandData appendBytes:"+" length:1]; } [commandData appendData:(NSData * _Nonnull)[argumentsString dataUsingEncoding:NSUTF8StringEncoding]]; [commandData appendBytes:"\r\n" length:2]; if (data.length) { [commandData appendData:(NSData * _Nonnull)data]; [commandData appendBytes:"\r\n.\r\n" length:5]; } dispatch_data_t dispatchData = dispatch_data_create(commandData.bytes, commandData.length, [self.class controlQueue], DISPATCH_DATA_DESTRUCTOR_DEFAULT); dispatch_io_write(_channel, 0, dispatchData, [self.class controlQueue], ^(bool done, dispatch_data_t __unused data, int error) { if (done && !error && observer) { [self->_blocks insertObject:observer atIndex:0]; } }); } - (void)getCircuits:(void (^)(NSArray * _Nonnull circuits))completion { [self getInfoForKeys:@[@"circuit-status"] completion:^(NSArray * _Nonnull values) { if (values.count < 1) { if (completion) { completion(@[]); } return; } NSArray *circuits = [TORCircuit circuitsFromString:(NSString * _Nonnull)values.firstObject]; NSMutableArray *ipResolveCalls = [NSMutableArray new]; NSMutableArray *map = [NSMutableArray new]; for (TORCircuit *circuit in circuits) { for (TORNode *node in circuit.nodes) { [ipResolveCalls addObject:[NSString stringWithFormat:@"ns/id/%@", node.fingerprint]]; [map addObject:node]; } } [self getInfoForKeys:ipResolveCalls completion:^(NSArray * _Nonnull values) { for (NSUInteger i = 0; i < values.count; i++) { [map[i] acquireIpAddressesFromNsResponse:values[i]]; } NSMutableArray *nodes = [NSMutableArray new]; for (TORCircuit *circuit in circuits) { [nodes addObjectsFromArray:circuit.nodes]; } [self resolveCountriesOfNodes:nodes testCapabilities:YES completion:^{ if (completion) { completion(circuits); } }]; }]; }]; } - (void)resetConnection:(void (^__nullable)(BOOL success))completion { [self sendCommand:TORCommandSignalReload arguments:nil data:nil observer: ^BOOL(NSArray * _Nonnull codes, NSArray * _Nonnull __unused lines, BOOL * _Nonnull stop) { if (codes.firstObject.integerValue == TORControlReplyCodeOK) { [self sendCommand:TORCommandSignalNewnym arguments:nil data:nil observer: ^BOOL(NSArray * _Nonnull codes, NSArray * _Nonnull __unused lines, BOOL * _Nonnull stop) { if (completion) { completion(codes.firstObject.integerValue == TORControlReplyCodeOK); } *stop = YES; return YES; }]; } else { if (completion) { completion(NO); } } *stop = YES; return YES; }]; } - (void)closeCircuitsByIds:(NSArray *)circuitIds completion:(void (^__nullable)(BOOL success))completion { long queueId; if (@available(macOS 10.10, *)) { queueId = QOS_CLASS_USER_INITIATED; } else { queueId = DISPATCH_QUEUE_PRIORITY_HIGH; } dispatch_async(dispatch_get_global_queue(queueId, 0), ^{ __block BOOL success = YES; dispatch_group_t group = dispatch_group_create(); for (NSString *circuitId in circuitIds) { dispatch_group_enter(group); [self sendCommand:TORCommandCloseCircuit arguments:@[circuitId] data:nil observer: ^BOOL(NSArray * _Nonnull codes, NSArray * _Nonnull __unused lines, BOOL * _Nonnull stop) { success = success && codes.firstObject.integerValue == TORControlReplyCodeOK; // Need to deparallelize to not mix up responses. dispatch_group_leave(group); *stop = YES; return YES; }]; } dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 10000000000)); // Wait 10 seconds. dispatch_async(dispatch_get_main_queue(), ^{ if (completion) { completion(success); } }); }); } - (void)closeCircuits:(NSArray *)circuits completion:(void (^__nullable)(BOOL success))completion { NSMutableArray *circuitIds = [NSMutableArray new]; for (TORCircuit *circuit in circuits) { if (circuit.circuitId.length > 0) { [circuitIds addObject:(NSString * _Nonnull)circuit.circuitId]; } } [self closeCircuitsByIds:circuitIds completion:completion]; } - (void)resolveCountriesOfNodes:(NSArray * _Nullable)nodes testCapabilities:(BOOL)testCapabilities completion:(void (^__nullable)(void))completion { BOOL __block ipv4Available = YES; BOOL __block ipv6Available = YES; if (testCapabilities) { dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); [self getInfoForKeys:@[@"ip-to-country/ipv4-available", @"ip-to-country/ipv6-available"] completion:^(NSArray * _Nonnull values) { ipv4Available = [values.firstObject isEqualToString:@"1"]; ipv6Available = [values.lastObject isEqualToString:@"1"]; dispatch_group_leave(group); }]; dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1000000000)); // Wait 1 second. } if (!ipv4Available && !ipv6Available) { if (completion) { completion(); } return; } NSMutableArray *geoipResolveCalls = [NSMutableArray new]; NSMutableArray *map = [NSMutableArray new]; for (TORNode *node in nodes) { if (node.countryCode.length) { continue; } if (ipv4Available && node.ipv4Address.length) { [geoipResolveCalls addObject:[NSString stringWithFormat:@"ip-to-country/%@", node.ipv4Address]]; [map addObject:node]; } else if (ipv6Available && node.ipv6Address.length) { [geoipResolveCalls addObject:[NSString stringWithFormat:@"ip-to-country/%@", node.ipv6Address]]; [map addObject:node]; } } if (geoipResolveCalls.count < 1) { if (completion) { completion(); } return; } [self getInfoForKeys:geoipResolveCalls completion:^(NSArray * _Nonnull values) { for (NSUInteger i = 0; i < values.count; i++) { map[i].countryCode = values[i]; } if (completion) { completion(); } }]; } + (NSArray *)parseStatus:(NSString *)status { NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^\\s\"]+=\"[^\"]*\"|[^\\s]+" options:0 error:nil]; NSArray *matches = [regex matchesInString:status options:0 range:NSMakeRange(0, status.length)]; NSMutableArray *tokens = [NSMutableArray new]; for (NSTextCheckingResult *match in matches) { [tokens addObject:[status substringWithRange:match.range]]; } return tokens; } @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TORNode.h ================================================ // // TORNode.h // Tor // // Created by Benjamin Erhart on 09.12.19. // #import NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(TorNode) @interface TORNode : NSObject /** Regular expression to identify and extract a valid IPv4 address. Taken from https://nbviewer.jupyter.org/github/rasbt/python_reference/blob/master/tutorials/useful_regex.ipynb */ @property (class, nonatomic, readonly) NSRegularExpression *ipv4Regex; /** Regular expression to identify and extract a valid IPv6 address. Taken from https://nbviewer.jupyter.org/github/rasbt/python_reference/blob/master/tutorials/useful_regex.ipynb */ @property (class, nonatomic, readonly) NSRegularExpression *ipv6Regex; /** The fingerprint aka. ID of a Tor node. */ @property (nonatomic, nullable) NSString *fingerprint; /** The nickname of a Tor node. */ @property (nonatomic, nullable) NSString *nickName; /** The IPv4 address of a Tor node. */ @property (nonatomic, nullable) NSString *ipv4Address; /** The IPv6 address of a Tor node. */ @property (nonatomic, nullable) NSString *ipv6Address; /** The country code of a Tor node's country. */ @property (nonatomic, nullable) NSString *countryCode; /** The localized country name of a Tor node's country. */ @property (nonatomic, readonly, nullable) NSString *localizedCountryName; /** If this node can act as an exit node or not. */ @property BOOL isExit; /** Create a `TORNode` object from a "LongName" node string which should contain the fingerprint and the nickname. See https://torproject.gitlab.io/torspec/control-spec.html#general-use-tokens @param longName A "LongName" identifying a Tor node. */ - (instancetype)initFromString:(NSString *)longName; /** Creates a list of `TORNode` objects from the response of a `ns/[*]` call which should contain the nickname and IP address(es). See https://torproject.gitlab.io/torspec/control-spec.html#getinfo @param nsString Response from `ns/[*]` call, identifying one or more Tor nodes. @return a list of `TORNode`s discovered in the given string. Might be empty. */ + (NSArray * _Nonnull)parseFromNsString:(NSString * _Nullable)nsString exitOnly:(BOOL)exitOnly; /** Acquires IPv4 and IPv6 addresses from the given string. See https://torproject.gitlab.io/torspec/control-spec.html#getinfo @param response Should be the response of a `ns/id/` call. */ - (void)acquireIpAddressesFromNsResponse:(NSString *)response; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TORNode.m ================================================ // // TORNode.m // Tor // // Created by Benjamin Erhart on 09.12.19. // #import "TORNode.h" #import "NSCharacterSet+PredefinedSets.h" @implementation TORNode // MARK: Class Properties static NSRegularExpression *_ipv4Regex; static NSRegularExpression *_ipv6Regex; + (NSRegularExpression *)ipv4Regex { if (!_ipv4Regex) { _ipv4Regex = [NSRegularExpression regularExpressionWithPattern:@"(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)" options:0 error:nil]; } return _ipv4Regex; } + (NSRegularExpression *)ipv6Regex { if (!_ipv6Regex) { _ipv6Regex = [NSRegularExpression regularExpressionWithPattern: @"((([\\da-f]{1,4}:){7}([\\da-f]{1,4}|:))|(([\\da-f]{1,4}:){6}(:[\\da-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([\\da-f]{1,4}:){5}(((:[\\da-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([\\da-f]{1,4}:){4}(((:[\\da-f]{1,4}){1,3})|((:[\\da-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([\\da-f]{1,4}:){3}(((:[\\da-f]{1,4}){1,4})|((:[\\da-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([\\da-f]{1,4}:){2}(((:[\\da-f]{1,4}){1,5})|((:[\\da-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([\\da-f]{1,4}:){1}(((:[\\da-f]{1,4}){1,6})|((:[\\da-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[\\da-f]{1,4}){1,7})|((:[\\da-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?" options:NSRegularExpressionCaseInsensitive error:nil]; } return _ipv6Regex; } // MARK: Class Methods: + (NSArray * _Nonnull)parseFromNsString:(NSString * _Nullable)nsString exitOnly:(BOOL)exitOnly { NSMutableArray *nodes = [NSMutableArray new]; NSMutableArray *raw = [NSMutableArray new]; // A typical NS string for a Tor node might look like this: // (Line breaks are not for readability but contained in original!) // // r ForPrivacyNET ADb6NqtDX9XQ9kBiZjaGfr+3LGg epP7Gxm+NYhwC3V7SPORQCPoVgc 2022-11-18 00:01:48 185.220.101.33 10133 0 // a [2a0b:f4c2:2::33]:10133 // s Exit Fast Running V2Dir Valid // w Bandwidth=37000 // // So, we watch out for a "r" line and add all lines which start with a valid prefix until the // next "r" line to get the full description. // But since we currently don't use the "w" line information, we ignore that as an optimization. for (NSString *line in [nsString componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet]) { if ([line hasPrefix:@"r"]) { if (raw.count > 0) { if (!exitOnly || [raw.lastObject rangeOfString:@"Exit"].location != NSNotFound) { [nodes addObject:[[TORNode alloc] initFromNsString:[raw componentsJoinedByString:@"\n"]]]; } raw = [[NSMutableArray alloc] initWithObjects:line, nil]; } } else if ([line hasPrefix:@"a"] || [line hasPrefix:@"s"]) { [raw addObject:line]; } } if (raw.count > 0 && (!exitOnly || [raw.lastObject rangeOfString:@"Exit"].location != NSNotFound)) { [nodes addObject:[[TORNode alloc] initFromNsString:[raw componentsJoinedByString:@"\n"]]]; } return nodes; } // MARK: Initializers - (instancetype)initFromString:(NSString *)longName { self = [super init]; if (self) { NSArray *components = [longName componentsSeparatedByCharactersInSet: NSCharacterSet.longNameDivider]; if (components.count > 0) { self.fingerprint = components[0]; } if (components.count > 1) { self.nickName = components[1]; } } return self; } - (instancetype)initFromNsString:(NSString *)nsString { self = [super init]; if (self) { NSRange r1 = [nsString rangeOfCharacterFromSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; if (r1.location != NSNotFound) { NSUInteger p1 = r1.location + r1.length; NSRange r2 = [nsString rangeOfCharacterFromSet:NSCharacterSet.whitespaceAndNewlineCharacterSet options:0 range:NSMakeRange(p1, nsString.length - p1)]; if (r2.location != NSNotFound) { self.nickName = [nsString substringWithRange:NSMakeRange(p1, r2.location - p1)]; } } [self acquireIpAddressesFromNsResponse:nsString]; } return self; } // MARK: Public Methods - (void)acquireIpAddressesFromNsResponse:(NSString *)response { NSArray *matches; NSRange range = NSMakeRange(0, response.length); matches = [TORNode.ipv4Regex matchesInString:response options:0 range:range]; if (matches.firstObject.numberOfRanges > 0) { self.ipv4Address = [response substringWithRange:[matches.firstObject rangeAtIndex:0]]; } matches = [TORNode.ipv6Regex matchesInString:response options:0 range:range]; if (matches.firstObject.numberOfRanges > 0) { self.ipv6Address = [response substringWithRange:[matches.firstObject rangeAtIndex:0]]; } if ([response rangeOfString:@"Exit"].location != NSNotFound) { self.isExit = YES; } } - (NSString *)localizedCountryName { if (!self.countryCode) { return nil; } NSString *countryCode = (NSString * _Nonnull)self.countryCode; if (@available(iOS 10.0, macOS 10.12, *)) { return [NSLocale.currentLocale localizedStringForCountryCode:countryCode]; } else { return [NSLocale.currentLocale displayNameForKey:NSLocaleCountryCode value:countryCode]; } } // MARK: NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)coder { if ((self = [super init])) { _fingerprint = [coder decodeObjectOfClass:NSString.class forKey:@"fingerprint"]; _nickName = [coder decodeObjectOfClass:NSString.class forKey:@"nickName"]; _ipv4Address = [coder decodeObjectOfClass:NSString.class forKey:@"ipv4Address"]; _ipv6Address = [coder decodeObjectOfClass:NSString.class forKey:@"ipv6Address"]; _countryCode = [coder decodeObjectOfClass:NSString.class forKey:@"countryCode"]; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.fingerprint forKey:@"fingerprint"]; [coder encodeObject:self.nickName forKey:@"nickName"]; [coder encodeObject:self.ipv4Address forKey:@"ipv4Address"]; [coder encodeObject:self.ipv6Address forKey:@"ipv6Address"]; [coder encodeObject:self.countryCode forKey:@"countryCode"]; } // MARK: NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p> fingerprint=%@, nickName=%@, ipv4Address=%@, ipv6Address=%@, countryCode=%@, localizedCountryName=%@", self.class, self, self.fingerprint, self.nickName, self.ipv4Address, self.ipv6Address, self.countryCode, self.localizedCountryName]; } - (BOOL)isEqual:(id)other { if (other == self) { return YES; } if (!other || ![other isKindOfClass:self.class]) { return NO; } return [self.fingerprint isEqualToString:(NSString * _Nonnull)((TORNode *)other).fingerprint]; } - (NSUInteger)hash { return self.fingerprint.hash; } @end ================================================ FILE: Tor/Classes/Core/TOROnionAuth.h ================================================ // // TOROnionAuth.h // Tor // // Created by Benjamin Erhart on 29.09.21. // #import #import "TORAuthKey.h" NS_ASSUME_NONNULL_BEGIN /** Support for Onion v3 service authentication configuration files. */ NS_SWIFT_NAME(TorOnionAuth) @interface TOROnionAuth : NSObject /** The directory where this instance expects the private key files to be in. */ @property (nonatomic, nullable, readonly) NSURL *privateUrl; /** The directory where this instance expects the public key files to be in. */ @property (nonatomic, nullable, readonly) NSURL *publicUrl; /** The found public and/or private keys in the base \c directory. @see -directory */ @property (nonatomic, nonnull, readonly) NSArray *keys; /** Initialize with a given directory. Will immediately read all keys on disk. If you have a lot of keys, you might want to do this in a background thread! @param privateUrl The base directory where the key files live. Should be the same as you set in \c for clients. @param publicUrl The base directory where the key files live. Should be the same as you set in \c for servers. @see https://2019.www.torproject.org/docs/tor-manual.html.en#ClientOnionAuthDir @see https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServiceDir @see https://2019.www.torproject.org/docs/tor-manual.html.en#_client_authorization */ - (instancetype)initWithPrivateDirUrl:(nullable NSURL *)privateUrl andPublicDirUrl:(nullable NSURL *)publicUrl NS_SWIFT_NAME(init(withPrivateDir:andPublicDir:)); /** Initialize with a given directory. Will immediately read all keys on disk. If you have a lot of keys, you might want to do this in a background thread! @param privatePath The base directory where the key files live. Should be the same as you set in \c for clients. @param publicPath The base directory where the key files live. Should be the same as you set in \c for servers. @see https://2019.www.torproject.org/docs/tor-manual.html.en#ClientOnionAuthDir @see https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServiceDir @see https://2019.www.torproject.org/docs/tor-manual.html.en#_client_authorization */ - (instancetype)initWithPrivateDir:(NSString *)privatePath andPublicDir:(NSString *)publicPath NS_SWIFT_NAME(init(withPrivateDir:andPublicDir:)); /** Add an authentication key (public or private) to the configuration. If a key with the same file name already exists, it will be overwritten. If not, it will be added at the end of the \c keys array. @param key A new or modified key. @returns \c YES on success, \c NO on failure. */ - (BOOL)set:(TORAuthKey *)key; /** Remove the key at the specified index. @param idx The index of the key in \c keys. @returns \c YES on success, \c NO on failure. */ - (BOOL)removeKeyAtIndex:(NSInteger)idx; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Core/TOROnionAuth.m ================================================ // // TOROnionAuth.m // Tor // // Created by Benjamin Erhart on 29.09.21. // #import "TOROnionAuth.h" @implementation TOROnionAuth - (instancetype)initWithPrivateDirUrl:(nullable NSURL *)privateUrl andPublicDirUrl:(nullable NSURL *)publicUrl { if ((self = [super init])) { if (![publicUrl.lastPathComponent isEqualToString:@"authorized_clients"]) { publicUrl = [publicUrl URLByAppendingPathComponent:@"authorized_clients" isDirectory:YES]; } _privateUrl = privateUrl; _publicUrl = publicUrl; _keys = [NSMutableArray new]; NSMutableArray *files = [NSMutableArray new]; NSError *error; NSURL *privateUrl = _privateUrl; if (privateUrl) { NSArray *privateFiles = [NSFileManager.defaultManager contentsOfDirectoryAtURL:privateUrl includingPropertiesForKeys:nil options:0 error:&error]; if (error) { NSLog(@"[%@] Error while reading keys: %@", NSStringFromClass(self.class), error.localizedDescription); } else { if (privateFiles) [files addObjectsFromArray:privateFiles]; } } NSURL *publicUrl = _publicUrl; if (publicUrl) { NSArray *publicFiles = [NSFileManager.defaultManager contentsOfDirectoryAtURL:publicUrl includingPropertiesForKeys:nil options:0 error:&error]; if (error) { NSLog(@"[%@] Error while reading keys: %@", NSStringFromClass(self.class), error.localizedDescription); } else { if (publicFiles) [files addObjectsFromArray:publicFiles]; } } for (NSURL *file in files) { if ([TORAuthKey isAuthFile:file]) { TORAuthKey *key = [[TORAuthKey alloc] initFromUrl:file]; if (key) [((NSMutableArray *)_keys) addObject:key]; } } } return self; } - (instancetype)initWithPrivateDir:(NSString *)privatePath andPublicDir:(NSString *)publicPath { return [self initWithPrivateDirUrl:[NSURL fileURLWithPath:privatePath] andPublicDirUrl:[NSURL fileURLWithPath:publicPath]]; } // MARK: Public Methods - (BOOL)set:(TORAuthKey *)key { NSURL *privateUrl = _privateUrl; NSURL *publicUrl = _publicUrl; if (key.isPrivate) { if (privateUrl) { [key setDirectory:privateUrl]; } else if (publicUrl) { [key setDirectory:publicUrl]; } } else { if (publicUrl) { [key setDirectory:publicUrl]; } else if (privateUrl) { [key setDirectory:privateUrl]; } } if ([key persist]) { NSUInteger i = [_keys indexOfObject:key]; if (i == NSNotFound) { [((NSMutableArray *)_keys) addObject:key]; } else { ((NSMutableArray *)_keys)[i] = key; } return YES; } return NO; } - (BOOL)removeKeyAtIndex:(NSInteger)idx { if (idx < 0 || (NSUInteger)idx >= _keys.count) return NO; TORAuthKey *key = _keys[idx]; NSError *error; [NSFileManager.defaultManager removeItemAtURL:key.file error:&error]; if (error) { NSLog(@"[%@] Error while removing key: %@", NSStringFromClass(self.class), error.localizedDescription); return NO; } [((NSMutableArray *)_keys) removeObjectAtIndex:idx]; return YES; } @end ================================================ FILE: Tor/Classes/Onionmasq/Onionmasq.h ================================================ // // Onionmasq.h // Tor // // Created by Benjamin Erhart on 30.08.23. // #import #import NS_ASSUME_NONNULL_BEGIN @interface Onionmasq : NSObject typedef void (^ReaderCb)(void); typedef bool (^WriterCb)(NSData *packet, NSNumber *version); typedef void (^EventCb)(id); typedef void (^LogCb)(NSString *message); /** Start Onionmasq. @param readerCallback Called, when Onionmasq wants to read to the TUN interface. After read, this method **needs to call** `receive`! @param writerCallback Called, when Onionmasq wants to write to the TUN interface. @param stateDir Directory, where Arti can store its state. OPTIONAL. If not provided, will use \c Library/Application \c Support/org.torproject.Arti. @param cacheDir Directory, where Arti can store its caching data. OPTIONAL. If not providied, will use \c Library/Cache/org.torproject.Arti. @param pcapFile File to write a network trace in PCAP format to. @param eventCallback Callback, when an event happens. @param logCallback Callback, when a log message arrives. */ + (void)startWithReader:(ReaderCb)readerCallback writer:(WriterCb)writerCallback stateDir:(NSURL * _Nullable)stateDir cacheDir:(NSURL * _Nullable)cacheDir pcapFile:(NSURL * _Nullable)pcapFile onEvent:(nullable EventCb)eventCallback onLog:(nullable LogCb)logCallback; /** Stop Onionmasq. */ + (void)stop; /** Refresh all circuits. This causes all new connections after the command is sent to use different circuits to the set currently used. */ + (void)refreshCircuits; + (void)setPcapPath:(NSURL *)path; /** Get the current count of received bytes since last reset. */ + (long long)getBytesReceived; /** Get the current count of sent bytes since last reset. */ + (long long)getBytesSent; /** Reset the global bandwidth counter. */ + (void)resetCounters; /** Set the country code that proxied connections should use. You can clear it back to "no country code" by passing in `nil`. */ + (void)setCountryCodeWith:(NSString * _Nullable)countryCode; /** You need to call this, when your `readerCallback` has read data from the TUN device. @param packets Packets of data. */ + (void)receive:(NSArray *)packets; @end NS_ASSUME_NONNULL_END ================================================ FILE: Tor/Classes/Onionmasq/Onionmasq.m ================================================ // // Onionmasq.m // Tor // // Created by Benjamin Erhart on 30.08.23. // #import "Onionmasq.h" #import "onionmasq_apple.h" @implementation Onionmasq BOOL initialized; ReaderCb readerBlock; WriterCb writerBlock; EventCb eventBlock; LogCb logBlock; NSRegularExpression *regex; + (void)startWithReader:(ReaderCb)readerCallback writer:(WriterCb)writerCallback stateDir:(NSURL * _Nullable)stateDir cacheDir:(NSURL * _Nullable)cacheDir pcapFile:(NSURL * _Nullable)pcapFile onEvent:(nullable EventCb)eventCallback onLog:(nullable LogCb)logCallback { readerBlock = readerCallback; writerBlock = writerCallback; eventBlock = eventCallback; logBlock = logCallback; NSFileManager *fm = NSFileManager.defaultManager; if (!stateDir) { stateDir = [[fm URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] firstObject]; } if (!cacheDir) { cacheDir = [[fm URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] firstObject]; } assert(stateDir.isFileURL); assert(cacheDir.isFileURL); if (!initialized) { // Remove ANSI colors. regex = [[NSRegularExpression alloc] initWithPattern:@"\\x1b\\[[0-9;]*m" options:NSRegularExpressionDotMatchesLineSeparators error:nil]; init(&eventCb, &logCb); if (pcapFile) { [self setPcapPath:pcapFile]; } } runProxy(&readerCb, &writerCb, [cacheDir.path cStringUsingEncoding:NSUTF8StringEncoding], [stateDir.path cStringUsingEncoding:NSUTF8StringEncoding]); } + (void)stop { closeProxy(); readerBlock = nil; writerBlock = nil; eventBlock = nil; logBlock = nil; } + (void)refreshCircuits { refreshCircuits(); } + (void)setPcapPath:(NSURL *)path { assert(path.isFileURL); NSFileManager *fm = NSFileManager.defaultManager; if (![fm fileExistsAtPath:path.path]) { [fm createFileAtPath:path.path contents:nil attributes:nil]; } setPcapPath([path.path cStringUsingEncoding:NSUTF8StringEncoding]); } + (long long)getBytesReceived { return getBytesReceived(); } + (long long)getBytesSent { return getBytesSent(); } + (void)resetCounters { resetCounters(); } + (void)setCountryCodeWith:(NSString *)countryCode { setCountryCode([countryCode cStringUsingEncoding:NSUTF8StringEncoding]); } + (void)receive:(NSArray *)packets { const uint8_t * pointers[packets.count]; unsigned long lens[packets.count]; for (NSUInteger i = 0; i < packets.count; i++) { pointers[i] = packets[i].bytes; lens[i] = packets[i].length; } receive(pointers, lens, packets.count); } void readerCb(void) { if (readerBlock) { readerBlock(); } } bool writerCb(const uint8_t *packet, size_t len) { if (writerBlock) { NSData *data = [[NSData alloc] initWithBytes:packet length:len]; NSNumber *v = [[NSNumber alloc] initWithShort:((const unsigned char *)data.bytes)[0] >> 4]; return writerBlock(data, v); } else { return false; } } void eventCb(const char * event) { if (eventBlock) { NSString *evt = [[NSString alloc] initWithUTF8String:event]; NSData *data = [evt dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (error) { eventBlock(evt); } else { eventBlock(object); } } } void logCb(const char * log) { if (logBlock) { NSMutableString *msg = [[NSMutableString alloc] initWithUTF8String:log]; [regex replaceMatchesInString:msg options:0 range:NSMakeRange(0, msg.length) withTemplate:@""]; logBlock(msg); } } @end ================================================ FILE: Tor/download.sh ================================================ #!/bin/sh VERSION=$1 NAMES=$2 shift shift checksums=( "$@" ) load() { curl --remote-name --progress-bar --location $1 } cd Tor/Assets # Test if file is older then 1 week. OLD="$(find geoip -mmin +10080 2>/dev/null)" # Only download, if files are not existing or older than 1 week. if [ ! -f geoip -o ! -z "$OLD" ]; then load https://gitlab.torproject.org/tpo/core/tor/-/raw/main/src/config/geoip load https://gitlab.torproject.org/tpo/core/tor/-/raw/main/src/config/geoip6 fi cd ../.. declare -i i=0 for name in $NAMES do if [ ! -d "$name.xcframework" ]; then load "https://github.com/iCepa/Tor.framework/releases/download/$VERSION/$name.xcframework.zip" actual=$(shasum -a 256 "$name.xcframework.zip" | awk '{print $1}') if [ "$actual" != "${checksums[$i]}" ]; then echo "ERROR: Checksum verification failed: $actual != ${checksums[$i]}" exit 1 fi unzip "$name.xcframework.zip" rm "$name.xcframework.zip" fi i+=1 done ================================================ FILE: Tor/mmap-cache.patch ================================================ From 4d6875d271dd5208e2c89a174fbff2e74004921b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20F=C3=A6r=C3=B8y?= Date: Sun, 27 Aug 2023 23:12:05 +0200 Subject: [PATCH] Memory map our journal file instead of reading directly. See: tpo/core/tor#40832 --- src/feature/nodelist/microdesc.c | 65 ++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c index 9e5f0bb9a4..64a25abb92 100644 --- a/src/feature/nodelist/microdesc.c +++ b/src/feature/nodelist/microdesc.c @@ -56,8 +56,8 @@ struct microdesc_cache_t { char *journal_fname; /** Mmap'd contents of the cache file, or NULL if there is none. */ tor_mmap_t *cache_content; - /** Number of bytes used in the journal file. */ - size_t journal_len; + /** Mmap'd contents of the journal file, or NULL if there is none. */ + tor_mmap_t *journal_content; /** Number of bytes in descriptors removed as too old. */ size_t bytes_dropped; @@ -422,9 +422,6 @@ microdescs_add_list_to_cache(microdesc_cache_t *cache, /* we already warned in dump_microdescriptor */ abort_writing_to_file(open_file); fd = -1; - } else { - md->saved_location = SAVED_IN_JOURNAL; - cache->journal_len += size; } } else { md->saved_location = where; @@ -483,6 +480,17 @@ microdesc_cache_clear(microdesc_cache_t *cache) } cache->cache_content = NULL; } + + if (cache->journal_content) { + int res = tor_munmap_file(cache->journal_content); + if (res != 0) { + log_warn(LD_FS, + "tor_munmap_file() failed clearing journal cache; " + "we are probably about to leak memory."); + /* TODO something smarter? */ + } + } + cache->total_len_seen = 0; cache->n_seen = 0; cache->bytes_dropped = 0; @@ -514,20 +522,19 @@ warn_if_nul_found(const char *inp, size_t len, int64_t offset, int microdesc_cache_reload(microdesc_cache_t *cache) { - struct stat st; - char *journal_content; smartlist_t *added; - tor_mmap_t *mm; + tor_mmap_t *mm_cache; + tor_mmap_t *mm_journal; int total = 0; microdesc_cache_clear(cache); cache->is_loaded = 1; - mm = cache->cache_content = tor_mmap_file(cache->cache_fname); - if (mm) { - warn_if_nul_found(mm->data, mm->size, 0, "scanning microdesc cache"); - added = microdescs_add_to_cache(cache, mm->data, mm->data+mm->size, + mm_cache = cache->cache_content = tor_mmap_file(cache->cache_fname); + if (mm_cache) { + warn_if_nul_found(mm_cache->data, mm_cache->size, 0, "scanning microdesc cache"); + added = microdescs_add_to_cache(cache, mm_cache->data, mm_cache->data+mm_cache->size, SAVED_IN_CACHE, 0, -1, NULL); if (added) { total += smartlist_len(added); @@ -535,21 +542,19 @@ microdesc_cache_reload(microdesc_cache_t *cache) } } - journal_content = read_file_to_str(cache->journal_fname, - RFTS_IGNORE_MISSING, &st); - if (journal_content) { - cache->journal_len = strlen(journal_content); - warn_if_nul_found(journal_content, (size_t)st.st_size, 0, - "reading microdesc journal"); - added = microdescs_add_to_cache(cache, journal_content, - journal_content+st.st_size, + mm_journal = cache->journal_content = tor_mmap_file(cache->journal_fname); + + if (mm_journal) { + warn_if_nul_found(mm_journal->data, mm_journal->size, 0, "reading microdesc journal"); + added = microdescs_add_to_cache(cache, mm_journal->data, + mm_journal->data+mm_journal->size, SAVED_IN_JOURNAL, 0, -1, NULL); if (added) { total += smartlist_len(added); smartlist_free(added); } - tor_free(journal_content); } + log_info(LD_DIR, "Reloaded microdescriptor cache. Found %d descriptors.", total); @@ -664,7 +669,8 @@ should_rebuild_md_cache(microdesc_cache_t *cache) { const size_t old_len = cache->cache_content ? cache->cache_content->size : 0; - const size_t journal_len = cache->journal_len; + const size_t journal_len = + cache->journal_content ? cache->journal_content->size : 0; const size_t dropped = cache->bytes_dropped; if (journal_len < 16384) @@ -727,7 +733,7 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) log_info(LD_DIR, "Rebuilding the microdescriptor cache..."); orig_size = (int)(cache->cache_content ? cache->cache_content->size : 0); - orig_size += (int)cache->journal_len; + orig_size += (int)(cache->journal_content ? cache->journal_content->size : 0); fd = start_writing_to_file(cache->cache_fname, OPEN_FLAGS_REPLACE|O_BINARY, @@ -826,8 +832,19 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) smartlist_free(wrote); + // We unmap the journal file and use write_str_to_file() to empty it. + res = tor_munmap_file(cache->journal_content); + + if (res != 0) { + log_warn(LD_FS, + "tor_munmap_file() failed clearing journal; " + "we are probably about to leak memory."); + /* TODO something smarter? */ + } + + cache->journal_content = NULL; write_str_to_file(cache->journal_fname, "", 1); - cache->journal_len = 0; + cache->bytes_dropped = 0; new_size = cache->cache_content ? (int)cache->cache_content->size : 0; -- 2.41.0 ================================================ FILE: Tor/onionmasq.sh ================================================ #!/usr/bin/env sh # Get absolute path to this script. SCRIPTDIR=$(cd `dirname $0` && pwd) WORKDIR="$SCRIPTDIR/onionmasq" FILENAME="libonionmasq_apple.a" # Assume we're in Xcode, which means we're probably cross-compiling. # In this case, we need to add an extra library search path for build scripts and proc-macros, # which run on the host instead of the target. # (macOS Big Sur does not have linkable libraries in /usr/lib/.) export LIBRARY_PATH="${SDKROOT}/usr/lib:${LIBRARY_PATH:-}" # The $PATH used by Xcode likely won't contain Cargo, fix that. # This assumes a default `rustup` setup. export PATH="$HOME/.cargo/bin:$PATH" cd "$WORKDIR" # NOTE: This won't be executed, when this script is set up as a "build phase script". # There seems no way to configure CocoaPods to make this run as a target script, # where it would be executed. # However, this is here for documentary purposes and in case you want to set # this manually as an "external target" script. # See https://kelan.io/2009/run-script-while-cleaning-in-xcode/ if [ "$ACTION" = "clean" ]; then make clean exit 0 fi export MACOSX_DEPLOYMENT_TARGET=10.13 CONF_LOWER="$(echo $CONFIGURATION | tr '[:upper:]' '[:lower:]')" if [ "${PLATFORM_NAME:-iphoneos}" = "macosx" ]; then if [ "${CONF_LOWER:-debug}" = "release" ]; then TARGET_PLATFORM="universal-macos" make "macos-${CONF_LOWER}" else if [ "${ARCHS:-x86}" = "arm64" ]; then TARGET_PLATFORM="aarch64-apple-darwin" else TARGET_PLATFORM="x86_64-apple-darwin" fi make "macos-verbose-${TARGET_PLATFORM}" fi elif [ "${PLATFORM_NAME:-iphoneos}" = "iphonesimulator" ]; then if [ "${ARCHS:-x86}" = "arm64" ]; then TARGET_PLATFORM="aarch64-apple-ios-sim" else TARGET_PLATFORM="x86_64-apple-ios" fi make "ios-${CONF_LOWER}-${TARGET_PLATFORM}" else TARGET_PLATFORM="aarch64-apple-ios" make "ios-${CONF_LOWER}-${TARGET_PLATFORM}" fi SOURCE="${WORKDIR}/target/${TARGET_PLATFORM}/${CONF_LOWER}/${FILENAME}" if [ -e "${SOURCE}" ]; then echo "Link '${SOURCE}' to '${BUILT_PRODUCTS_DIR}'" rm -f "${BUILT_PRODUCTS_DIR}/${FILENAME}" ln -s "${SOURCE}" "${BUILT_PRODUCTS_DIR}" fi ================================================ FILE: Tor.podspec ================================================ Pod::Spec.new do |m| m.name = 'Tor' m.version = '409.6.1' m.summary = 'Tor.framework is the easiest way to embed Tor in your iOS application.' m.description = 'Tor.framework is the easiest way to embed Tor in your iOS application. Currently, the framework compiles in static versions of tor, libevent, openssl, and liblzma.' m.homepage = 'https://github.com/iCepa/Tor.framework' m.license = { :type => 'MIT', :file => 'LICENSE' } m.authors = { 'Conrad Kramer' => 'conrad@conradkramer.com', 'Chris Ballinger' => 'chris@chatsecure.org', 'Mike Tigas' => 'mike@tig.as', 'Benjamin Erhart' => 'berhart@netzarchitekten.com', } m.source = { :git => 'https://github.com/iCepa/Tor.framework.git', :branch => 'pure_pod', :tag => "v#{m.version}" } m.social_media_url = 'https://chaos.social/@tla' m.ios.deployment_target = '15.0' m.macos.deployment_target = '11.0' m.prepare_command = "Tor/download.sh v#{m.version} \"tor tor-nolzma\" 518a2984a6f693265833d31c7ed8c7cb0556765f1a3e5b2d7d0138c14b32f70d 9b621374e184a2634550c6fb5d7590c57b3fd3fc160947950e1f7b013fc62a73" m.subspec 'Core' do |s| s.requires_arc = true s.source_files = 'Tor/Classes/Core/**/*' end m.subspec 'CTor' do |s| s.dependency 'Tor/Core' s.source_files = 'Tor/Classes/CTor/**/*' s.vendored_frameworks = 'tor.xcframework' s.libraries = 'z' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/tor.xcframework/ios-arm64/tor.framework/Headers"', } s.preserve_paths = 'tor.xcframework', 'download.sh' end m.subspec 'CTor-NoLZMA' do |s| s.dependency 'Tor/Core' s.source_files = 'Tor/Classes/CTor/**/*' s.vendored_frameworks = 'tor-nolzma.xcframework' s.libraries = 'z' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/tor-nolzma.xcframework/ios-arm64/tor-nolzma.framework/Headers"', } s.preserve_paths = 'tor-nolzma.xcframework', 'Tor/download.sh' end m.subspec 'GeoIP' do |s| s.dependency 'Tor/CTor' s.resource_bundles = { 'GeoIP' => ['Tor/Assets/geoip', 'Tor/Assets/geoip6'] } end m.subspec 'GeoIP-NoLZMA' do |s| s.dependency 'Tor/CTor-NoLZMA' s.resource_bundles = { 'GeoIP' => ['Tor/Assets/geoip', 'Tor/Assets/geoip6'] } end m.default_subspecs = 'CTor' end ================================================ FILE: build-xcframework.sh ================================================ #!/bin/sh set -eo pipefail PATH=$PATH:/usr/local/bin:/usr/local/opt/gettext/bin:/usr/local/opt/automake/bin:/usr/local/opt/aclocal/bin:/opt/homebrew/bin XZ_VERSION="v5.8.2" OPENSSL_VERSION="openssl-3.6.1" LIBEVENT_VERSION="release-2.1.12-stable" TOR_VERSION="tor-0.4.9.6" ARTI_MOBILE_VERSION="arti-1.7.0" ARTI_VERSION="main" cd "$(dirname "$0")" ROOT="$(pwd -P)" DEBUG="" WITH_CTOR="" WITH_ARTI="" while getopts acdl flag do case "$flag" in d) DEBUG="1";; c) WITH_CTOR="1";; a) WITH_ARTI="1";; esac done if [ -z $DEBUG ]; then BUILDDIR="$(mktemp -d)" else set -x BUILDDIR="$ROOT/build" mkdir -p "$BUILDDIR" fi echo "Build dir: $BUILDDIR" build_liblzma() { SDK=$1 ARCH=$2 MIN=$3 SOURCE="$BUILDDIR/xz" LOG="$BUILDDIR/liblzma-$SDK-$ARCH.log" if [ ! -d "$SOURCE" ]; then echo "- Check out XZ project" cd "$BUILDDIR" git clone --recursive --shallow-submodules --depth 1 --branch "$XZ_VERSION" https://github.com/tukaani-project/xz.git >> "$LOG" 2>&1 fi echo "- Build liblzma for $ARCH ($SDK)" cd "$SOURCE" if [ -f Makefile ]; then make distclean >> "$LOG" 2>&1 fi # Generate the configure script. if [ ! -f ./configure ]; then LIBTOOLIZE=glibtoolize ./autogen.sh >> "$LOG" 2>&1 fi SDKPATH="$(xcrun --sdk ${SDK} --show-sdk-path)" CLANG="$(xcrun -f --sdk ${SDK} clang)" ./configure \ --disable-shared \ --enable-static \ --disable-doc \ --disable-scripts \ --disable-xz \ --disable-xzdec \ --disable-lzmadec \ --disable-lzmainfo \ --disable-lzma-links \ --prefix "$BUILDDIR/$SDK/liblzma-$ARCH" \ CC="$CLANG -arch ${ARCH}" \ CPP="$CLANG -E -arch ${ARCH}" \ CFLAGS="-isysroot ${SDKPATH} -m$SDK-version-min=$MIN -fembed-bitcode -Wno-unknown-warning-option" \ LDFLAGS="-isysroot ${SDKPATH} -fembed-bitcode" \ cross_compiling="yes" \ ac_cv_func_clock_gettime="no" \ >> "$LOG" 2>&1 make -j$(sysctl -n hw.logicalcpu_max) >> "$LOG" 2>&1 make install >> "$LOG" 2>&1 } build_libssl() { SDK=$1 ARCH=$2 MIN=$3 SOURCE="$BUILDDIR/openssl" LOG="$BUILDDIR/libssl-$SDK-$ARCH.log" if [ ! -d "$SOURCE" ]; then echo "- Check out OpenSSL project" cd "$BUILDDIR" git clone --recursive --shallow-submodules --depth 1 --branch "$OPENSSL_VERSION" https://github.com/openssl/openssl.git >> "$LOG" 2>&1 fi echo "- Build OpenSSL for $ARCH ($SDK)" cd "$SOURCE" if [ -f Makefile ]; then make distclean >> "$LOG" 2>&1 fi if [ "$SDK" = "iphoneos" ]; then if [ "$ARCH" = "arm64" ]; then PLATFORM_FLAGS="no-async zlib-dynamic enable-ec_nistp_64_gcc_128" CONFIG="ios64-xcrun" elif [ "$ARCH" = "armv7" ]; then PLATFORM_FLAGS="no-async zlib-dynamic" CONFIG="ios-xcrun" else echo "OpenSSL configuration error: $ARCH on $SDK not supported!" fi elif [ "$SDK" = "iphonesimulator" ]; then if [ "$ARCH" = "arm64" ]; then PLATFORM_FLAGS="no-async zlib-dynamic enable-ec_nistp_64_gcc_128" CONFIG="iossimulator-xcrun" elif [ "$ARCH" = "i386" ]; then PLATFORM_FLAGS="no-asm" CONFIG="iossimulator-xcrun" elif [ "$ARCH" = "x86_64" ]; then PLATFORM_FLAGS="no-asm enable-ec_nistp_64_gcc_128" CONFIG="iossimulator-xcrun" else echo "OpenSSL configuration error: $ARCH on $SDK not supported!" fi elif [ "$SDK" = "macosx" ]; then if [ "$ARCH" = "i386" ]; then PLATFORM_FLAGS="no-asm" CONFIG="darwin-i386-cc" elif [ "$ARCH" = "x86_64" ]; then PLATFORM_FLAGS="no-asm enable-ec_nistp_64_gcc_128" CONFIG="darwin64-x86_64-cc" elif [ "$ARCH" = "arm64" ]; then PLATFORM_FLAGS="no-asm enable-ec_nistp_64_gcc_128" CONFIG="darwin64-arm64-cc" else echo "OpenSSL configuration error: $ARCH on $SDK not supported!" fi fi if [ -n "$CONFIG" ]; then ./Configure \ no-shared \ ${PLATFORM_FLAGS} \ --prefix="$BUILDDIR/$SDK/libssl-$ARCH" \ ${CONFIG} \ CC="$(xcrun --sdk $SDK --find clang) -isysroot $(xcrun --sdk $SDK --show-sdk-path) -arch ${ARCH} -m$SDK-version-min=$MIN -fembed-bitcode" \ >> "$LOG" 2>&1 make depend >> "$LOG" 2>&1 make "-j$(sysctl -n hw.logicalcpu_max)" build_libs >> "$LOG" 2>&1 make install_dev >> "$LOG" 2>&1 fi } build_libevent() { SDK=$1 ARCH=$2 MIN=$3 SOURCE="$BUILDDIR/libevent" LOG="$BUILDDIR/libevent-$SDK-$ARCH.log" if [ ! -d "$SOURCE" ]; then echo "- Check out libevent project" cd "$BUILDDIR" git clone --recursive --shallow-submodules --depth 1 --branch "$LIBEVENT_VERSION" https://github.com/libevent/libevent.git >> "$LOG" 2>&1 fi echo "- Build libevent for $ARCH ($SDK)" cd "$SOURCE" if [ -f Makefile ]; then make distclean >> "$LOG" 2>&1 fi # Generate the configure script. if [ ! -f ./configure ]; then ./autogen.sh >> "$LOG" 2>&1 fi CLANG="$(xcrun -f --sdk ${SDK} clang)" SDKPATH="$(xcrun --sdk ${SDK} --show-sdk-path)" DEST="$BUILDDIR/$SDK/libevent-$ARCH" ./configure \ --disable-shared \ --disable-openssl \ --disable-libevent-regress \ --disable-samples \ --disable-doxygen-html \ --enable-static \ --enable-gcc-hardening \ --disable-debug-mode \ --prefix="$DEST" \ CC="$CLANG -arch ${ARCH}" \ CPP="$CLANG -E -arch ${ARCH}" \ CFLAGS="-isysroot ${SDKPATH} -m$SDK-version-min=$MIN -fembed-bitcode" \ LDFLAGS="-isysroot ${SDKPATH} -L$DEST -fembed-bitcode" \ cross_compiling="yes" \ ac_cv_func_clock_gettime="no" \ ac_cv_func_pipe2="no" \ >> "$LOG" 2>&1 make -j$(sysctl -n hw.logicalcpu_max) >> "$LOG" 2>&1 make install >> "$LOG" 2>&1 } build_libtor() { SDK=$1 ARCH=$2 MIN=$3 NO_LZMA=$4 SOURCE="$BUILDDIR/tor" if [ -z "$NO_LZMA" ]; then LOG="$BUILDDIR/libtor-$SDK-$ARCH.log" LZMA="yes" DEST="$BUILDDIR/$SDK/libtor-$ARCH" else LOG="$BUILDDIR/libtor-nolzma-$SDK-$ARCH.log" LZMA="no" DEST="$BUILDDIR/$SDK/libtor-nolzma-$ARCH" fi if [ ! -d "$SOURCE" ]; then echo "- Check out Tor project" cd "$BUILDDIR" git clone --recursive --shallow-submodules --depth 1 --branch "$TOR_VERSION" https://gitlab.torproject.org/tpo/core/tor.git >> "$LOG" 2>&1 fi if [ -z "$NO_LZMA" ]; then echo "- Build libtor for $ARCH ($SDK)" else echo "- Build libtor-nolzma for $ARCH ($SDK)" fi cd "$SOURCE" if [ -f Makefile ]; then make distclean >> "$LOG" 2>&1 fi ## Apply patches: git restore . >> "$LOG" 2>&1 git apply "$ROOT/Tor/mmap-cache.patch" >> "$LOG" 2>&1 # Generate the configure script. if [ ! -f ./configure ]; then # FIXME: This fixes `Tor/tor/autogen.sh`. Check if that was changed and remove this patch. sed -i'.backup' -e 's/all,error/no-obsolete,error/' autogen.sh ./autogen.sh >> "$LOG" 2>&1 # FIXME: Undoes the patch. Remove, when it becomes unnecessary. rm autogen.sh && mv autogen.sh.backup autogen.sh fi CLANG="$(xcrun -f --sdk ${SDK} clang)" SDKPATH="$(xcrun --sdk ${SDK} --show-sdk-path)" ./configure \ --enable-silent-rules \ --enable-pic \ --disable-module-relay \ --disable-module-dirauth \ --disable-tool-name-check \ --disable-unittests \ --enable-static-openssl \ --enable-static-libevent \ --disable-asciidoc \ --disable-system-torrc \ --disable-linker-hardening \ --disable-dependency-tracking \ --disable-manpage \ --disable-html-manual \ --disable-gcc-warnings-advisory \ --enable-lzma="$LZMA" \ --disable-zstd \ --with-libevent-dir="$BUILDDIR/$SDK/libevent-$ARCH" \ --with-openssl-dir="$BUILDDIR/$SDK/libssl-$ARCH" \ --prefix="$DEST" \ CC="$CLANG -arch ${ARCH} -isysroot ${SDKPATH}" \ CPP="$CLANG -E -arch ${ARCH} -isysroot ${SDKPATH}" \ CPPFLAGS="-fembed-bitcode -Isrc/core -I$BUILDDIR/$SDK/libssl-$ARCH/include -I$BUILDDIR/$SDK/libevent-$ARCH/include -m$SDK-version-min=$MIN" \ LDFLAGS="-lz -fembed-bitcode" \ LZMA_CFLAGS="-I$BUILDDIR/$SDK/liblzma-$ARCH/include" \ LZMA_LIBS="$BUILDDIR/$SDK/liblzma-$ARCH/lib/liblzma.a" \ cross_compiling="yes" \ ac_cv_func__NSGetEnviron="no" \ ac_cv_func_clock_gettime="no" \ ac_cv_func_getentropy="no" \ ac_cv_func_pipe2="no" \ >> "$LOG" 2>&1 # There seems to be a race condition with the above configure and the later cp. # Just sleep a little so the correct file is copied and delete the old one before. sleep 2 rm -f src/lib/cc/orconfig.h >> "$LOG" 2>&1 cp orconfig.h "src/lib/cc/" >> "$LOG" 2>&1 make libtor.a -j$(sysctl -n hw.logicalcpu_max) V=1 >> "$LOG" 2>&1 mkdir -p "$DEST/lib" >> "$LOG" 2>&1 mkdir -p "$DEST/include" >> "$LOG" 2>&1 mv libtor.a "$DEST/lib" >> "$LOG" 2>&1 rsync --archive --include='*.h' -f 'hide,! */' --prune-empty-dirs src/* "$DEST/include" >> "$LOG" 2>&1 cp orconfig.h "$DEST/include/" >> "$LOG" 2>&1 mv micro-revision.i "$DEST" >> "$LOG" 2>&1 } rust_target() { SDK=$1 ARCH=$2 export IPHONEOS_DEPLOYMENT_TARGET=15.0 export MACOSX_DEPLOYMENT_TARGET=11.0 TARGET="aarch64-apple-ios" if [ "${SDK:-iphoneos}" = "macosx" ]; then if [ "${ARCH:-x86}" = "arm64" ]; then TARGET="aarch64-apple-darwin" else TARGET="x86_64-apple-darwin" fi elif [ "${SDK:-iphoneos}" = "iphonesimulator" ]; then if [ "${ARCH:-x86}" = "arm64" ]; then TARGET="aarch64-apple-ios-sim" else TARGET="x86_64-apple-ios" fi fi } build_libarti() { SDK=$1 ARCH=$2 SOURCE="$BUILDDIR/arti-mobile-ex" LOG="$BUILDDIR/arti-$SDK-$ARCH.log" if [ ! -d "$SOURCE" ]; then echo "- Check out arti-mobile-ex project" cd "$BUILDDIR" git clone --recursive --shallow-submodules --depth 1 --branch "$ARTI_MOBILE_VERSION" https://gitlab.com/guardianproject/tormobile/arti-mobile-ex.git >> "$LOG" 2>&1 fi echo "- Build arti-mobile-ex for $ARCH ($SDK)" cd "$SOURCE/common" rust_target $SDK $ARCH cargo build --locked --target "$TARGET" --release --target-dir "$BUILDDIR/$SDK/libarti-$ARCH" >> "$LOG" 2>&1 mkdir -p "$BUILDDIR/$SDK/libarti-$ARCH/lib" >> "$LOG" 2>&1 mv "$BUILDDIR/$SDK/libarti-$ARCH/$TARGET/release/libarti_mobile_ex.a" "$BUILDDIR/$SDK/libarti-$ARCH/lib/" >> "$LOG" 2>&1 } ### Experimental! Not working. #build_libartirpc() { # SDK=$1 # ARCH=$2 # # SOURCE="$BUILDDIR/arti" # LOG="$BUILDDIR/artirpc-$SDK-$ARCH.log" # # if [ ! -d "$SOURCE" ]; then # echo "- Check out arti project" # # cd "$BUILDDIR" # git clone --recursive --shallow-submodules --depth 1 --branch "$ARTI_VERSION" https://gitlab.torproject.org/tpo/core/arti.git >> "$LOG" 2>&1 # fi # # echo "- Build arti-rpc-client-core for $ARCH ($SDK)" # # cd "$SOURCE" # # rust_target $SDK $ARCH # # cargo build --locked --package arti-rpc-client-core --features=full --target "$TARGET" --release --target-dir "$BUILDDIR/$SDK/libartirpc-$ARCH" >> "$LOG" 2>&1 # # mkdir -p "$BUILDDIR/$SDK/libartirpc-$ARCH/lib" >> "$LOG" 2>&1 # mv "$BUILDDIR/$SDK/libartirpc-$ARCH/$TARGET/release/libarti_rpc_client_core.a" "$BUILDDIR/$SDK/libartirpc-$ARCH/lib/" >> "$LOG" 2>&1 #} fatten() { NAME=$1 SDK=$2 LIB=${3:-$NAME} LOG="$BUILDDIR/framework.log" echo "- Fatten $LIB in $NAME ($SDK)" mkdir -p "$BUILDDIR/$SDK/$NAME/lib" >> "$LOG" 2>&1 lipo \ -arch arm64 "$BUILDDIR/$SDK/$NAME-arm64/lib/$LIB.a" \ -arch x86_64 "$BUILDDIR/$SDK/$NAME-x86_64/lib/$LIB.a" \ -create -output "$BUILDDIR/$SDK/$NAME/lib/$LIB.a" >> "$LOG" 2>&1 } write_info_plist() { SDK=$1 NAME=$2 VERSION=$3 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102088 cat > "$BUILDDIR/$SDK/$NAME.framework/Info.plist" < CFBundleExecutable $NAME CFBundleIdentifier org.torproject.$NAME CFBundleInfoDictionaryVersion 6.0 CFBundleName $NAME CFBundlePackageType FMWK CFBundleShortVersionString $VERSION CFBundleSupportedPlatforms $SDK CFBundleVersion $VERSION EOF } create_framework() { SDK=$1 IS_FAT=$2 NO_LZMA=$3 LOG="$BUILDDIR/framework.log" if [ -z "$NO_LZMA" ]; then NAME="tor" else NAME="tor-nolzma" fi rm -rf "$BUILDDIR/$SDK/$NAME.framework" >> "$LOG" 2>&1 mkdir -p "$BUILDDIR/$SDK/$NAME.framework/Headers" >> "$LOG" 2>&1 if [ -z "$IS_FAT" ]; then echo "- Create framework for $SDK" POSTFIX="-arm64" else echo "- Create framework for fat $SDK" POSTFIX="" fi if [ -z "$NO_LZMA" ]; then LIBS=("$BUILDDIR/$SDK/libssl$POSTFIX/lib/libssl.a" \ "$BUILDDIR/$SDK/libssl$POSTFIX/lib/libcrypto.a" \ "$BUILDDIR/$SDK/libevent$POSTFIX/lib/libevent.a" \ "$BUILDDIR/$SDK/liblzma$POSTFIX/lib/liblzma.a" \ "$BUILDDIR/$SDK/libtor$POSTFIX/lib/libtor.a") else LIBS=("$BUILDDIR/$SDK/libssl$POSTFIX/lib/libssl.a" \ "$BUILDDIR/$SDK/libssl$POSTFIX/lib/libcrypto.a" \ "$BUILDDIR/$SDK/libevent$POSTFIX/lib/libevent.a" \ "$BUILDDIR/$SDK/libtor-nolzma$POSTFIX/lib/libtor.a") fi libtool -static -o "$BUILDDIR/$SDK/$NAME.framework/$NAME" "${LIBS[@]}" >> "$LOG" 2>&1 HEADERS=("$BUILDDIR/$SDK/libssl-arm64/include"/* \ "$BUILDDIR/$SDK/libevent-arm64/include"/* \ "$BUILDDIR/$SDK/libtor-arm64/include"/*) if [ ! -z "$NO_LZMA" ]; then HEADERS=("$BUILDDIR/$SDK/liblzma-arm64/include"/* "${HEADERS[@]}") fi cp -r "${HEADERS[@]}" "$BUILDDIR/$SDK/$NAME.framework/Headers" >> "$LOG" 2>&1 write_info_plist "$SDK" "$NAME" "${TOR_VERSION##*-}" } create_framework_a() { SDK=$1 IS_FAT=$2 LOG="$BUILDDIR/framework.log" NAME="arti" rm -rf "$BUILDDIR/$SDK/$NAME.framework" >> "$LOG" 2>&1 mkdir -p "$BUILDDIR/$SDK/$NAME.framework/Headers" >> "$LOG" 2>&1 if [ -z "$IS_FAT" ]; then echo "- Create framework for $SDK" POSTFIX="-arm64" else echo "- Create framework for fat $SDK" POSTFIX="" fi LIBS=("$BUILDDIR/$SDK/libarti$POSTFIX/lib/libarti_mobile_ex.a") # "$BUILDDIR/$SDK/libartirpc$POSTFIX/lib/libarti_rpc_client_core.a") libtool -static -o "$BUILDDIR/$SDK/$NAME.framework/$NAME" "${LIBS[@]}" >> "$LOG" 2>&1 cd "$BUILDDIR/arti-mobile-ex/common" cbindgen --lang c --output "$BUILDDIR/$SDK/$NAME.framework/Headers/arti-mobile.h" src/apple.rs >> "$LOG" 2>&1 # cp "$BUILDDIR/arti/crates/arti-rpc-client-core/arti-rpc-client-core.h" \ # "$BUILDDIR/$SDK/$NAME.framework/Headers" >> "$LOG" 2>&1 write_info_plist "$SDK" "$NAME" "${ARTI_MOBILE_VERSION##*-}" } create_xcframework() { NAMES=$1 LOG="$BUILDDIR/framework.log" for name in $NAMES do echo "- Create xcframework for $name" rm -rf "$ROOT/$name.xcframework" "$ROOT/$name.xcframework.zip" >> "$LOG" 2>&1 xcodebuild -create-xcframework \ -framework "$BUILDDIR/iphoneos/$name.framework" \ -framework "$BUILDDIR/iphonesimulator/$name.framework" \ -framework "$BUILDDIR/macosx/$name.framework" \ -output "$ROOT/$name.xcframework" >> "$LOG" 2>&1 cd "$ROOT" zip -r -9 "$name.xcframework.zip" "$name.xcframework" >> "$LOG" 2>&1 shasum -a 256 "$name.xcframework.zip" done } if [ ! -z $WITH_CTOR ]; then build_liblzma iphoneos arm64 15.0 build_libssl iphoneos arm64 15.0 build_libevent iphoneos arm64 15.0 build_libtor iphoneos arm64 15.0 build_libtor iphoneos arm64 15.0 nolzma create_framework iphoneos create_framework iphoneos "" nolzma build_liblzma iphonesimulator arm64 15.0 build_liblzma iphonesimulator x86_64 15.0 fatten liblzma iphonesimulator build_libssl iphonesimulator arm64 15.0 build_libssl iphonesimulator x86_64 15.0 fatten libssl iphonesimulator fatten libssl iphonesimulator libcrypto build_libevent iphonesimulator arm64 15.0 build_libevent iphonesimulator x86_64 15.0 fatten libevent iphonesimulator build_libtor iphonesimulator arm64 15.0 build_libtor iphonesimulator x86_64 15.0 fatten libtor iphonesimulator build_libtor iphonesimulator arm64 15.0 nolzma build_libtor iphonesimulator x86_64 15.0 nolzma fatten libtor-nolzma iphonesimulator libtor create_framework iphonesimulator fat create_framework iphonesimulator fat nolzma build_liblzma macosx arm64 11.0 build_liblzma macosx x86_64 11.0 fatten liblzma macosx build_libssl macosx arm64 11.0 build_libssl macosx x86_64 11.0 fatten libssl macosx fatten libssl macosx libcrypto build_libevent macosx arm64 11.0 build_libevent macosx x86_64 11.0 fatten libevent macosx build_libtor macosx arm64 11.0 build_libtor macosx x86_64 11.0 fatten libtor macosx build_libtor macosx arm64 11.0 nolzma build_libtor macosx x86_64 11.0 nolzma fatten libtor-nolzma macosx libtor create_framework macosx fat create_framework macosx fat nolzma create_xcframework "tor tor-nolzma" fi if [ ! -z $WITH_ARTI ]; then build_libarti iphoneos arm64 # build_libartirpc iphoneos arm64 create_framework_a iphoneos build_libarti iphonesimulator arm64 build_libarti iphonesimulator x86_64 fatten libarti iphonesimulator libarti_mobile_ex # build_libartirpc iphonesimulator arm64 # build_libartirpc iphonesimulator x86_64 # fatten libartirpc iphonesimulator libarti_rpc_client_core create_framework_a iphonesimulator fat build_libarti macosx arm64 build_libarti macosx x86_64 fatten libarti macosx libarti_mobile_ex # build_libartirpc macosx arm64 # build_libartirpc macosx x86_64 # fatten libartirpc macosx libarti_rpc_client_core create_framework_a macosx fat create_xcframework arti fi if [ -z $DEBUG ]; then rm -rf "$BUILDDIR" fi ================================================ FILE: docs/Tor.json ================================================ { "406.8.2": "https://github.com/iCepa/Tor.framework/releases/download/v406.8.2/Tor.framework.zip", "406.8.1": "https://github.com/iCepa/Tor.framework/releases/download/v406.8.1/Tor.framework.zip", "406.7.2": "https://github.com/iCepa/Tor.framework/releases/download/v406.7.2/Tor.framework.zip", "406.7.1": "https://github.com/iCepa/Tor.framework/releases/download/v406.7.1/Tor.framework.zip", "406.5.1": "https://github.com/iCepa/Tor.framework/releases/download/v406.5.1/Tor.framework.zip", "405.8.1": "https://github.com/iCepa/Tor.framework/releases/download/v405.8.1/Tor.framework.zip", "405.7.1": "https://github.com/iCepa/Tor.framework/releases/download/v405.7.1/Tor.framework.zip", "404.6.2": "https://github.com/iCepa/Tor.framework/releases/download/v404.6.2/Tor.framework.zip", "404.6.1": "https://github.com/iCepa/Tor.framework/releases/download/v404.6.1/Tor.framework.zip", "404.5.1": "https://github.com/iCepa/Tor.framework/releases/download/v404.5.1/Tor.framework.zip", "403.6.1": "https://github.com/iCepa/Tor.framework/releases/download/v403.6.1/Tor.framework.zip", "403.5.1": "https://github.com/iCepa/Tor.framework/releases/download/v403.5.1/Tor.framework.zip", "401.6.1": "https://github.com/iCepa/Tor.framework/releases/download/v401.6.1/Tor.framework.zip", "400.6.3": "https://github.com/iCepa/Tor.framework/releases/download/v400.6.3/Tor.framework.zip", "400.6.2": "https://github.com/iCepa/Tor.framework/releases/download/v400.6.2/Tor.framework.zip", "400.6.1": "https://github.com/iCepa/Tor.framework/releases/download/v400.6.1/Tor.framework.zip", "400.5.2": "https://github.com/iCepa/Tor.framework/releases/download/v400.5.2/Tor.framework.zip", "400.5.1": "https://github.com/iCepa/Tor.framework/releases/download/v400.5.1/Tor.framework.zip", "305.8.1": "https://github.com/iCepa/Tor.framework/releases/download/v305.8.1/Tor.framework.zip", "305.7.2": "https://github.com/iCepa/Tor.framework/releases/download/v305.7.2/Tor.framework.zip", "305.7.1": "https://github.com/iCepa/Tor.framework/releases/download/v305.7.1/Tor.framework.zip", "305.2.1": "https://github.com/iCepa/Tor.framework/releases/download/v305.2.1/Tor.framework.zip", "31.9.2": "https://github.com/mtigas/Tor.framework/releases/download/v31.9.2/Tor.framework.zip", "31.9.1": "https://github.com/mtigas/Tor.framework/releases/download/v31.9.1/Tor.framework.zip", "31.8.3": "https://github.com/iCepa/Tor.framework/releases/download/v31.8.3/Tor.framework.zip", "31.8.2": "https://github.com/iCepa/Tor.framework/releases/download/v31.8.2/Tor.framework.zip", "31.8.1": "https://github.com/iCepa/Tor.framework/releases/download/v31.8.1/Tor.framework.zip" } ================================================ FILE: docs/index.html ================================================ tor.framework