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:
Riley Testut
2019-10-28 13:16:55 -07:00
parent 1bde885b17
commit e785fc47ee
8 changed files with 316 additions and 102 deletions

View File

@@ -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 */,

View File

@@ -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"/>

View 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))
}
}

View File

@@ -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
}
}
}

View File

@@ -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
{ {

View File

@@ -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)
}
} }
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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