2020-05-02 22:06:57 -07:00
//
// V e r i f y A p p O p e r a t i o n . s w i f t
// A l t S t o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 5 / 2 / 2 0 .
// C o p y r i g h t © 2 0 2 0 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import Foundation
2024-05-06 10:14:05 -04:00
import AltStoreCore
2020-05-02 22:06:57 -07:00
import AltSign
import Roxas
2024-05-06 10:14:05 -04:00
extension VerificationError
2020-05-02 22:06:57 -07:00
{
2024-05-06 10:14:05 -04:00
enum Code : Int , ALTErrorCode , CaseIterable {
typealias Error = VerificationError
case privateEntitlements
case mismatchedBundleIdentifiers
case iOSVersionNotSupported
2020-05-02 22:06:57 -07:00
}
2024-05-06 10:14:05 -04:00
static func privateEntitlements ( _ entitlements : [ String : Any ] , app : ALTApplication ) -> VerificationError {
VerificationError ( code : . privateEntitlements , app : app , entitlements : entitlements )
2020-05-02 22:06:57 -07:00
}
2024-05-06 10:14:05 -04:00
static func mismatchedBundleIdentifiers ( sourceBundleID : String , app : ALTApplication ) -> VerificationError {
VerificationError ( code : . mismatchedBundleIdentifiers , app : app , sourceBundleID : sourceBundleID )
}
static func iOSVersionNotSupported ( app : ALTApplication ) -> VerificationError {
VerificationError ( code : . iOSVersionNotSupported , app : app )
}
}
struct VerificationError : ALTLocalizedError {
let code : Code
var errorTitle : String ?
var errorFailure : String ?
var app : ALTApplication ?
var entitlements : [ String : Any ] ?
var sourceBundleID : String ?
var errorFailureReason : String {
switch self . code
2020-05-02 22:06:57 -07:00
{
2024-05-06 10:14:05 -04:00
case . privateEntitlements :
let appName = ( self . app ? . name as String ? ) . map { String ( format : NSLocalizedString ( " '%@' " , comment : " " ) , $0 ) } ? ?
NSLocalizedString ( " Unknown app " , comment : " " )
return String ( format : NSLocalizedString ( " “%@” requires private permissions. " , comment : " " ) , appName )
case . mismatchedBundleIdentifiers :
if let app , let sourceBundleID {
return String ( format : NSLocalizedString ( " The bundle ID '%@' does not match the one specified by the source ('%@'). " , comment : " " ) , app . bundleIdentifier , sourceBundleID )
} else {
return NSLocalizedString ( " The bundle ID does not match the one specified by the source. " , comment : " " )
2021-02-26 13:46:49 -06:00
}
2024-05-06 10:14:05 -04:00
case . iOSVersionNotSupported :
var failureReason : String !
if let app {
var version = " iOS \( app . minimumiOSVersion . majorVersion ) . \( app . minimumiOSVersion . minorVersion ) "
if app . minimumiOSVersion . patchVersion > 0 {
version += " . \( app . minimumiOSVersion . patchVersion ) "
}
failureReason = String ( format : NSLocalizedString ( " %@ requires %@. " , comment : " " ) , app . name , version )
} else {
let version = ProcessInfo . processInfo . operatingSystemVersionString
failureReason = String ( format : NSLocalizedString ( " This app does not support iOS %@. " , comment : " " ) , version )
}
return failureReason
2020-05-02 22:06:57 -07:00
}
}
}
@objc ( VerifyAppOperation )
2023-01-04 09:52:12 -05:00
final class VerifyAppOperation : ResultOperation < Void >
2020-05-02 22:06:57 -07:00
{
let context : AppOperationContext
var verificationHandler : ( ( VerificationError ) -> Bool ) ?
init ( context : AppOperationContext )
{
self . context = context
super . init ( )
}
override func main ( )
{
super . main ( )
do
{
if let error = self . context . error
{
throw error
}
guard let app = self . context . app else { throw OperationError . invalidParameters }
2020-05-07 13:13:05 -07:00
guard app . bundleIdentifier = = self . context . bundleIdentifier else {
2024-05-06 10:14:05 -04:00
throw VerificationError . mismatchedBundleIdentifiers ( sourceBundleID : self . context . bundleIdentifier , app : app )
2020-05-07 13:13:05 -07:00
}
2021-02-26 13:46:49 -06:00
guard ProcessInfo . processInfo . isOperatingSystemAtLeast ( app . minimumiOSVersion ) else {
2024-05-06 10:14:05 -04:00
throw VerificationError . iOSVersionNotSupported ( app : app )
2021-02-26 13:46:49 -06:00
}
2020-11-03 14:02:19 -08:00
if #available ( iOS 13.5 , * )
2020-05-02 22:06:57 -07:00
{
2020-11-03 14:02:19 -08:00
// N o p s y c h i c p a p e r , s o w e c a n i g n o r e p r i v a t e e n t i t l e m e n t s
app . hasPrivateEntitlements = false
}
else
{
// M a k e s u r e t h i s g o e s l a s t , s i n c e o n c e u s e r r e s p o n d s t o a l e r t w e d o n ' t d o a n y m o r e a p p v e r i f i c a t i o n .
if let commentStart = app . entitlementsString . range ( of : " <!---><!--> " ) , let commentEnd = app . entitlementsString . range ( of : " <!-- --> " )
{
// P s y c h i c P a p e r p r i v a t e e n t i t l e m e n t s .
let entitlementsStart = app . entitlementsString . index ( after : commentStart . upperBound )
let rawEntitlements = String ( app . entitlementsString [ entitlementsStart . . < commentEnd . lowerBound ] )
let plistTemplate = " " "
<? xml version = " 1.0 " encoding = " UTF-8 " ? >
<! DOCTYPE plist PUBLIC " -//Apple Computer//DTD PLIST 1.0//EN " " http://www.apple.com/DTDs/PropertyList-1.0.dtd " >
< plist version = " 1.0 " >
< dict >
% @
</ dict >
</ plist >
" " "
let entitlementsPlist = String ( format : plistTemplate , rawEntitlements )
let entitlements = try PropertyListSerialization . propertyList ( from : entitlementsPlist . data ( using : . utf8 ) ! , options : [ ] , format : nil ) as ! [ String : Any ]
app . hasPrivateEntitlements = true
2024-05-06 10:14:05 -04:00
let error = VerificationError . privateEntitlements ( entitlements , app : app )
2020-11-03 14:02:19 -08:00
self . process ( error ) { ( result ) in
self . finish ( result . mapError { $0 as Error } )
}
return
}
else
{
app . hasPrivateEntitlements = false
2020-05-02 22:06:57 -07:00
}
}
self . finish ( . success ( ( ) ) )
}
catch
{
self . finish ( . failure ( error ) )
}
}
}
private extension VerifyAppOperation
{
func process ( _ error : VerificationError , completion : @ escaping ( Result < Void , VerificationError > ) -> Void )
{
guard let presentingViewController = self . context . presentingViewController else { return completion ( . failure ( error ) ) }
DispatchQueue . main . async {
2024-05-06 10:14:05 -04:00
switch error . code
2020-05-02 22:06:57 -07:00
{
2024-05-06 10:14:05 -04:00
case . privateEntitlements :
guard let entitlements = error . entitlements else { return completion ( . failure ( error ) ) }
2020-05-02 22:06:57 -07:00
let permissions = entitlements . keys . sorted ( ) . joined ( separator : " \n " )
let message = String ( format : NSLocalizedString ( " " "
You must allow access to these private permissions before continuing :
% @
Private permissions allow apps to do more than normally allowed by iOS , including potentially accessing sensitive private data . Make sure to only install apps from sources you trust .
" " " , comment: " " ), permissions)
let alertController = UIAlertController ( title : error . failureReason ? ? error . localizedDescription , message : message , preferredStyle : . alert )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Allow Access " , comment : " " ) , style : . destructive ) { ( action ) in
completion ( . success ( ( ) ) )
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Deny Access " , comment : " " ) , style : . default , handler : { ( action ) in
completion ( . failure ( error ) )
} ) )
presentingViewController . present ( alertController , animated : true , completion : nil )
2020-05-07 13:13:05 -07:00
case . mismatchedBundleIdentifiers : return completion ( . failure ( error ) )
2021-02-26 13:46:49 -06:00
case . iOSVersionNotSupported : return completion ( . failure ( error ) )
2020-05-02 22:06:57 -07:00
}
}
}
}