diff --git a/.gitignore b/.gitignore index 5276789..6250234 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ fastlane/test_output iOSInjectionProject/ .DS_Store + +dist/ diff --git a/Shared/Hasher.h b/Shared/Hasher.h new file mode 100644 index 0000000..44fea3d --- /dev/null +++ b/Shared/Hasher.h @@ -0,0 +1,18 @@ +// +// Hasher.h +// TCCKronos +// +// Created by Luke Roberts on 20/11/2023. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Hasher : NSObject + ++ (NSString *)calculateSHA256ForFileAtPath:(NSString *)path; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Shared/Hasher.m b/Shared/Hasher.m new file mode 100644 index 0000000..c6db026 --- /dev/null +++ b/Shared/Hasher.m @@ -0,0 +1,44 @@ +// +// Hasher.m +// TCCKronos +// +// Created by Luke Roberts on 20/11/2023. +// + +#import "Hasher.h" + +#import + +@implementation Hasher + + ++ (NSString *)calculateSHA256ForFileAtPath:(NSString *)path +{ + NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path]; + if (file == nil) return nil; + + CC_SHA256_CTX hashObject; + CC_SHA256_Init(&hashObject); + + BOOL done = NO; + while (!done) + { + NSData *fileData = [file readDataOfLength:4096]; + CC_SHA256_Update(&hashObject, [fileData bytes], (CC_LONG)[fileData length]); + if ([fileData length] == 0) done = YES; + } + + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256_Final(digest, &hashObject); + [file closeFile]; + + NSMutableString *hash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) + { + [hash appendFormat:@"%02x", digest[i]]; + } + + return [hash copy]; +} + +@end diff --git a/TCCKronos/AppDelegate.m b/TCCKronos/AppDelegate.m index 509c6c3..9ad8ebf 100644 --- a/TCCKronos/AppDelegate.m +++ b/TCCKronos/AppDelegate.m @@ -11,7 +11,9 @@ #import "XPCConnection.h" #import "TCCNotifier/TCCEventNotifier.h" #import "ExtensionToggling/InstallExtension.h" + #import "Constants.h" +#import "Hasher.h" @import Sentry; @@ -66,6 +68,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { self.settingsWindowController = [[SettingsWindowController alloc] initWithWindowNibName:@"SettingsWindow"]; [self.settingsWindowController showWindow:self]; } + // Setup the XPC Connection to our system extension _xpcConnection = [XPCConnection shared]; @@ -80,6 +83,9 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { } } + // Check to see if the hash of any installed launch items matches the one in the bundle + [self checkLaunchdPlist]; + _xpcConnectRetry = [[DispatchTimer alloc] initWithInterval:2 * NSEC_PER_SEC tolorance:1 * NSEC_PER_SEC @@ -109,6 +115,48 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { } } +- (void)checkLaunchdPlist { + NSError* error = nil; + + NSString* resourcePath = [[NSBundle mainBundle] resourcePath]; + NSString* appBundlePath = [resourcePath stringByDeletingLastPathComponent]; + NSURL* plist = [NSURL URLWithString:@"Contents/Library/LaunchAgents/io.phorion.kronos.plist" + relativeToURL:[NSURL URLWithString:appBundlePath]]; + + NSString* absolutePath = [plist absoluteString]; + + NSString* targetPath = [@"~/Library/LaunchAgents/io.phorion.kronos.plist" stringByExpandingTildeInPath]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:targetPath] == YES) { + if ([[Hasher calculateSHA256ForFileAtPath:targetPath] isEqualToString:[Hasher calculateSHA256ForFileAtPath:absolutePath]]) { + return; + } + + NSLog(@"Found existing plist at %@", targetPath); + + [[NSFileManager defaultManager] removeItemAtPath:targetPath error:&error]; + + if (error) { + NSLog(@"An error occured: %@", [error localizedDescription]); + return; + } + + if ([[NSFileManager defaultManager] isReadableFileAtPath:absolutePath]) { + [[NSFileManager defaultManager] copyItemAtPath:absolutePath + toPath:targetPath + error:&error]; + + if (error) { + NSLog(@"An error occured: %@", [error localizedDescription]); + return; + } + } else { + NSLog(@"Couldn't find plist to copy at: %@", absolutePath); + return; + } + } +} + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:SETTING_SENTRY]) { diff --git a/TCCKronos/io.phorion.kronos.plist b/TCCKronos/io.phorion.kronos.plist index df9a0c0..c85005d 100644 --- a/TCCKronos/io.phorion.kronos.plist +++ b/TCCKronos/io.phorion.kronos.plist @@ -5,7 +5,7 @@ Label io.phorion.kronos Program - /Applications/TCCKronos.app/Contents/MacOS/TCCKronos + /Applications/Kronos.app/Contents/MacOS/Kronos RunAtLoad diff --git a/tcc-kronos.xcodeproj/project.pbxproj b/tcc-kronos.xcodeproj/project.pbxproj index ca36982..883f56d 100644 --- a/tcc-kronos.xcodeproj/project.pbxproj +++ b/tcc-kronos.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 79C0012A2ACF4F3C00161B54 /* TCCPermissionToggler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 79C001292ACF4F3C00161B54 /* TCCPermissionToggler.xib */; }; 79F9BC832ACB7FF8009220D0 /* TCCHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F589FEB82AB3B2510021A8ED /* TCCHelper.m */; }; 79F9BC852ACE0053009220D0 /* ApplicationUsage.xib in Resources */ = {isa = PBXBuildFile; fileRef = 79F9BC842ACE0053009220D0 /* ApplicationUsage.xib */; }; + 84E00B572B0BBEE000A63437 /* Hasher.m in Sources */ = {isa = PBXBuildFile; fileRef = 84E00B562B0BBEE000A63437 /* Hasher.m */; }; CC719E1BAC57E0BF915CAA36 /* Pods_TCCKronosTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9A836C3F3EE29C4B4EAFE98 /* Pods_TCCKronosTests.framework */; }; D72CB7CA2B040A8200E74BCE /* SettingsReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = D72CB7C92B040A8200E74BCE /* SettingsReceiver.m */; }; F53D062B2AD774E200A201C3 /* io.phorion.kronos.plist in Resources */ = {isa = PBXBuildFile; fileRef = F53D062A2AD774E200A201C3 /* io.phorion.kronos.plist */; }; @@ -149,6 +150,8 @@ 79C001282ACF4DB400161B54 /* TCCPermissionWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TCCPermissionWindowController.h; sourceTree = ""; }; 79C001292ACF4F3C00161B54 /* TCCPermissionToggler.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TCCPermissionToggler.xib; sourceTree = ""; }; 79F9BC842ACE0053009220D0 /* ApplicationUsage.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ApplicationUsage.xib; sourceTree = ""; }; + 84E00B552B0BBEE000A63437 /* Hasher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Hasher.h; sourceTree = ""; }; + 84E00B562B0BBEE000A63437 /* Hasher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Hasher.m; sourceTree = ""; }; 96DBC976FC5E5495B283C162 /* Pods-TCCKronos.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TCCKronos.debug.xcconfig"; path = "Target Support Files/Pods-TCCKronos/Pods-TCCKronos.debug.xcconfig"; sourceTree = ""; }; 9E6A023D02D2109D922FC6EA /* Pods-TCCKronosExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TCCKronosExtension.release.xcconfig"; path = "Target Support Files/Pods-TCCKronosExtension/Pods-TCCKronosExtension.release.xcconfig"; sourceTree = ""; }; A7C490B56A9643FE0D4E569B /* Pods-TCCKronosTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TCCKronosTests.release.xcconfig"; path = "Target Support Files/Pods-TCCKronosTests/Pods-TCCKronosTests.release.xcconfig"; sourceTree = ""; }; @@ -416,6 +419,8 @@ F589FEAE2AB2F1750021A8ED /* DispatchTimer.m */, F5B59A922AD2E2D80025DD12 /* Signing.h */, F5B59A932AD2E2D80025DD12 /* Signing.m */, + 84E00B552B0BBEE000A63437 /* Hasher.h */, + 84E00B562B0BBEE000A63437 /* Hasher.m */, ); path = Shared; sourceTree = ""; @@ -730,6 +735,7 @@ 79C001222ACE138B00161B54 /* ApplicationUsageController.m in Sources */, F589FEAF2AB2F1750021A8ED /* DispatchTimer.m in Sources */, 79A426EF2AC9C03500BD524A /* KronosUIController.m in Sources */, + 84E00B572B0BBEE000A63437 /* Hasher.m in Sources */, F5FB4FDD2AB2751600E2ED2E /* Utils.m in Sources */, 79A5E0EB2AAB1D7A00A8003D /* StatusBar.m in Sources */, F54FC5DE2AD00B3300DCEC7B /* Bundle.m in Sources */,