mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Fixes issue where AltStore revokes its own certificate
Uses embedded certificate from AltServer if possible, but then falls back to asking user to refresh AltStore manually if the certificate used to install AltStore is revoked.
This commit is contained in:
@@ -115,6 +115,7 @@
|
|||||||
BF54E8212315EF0D000AE0D8 /* ALTPatreonBenefitType.m in Sources */ = {isa = PBXBuildFile; fileRef = BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */; };
|
BF54E8212315EF0D000AE0D8 /* ALTPatreonBenefitType.m in Sources */ = {isa = PBXBuildFile; fileRef = BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */; };
|
||||||
BF5AB3A82285FE7500DC914B /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; };
|
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, ); }; };
|
BF5AB3A92285FE7500DC914B /* AltSign.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6F439123644C6E00A0B879 /* RefreshAltStoreViewController.swift */; };
|
||||||
BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF74989A23621C0700CED65F /* ForwardingNavigationController.swift */; };
|
BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF74989A23621C0700CED65F /* ForwardingNavigationController.swift */; };
|
||||||
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */; };
|
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */; };
|
||||||
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5322BC044E002A40FE /* AppOperationContext.swift */; };
|
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5322BC044E002A40FE /* AppOperationContext.swift */; };
|
||||||
@@ -396,6 +397,7 @@
|
|||||||
BF54E81F2315EF0D000AE0D8 /* ALTPatreonBenefitType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTPatreonBenefitType.h; sourceTree = "<group>"; };
|
BF54E81F2315EF0D000AE0D8 /* ALTPatreonBenefitType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTPatreonBenefitType.h; sourceTree = "<group>"; };
|
||||||
BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTPatreonBenefitType.m; sourceTree = "<group>"; };
|
BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTPatreonBenefitType.m; sourceTree = "<group>"; };
|
||||||
BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
BF6F439123644C6E00A0B879 /* RefreshAltStoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAltStoreViewController.swift; sourceTree = "<group>"; };
|
||||||
BF74989A23621C0700CED65F /* ForwardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardingNavigationController.swift; sourceTree = "<group>"; };
|
BF74989A23621C0700CED65F /* ForwardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardingNavigationController.swift; sourceTree = "<group>"; };
|
||||||
BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallAppOperation.swift; sourceTree = "<group>"; };
|
BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallAppOperation.swift; sourceTree = "<group>"; };
|
||||||
BF770E5322BC044E002A40FE /* AppOperationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppOperationContext.swift; sourceTree = "<group>"; };
|
BF770E5322BC044E002A40FE /* AppOperationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppOperationContext.swift; sourceTree = "<group>"; };
|
||||||
@@ -1068,6 +1070,7 @@
|
|||||||
BFE6325922A83BEB00F30809 /* Authentication.storyboard */,
|
BFE6325922A83BEB00F30809 /* Authentication.storyboard */,
|
||||||
BFF0B6932321CB85007A79E1 /* AuthenticationViewController.swift */,
|
BFF0B6932321CB85007A79E1 /* AuthenticationViewController.swift */,
|
||||||
BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */,
|
BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */,
|
||||||
|
BF6F439123644C6E00A0B879 /* RefreshAltStoreViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Authentication;
|
path = Authentication;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1487,6 +1490,7 @@
|
|||||||
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
||||||
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
||||||
BF02419422F2156E00129732 /* RefreshAttempt.swift in Sources */,
|
BF02419422F2156E00129732 /* RefreshAttempt.swift in Sources */,
|
||||||
|
BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */,
|
||||||
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */,
|
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */,
|
||||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
||||||
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */,
|
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
@@ -443,13 +442,77 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1353" y="736"/>
|
<point key="canvasLocation" x="1353" y="736"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--Refresh AltStore-->
|
||||||
|
<scene sceneID="9Vh-dM-OqX">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="R83-kV-365">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fpO-Bf-gFY" customClass="RSTPlaceholderView">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
</view>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="tDQ-ao-1Jg">
|
||||||
|
<rect key="frame" x="16" y="570" width="343" height="89"/>
|
||||||
|
<subviews>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xcg-hT-tDe" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
||||||
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="51" id="SJA-N9-Z6u"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||||
|
<color key="tintColor" name="SettingsHighlighted"/>
|
||||||
|
<state key="normal" title="Refresh Now">
|
||||||
|
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</state>
|
||||||
|
<connections>
|
||||||
|
<action selector="refreshAltStore:" destination="aoK-yE-UVT" eventType="primaryActionTriggered" id="WQu-9b-Zgg"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qua-VA-asJ">
|
||||||
|
<rect key="frame" x="0.0" y="59" width="343" height="30"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||||
|
<state key="normal" title="Refresh Later">
|
||||||
|
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</state>
|
||||||
|
<connections>
|
||||||
|
<action selector="cancel:" destination="aoK-yE-UVT" eventType="primaryActionTriggered" id="ffO-0a-LdE"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" name="SettingsBackground"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="fpO-Bf-gFY" firstAttribute="leading" secondItem="iwE-xE-ziz" secondAttribute="leading" id="A77-nX-Wg2"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="tDQ-ao-1Jg" secondAttribute="trailing" id="KPg-sO-Rnc"/>
|
||||||
|
<constraint firstItem="fpO-Bf-gFY" firstAttribute="trailing" secondItem="iwE-xE-ziz" secondAttribute="trailing" id="SGI-1D-Eaw"/>
|
||||||
|
<constraint firstItem="fpO-Bf-gFY" firstAttribute="bottom" secondItem="R83-kV-365" secondAttribute="bottom" id="cHl-7X-dW1"/>
|
||||||
|
<constraint firstAttribute="bottomMargin" secondItem="tDQ-ao-1Jg" secondAttribute="bottom" id="kLN-e7-BJE"/>
|
||||||
|
<constraint firstItem="fpO-Bf-gFY" firstAttribute="top" secondItem="R83-kV-365" secondAttribute="top" id="oKo-10-7kD"/>
|
||||||
|
<constraint firstItem="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="iwE-xE-ziz"/>
|
||||||
|
</view>
|
||||||
|
<navigationItem key="navigationItem" title="Refresh AltStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
||||||
|
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="placeholderView" destination="fpO-Bf-gFY" id="q7d-au-d94"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="Chr-7g-qEw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="2101.5999999999999" y="733.5832083958021"/>
|
||||||
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<namedColor name="SettingsBackground">
|
<namedColor name="SettingsBackground">
|
||||||
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="SettingsHighlighted">
|
<namedColor name="SettingsHighlighted">
|
||||||
<color red="0.0078431372549019607" green="0.32156862745098042" blue="0.40392156862745099" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.0080000003799796104" green="0.32199999690055847" blue="0.40400001406669617" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
<color key="tintColor" name="Primary"/>
|
<color key="tintColor" name="Primary"/>
|
||||||
|
|||||||
87
AltStore/Authentication/RefreshAltStoreViewController.swift
Normal file
87
AltStore/Authentication/RefreshAltStoreViewController.swift
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
//
|
||||||
|
// RefreshAltStoreViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 10/26/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
class RefreshAltStoreViewController: UIViewController
|
||||||
|
{
|
||||||
|
var signer: ALTSigner!
|
||||||
|
|
||||||
|
var completionHandler: ((Result<Void, Error>) -> Void)?
|
||||||
|
|
||||||
|
@IBOutlet private var placeholderView: RSTPlaceholderView!
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
self.placeholderView.textLabel.isHidden = true
|
||||||
|
|
||||||
|
self.placeholderView.detailTextLabel.textAlignment = .left
|
||||||
|
self.placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
||||||
|
self.placeholderView.detailTextLabel.text = NSLocalizedString("AltStore was unable to use an existing signing certificate, so it had to create a new one. This will cause any apps installed with an existing certificate to expire — including AltStore.\n\nTo prevent AltStore from expiring early, please refresh the app now. AltStore will quit once refreshing is complete.", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension RefreshAltStoreViewController
|
||||||
|
{
|
||||||
|
@IBAction func refreshAltStore(_ sender: PillButton)
|
||||||
|
{
|
||||||
|
guard let altStore = InstalledApp.fetchAltStore(in: DatabaseManager.shared.viewContext) else { return }
|
||||||
|
|
||||||
|
func refresh()
|
||||||
|
{
|
||||||
|
sender.isIndicatingActivity = true
|
||||||
|
|
||||||
|
if let progress = AppManager.shared.refreshProgress(for: altStore) ?? AppManager.shared.installationProgress(for: altStore)
|
||||||
|
{
|
||||||
|
// Cancel pending AltStore refresh so we can start a new one.
|
||||||
|
progress.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
let group = OperationGroup()
|
||||||
|
group.signer = self.signer // Prevent us from trying to authenticate a second time.
|
||||||
|
group.completionHandler = { (result) in
|
||||||
|
if let error = result.error ?? result.value?.values.compactMap({ $0.error }).first
|
||||||
|
{
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
sender.progress = nil
|
||||||
|
sender.isIndicatingActivity = false
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Failed to Refresh AltStore", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Try Again", comment: ""), style: .default, handler: { (action) in
|
||||||
|
refresh()
|
||||||
|
}))
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Refresh Later", comment: ""), style: .cancel, handler: { (action) in
|
||||||
|
self.completionHandler?(.failure(error))
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.completionHandler?(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = AppManager.shared.refresh([altStore], presentingViewController: self, group: group)
|
||||||
|
sender.progress = group.progress
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func cancel(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
self.completionHandler?(.failure(OperationError.cancelled))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,11 +11,65 @@ import KeychainAccess
|
|||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
|
@propertyWrapper
|
||||||
|
struct KeychainItem<Value>
|
||||||
|
{
|
||||||
|
let key: String
|
||||||
|
|
||||||
|
var wrappedValue: Value? {
|
||||||
|
get {
|
||||||
|
switch Value.self
|
||||||
|
{
|
||||||
|
case is Data.Type: return try? Keychain.shared.keychain.getData(self.key) as? Value
|
||||||
|
case is String.Type: return try? Keychain.shared.keychain.getString(self.key) as? Value
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
switch Value.self
|
||||||
|
{
|
||||||
|
case is Data.Type: Keychain.shared.keychain[data: self.key] = newValue as? Data
|
||||||
|
case is String.Type: Keychain.shared.keychain[self.key] = newValue as? String
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(key: String)
|
||||||
|
{
|
||||||
|
self.key = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Keychain
|
class Keychain
|
||||||
{
|
{
|
||||||
static let shared = Keychain()
|
static let shared = Keychain()
|
||||||
|
|
||||||
private let keychain = KeychainAccess.Keychain(service: "com.rileytestut.AltStore").accessibility(.afterFirstUnlock).synchronizable(true)
|
fileprivate let keychain = KeychainAccess.Keychain(service: "com.rileytestut.AltStore").accessibility(.afterFirstUnlock).synchronizable(true)
|
||||||
|
|
||||||
|
@KeychainItem(key: "appleIDEmailAddress")
|
||||||
|
var appleIDEmailAddress: String?
|
||||||
|
|
||||||
|
@KeychainItem(key: "appleIDPassword")
|
||||||
|
var appleIDPassword: String?
|
||||||
|
|
||||||
|
@KeychainItem(key: "signingCertificatePrivateKey")
|
||||||
|
var signingCertificatePrivateKey: Data?
|
||||||
|
|
||||||
|
@KeychainItem(key: "signingCertificateSerialNumber")
|
||||||
|
var signingCertificateSerialNumber: String?
|
||||||
|
|
||||||
|
@KeychainItem(key: "signingCertificate")
|
||||||
|
var signingCertificate: Data?
|
||||||
|
|
||||||
|
@KeychainItem(key: "signingCertificatePassword")
|
||||||
|
var signingCertificatePassword: String?
|
||||||
|
|
||||||
|
@KeychainItem(key: "patreonAccessToken")
|
||||||
|
var patreonAccessToken: String?
|
||||||
|
|
||||||
|
@KeychainItem(key: "patreonRefreshToken")
|
||||||
|
var patreonRefreshToken: String?
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
@@ -29,66 +83,3 @@ class Keychain
|
|||||||
self.signingCertificateSerialNumber = nil
|
self.signingCertificateSerialNumber = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Keychain
|
|
||||||
{
|
|
||||||
var appleIDEmailAddress: String? {
|
|
||||||
get {
|
|
||||||
let emailAddress = try? self.keychain.get("appleIDEmailAddress")
|
|
||||||
return emailAddress
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.keychain["appleIDEmailAddress"] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var appleIDPassword: String? {
|
|
||||||
get {
|
|
||||||
let password = try? self.keychain.get("appleIDPassword")
|
|
||||||
return password
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.keychain["appleIDPassword"] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var signingCertificatePrivateKey: Data? {
|
|
||||||
get {
|
|
||||||
let privateKey = try? self.keychain.getData("signingCertificatePrivateKey")
|
|
||||||
return privateKey
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.keychain[data: "signingCertificatePrivateKey"] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var signingCertificateSerialNumber: String? {
|
|
||||||
get {
|
|
||||||
let serialNumber = try? self.keychain.get("signingCertificateSerialNumber")
|
|
||||||
return serialNumber
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.keychain["signingCertificateSerialNumber"] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var patreonAccessToken: String? {
|
|
||||||
get {
|
|
||||||
let accessToken = try? self.keychain.get("patreonAccessToken")
|
|
||||||
return accessToken
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.keychain["patreonAccessToken"] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var patreonRefreshToken: String? {
|
|
||||||
get {
|
|
||||||
let refreshToken = try? self.keychain.get("patreonRefreshToken")
|
|
||||||
return refreshToken
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.keychain["patreonRefreshToken"] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ extension AppManager
|
|||||||
|
|
||||||
func refresh(_ installedApps: [InstalledApp], presentingViewController: UIViewController?, group: OperationGroup? = nil) -> OperationGroup
|
func refresh(_ installedApps: [InstalledApp], presentingViewController: UIViewController?, group: OperationGroup? = nil) -> OperationGroup
|
||||||
{
|
{
|
||||||
let apps = installedApps.filter { self.refreshProgress(for: $0) == nil }
|
let apps = installedApps.filter { self.refreshProgress(for: $0) == nil || self.refreshProgress(for: $0)?.isCancelled == true }
|
||||||
|
|
||||||
let group = self.install(apps, forceDownload: false, presentingViewController: presentingViewController, group: group)
|
let group = self.install(apps, forceDownload: false, presentingViewController: presentingViewController, group: group)
|
||||||
|
|
||||||
@@ -183,18 +183,6 @@ private extension AppManager
|
|||||||
let group = group ?? OperationGroup()
|
let group = group ?? OperationGroup()
|
||||||
var operations = [Operation]()
|
var operations = [Operation]()
|
||||||
|
|
||||||
|
|
||||||
/* Authenticate */
|
|
||||||
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
|
|
||||||
authenticationOperation.resultHandler = { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): group.error = error
|
|
||||||
case .success(let signer): group.signer = signer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
operations.append(authenticationOperation)
|
|
||||||
|
|
||||||
/* Find Server */
|
/* Find Server */
|
||||||
let findServerOperation = FindServerOperation(group: group)
|
let findServerOperation = FindServerOperation(group: group)
|
||||||
findServerOperation.resultHandler = { (result) in
|
findServerOperation.resultHandler = { (result) in
|
||||||
@@ -204,9 +192,23 @@ private extension AppManager
|
|||||||
case .success(let server): group.server = server
|
case .success(let server): group.server = server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
findServerOperation.addDependency(authenticationOperation)
|
|
||||||
operations.append(findServerOperation)
|
operations.append(findServerOperation)
|
||||||
|
|
||||||
|
if group.signer == nil
|
||||||
|
{
|
||||||
|
/* Authenticate */
|
||||||
|
let authenticationOperation = AuthenticationOperation(presentingViewController: presentingViewController)
|
||||||
|
authenticationOperation.resultHandler = { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): group.error = error
|
||||||
|
case .success(let signer): group.signer = signer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
operations.append(authenticationOperation)
|
||||||
|
|
||||||
|
findServerOperation.addDependency(authenticationOperation)
|
||||||
|
}
|
||||||
|
|
||||||
for app in apps
|
for app in apps
|
||||||
{
|
{
|
||||||
@@ -344,7 +346,11 @@ private extension AppManager
|
|||||||
guard !context.isFinished else { return }
|
guard !context.isFinished else { return }
|
||||||
context.isFinished = true
|
context.isFinished = true
|
||||||
|
|
||||||
self.refreshProgress[context.bundleIdentifier] = nil
|
if let progress = self.refreshProgress[context.bundleIdentifier], progress == context.group.progress(forAppWithBundleIdentifier: context.bundleIdentifier)
|
||||||
|
{
|
||||||
|
// Only remove progress if it hasn't been replaced by another one.
|
||||||
|
self.refreshProgress[context.bundleIdentifier] = nil
|
||||||
|
}
|
||||||
|
|
||||||
if let error = context.error
|
if let error = context.error
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
|
|||||||
|
|
||||||
private lazy var navigationController: UINavigationController = {
|
private lazy var navigationController: UINavigationController = {
|
||||||
let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
|
let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
|
||||||
navigationController.presentationController?.delegate = self
|
if #available(iOS 13.0, *)
|
||||||
|
{
|
||||||
|
navigationController.isModalInPresentation = true
|
||||||
|
}
|
||||||
return navigationController
|
return navigationController
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -150,18 +153,25 @@ class AuthenticationOperation: ResultOperation<ALTSigner>
|
|||||||
Keychain.shared.appleIDEmailAddress = altAccount.appleID // "account" may have nil appleID since we just saved.
|
Keychain.shared.appleIDEmailAddress = altAccount.appleID // "account" may have nil appleID since we just saved.
|
||||||
Keychain.shared.appleIDPassword = self.appleIDPassword
|
Keychain.shared.appleIDPassword = self.appleIDPassword
|
||||||
|
|
||||||
Keychain.shared.signingCertificateSerialNumber = signer.certificate.serialNumber
|
Keychain.shared.signingCertificate = signer.certificate.p12Data()
|
||||||
Keychain.shared.signingCertificatePrivateKey = signer.certificate.privateKey
|
Keychain.shared.signingCertificatePassword = signer.certificate.machineIdentifier
|
||||||
|
|
||||||
super.finish(.success(signer))
|
// Refresh screen must go last since a successful refresh will cause the app to quit.
|
||||||
|
self.showRefreshScreenIfNecessary() { (didShowRefreshAlert) in
|
||||||
|
super.finish(.success(signer))
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.navigationController.dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
super.finish(.failure(error))
|
super.finish(.failure(error))
|
||||||
}
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
DispatchQueue.main.async {
|
self.navigationController.dismiss(animated: true, completion: nil)
|
||||||
self.navigationController.dismiss(animated: true, completion: nil)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,19 +362,45 @@ private extension AuthenticationOperation
|
|||||||
let certificates = try Result(certificates, error).get()
|
let certificates = try Result(certificates, error).get()
|
||||||
|
|
||||||
if
|
if
|
||||||
|
let data = Keychain.shared.signingCertificate,
|
||||||
|
let localCertificate = ALTCertificate(p12Data: data, password: nil),
|
||||||
|
let certificate = certificates.first(where: { $0.serialNumber == localCertificate.serialNumber })
|
||||||
|
{
|
||||||
|
// We have a certificate stored in the keychain and it hasn't been revoked.
|
||||||
|
localCertificate.machineIdentifier = certificate.machineIdentifier
|
||||||
|
completionHandler(.success(localCertificate))
|
||||||
|
}
|
||||||
|
else if
|
||||||
let serialNumber = Keychain.shared.signingCertificateSerialNumber,
|
let serialNumber = Keychain.shared.signingCertificateSerialNumber,
|
||||||
let privateKey = Keychain.shared.signingCertificatePrivateKey,
|
let privateKey = Keychain.shared.signingCertificatePrivateKey,
|
||||||
let certificate = certificates.first(where: { $0.serialNumber == serialNumber })
|
let certificate = certificates.first(where: { $0.serialNumber == serialNumber })
|
||||||
{
|
{
|
||||||
|
// LEGACY
|
||||||
|
// We have the private key for one of the certificates, so add it to certificate and use it.
|
||||||
certificate.privateKey = privateKey
|
certificate.privateKey = privateKey
|
||||||
completionHandler(.success(certificate))
|
completionHandler(.success(certificate))
|
||||||
}
|
}
|
||||||
|
else if
|
||||||
|
let serialNumber = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.certificateID) as? String,
|
||||||
|
let certificate = certificates.first(where: { $0.serialNumber == serialNumber }),
|
||||||
|
let machineIdentifier = certificate.machineIdentifier,
|
||||||
|
FileManager.default.fileExists(atPath: Bundle.main.certificateURL.path),
|
||||||
|
let data = try? Data(contentsOf: Bundle.main.certificateURL),
|
||||||
|
let localCertificate = ALTCertificate(p12Data: data, password: machineIdentifier)
|
||||||
|
{
|
||||||
|
// We have an embedded certificate that hasn't been revoked.
|
||||||
|
localCertificate.machineIdentifier = machineIdentifier
|
||||||
|
completionHandler(.success(localCertificate))
|
||||||
|
}
|
||||||
else if certificates.isEmpty
|
else if certificates.isEmpty
|
||||||
{
|
{
|
||||||
|
// No certificates, so request a new one.
|
||||||
requestCertificate()
|
requestCertificate()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// We don't have private keys for any of the certificates,
|
||||||
|
// so we need to revoke one and create a new one.
|
||||||
replaceCertificate(from: certificates)
|
replaceCertificate(from: certificates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,19 +428,26 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func showRefreshScreenIfNecessary(completionHandler: @escaping (Bool) -> Void)
|
||||||
extension AuthenticationOperation: UIAdaptivePresentationControllerDelegate
|
|
||||||
{
|
|
||||||
func presentationControllerDidDismiss(_ presentationController: UIPresentationController)
|
|
||||||
{
|
{
|
||||||
if let signer = self.signer
|
guard let signer = self.signer else { return completionHandler(false) }
|
||||||
{
|
guard let application = ALTApplication(fileURL: Bundle.main.bundleURL), let provisioningProfile = application.provisioningProfile else { return completionHandler(false) }
|
||||||
self.finish(.success(signer))
|
|
||||||
}
|
// If we're not using the same certificate used to install AltStore, warn user that they need to refresh.
|
||||||
else
|
guard !provisioningProfile.certificates.contains(signer.certificate) else { return completionHandler(false) }
|
||||||
{
|
|
||||||
self.finish(.failure(OperationError.cancelled))
|
DispatchQueue.main.async {
|
||||||
|
let refreshViewController = self.storyboard.instantiateViewController(withIdentifier: "refreshAltStoreViewController") as! RefreshAltStoreViewController
|
||||||
|
refreshViewController.signer = signer
|
||||||
|
refreshViewController.completionHandler = { _ in
|
||||||
|
completionHandler(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.present(refreshViewController)
|
||||||
|
{
|
||||||
|
completionHandler(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,12 @@ class OperationGroup
|
|||||||
|
|
||||||
func progress(for app: AppProtocol) -> Progress?
|
func progress(for app: AppProtocol) -> Progress?
|
||||||
{
|
{
|
||||||
let progress = self.progressByBundleIdentifier[app.bundleIdentifier]
|
return self.progress(forAppWithBundleIdentifier: app.bundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func progress(forAppWithBundleIdentifier bundleIdentifier: String) -> Progress?
|
||||||
|
{
|
||||||
|
let progress = self.progressByBundleIdentifier[bundleIdentifier]
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,6 +400,21 @@ private extension ResignAppOperation
|
|||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
||||||
|
|
||||||
|
if
|
||||||
|
let data = Keychain.shared.signingCertificate,
|
||||||
|
let signingCertificate = ALTCertificate(p12Data: data, password: nil),
|
||||||
|
let encryptingPassword = Keychain.shared.signingCertificatePassword
|
||||||
|
{
|
||||||
|
additionalValues[Bundle.Info.certificateID] = signingCertificate.serialNumber
|
||||||
|
|
||||||
|
let encryptedData = signingCertificate.encryptedP12Data(withPassword: encryptingPassword)
|
||||||
|
try encryptedData?.write(to: appBundle.certificateURL, options: .atomic)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare app
|
// Prepare app
|
||||||
|
|||||||
Reference in New Issue
Block a user