diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index 792f04f9..4ed8642b 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -26,10 +26,12 @@ extension AppDelegate static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification") static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish") + static let exportCertificateNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ExportCertificateNotification") static let importAppDeepLinkURLKey = "fileURL" static let appBackupResultKey = "result" static let addSourceDeepLinkURLKey = "sourceURL" + static let exportCertificateCallbackTemplateKey = "callback" } @UIApplicationMain @@ -289,6 +291,16 @@ private extension AppDelegate } return true + + case "certificate": + let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:] + guard let callbackTemplate = queryItems["callback"]?.removingPercentEncoding else { return false } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: AppDelegate.exportCertificateNotification, object: nil, userInfo: [AppDelegate.exportCertificateCallbackTemplateKey: callbackTemplate]) + } + + return true default: return false } diff --git a/AltStore/SceneDelegate.swift b/AltStore/SceneDelegate.swift index b9123e09..5e5ea4f9 100644 --- a/AltStore/SceneDelegate.swift +++ b/AltStore/SceneDelegate.swift @@ -140,6 +140,14 @@ private extension SceneDelegate NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL]) } + case "certificate": + let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:] + guard let callbackTemplate = queryItems["callback_template"]?.removingPercentEncoding else { return } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: AppDelegate.exportCertificateNotification, object: nil, userInfo: [AppDelegate.exportCertificateCallbackTemplateKey: callbackTemplate]) + } + default: break } } diff --git a/AltStore/Settings/SettingsViewController.swift b/AltStore/Settings/SettingsViewController.swift index 6ebda599..31dbadba 100644 --- a/AltStore/Settings/SettingsViewController.swift +++ b/AltStore/Settings/SettingsViewController.swift @@ -143,6 +143,7 @@ final class SettingsViewController: UITableViewController NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openExportCertificateConfirm(_:)), name: AppDelegate.exportCertificateNotification, object: nil) } @@ -761,6 +762,48 @@ private extension SettingsViewController self.performSegue(withIdentifier: "showErrorLog", sender: nil) } } + + @objc func openExportCertificateConfirm(_ notification: Notification) + { + func export() + { + guard let template = notification.userInfo?[AppDelegate.exportCertificateCallbackTemplateKey] as? String, + template.contains("$(BASE64_CERT)") else { + let toastView = ToastView(text: NSLocalizedString("No $(BASE64_CERT) placeholder found", comment: ""), detailText: nil) + toastView.show(in: self) + return + } + guard let data = Keychain.shared.signingCertificate, + let password = Keychain.shared.signingCertificatePassword else { + let toastView = ToastView(text: NSLocalizedString("Failed to find certificate or password", comment: ""), detailText: nil) + toastView.show(in: self) + return + } + let base64encodedCert = data.base64EncodedString() + var allowedQueryParamAndKey = NSCharacterSet.urlQueryAllowed + allowedQueryParamAndKey.remove(charactersIn: ";/?:@&=+$, ") + guard let encodedCert = base64encodedCert.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey) else { + let toastView = ToastView(text: NSLocalizedString("Failed to encode certificate!", comment: ""), detailText: nil) + toastView.show(in: self) + return + } + var urlStr = template.replacingOccurrences(of: "$(BASE64_CERT)", with: encodedCert, options: .literal, range: nil) + urlStr = urlStr.replacingOccurrences(of: "$(PASSWORD)", with: password, options: .literal, range: nil) + + print(urlStr) + guard let callbackUrl = URL(string: urlStr) else { + let toastView = ToastView(text: NSLocalizedString("Failed to initialize callback URL!", comment: ""), detailText: nil) + toastView.show(in: self) + return + } + UIApplication.shared.open(callbackUrl) + } + + let alertController = UIAlertController(title: NSLocalizedString("Export Certificate", comment: ""), message: NSLocalizedString("Do you want to export your certificate to an external app? That app will be able to sign apps using your certificate.", comment: ""), preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: NSLocalizedString("Export", comment: ""), style: .default) { _ in export() }) + alertController.addAction(.cancel) + self.present(alertController, animated: true, completion: nil) + } } extension SettingsViewController diff --git a/AltStore/TabBarController.swift b/AltStore/TabBarController.swift index 73ee91d7..d9f92cdd 100644 --- a/AltStore/TabBarController.swift +++ b/AltStore/TabBarController.swift @@ -36,6 +36,7 @@ final class TabBarController: UITabBarController NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.exportCertificate(_:)), name: AppDelegate.exportCertificateNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil) } @@ -141,4 +142,9 @@ private extension TabBarController { self.selectedIndex = Tab.settings.rawValue } + + @objc func exportCertificate(_ notification: Notification) + { + self.selectedIndex = Tab.settings.rawValue + } }