From 3dab3271bdbadc2e20b4de02fe4c07466ed2e4ae Mon Sep 17 00:00:00 2001 From: JJTech0130 Date: Tue, 26 Mar 2024 15:38:20 -0400 Subject: [PATCH] yay! New SwiftUI, relay functions --- ValidationRelay.xcodeproj/project.pbxproj | 45 +++++- .../xcshareddata/swiftpm/Package.resolved | 14 ++ .../xcschemes/ValidationRelay.xcscheme | 15 +- ValidationRelay/ContentView.swift | 98 ++++++++++-- ValidationRelay/Relay.swift | 149 ++++++++++++++++++ ValidationRelay/ValidationData.swift | 21 ++- .../ValidationRelay-Bridging-Header.h | 21 ++- ValidationRelay/ValidationRelay.entitlements | 5 + ValidationRelay/ValidationRelayApp.swift | 2 +- 9 files changed, 341 insertions(+), 29 deletions(-) create mode 100644 ValidationRelay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 ValidationRelay/Relay.swift diff --git a/ValidationRelay.xcodeproj/project.pbxproj b/ValidationRelay.xcodeproj/project.pbxproj index 669cf7b..e6d4954 100644 --- a/ValidationRelay.xcodeproj/project.pbxproj +++ b/ValidationRelay.xcodeproj/project.pbxproj @@ -12,20 +12,25 @@ 480989A62BB0C01A00B49AE8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 480989A52BB0C01A00B49AE8 /* Assets.xcassets */; }; 480989A92BB0C01A00B49AE8 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 480989A82BB0C01A00B49AE8 /* Preview Assets.xcassets */; }; 480989B32BB0C19300B49AE8 /* absd.defs in Sources */ = {isa = PBXBuildFile; fileRef = 480989B22BB0C0F800B49AE8 /* absd.defs */; settings = {ATTRIBUTES = (Client, ); }; }; + 48D289112BB3579000EA9DEC /* libMobileGestalt.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 48D289102BB3579000EA9DEC /* libMobileGestalt.tbd */; }; 48D8604A2BB0DD110092EF79 /* ValidationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48D860492BB0DD110092EF79 /* ValidationData.swift */; }; + 48D8604D2BB1B8CC0092EF79 /* NWWebSocket in Frameworks */ = {isa = PBXBuildFile; productRef = 48D8604C2BB1B8CC0092EF79 /* NWWebSocket */; }; + 48D8604F2BB1B8EB0092EF79 /* Relay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48D8604E2BB1B8EB0092EF79 /* Relay.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 4809899E2BB0C01900B49AE8 /* ValidationRelay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ValidationRelay.app; sourceTree = BUILT_PRODUCTS_DIR; }; 480989A12BB0C01900B49AE8 /* ValidationRelayApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidationRelayApp.swift; sourceTree = ""; }; - 480989A32BB0C01900B49AE8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 480989A32BB0C01900B49AE8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; usesTabs = 0; }; 480989A52BB0C01A00B49AE8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 480989A82BB0C01A00B49AE8 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 480989AF2BB0C0AF00B49AE8 /* ValidationRelay-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ValidationRelay-Bridging-Header.h"; sourceTree = ""; }; 480989B22BB0C0F800B49AE8 /* absd.defs */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.mig; path = absd.defs; sourceTree = ""; }; 480989B42BB0D16400B49AE8 /* ValidationRelay.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ValidationRelay.entitlements; sourceTree = ""; }; + 48D289102BB3579000EA9DEC /* libMobileGestalt.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libMobileGestalt.tbd; path = usr/lib/libMobileGestalt.tbd; sourceTree = SDKROOT; }; 48D860482BB0D7BF0092EF79 /* build.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build.sh; sourceTree = ""; }; 48D860492BB0DD110092EF79 /* ValidationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidationData.swift; sourceTree = ""; }; + 48D8604E2BB1B8EB0092EF79 /* Relay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relay.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -33,6 +38,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 48D289112BB3579000EA9DEC /* libMobileGestalt.tbd in Frameworks */, + 48D8604D2BB1B8CC0092EF79 /* NWWebSocket in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -45,6 +52,7 @@ 48D860482BB0D7BF0092EF79 /* build.sh */, 480989A02BB0C01900B49AE8 /* ValidationRelay */, 4809899F2BB0C01900B49AE8 /* Products */, + 48D2890F2BB3579000EA9DEC /* Frameworks */, ); sourceTree = ""; }; @@ -67,6 +75,7 @@ 480989A72BB0C01A00B49AE8 /* Preview Content */, 480989AF2BB0C0AF00B49AE8 /* ValidationRelay-Bridging-Header.h */, 48D860492BB0DD110092EF79 /* ValidationData.swift */, + 48D8604E2BB1B8EB0092EF79 /* Relay.swift */, ); path = ValidationRelay; sourceTree = ""; @@ -79,6 +88,14 @@ path = "Preview Content"; sourceTree = ""; }; + 48D2890F2BB3579000EA9DEC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 48D289102BB3579000EA9DEC /* libMobileGestalt.tbd */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -95,6 +112,9 @@ dependencies = ( ); name = ValidationRelay; + packageProductDependencies = ( + 48D8604C2BB1B8CC0092EF79 /* NWWebSocket */, + ); productName = ValidationRelay; productReference = 4809899E2BB0C01900B49AE8 /* ValidationRelay.app */; productType = "com.apple.product-type.application"; @@ -124,6 +144,9 @@ Base, ); mainGroup = 480989952BB0C01900B49AE8; + packageReferences = ( + 48D8604B2BB1B8CC0092EF79 /* XCRemoteSwiftPackageReference "NWWebSocket" */, + ); productRefGroup = 4809899F2BB0C01900B49AE8 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -154,6 +177,7 @@ 480989B32BB0C19300B49AE8 /* absd.defs in Sources */, 480989A42BB0C01900B49AE8 /* ContentView.swift in Sources */, 480989A22BB0C01900B49AE8 /* ValidationRelayApp.swift in Sources */, + 48D8604F2BB1B8EB0092EF79 /* Relay.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -374,6 +398,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 48D8604B2BB1B8CC0092EF79 /* XCRemoteSwiftPackageReference "NWWebSocket" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pusher/NWWebSocket.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.5.4; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 48D8604C2BB1B8CC0092EF79 /* NWWebSocket */ = { + isa = XCSwiftPackageProductDependency; + package = 48D8604B2BB1B8CC0092EF79 /* XCRemoteSwiftPackageReference "NWWebSocket" */; + productName = NWWebSocket; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 480989962BB0C01900B49AE8 /* Project object */; } diff --git a/ValidationRelay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ValidationRelay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..55f6b9e --- /dev/null +++ b/ValidationRelay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "nwwebsocket", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pusher/NWWebSocket.git", + "state" : { + "revision" : "1e545fcb53966272fc042aa17ae932f11239e00f", + "version" : "0.5.4" + } + } + ], + "version" : 2 +} diff --git a/ValidationRelay.xcodeproj/xcshareddata/xcschemes/ValidationRelay.xcscheme b/ValidationRelay.xcodeproj/xcshareddata/xcschemes/ValidationRelay.xcscheme index 79bab86..e2540fd 100644 --- a/ValidationRelay.xcodeproj/xcshareddata/xcschemes/ValidationRelay.xcscheme +++ b/ValidationRelay.xcodeproj/xcshareddata/xcschemes/ValidationRelay.xcscheme @@ -39,17 +39,8 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - - - - - - - + - + URL { + if selectedRelay == "Custom" { + if let url = URL(string: customRelayURL) { + return url + } + } else if selectedRelay == "pypush" { + return URL(string: "wss://registration-relay.jjtech.dev/api/v1/provider")! } - .padding() - .onAppear { - test() + + // Default to Beeper relay + selectedRelay = "Beeper" + return URL(string: "wss://registration-relay.beeper.com/api/v1/provider")! + } + + var body: some View { + List { + Section { + Toggle("Relay", isOn: $wantRelayConnected) + .onChange(of: wantRelayConnected) { newValue in + // Connect or disconnect the relay + if newValue { + relayConnectionManager.connect(getCurrentRelayURL()) + } else { + relayConnectionManager.disconnect() + } + } + HStack { + Text("Registration Code") + Spacer() + Text(relayConnectionManager.registrationCode) + .foregroundColor(.secondary) + } + } footer: { + Text(relayConnectionManager.connectionStatusMessage) + } + Section { + // TODO: Actually support running in the background + Toggle("Run in Background", isOn: .constant(false)) + .disabled(true) + Picker("Relay", selection: $selectedRelay) { + Text("Beeper").tag("Beeper") + //Text("pypush").tag("pypush") + Text("Custom").tag("Custom") + } + .pickerStyle(.segmented) + .onChange(of: selectedRelay) { newValue in + // Disconnect when the user is switching relay servers + wantRelayConnected = false + } + if (selectedRelay == "Custom") { + TextField("Custom Relay Server URL", text: $customRelayURL) + .textContentType(.URL) + .autocorrectionDisabled() + .autocapitalization(.none) + } + } header: { + Text("Connection settings") + } footer: { + Text("Beeper's relay server is recommended for most users") + } + + Section { + Button("Reset Registration Code") { + // Do reset stuff + } + //.foregroundColor(.red) + .frame(maxWidth: .infinity) + .disabled(true) + } footer: { + Text("You will need to re-enter the code on your other devices") + } } + .listStyle(.grouped) } + } #Preview { - ContentView() + ContentView(relayConnectionManager: RelayConnectionManager()) } diff --git a/ValidationRelay/Relay.swift b/ValidationRelay/Relay.swift new file mode 100644 index 0000000..2013aba --- /dev/null +++ b/ValidationRelay/Relay.swift @@ -0,0 +1,149 @@ +// +// Relay.swift +// ValidationRelay +// +// Created by James Gill on 3/25/24. +// + +import Foundation +import Network +import NWWebSocket +import SwiftUI + +func getIdentifiers() -> [String: String] { + // TODO: Don't fake these + // Use uname to get hardware version + // Use UIDevice to get software version + var ustruct: utsname = utsname() + uname(&ustruct) + var ustruct2 = ustruct + let machine = withUnsafePointer(to: &ustruct2.machine) { + $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: ustruct.machine)) { + String(cString: $0) + } + } + + + let identifiers = [ + "hardware_version": machine, + "software_name": "iPhone OS", + "software_version": UIDevice.current.systemVersion, + "software_build_id": buildNumber()!, + "unique_device_id": MGCopyAnswer("UniqueDeviceID" as CFString)!.takeRetainedValue() as! String, + "serial_number": MGCopyAnswer("SerialNumber" as CFString)!.takeRetainedValue() as! String + + ] + return identifiers +} +class RelayConnectionManager: WebSocketConnectionDelegate, ObservableObject { + + var connection: WebSocketConnection? + + @Published var registrationCode: String = "None" + @Published var connectionStatusMessage: String = "" + + // These must all be saved together + @AppStorage("savedRegistrationSecret") var savedRegistrationSecret = "" + @AppStorage("savedRegistrationCode") var savedRegistrationCode = "" + @AppStorage("savedRegistrationURL") var savedRegistrationURL = "" + + var currentURL: URL? = nil + + func connect(_ url: URL) { + connectionStatusMessage = "Connecting..." + currentURL = url + let connection = NWWebSocket(url: url, connectAutomatically: true) + connection.delegate = self + connection.connect() + self.connection = connection + //print(getIdentifiers()) + } + + func disconnect() { + connection?.disconnect(closeCode: .protocolCode(.normalClosure)) + currentURL = nil + } + + func webSocketDidConnect(connection: WebSocketConnection) { + connectionStatusMessage = "Connected" + var registerCommand = ["command": "register", "data": ["": ""]] as [String : Any] + if currentURL?.absoluteString == savedRegistrationURL { + print("Using saved registration code") + registerCommand["data"] = ["code": savedRegistrationCode, "secret": savedRegistrationSecret] + } + let data = try! JSONSerialization.data(withJSONObject: registerCommand) + print(String(data: data, encoding: .utf8)!) + connection.send(string: String(data: data, encoding: .utf8)!) + } + func webSocketDidDisconnect(connection: WebSocketConnection, closeCode: NWProtocolWebSocket.CloseCode, reason: Data?) { + print("Disconnected") + // Check if "error" is in the current status message, if so don't clear it + if !connectionStatusMessage.contains("Error") { + connectionStatusMessage = "" + } + } + + func webSocketViabilityDidChange(connection: WebSocketConnection, isViable: Bool) { + // Respond to a WebSocket connection viability change event + print("WebSocket connection viability changed to \(isViable), ignoring") + } + + func webSocketDidAttemptBetterPathMigration(result: Result) { + // Respond to when a WebSocket connection migrates to a better network path + // (e.g. A device moves from a cellular connection to a Wi-Fi connection) + print("WebSocket connection attempted better path migration, ignoring") + } + + func webSocketDidReceiveError(connection: WebSocketConnection, error: NWError) { + print(error) + connectionStatusMessage = "Error connecting to relay: \(error)" + } + + func webSocketDidReceivePong(connection: WebSocketConnection) { + // Respond to a WebSocket connection receiving a Pong from the peer + } + + func webSocketDidReceiveMessage(connection: WebSocketConnection, string: String) { + print("Got string msg") + // Parse as JSON + let json = try! JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: []) + print(json) + + // Save registration code + if let jsonDict = json as? [String: Any] { + if let data = jsonDict["data"] as? [String: Any] { + if let code = data["code"] as? String { + registrationCode = code + savedRegistrationCode = code + savedRegistrationSecret = data["secret"] as? String ?? "" + savedRegistrationURL = currentURL?.absoluteString ?? "" + } + } + if let command = jsonDict["command"] as? String { + if command == "get-version-info" { + let versionInfo = ["command": "response", "data": ["versions": getIdentifiers()], "id": jsonDict["id"]!] as [String : Any] + print("Sending version info: \(versionInfo)") + let data = try! JSONSerialization.data(withJSONObject: versionInfo) + connection.send(string: String(data: data, encoding: .utf8)!) + } + if command == "get-validation-data" { + print("Sending val data") + let v = generateValidationData() + print("Validation data: \(v.base64EncodedString())") + let validationData = ["command": "response", "data": ["data": v.base64EncodedString()], "id": jsonDict["id"]!] as [String : Any] + let data = try! JSONSerialization.data(withJSONObject: validationData) + connection.send(string: String(data: data, encoding: .utf8)!) + } + } + } + // Respond to a WebSocket connection receiving a `String` message + } + + func webSocketDidReceiveMessage(connection: WebSocketConnection, data: Data) { + // Respond to a WebSocket connection receiving a binary `Data` message + // let json = try! JSONSerialization.jsonObject(with: data, options: []) + print("got data msg") + print(data) + } +} + diff --git a/ValidationRelay/ValidationData.swift b/ValidationRelay/ValidationData.swift index 4e10cae..283a4cb 100644 --- a/ValidationRelay/ValidationData.swift +++ b/ValidationRelay/ValidationData.swift @@ -7,6 +7,22 @@ import Foundation +//struct ValidationSession { +// init() { +// // Setup session, make request +// } +// +// var expiry: Date { +// get { +// return Date() +// } +// } +// +// func sign(_ data: Data = Data()) -> Data { +// return Data() +// } +//} + /// Makes an HTTP request to http://static.ess.apple.com/identity/validation/cert-1.0.plist /// parses the plist and extracts the raw certificate data func getCertificate() -> Data { @@ -39,7 +55,7 @@ func initializeValidation(_ request: Data) -> Data { return sessionInfo } -func test() { +func generateValidationData() -> Data { let cert: Data = getCertificate() var val_ctx: UInt64 = 0 var session_req: NSData? = NSData() @@ -60,5 +76,6 @@ func test() { NSLog("VALIDATION DATA \(signature!.base64EncodedString())") - + return signature! as Data } + diff --git a/ValidationRelay/ValidationRelay-Bridging-Header.h b/ValidationRelay/ValidationRelay-Bridging-Header.h index db57f66..0623237 100644 --- a/ValidationRelay/ValidationRelay-Bridging-Header.h +++ b/ValidationRelay/ValidationRelay-Bridging-Header.h @@ -5,11 +5,14 @@ #import "absd.h" #import #import +#import extern kern_return_t bootstrap_look_up(mach_port_t bp, const char *service_name, mach_port_t *sp); mach_port_t ABSD_PORT = MACH_PORT_NULL; +uint32_t NAC_MAGIC = 0x50936603; + int NACInit(NSData *certificate, uint64_t *out_val_ctx, NSData **out_session_request) { if (ABSD_PORT == MACH_PORT_NULL) { kern_return_t kret = bootstrap_look_up(bootstrap_port, "com.apple.absd", &ABSD_PORT); @@ -23,7 +26,7 @@ int NACInit(NSData *certificate, uint64_t *out_val_ctx, NSData **out_session_req mach_msg_type_number_t session_requestCnt = 0; uint64_t val_ctx = 0; - int ret = rawNACInit(ABSD_PORT, 0x50936603, (vm_offset_t)[certificate bytes], [certificate length], &val_ctx, &session_request, &session_requestCnt); + int ret = rawNACInit(ABSD_PORT, NAC_MAGIC, (vm_offset_t)[certificate bytes], [certificate length], &val_ctx, &session_request, &session_requestCnt); if (ret != 0) { NSLog(@"remoteNACInit failed: %d", ret); return ret; @@ -36,13 +39,13 @@ int NACInit(NSData *certificate, uint64_t *out_val_ctx, NSData **out_session_req } int NACKeyEstablishment(uint64_t val_ctx, NSData *session_response) { - return rawNACKeyEstablishment(ABSD_PORT, 0x50936603, val_ctx, (vm_offset_t)[session_response bytes], [session_response length]); + return rawNACKeyEstablishment(ABSD_PORT, NAC_MAGIC, val_ctx, (vm_offset_t)[session_response bytes], [session_response length]); } int NACSign(uint64_t val_ctx, NSData *data, NSData **out_signature) { vm_offset_t signature = 0; mach_msg_type_number_t signatureCnt = 0; - int ret = rawNACSign(ABSD_PORT, 0x50936603, val_ctx, (vm_offset_t)[data bytes], [data length], &signature, &signatureCnt); + int ret = rawNACSign(ABSD_PORT, NAC_MAGIC, val_ctx, (vm_offset_t)[data bytes], [data length], &signature, &signatureCnt); if (ret != 0) { NSLog(@"remoteNACSign failed: %d", ret); return ret; @@ -50,3 +53,15 @@ int NACSign(uint64_t val_ctx, NSData *data, NSData **out_signature) { *out_signature = [NSData dataWithBytes:(void *)signature length:signatureCnt]; return 0; } + +NSString* buildNumber() { + size_t malloc_size = 10; + char *buildNumberBuf = malloc(malloc_size); + sysctlbyname("kern.osversion\0", (void *)buildNumberBuf, &malloc_size, NULL, 0); + + // we don't need to free `buildNumberBuf` if we pass it into this method + NSString *buildNumber = [NSString stringWithCString:buildNumberBuf encoding:NSUTF8StringEncoding]; + return buildNumber; +} + +extern CFTypeRef MGCopyAnswer(CFStringRef property); diff --git a/ValidationRelay/ValidationRelay.entitlements b/ValidationRelay/ValidationRelay.entitlements index 4e0c3b6..951a702 100644 --- a/ValidationRelay/ValidationRelay.entitlements +++ b/ValidationRelay/ValidationRelay.entitlements @@ -2,6 +2,11 @@ + com.apple.private.MobileGestalt.AllowedProtectedKeys + + SerialNumber + UniqueDeviceID + abs-client 772496756 diff --git a/ValidationRelay/ValidationRelayApp.swift b/ValidationRelay/ValidationRelayApp.swift index 4cd863e..f7b762c 100644 --- a/ValidationRelay/ValidationRelayApp.swift +++ b/ValidationRelay/ValidationRelayApp.swift @@ -11,7 +11,7 @@ import SwiftUI struct ValidationRelayApp: App { var body: some Scene { WindowGroup { - ContentView() + ContentView(relayConnectionManager: RelayConnectionManager()) } } }