fix: Account exporting/importing

This commit is contained in:
ny
2025-11-07 16:52:55 -05:00
parent a0ae0cb2b1
commit 15ffe766d3
4 changed files with 112 additions and 42 deletions

View File

@@ -10,6 +10,8 @@ import UIKit
import Roxas import Roxas
import minimuxer import minimuxer
import WidgetKit import WidgetKit
import AltSign
import AltStoreCore import AltStoreCore
import UniformTypeIdentifiers import UniformTypeIdentifiers
@@ -70,7 +72,9 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
} }
#if !targetEnvironment(simulator) #if !targetEnvironment(simulator)
guard let pf = PairingFileManager.shared.fetchPairingFile(presentingVC: self) else {
detectAndImportAccountFile()
guard let pf = fetchPairingFile() else {
displayError("Device pairing file not found.") displayError("Device pairing file not found.")
return return
} }
@@ -102,6 +106,11 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let url = urls[0] let url = urls[0]
let isSecuredURL = url.startAccessingSecurityScopedResource() == true let isSecuredURL = url.startAccessingSecurityScopedResource() == true
defer {
if (isSecuredURL) {
url.stopAccessingSecurityScopedResource()
}
}
do { do {
let data = try Data(contentsOf: url) let data = try Data(contentsOf: url)
@@ -114,14 +123,68 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
} catch { } catch {
displayError("Unable to read pairing file") displayError("Unable to read pairing file")
} }
if isSecuredURL { url.stopAccessingSecurityScopedResource() } controller.dismiss(animated: true, completion: nil)
controller.dismiss(animated: true)
} }
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.") displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
} }
func start_minimuxer_threads(_ pairing_file: String) {
target_minimuxer_address()
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do {
// enable minimuxer console logging only if enabled in settings
let isMinimuxerConsoleLoggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
try minimuxer.startWithLogger(pairing_file, documentsDirectory, isMinimuxerConsoleLoggingEnabled)
} catch {
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
}
start_auto_mounter(documentsDirectory)
// Create destinationViewController now so view controllers can register for receiving Notifications.
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as? TabBarController
}
func importAccountAtFile(_ file: URL, remove: Bool = false) {
_ = file.startAccessingSecurityScopedResource()
defer { file.stopAccessingSecurityScopedResource() }
guard let accountD = try? Data(contentsOf: file) else {
let toastView = ToastView(text: NSLocalizedString("Could not read data from file!", comment: ""), detailText: "\(file)")
return toastView.show(in: self)
}
guard let account = try? Foundation.JSONDecoder().decode(ImportedAccount.self, from: accountD) else {
let toastView = ToastView(text: NSLocalizedString("Could not parse data from file!", comment: ""), detailText: "\(file)")
return toastView.show(in: self)
}
print("We want to import this account probably: \(account)")
if remove {
try? FileManager.default.removeItem(at: file)
}
Keychain.shared.appleIDEmailAddress = account.email
Keychain.shared.appleIDPassword = account.password
Keychain.shared.adiPb = account.adiPB
Keychain.shared.identifier = account.local_user
if let altCert = ALTCertificate(p12Data: account.cert, password: account.certpass) {
Keychain.shared.signingCertificate = altCert.encryptedP12Data(withPassword: "")!
Keychain.shared.signingCertificatePassword = account.certpass
let toastView = ToastView(text: NSLocalizedString("Successfully imported '\(account.email)'!", comment: ""), detailText: "SideStore should be fully operational!")
return toastView.show(in: self)
} else {
let toastView = ToastView(text: NSLocalizedString("Failed to import account certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct. Still imported account/adi.pb details!")
return toastView.show(in: self)
}
}
func detectAndImportAccountFile() {
let accountFileURL = FileManager.default.documentsDirectory.appendingPathComponent("Account.sideconf")
#if !DEBUG
importAccountAtFile(accountFileURL, remove: true)
#else
importAccountAtFile(accountFileURL)
#endif
}
} }
extension LaunchViewController { extension LaunchViewController {

View File

@@ -261,28 +261,34 @@ final class SettingsViewController: UITableViewController
} }
func importAccountAtFile(_ file: URL, remove: Bool = false) { func importAccountAtFile(_ file: URL, remove: Bool = false) {
if let accountData = try? Data(contentsOf: file), _ = file.startAccessingSecurityScopedResource()
let account = try? Foundation.JSONDecoder().decode(ImportedAccount.self, from: accountData) { defer { file.stopAccessingSecurityScopedResource() }
print("We want to import this account probably: \(account)") guard let accountD = try? Data(contentsOf: file) else {
if remove { let toastView = ToastView(text: NSLocalizedString("Could not read data from file!", comment: ""), detailText: "\(file)")
try? FileManager.default.removeItem(at: file) return toastView.show(in: self)
} }
Keychain.shared.appleIDEmailAddress = account.email guard let account = try? Foundation.JSONDecoder().decode(ImportedAccount.self, from: accountD) else {
Keychain.shared.appleIDPassword = account.password let toastView = ToastView(text: NSLocalizedString("Could not parse data from file!", comment: ""), detailText: "\(file)")
Keychain.shared.adiPb = account.adiPB return toastView.show(in: self)
Keychain.shared.adiSerial = account.serial }
Keychain.shared.identifier = account.local_user print("We want to import this account probably: \(account)")
signIn() if remove {
update() try? FileManager.default.removeItem(at: file)
if let altCert = ALTCertificate(p12Data: account.cert, password: account.certpass) { }
Keychain.shared.signingCertificate = altCert.encryptedP12Data(withPassword: "")! Keychain.shared.appleIDEmailAddress = account.email
Keychain.shared.signingCertificatePassword = account.certpass Keychain.shared.appleIDPassword = account.password
let toastView = ToastView(text: NSLocalizedString("Successfully imported '\(account.email)'!", comment: ""), detailText: "SideStore should be fully operational!") Keychain.shared.adiPb = account.adiPB
return toastView.show(in: self) Keychain.shared.identifier = account.local_user
} else { signIn()
let toastView = ToastView(text: NSLocalizedString("Failed to import account certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct. Still imported account/adi.pb details!") update()
return toastView.show(in: self) if let altCert = ALTCertificate(p12Data: account.cert, password: account.certpass) {
} Keychain.shared.signingCertificate = altCert.encryptedP12Data(withPassword: "")!
Keychain.shared.signingCertificatePassword = account.certpass
let toastView = ToastView(text: NSLocalizedString("Successfully imported '\(account.email)'!", comment: ""), detailText: "SideStore should be fully operational!")
return toastView.show(in: self)
} else {
let toastView = ToastView(text: NSLocalizedString("Failed to import account certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct. Still imported account/adi.pb details!")
return toastView.show(in: self)
} }
} }
@@ -300,7 +306,6 @@ final class SettingsViewController: UITableViewController
let password = Keychain.shared.appleIDPassword, let password = Keychain.shared.appleIDPassword,
let cert = Keychain.shared.signingCertificate, let cert = Keychain.shared.signingCertificate,
let identifier = Keychain.shared.identifier, let identifier = Keychain.shared.identifier,
let adiSerial = Keychain.shared.adiSerial,
let adiPB = Keychain.shared.adiPb else { let adiPB = Keychain.shared.adiPb else {
#if DEBUG #if DEBUG
print(Keychain.shared.appleIDEmailAddress ?? "Empty email") print(Keychain.shared.appleIDEmailAddress ?? "Empty email")
@@ -308,17 +313,16 @@ final class SettingsViewController: UITableViewController
print(Keychain.shared.signingCertificate ?? "Empty cert") print(Keychain.shared.signingCertificate ?? "Empty cert")
print(Keychain.shared.identifier ?? "Empty identifier") print(Keychain.shared.identifier ?? "Empty identifier")
print(Keychain.shared.adiPb ?? "Empty adiPb") print(Keychain.shared.adiPb ?? "Empty adiPb")
print(Keychain.shared.adiSerial ?? "Empty adiSerial")
#endif #endif
return nil return nil
} }
return ImportedAccount(email: email, password: password, cert: cert, certpass: certpass, local_user: identifier, serial: adiSerial, adiPB: adiPB) return ImportedAccount(email: email, password: password, cert: cert, certpass: certpass, local_user: identifier, adiPB: adiPB)
} }
func showExportAccount() { func showExportAccount() {
Task { Task {
let password = await withUnsafeContinuation { (c: UnsafeContinuation<String?,Never>) in guard let password = await withUnsafeContinuation({ (c: UnsafeContinuation<String?,Never>) in
let alertController = UIAlertController(title: NSLocalizedString("Please enter the password for the certificate.", comment: ""), message: nil, preferredStyle: .alert) let alertController = UIAlertController(title: NSLocalizedString("Please enter the password for the certificate.", comment: ""), message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in alertController.addTextField { (textField) in
@@ -338,19 +342,16 @@ final class SettingsViewController: UITableViewController
}) })
self.present(alertController, animated: true) self.present(alertController, animated: true)
} }) else {
guard let password else {
return return
} }
guard let account = exportAccount(password) else { guard let account = exportAccount(password) else {
let toastView = ToastView(text: NSLocalizedString("Failed to export account!", comment: ""), detailText: "Account not found.") let toastView = ToastView(text: NSLocalizedString("Failed to export account!", comment: ""), detailText: "Account not found.")
toastView.show(in: self) return toastView.show(in: self)
return
} }
guard let accountData = try? Foundation.JSONEncoder().encode(account).base64EncodedData() else { guard let accountData = try? Foundation.JSONEncoder().encode(account) else {
let toastView = ToastView(text: NSLocalizedString("Failed to export account data!", comment: ""), detailText: "Account malformed.") let toastView = ToastView(text: NSLocalizedString("Failed to export account data!", comment: ""), detailText: "Account malformed.")
toastView.show(in: self) toastView.show(in: self)
return return
@@ -1364,15 +1365,25 @@ extension SettingsViewController
guard let confUrl else { guard let confUrl else {
return return
} }
_ = confUrl.startAccessingSecurityScopedResource()
defer { confUrl.stopAccessingSecurityScopedResource() }
importAccountAtFile(confUrl) importAccountAtFile(confUrl)
} }
case .importCert: case .importCert:
let importVc = UIDocumentPickerViewController(forOpeningContentTypes: [UTType(filenameExtension: "p12")!], asCopy: false)
ImportExport.documentPickerHandler = DocumentPickerHandler { url in
guard let url else {
return
}
importVc.delegate = ImportExport.documentPickerHandler
self.present(importVc, animated: true)
_ = url.startAccessingSecurityScopedResource()
defer { url.stopAccessingSecurityScopedResource() }
}
Task { Task {
let certUrl = await withUnsafeContinuation { c in let certUrl = await withUnsafeContinuation { c in
let importVc = UIDocumentPickerViewController(forOpeningContentTypes: [UTType(filenameExtension: "p12")!], asCopy: false) let importVc = UIDocumentPickerViewController(forOpeningContentTypes: [UTType(filenameExtension: "p12")!], asCopy: false)
ImportExport.documentPickerHandler = DocumentPickerHandler { url in ImportExport.documentPickerHandler = DocumentPickerHandler { url in
_ = url?.startAccessingSecurityScopedResource()
defer { url?.stopAccessingSecurityScopedResource() }
c.resume(returning: url) c.resume(returning: url)
} }
importVc.delegate = ImportExport.documentPickerHandler importVc.delegate = ImportExport.documentPickerHandler

View File

@@ -14,6 +14,5 @@ struct ImportedAccount: Codable {
let cert: Data let cert: Data
let certpass: String let certpass: String
let local_user: String let local_user: String
let serial: String
let adiPB: String let adiPB: String
} }

View File

@@ -80,9 +80,6 @@ public class Keychain
@KeychainItem(key: "identifier") @KeychainItem(key: "identifier")
public var identifier: String? public var identifier: String?
@KeychainItem(key: "adiSerial")
public var adiSerial: String?
@KeychainItem(key: "adiPb") @KeychainItem(key: "adiPb")
public var adiPb: String? public var adiPb: String?