diff --git a/AltStoreCore/ALTAccount.h b/AltStoreCore/ALTAccount.h new file mode 100644 index 0000000..1678cf6 --- /dev/null +++ b/AltStoreCore/ALTAccount.h @@ -0,0 +1,24 @@ +// +// ALTAccount.h +// AltSign +// +// Created by Riley Testut on 5/10/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ALTAccount : NSObject + +@property (nonatomic, copy) NSString *appleID; +@property (nonatomic, copy) NSString *identifier; + +@property (nonatomic, readonly) NSString *name; +@property (nonatomic, copy) NSString *firstName; +@property (nonatomic, copy) NSString *lastName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AltStoreCore/ALTCertificate.h b/AltStoreCore/ALTCertificate.h new file mode 100644 index 0000000..04c1f0b --- /dev/null +++ b/AltStoreCore/ALTCertificate.h @@ -0,0 +1,33 @@ +// +// ALTCertificate.h +// AltSign +// +// Created by Riley Testut on 5/10/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ALTCertificate : NSObject + +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *serialNumber; + +@property (nonatomic, copy, nullable) NSString *identifier; +@property (nonatomic, copy, nullable) NSString *machineName; +@property (nonatomic, copy, nullable) NSString *machineIdentifier; + +@property (nonatomic, copy, nullable) NSData *data; +@property (nonatomic, copy, nullable) NSData *privateKey; + +- (nullable instancetype)initWithData:(NSData *)data; +- (nullable instancetype)initWithP12Data:(NSData *)p12Data password:(nullable NSString *)password; + +- (nullable NSData *)p12Data; +- (nullable NSData *)encryptedP12DataWithPassword:(NSString *)password; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AltStoreCore/ALTProvisioningProfile.h b/AltStoreCore/ALTProvisioningProfile.h new file mode 100644 index 0000000..2416d23 --- /dev/null +++ b/AltStoreCore/ALTProvisioningProfile.h @@ -0,0 +1,45 @@ +// +// ALTProvisioningProfile.h +// AltSign +// +// Created by Riley Testut on 5/22/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#import + +//#import "ALTCapabilities.h" +#import "ALTCertificate.h" + +@class ALTAppID; + +NS_ASSUME_NONNULL_BEGIN + +@interface ALTProvisioningProfile : NSObject + +@property (copy, nonatomic, readonly) NSString *name; +@property (copy, nonatomic, readonly, nullable) NSString *identifier; +@property (copy, nonatomic, readonly) NSUUID *UUID; + +@property (copy, nonatomic, readonly) NSString *bundleIdentifier; +@property (copy, nonatomic, readonly) NSString *teamIdentifier; + +@property (copy, nonatomic, readonly) NSDate *creationDate; +@property (copy, nonatomic, readonly) NSDate *expirationDate; + +@property (copy, nonatomic, readonly) NSDictionary *entitlements; +@property (copy, nonatomic, readonly) NSArray *certificates; +@property (copy, nonatomic, readonly) NSArray *deviceIDs; + +@property (readonly) BOOL isFreeProvisioningProfile; + +@property (copy, nonatomic, readonly) NSData *data; + +- (nullable instancetype)initWithData:(NSData *)data NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithURL:(NSURL *)fileURL; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AltStoreCore/ALTProvisioningProfileWrapper.h b/AltStoreCore/ALTProvisioningProfileWrapper.h new file mode 100644 index 0000000..554b7a9 --- /dev/null +++ b/AltStoreCore/ALTProvisioningProfileWrapper.h @@ -0,0 +1,38 @@ +// +// ALTProvisioningProfile.h +// AltSign +// +// Created by Riley Testut on 5/22/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#import + +#import "ALTProvisioningProfile.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ALTProvisioningProfileWrapper : NSObject + +@property (copy, nonatomic) NSString *name; +@property (copy, nonatomic, nullable) NSString *identifier; +@property (copy, nonatomic) NSUUID *UUID; + +@property (copy, nonatomic) NSString *bundleIdentifier; +@property (copy, nonatomic) NSString *teamIdentifier; + +@property (copy, nonatomic) NSDate *creationDate; +@property (copy, nonatomic) NSDate *expirationDate; + +@property (copy, nonatomic) NSDictionary *entitlements; +@property (copy, nonatomic) NSArray *certificates; +@property (copy, nonatomic) NSArray *deviceIDs; + +@property BOOL isFreeProvisioningProfile; + +@property (copy, nonatomic) NSData *data; + +- (nullable instancetype)initWithProfile:(ALTProvisioningProfile *)profile; +@end + +NS_ASSUME_NONNULL_END diff --git a/AltStoreCore/ALTProvisioningProfileWrapper.m b/AltStoreCore/ALTProvisioningProfileWrapper.m new file mode 100644 index 0000000..cd08c6a --- /dev/null +++ b/AltStoreCore/ALTProvisioningProfileWrapper.m @@ -0,0 +1,23 @@ +#import "ALTProvisioningProfileWrapper.h" + +@implementation ALTProvisioningProfileWrapper + +- (nullable instancetype)initWithProfile:(ALTProvisioningProfile *)profile { + self = [self init]; + self.name = profile.name; + self.identifier = profile.identifier; + self.UUID = profile.UUID; + self.name = profile.name; + self.bundleIdentifier = profile.bundleIdentifier; + self.teamIdentifier = profile.teamIdentifier; + self.creationDate = profile.creationDate; + self.expirationDate = profile.expirationDate; + self.entitlements = profile.entitlements; + self.certificates = profile.certificates; + self.deviceIDs = profile.deviceIDs; + self.isFreeProvisioningProfile = profile.isFreeProvisioningProfile; + self.data = profile.data; + return self; +} + +@end diff --git a/AltStoreCore/ALTSigner.h b/AltStoreCore/ALTSigner.h new file mode 100644 index 0000000..310640e --- /dev/null +++ b/AltStoreCore/ALTSigner.h @@ -0,0 +1,30 @@ +// +// ALTSigner.h +// AltSign +// +// Created by Riley Testut on 5/22/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#import + +#import "ALTCertificate.h" +#import "ALTProvisioningProfile.h" +#import "ALTTeam.h" + +@class ALTAppID; + +NS_ASSUME_NONNULL_BEGIN + +@interface ALTSigner : NSObject + +@property (nonatomic) ALTTeam *team; +@property (nonatomic) ALTCertificate *certificate; + +- (instancetype)initWithTeam:(ALTTeam *)team certificate:(ALTCertificate *)certificate; + +- (NSProgress *)signAppAtURL:(NSURL *)appURL provisioningProfiles:(NSArray *)profiles completionHandler:(void (^)(BOOL success, NSError * error))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AltStoreCore/ALTTeam.h b/AltStoreCore/ALTTeam.h new file mode 100644 index 0000000..9ab3050 --- /dev/null +++ b/AltStoreCore/ALTTeam.h @@ -0,0 +1,36 @@ +// +// ALTTeam.h +// AltSign +// +// Created by Riley Testut on 5/10/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#import + +#import "ALTAccount.h" + +typedef NS_ENUM(int16_t, ALTTeamType) +{ + ALTTeamTypeUnknown = 0, + ALTTeamTypeFree = 1, + ALTTeamTypeIndividual = 2, + ALTTeamTypeOrganization = 3, +}; + +NS_ASSUME_NONNULL_BEGIN + +@interface ALTTeam : NSObject + +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *identifier; +@property (nonatomic) ALTTeamType type; + +@property (nonatomic) ALTAccount *account; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithName:(NSString *)name identifier:(NSString *)identifier type:(ALTTeamType)type account:(ALTAccount *)account NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/LCJITLessSetupViewController.m b/LCJITLessSetupViewController.m index 2cec57c..78b9e3e 100644 --- a/LCJITLessSetupViewController.m +++ b/LCJITLessSetupViewController.m @@ -18,12 +18,12 @@ - (void)loadView { self.view.backgroundColor = UIColor.systemBackgroundColor; self.title = @"LiveContainer JIT-less setup"; - if (!LCUtils.storeCertPassword) { + if (![LCUtils sidestoreKeychainItem:@"signingCertificate"]) { [self showDialogTitle:@"Error" message:@"Failed to find certificate password" handler:nil]; return; } - [LCUtils updateCertPassword]; + [LCUtils updateCertificate]; [LCUtils changeMainExecutableTo:@"LiveContainer_PleaseDoNotShortenTheExecutableNameBecauseItIsUsedToReserveSpaceForOverwritingThankYou"]; NSError *error; diff --git a/LCRootViewController.m b/LCRootViewController.m index 71ba029..03f5cb5 100644 --- a/LCRootViewController.m +++ b/LCRootViewController.m @@ -141,9 +141,11 @@ - (void)loadView { [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonTapped)] ]; - if (!LCUtils.certPassword) { + if (!LCUtils.certificateData) { self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Setup JIT-less" style:UIBarButtonItemStylePlain target:self action:@selector(setupJITLessTapped)]; - } + } /* else { + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Test JIT-less" style:UIBarButtonItemStylePlain target:self action:@selector(testJITLessTapped)]; + } */ } - (void)viewWillAppear:(BOOL)animated { @@ -357,7 +359,7 @@ - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocum return; } [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - [self patchExecAtIndexPathIfNeed:indexPath]; + [self patchExecAndSignIfNeed:indexPath]; }); } @@ -482,10 +484,10 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath self.navigationItem.leftBarButtonItems[0].enabled = YES; //[tableView deselectRowAtIndexPath:indexPath animated:YES]; [NSUserDefaults.standardUserDefaults setObject:self.objects[indexPath.row] forKey:@"selected"]; - [self patchExecAtIndexPathIfNeed:indexPath]; + [self patchExecAndSignIfNeed:indexPath]; } -- (void)patchExecAtIndexPathIfNeed:(NSIndexPath *)indexPath { +- (void)patchExecAndSignIfNeed:(NSIndexPath *)indexPath { NSString *appPath = [NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[indexPath.row]]; NSString *infoPath = [NSString stringWithFormat:@"%@/Info.plist", appPath]; NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath]; @@ -506,6 +508,54 @@ - (void)patchExecAtIndexPathIfNeed:(NSIndexPath *)indexPath { info[@"LCPatchRevision"] = @(currentPatchRev); [info writeToFile:infoPath atomically:YES]; } + + // Sign app if JIT-less is set up + if (LCUtils.certificateData) { + NSUInteger signID = LCUtils.certificateData.hash; + if ([info[@"LCJITLessSignID"] unsignedLongValue] != signID) { + // We need to temporarily change bundle ID to LiveContainer to sign properly + info[@"LCBundleIdentifier"] = info[@"CFBundleIdentifier"]; + info[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier; + [info writeToFile:infoPath atomically:YES]; + info[@"CFBundleIdentifier"] = info[@"LCBundleIdentifier"]; + [info removeObjectForKey:@"LCBundleIdentifier"]; + + // Don't let main executable get entitlements + NSString *appExecPath = [appPath stringByAppendingPathComponent:info[@"CFBundleExecutable"]]; + NSString *tmpExecPath = [appPath stringByAppendingPathComponent:@"LiveContainer.tmp"]; + [NSFileManager.defaultManager copyItemAtPath:appExecPath toPath:tmpExecPath error:nil]; + + __block NSProgress *progress = [LCUtils signAppBundle:appPath + + completionHandler:^(BOOL success, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + [self showDialogTitle:@"Error while signing app" message:error.localizedDescription]; + } else { + info[@"LCJITLessSignID"] = @(signID); + } + + // Restore main executable + [NSFileManager.defaultManager removeItemAtPath:appExecPath error:nil]; + [NSFileManager.defaultManager moveItemAtPath:tmpExecPath toPath:appExecPath error:nil]; + + // Save sign ID and restore bundle ID + [info writeToFile:infoPath atomically:YES]; + + [progress removeObserver:self forKeyPath:@"fractionCompleted"]; + [self.progressView removeFromSuperview]; + [self.tableView reloadData]; + }); + }]; + if (progress) { + [progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:nil]; + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + cell.textLabel.text = @"Signing"; + [cell.imageView addSubview:self.progressView]; + cell.userInteractionEnabled = NO; + } + } + } } - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point { diff --git a/LCUtils.h b/LCUtils.h index 74a0a11..5b05ce0 100644 --- a/LCUtils.h +++ b/LCUtils.h @@ -8,9 +8,11 @@ @interface LCUtils : NSObject -+ (NSString *)certPassword; -+ (void)updateCertPassword; -+ (NSData *)storeCertPassword; ++ (NSData *)certificateData; ++ (void)updateCertificate; + ++ (NSData *)sidestoreKeychainItem:(NSString *)key; ++ (NSProgress *)signAppBundle:(NSString *)path completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; + (BOOL)isAppGroupSideStore; + (NSError *)changeMainExecutableTo:(NSString *)exec; diff --git a/LCUtils.m b/LCUtils.m index e061df9..17366b0 100644 --- a/LCUtils.m +++ b/LCUtils.m @@ -1,3 +1,5 @@ +#import "AltStoreCore/ALTSigner.h" +#import "AltStoreCore/ALTProvisioningProfileWrapper.h" #import "LCUtils.h" #include @@ -5,11 +7,11 @@ @implementation LCUtils #pragma mark Certificate password -+ (NSString *)storeCertPassword { ++ (NSData *)sidestoreKeychainItem:(NSString *)key { NSDictionary *dict = @{ (id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrService: @"com.SideStore.SideStore", - (id)kSecAttrAccount: @"signingCertificatePassword", + (id)kSecAttrAccount: key, (id)kSecAttrSynchronizable: (id)kSecAttrSynchronizableAny, (id)kSecMatchLimit: (id)kSecMatchLimitOne, (id)kSecReturnData: (id)kCFBooleanTrue @@ -17,18 +19,61 @@ + (NSString *)storeCertPassword { CFTypeRef result = nil; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); if (status == errSecSuccess) { - return [[NSString alloc] initWithData:(__bridge NSData *)result encoding:NSUTF8StringEncoding]; + return (__bridge NSData *)result; } else { return nil; } } -+ (void)updateCertPassword { - [NSUserDefaults.standardUserDefaults setObject:self.storeCertPassword forKey:@"LCCertificateID"]; ++ (void)updateCertificate { + [NSUserDefaults.standardUserDefaults setObject:[self sidestoreKeychainItem:@"signingCertificate"] forKey:@"LCCertificateData"]; } -+ (NSString *)certPassword { - return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateID"]; ++ (NSData *)certificateData { + return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateData"]; +} + ++ (NSProgress *)signAppBundle:(NSString *)path completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { + NSError *error; + + // Remove PlugIns folder + [NSFileManager.defaultManager removeItemAtPath:[path stringByAppendingPathComponent:@"PlugIns"] error:nil]; + + // I'm too lazy to reimplement signer, so let's borrow everything from SideStore + // For sure this will break in the future as SideStore team planned to rewrite it + NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID]; + NSURL *storeBundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.SideStore.SideStore/App.app"]; + NSURL *storeFrameworksPath = [storeBundlePath URLByAppendingPathComponent:@"Frameworks"]; + NSURL *storeProfilePath = [storeBundlePath URLByAppendingPathComponent:@"embedded.mobileprovision"]; + + // Load libraries from Documents, yeah + [[NSBundle bundleWithURL:[storeFrameworksPath URLByAppendingPathComponent:@"OpenSSL.framework"]] loadAndReturnError:&error]; + if (error) { + completionHandler(NO, error); + return nil; + } + [[NSBundle bundleWithURL:[storeFrameworksPath URLByAppendingPathComponent:@"Roxas.framework"]] loadAndReturnError:&error]; + if (error) { + completionHandler(NO, error); + return nil; + } + [[NSBundle bundleWithURL:[storeFrameworksPath URLByAppendingPathComponent:@"AltStoreCore.framework"]] loadAndReturnError:&error]; + if (error) { + completionHandler(NO, error); + return nil; + } + + ALTCertificate *cert = [[NSClassFromString(@"ALTCertificate") alloc] initWithP12Data:self.certificateData password:@""]; + ALTProvisioningProfile *profile = [[NSClassFromString(@"ALTProvisioningProfile") alloc] initWithURL:storeProfilePath]; + + ALTProvisioningProfileWrapper *profileWrapper = [[ALTProvisioningProfileWrapper alloc] initWithProfile:profile]; + profileWrapper.bundleIdentifier = NSBundle.mainBundle.bundleIdentifier; + + ALTAccount *account = [NSClassFromString(@"ALTAccount") new]; + ALTTeam *team = [[NSClassFromString(@"ALTTeam") alloc] initWithName:@"" identifier:@"" /*profile.teamIdentifier*/ type:ALTTeamTypeUnknown account:account]; + ALTSigner *signer = [[NSClassFromString(@"ALTSigner") alloc] initWithTeam:team certificate:cert]; + + return [signer signAppAtURL:[NSURL fileURLWithPath:path] provisioningProfiles:@[(id)profileWrapper] completionHandler:completionHandler]; } #pragma mark Setup diff --git a/Makefile b/Makefile index 36f2897..e1b5b34 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ CONFIG_BRANCH = $(shell git branch --show-current) CONFIG_COMMIT = $(shell git log --oneline | sed '2,10000000d' | cut -b 1-7) # Build the UI library -LiveContainerUI_FILES = LCAppDelegate.m LCJITLessSetupViewController.m LCRootViewController.m LCUtils.m MBRoundProgressView.m unarchive.m AppInfo.m +LiveContainerUI_FILES = LCAppDelegate.m LCJITLessSetupViewController.m LCRootViewController.m LCUtils.m MBRoundProgressView.m unarchive.m AppInfo.m AltStoreCore/ALTProvisioningProfileWrapper.m LiveContainerUI_CFLAGS = \ -fobjc-arc \ -DCONFIG_TYPE=\"$(CONFIG_TYPE)\" \ diff --git a/Resources/Info.plist b/Resources/Info.plist index b2aaae7..f601466 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -42,7 +42,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 2.0 CFBundleSignature ???? CFBundleSupportedPlatforms diff --git a/control b/control index 7483cf6..7eeb81e 100644 --- a/control +++ b/control @@ -1,8 +1,8 @@ Package: com.kdt.livecontainer Name: livecontainer -Version: 1.0 +Version: 2.0 Architecture: iphoneos-arm -Description: Run unsigned iOS app without actually installing it! +Description: Run iOS app without actually installing it! Maintainer: khanhduytran0 Author: khanhduytran0 Section: Utilities