From f7beccbaa68ba7e757de29c795763d1da0cab515 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 29 May 2019 15:50:53 -0700 Subject: [PATCH] Resigns + installs test app to connected devices --- AltServer/Base.lproj/Main.storyboard | 133 ++++++- AltServer/{ => Devices}/ALTDeviceManager.h | 0 AltServer/{ => Devices}/ALTDeviceManager.mm | 0 .../Extensions/Result+Conveniences.swift | 41 +++ AltServer/ViewController.swift | 345 +++++++++++++++++- AltStore.xcodeproj/project.pbxproj | 54 ++- Dependencies/AltSign | 2 +- 7 files changed, 552 insertions(+), 23 deletions(-) rename AltServer/{ => Devices}/ALTDeviceManager.h (100%) rename AltServer/{ => Devices}/ALTDeviceManager.mm (100%) create mode 100644 AltServer/Extensions/Result+Conveniences.swift diff --git a/AltServer/Base.lproj/Main.storyboard b/AltServer/Base.lproj/Main.storyboard index 71d4b3b4..e0524817 100644 --- a/AltServer/Base.lproj/Main.storyboard +++ b/AltServer/Base.lproj/Main.storyboard @@ -709,22 +709,131 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + diff --git a/AltServer/ALTDeviceManager.h b/AltServer/Devices/ALTDeviceManager.h similarity index 100% rename from AltServer/ALTDeviceManager.h rename to AltServer/Devices/ALTDeviceManager.h diff --git a/AltServer/ALTDeviceManager.mm b/AltServer/Devices/ALTDeviceManager.mm similarity index 100% rename from AltServer/ALTDeviceManager.mm rename to AltServer/Devices/ALTDeviceManager.mm diff --git a/AltServer/Extensions/Result+Conveniences.swift b/AltServer/Extensions/Result+Conveniences.swift new file mode 100644 index 00000000..ad585a18 --- /dev/null +++ b/AltServer/Extensions/Result+Conveniences.swift @@ -0,0 +1,41 @@ +// +// Result+Conveniences.swift +// AltStore +// +// Created by Riley Testut on 5/22/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +import Foundation + +extension Result +{ + init(_ value: Success?, _ error: Failure?) + { + switch (value, error) + { + case (let value?, _): self = .success(value) + case (_, let error?): self = .failure(error) + case (nil, nil): preconditionFailure("Either value or error must be non-nil") + } + } +} + +extension Result where Success == Void +{ + init(_ success: Bool, _ error: Failure?) + { + if success + { + self = .success(()) + } + else if let error = error + { + self = .failure(error) + } + else + { + preconditionFailure("Error must be non-nil if success is false") + } + } +} diff --git a/AltServer/ViewController.swift b/AltServer/ViewController.swift index 965a9697..fa454ed3 100644 --- a/AltServer/ViewController.swift +++ b/AltServer/ViewController.swift @@ -8,19 +8,358 @@ import Cocoa -class ViewController: NSViewController { +enum InstallError: Error +{ + case invalidCredentials + case noTeam + case missingPrivateKey + case missingCertificate + + var localizedDescription: String { + switch self + { + case .invalidCredentials: return "The provided Apple ID and password are incorrect." + case .noTeam: return "You are not a member of any developer teams." + case .missingPrivateKey: return "The developer certificate's private key could not be found." + case .missingCertificate: return "The developer certificate could not be found." + } + } +} +class ViewController: NSViewController +{ + @IBOutlet private var emailAddressTextField: NSTextField! + @IBOutlet private var passwordTextField: NSSecureTextField! + + @IBOutlet private var devicesButton: NSPopUpButton! + + private var currentDevice: ALTDevice? + override func viewDidLoad() { super.viewDidLoad() + + self.update() + } + + func update() + { + self.devicesButton.removeAllItems() + + let devices = ALTDeviceManager.shared.connectedDevices + + if devices.isEmpty + { + self.devicesButton.addItem(withTitle: "No Connected Device") + } + else + { + for device in devices + { + self.devicesButton.addItem(withTitle: device.name) + } + } + + if let currentDevice = self.currentDevice, let index = devices.firstIndex(of: currentDevice) + { + self.devicesButton.selectItem(at: index) + } + else + { + self.currentDevice = devices.first + self.devicesButton.selectItem(at: 0) + } } } private extension ViewController { - @IBAction func listConnectedDevices(_ sender: NSButton) + @IBAction func installAltStore(_ sender: NSButton) + { + guard let device = self.currentDevice else { return } + guard !self.emailAddressTextField.stringValue.isEmpty, !self.passwordTextField.stringValue.isEmpty else { return } + + self.installAltStore(to: device) + } + + @IBAction func chooseDevice(_ sender: NSPopUpButton) { let devices = ALTDeviceManager.shared.connectedDevices - print(devices) + guard !devices.isEmpty else { return } + + let index = sender.indexOfSelectedItem + + let device = devices[index] + self.currentDevice = device + } +} + +private extension ViewController +{ + func installAltStore(to device: ALTDevice) + { + func present(_ error: Error, title: String) + { + DispatchQueue.main.async { + let alert = NSAlert(error: error) + alert.runModal() + } + } + + self.authenticate() { (result) in + do + { + let account = try result.get() + self.fetchTeam(for: account) { (result) in + do + { + let team = try result.get() + + self.register(device, team: team) { (result) in + do + { + let device = try result.get() + + self.fetchCertificate(for: team) { (result) in + do + { + let certificate = try result.get() + + self.registerAppID(name: "AltStore", identifier: "com.rileytestut.AltStore", team: team) { (result) in + do + { + let appID = try result.get() + self.fetchProvisioningProfile(for: appID, team: team) { (result) in + do + { + let provisioningProfile = try result.get() + try self.installIPA(to: device, team: team, appID: appID, certificate: certificate, profile: provisioningProfile) + } + catch + { + present(error, title: "Failed to Fetch Provisioning Profile") + } + } + + } + catch + { + present(error, title: "Failed to Register App") + } + } + } + catch + { + present(error, title: "Failed to Fetch Certificate") + } + } + } + catch + { + present(error, title: "Failed to Register Device") + } + } + } + catch + { + present(error, title: "Failed to Fetch Team") + } + } + } + catch + { + present(error, title: "Failed to Authenticate") + } + } + } + + func authenticate(completionHandler: @escaping (Result) -> Void) + { + ALTAppleAPI.shared.authenticate(appleID: self.emailAddressTextField.stringValue, password: self.passwordTextField.stringValue) { (account, error) in + let result = Result(account, error) + completionHandler(result) + } + } + + func fetchTeam(for account: ALTAccount, completionHandler: @escaping (Result) -> Void) + { + ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in + do + { + let teams = try Result(teams, error).get() + guard let team = teams.first else { throw InstallError.noTeam } + + completionHandler(.success(team)) + } + catch + { + completionHandler(.failure(error)) + } + } + } + + func fetchCertificate(for team: ALTTeam, completionHandler: @escaping (Result) -> Void) + { + ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in + do + { + let certificates = try Result(certificates, error).get() + + if let certificate = certificates.first + { + ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in + do + { + try Result(success, error).get() + self.fetchCertificate(for: team, completionHandler: completionHandler) + } + catch + { + completionHandler(.failure(error)) + } + } + } + else + { + ALTAppleAPI.shared.addCertificate(machineName: "AltStore", to: team) { (certificate, error) in + do + { + let certificate = try Result(certificate, error).get() + guard let privateKey = certificate.privateKey else { throw InstallError.missingPrivateKey } + + ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in + do + { + let certificates = try Result(certificates, error).get() + + guard let certificate = certificates.first(where: { $0.identifier == certificate.identifier }) else { + throw InstallError.missingCertificate + } + + certificate.privateKey = privateKey + + completionHandler(.success(certificate)) + } + catch + { + completionHandler(.failure(error)) + } + } + } + catch + { + completionHandler(.failure(error)) + } + } + } + } + catch + { + completionHandler(.failure(error)) + } + } + } + + func registerAppID(name appName: String, identifier: String, team: ALTTeam, completionHandler: @escaping (Result) -> Void) + { + var bundleID = "com." + team.account.firstName.lowercased() + team.account.lastName.lowercased() + "." + identifier + bundleID = bundleID.replacingOccurrences(of: " ", with: "") + + ALTAppleAPI.shared.fetchAppIDs(for: team) { (appIDs, error) in + do + { + let appIDs = try Result(appIDs, error).get() + + if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID }) + { + completionHandler(.success(appID)) + } + else + { + ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team) { (appID, error) in + completionHandler(Result(appID, error)) + } + } + } + catch + { + completionHandler(.failure(error)) + } + } + } + + func register(_ device: ALTDevice, team: ALTTeam, completionHandler: @escaping (Result) -> Void) + { + ALTAppleAPI.shared.fetchDevices(for: team) { (devices, error) in + do + { + let devices = try Result(devices, error).get() + + if let device = devices.first(where: { $0.identifier == device.identifier }) + { + completionHandler(.success(device)) + } + else + { + ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, team: team) { (device, error) in + completionHandler(Result(device, error)) + } + } + } + catch + { + completionHandler(.failure(error)) + } + } + } + + func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, completionHandler: @escaping (Result) -> Void) + { + ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team) { (profile, error) in + completionHandler(Result(profile, error)) + } + } + + func installIPA(to device: ALTDevice, team: ALTTeam, appID: ALTAppID, certificate: ALTCertificate, profile: ALTProvisioningProfile) throws + { + let ipaURL = Bundle.main.url(forResource: "App", withExtension: ".ipa")! + + let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + + do + { + try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil) + + let appBundleURL = try FileManager.default.unzipAppBundle(at: ipaURL, toDirectory: destinationDirectoryURL) + print(appBundleURL) + + let infoPlistURL = appBundleURL.appendingPathComponent("Info.plist") + + guard var infoDictionary = NSDictionary(contentsOf: infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) } + infoDictionary["altstore"] = ["udid": device.identifier] + try (infoDictionary as NSDictionary).write(to: infoPlistURL) + + let zippedURL = try FileManager.default.zipAppBundle(at: appBundleURL) + + let resigner = ALTSigner(team: team, certificate: certificate) + resigner.signApp(at: zippedURL, provisioningProfile: profile) { (resignedURL, error) in + do + { + let resignedURL = try Result(resignedURL, error).get() + ALTDeviceManager.shared.installApp(at: resignedURL, to: device) { (success, error) in + let result = Result(success, error) + print(result) + } + } + catch + { + print("Failed to install app", error) + } + } + } + catch + { + print("Failed to install AltStore", error) + } } } diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 6ba93774..2eb9a819 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -110,6 +110,8 @@ BF5AB3A82285FE7500DC914B /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; }; BF5AB3A92285FE7500DC914B /* AltSign.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BF9B63C6229DD44E002F0A62 /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9B63C5229DD44D002F0A62 /* AltSign.framework */; }; + BF9B63F6229DE476002F0A62 /* App.ipa in Resources */ = {isa = PBXBuildFile; fileRef = BF9B63F5229DE476002F0A62 /* App.ipa */; }; + BF9B6426229E1331002F0A62 /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9B6425229E1331002F0A62 /* Result+Conveniences.swift */; }; BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB11691229322E400BB457C /* DatabaseManager.swift */; }; BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */; }; BFB1169D22932DB100BB457C /* Apps.json in Resources */ = {isa = PBXBuildFile; fileRef = BFB1169C22932DB100BB457C /* Apps.json */; }; @@ -275,6 +277,8 @@ BF4588962298DE6E00BD7491 /* libzip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libzip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF9B63C5229DD44D002F0A62 /* AltSign.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BF9B63F5229DE476002F0A62 /* App.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; path = App.ipa; sourceTree = ""; }; + BF9B6425229E1331002F0A62 /* Result+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Result+Conveniences.swift"; sourceTree = ""; }; BFB11691229322E400BB457C /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; }; BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+ManagedObjectContext.swift"; sourceTree = ""; }; BFB1169C22932DB100BB457C /* Apps.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Apps.json; sourceTree = ""; }; @@ -334,14 +338,12 @@ isa = PBXGroup; children = ( BF45868F229872EA00BD7491 /* AppDelegate.swift */, - BF458691229872EA00BD7491 /* ViewController.swift */, - BF4586C32298CDB800BD7491 /* ALTDeviceManager.h */, - BF4586C42298CDB800BD7491 /* ALTDeviceManager.mm */, - BF458693229872EA00BD7491 /* Assets.xcassets */, BF458695229872EA00BD7491 /* Main.storyboard */, - BF458698229872EA00BD7491 /* Info.plist */, - BF458699229872EA00BD7491 /* AltServer.entitlements */, - BF4586C22298CDB800BD7491 /* AltServer-Bridging-Header.h */, + BF458691229872EA00BD7491 /* ViewController.swift */, + BF703195229F36FF006E110F /* Devices */, + BF703193229F36E5006E110F /* Extensions */, + BF703194229F36F6006E110F /* Resources */, + BF703196229F370F006E110F /* Supporting Files */, ); path = AltServer; sourceTree = ""; @@ -484,6 +486,42 @@ name = libcnary; sourceTree = ""; }; + BF703193229F36E5006E110F /* Extensions */ = { + isa = PBXGroup; + children = ( + BF9B6425229E1331002F0A62 /* Result+Conveniences.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + BF703194229F36F6006E110F /* Resources */ = { + isa = PBXGroup; + children = ( + BF458693229872EA00BD7491 /* Assets.xcassets */, + BF9B63F5229DE476002F0A62 /* App.ipa */, + ); + name = Resources; + sourceTree = ""; + }; + BF703195229F36FF006E110F /* Devices */ = { + isa = PBXGroup; + children = ( + BF4586C32298CDB800BD7491 /* ALTDeviceManager.h */, + BF4586C42298CDB800BD7491 /* ALTDeviceManager.mm */, + ); + path = Devices; + sourceTree = ""; + }; + BF703196229F370F006E110F /* Supporting Files */ = { + isa = PBXGroup; + children = ( + BF458698229872EA00BD7491 /* Info.plist */, + BF458699229872EA00BD7491 /* AltServer.entitlements */, + BF4586C22298CDB800BD7491 /* AltServer-Bridging-Header.h */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; BFB1169E22933DDC00BB457C /* Updates */ = { isa = PBXGroup; children = ( @@ -762,6 +800,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BF9B63F6229DE476002F0A62 /* App.ipa in Resources */, BF458694229872EA00BD7491 /* Assets.xcassets in Resources */, BF458697229872EA00BD7491 /* Main.storyboard in Resources */, ); @@ -787,6 +826,7 @@ files = ( BF458692229872EA00BD7491 /* ViewController.swift in Sources */, BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */, + BF9B6426229E1331002F0A62 /* Result+Conveniences.swift in Sources */, BF4586C52298CDB800BD7491 /* ALTDeviceManager.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Dependencies/AltSign b/Dependencies/AltSign index 0862204b..b490973e 160000 --- a/Dependencies/AltSign +++ b/Dependencies/AltSign @@ -1 +1 @@ -Subproject commit 0862204b83a95c51434bcfe3a980493208fdd064 +Subproject commit b490973e8430a9efc277d9c21b204ea5cc896301