From f8b80662bd9d582ede4bcb3078919db55cac4aa1 Mon Sep 17 00:00:00 2001 From: michael starke Date: Tue, 14 Feb 2017 15:40:52 +0100 Subject: [PATCH] Renamed MPPluginManager to MPPluginHost --- MacPass.xcodeproj/project.pbxproj | 18 +-- MacPass/MPAppDelegate.m | 4 +- MacPass/MPPlugin.h | 12 +- MacPass/MPPlugin.m | 12 +- MacPass/MPPluginHost.h | 22 ++- MacPass/MPPluginHost.m | 198 ++++++++++++++++++++++--- MacPass/MPPluginManager.h | 29 ---- MacPass/MPPluginManager.m | 211 --------------------------- MacPass/MPPluginSettingsController.m | 6 +- 9 files changed, 216 insertions(+), 296 deletions(-) delete mode 100644 MacPass/MPPluginManager.h delete mode 100644 MacPass/MPPluginManager.m diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index 5fec182f..5c8fe3f6 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -213,8 +213,7 @@ 4CCFA13D1BF0CC7A0078E0A1 /* Test_Password_1234.kdb in Resources */ = {isa = PBXBuildFile; fileRef = 4CCFA1321BF0CC7A0078E0A1 /* Test_Password_1234.kdb */; }; 4CCFA13E1BF0CC7A0078E0A1 /* Test_Password_1234.kdbx in Resources */ = {isa = PBXBuildFile; fileRef = 4CCFA1331BF0CC7A0078E0A1 /* Test_Password_1234.kdbx */; }; 4CD034AA1BFE113B003C002C /* MPPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CD034A51BFE113B003C002C /* MPPlugin.m */; }; - 4CD034AB1BFE113B003C002C /* MPPluginHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CD034A71BFE113B003C002C /* MPPluginHost.m */; }; - 4CD034AC1BFE113B003C002C /* MPPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CD034A91BFE113B003C002C /* MPPluginManager.m */; }; + 4CD034AC1BFE113B003C002C /* MPPluginHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CD034A91BFE113B003C002C /* MPPluginHost.m */; }; 4CD2B9061849424B0051B395 /* MPAutotypeContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CD2B9051849424B0051B395 /* MPAutotypeContext.m */; }; 4CD5D705177A5F3300100649 /* MPDatabaseSettingsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CD5D704177A5F3300100649 /* MPDatabaseSettingsWindowController.m */; }; 4CD60C131C104AD4005BE5F8 /* HNHUi.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4CC281881C0F675B00B9174D /* HNHUi.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -645,10 +644,8 @@ 4CCFA1331BF0CC7A0078E0A1 /* Test_Password_1234.kdbx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Test_Password_1234.kdbx; sourceTree = ""; }; 4CD034A41BFE113B003C002C /* MPPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPPlugin.h; path = MacPass/MPPlugin.h; sourceTree = SOURCE_ROOT; }; 4CD034A51BFE113B003C002C /* MPPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MPPlugin.m; path = MacPass/MPPlugin.m; sourceTree = SOURCE_ROOT; }; - 4CD034A61BFE113B003C002C /* MPPluginHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPPluginHost.h; path = MacPass/MPPluginHost.h; sourceTree = SOURCE_ROOT; }; - 4CD034A71BFE113B003C002C /* MPPluginHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MPPluginHost.m; path = MacPass/MPPluginHost.m; sourceTree = SOURCE_ROOT; }; - 4CD034A81BFE113B003C002C /* MPPluginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPPluginManager.h; path = MacPass/MPPluginManager.h; sourceTree = SOURCE_ROOT; }; - 4CD034A91BFE113B003C002C /* MPPluginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MPPluginManager.m; path = MacPass/MPPluginManager.m; sourceTree = SOURCE_ROOT; }; + 4CD034A81BFE113B003C002C /* MPPluginHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPPluginHost.h; path = MacPass/MPPluginHost.h; sourceTree = SOURCE_ROOT; }; + 4CD034A91BFE113B003C002C /* MPPluginHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MPPluginHost.m; path = MacPass/MPPluginHost.m; sourceTree = SOURCE_ROOT; }; 4CD2B9041849424B0051B395 /* MPAutotypeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAutotypeContext.h; sourceTree = ""; }; 4CD2B9051849424B0051B395 /* MPAutotypeContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAutotypeContext.m; sourceTree = ""; }; 4CD5D703177A5F3300100649 /* MPDatabaseSettingsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDatabaseSettingsWindowController.h; sourceTree = ""; }; @@ -1440,10 +1437,8 @@ children = ( 4CD034A41BFE113B003C002C /* MPPlugin.h */, 4CD034A51BFE113B003C002C /* MPPlugin.m */, - 4CD034A61BFE113B003C002C /* MPPluginHost.h */, - 4CD034A71BFE113B003C002C /* MPPluginHost.m */, - 4CD034A81BFE113B003C002C /* MPPluginManager.h */, - 4CD034A91BFE113B003C002C /* MPPluginManager.m */, + 4CD034A81BFE113B003C002C /* MPPluginHost.h */, + 4CD034A91BFE113B003C002C /* MPPluginHost.m */, ); name = Plugin; path = MacPassPlugin; @@ -1700,7 +1695,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4CD034AC1BFE113B003C002C /* MPPluginManager.m in Sources */, + 4CD034AC1BFE113B003C002C /* MPPluginHost.m in Sources */, 4C77E37315B84A240093A587 /* main.m in Sources */, 4CBA2ABA17074C07006D8139 /* MPSettingsHelper.m in Sources */, 4C77E37A15B84A240093A587 /* MPAppDelegate.m in Sources */, @@ -1710,7 +1705,6 @@ 4CA0B2FC15BCAF8600654E32 /* MPSettingsWindowController.m in Sources */, 4C4F72D118DF704400E8D378 /* DDHotKeyTextField.m in Sources */, 4C83814215BF4677001AE468 /* MPDocumentWindowController.m in Sources */, - 4CD034AB1BFE113B003C002C /* MPPluginHost.m in Sources */, 4C2E382316D1421B00037A9D /* MPIconHelper.m in Sources */, 4C2E382616D1470200037A9D /* MPViewController.m in Sources */, 4C65FAE916D16DDB006E0577 /* MPPasswordInputController.m in Sources */, diff --git a/MacPass/MPAppDelegate.m b/MacPass/MPAppDelegate.m index 56858721..5692f04a 100644 --- a/MacPass/MPAppDelegate.m +++ b/MacPass/MPAppDelegate.m @@ -29,7 +29,7 @@ #import "MPDocumentWindowController.h" #import "MPLockDaemon.h" #import "MPPasswordCreatorViewController.h" -#import "MPPluginManager.h" +#import "MPPluginHost.h" #import "MPSettingsHelper.h" #import "MPSettingsWindowController.h" #import "MPStringLengthValueTransformer.h" @@ -161,7 +161,7 @@ NSString *const MPDidChangeStoredKeyFilesSettings = @"com.hicknhack.macpass.MPDi [MPLockDaemon defaultDaemon]; [MPAutotypeDaemon defaultDaemon]; /* Create Plugin Manager */ - [MPPluginManager sharedManager]; + [MPPluginHost sharedHost]; #ifndef DEBUG /* Only enable updater in Release */ [SUUpdater sharedUpdater]; diff --git a/MacPass/MPPlugin.h b/MacPass/MPPlugin.h index 8e4b85e8..42e166ff 100644 --- a/MacPass/MPPlugin.h +++ b/MacPass/MPPlugin.h @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN -@class MPPluginManager; +@class MPPluginHost; FOUNDATION_EXPORT NSString *const kMPPluginFileExtension; @@ -20,11 +20,10 @@ FOUNDATION_EXPORT NSString *const kMPPluginFileExtension; @property (copy, readonly) NSString *name; @property (copy, readonly) NSString *version; -- (instancetype)initWithPluginManager:(MPPluginManager *)manager NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithPluginHost:(MPPluginHost *)host NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; - (void)didLoadPlugin; -- (void)willUnloadPlugin; @end @@ -51,5 +50,10 @@ FOUNDATION_EXPORT NSString *const kMPPluginFileExtension; @end +@interface MPPlugin (Deprecated) -NS_ASSUME_NONNULL_END \ No newline at end of file +- (instancetype)initWithPluginManager:(id)manager; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MacPass/MPPlugin.m b/MacPass/MPPlugin.m index d8707576..d7b3f3c2 100644 --- a/MacPass/MPPlugin.m +++ b/MacPass/MPPlugin.m @@ -7,14 +7,14 @@ // #import "MPPlugin.h" -#import "MPPluginManager.h" +#import "MPPluginHost.h" #import "MPSettingsHelper.h" NSString *const kMPPluginFileExtension = @"mpplugin"; @implementation MPPlugin -- (instancetype)initWithPluginManager:(MPPluginManager *)manager { +- (instancetype)initWithPluginHost:(MPPluginHost *)host { self = [super init]; return self; } @@ -49,8 +49,14 @@ NSString *const kMPPluginFileExtension = @"mpplugin"; } -- (void)willUnloadPlugin { +@end +@implementation MPPlugin (Deprecated) + +- (instancetype)initWithPluginManager:(id)manager { + NSLog(@"Deprecated initalizer. Use initWithPluginHost: instead!"); + self = [self initWithPluginManager:nil]; + return self; } @end diff --git a/MacPass/MPPluginHost.h b/MacPass/MPPluginHost.h index 9101d2ab..c2ac0a29 100644 --- a/MacPass/MPPluginHost.h +++ b/MacPass/MPPluginHost.h @@ -2,30 +2,26 @@ // MPPluginHost.h // MacPass // -// Created by Michael Starke on 13/11/15. -// Copyright © 2015 HicknHack Software GmbH. All rights reserved. +// Created by Michael Starke on 16/07/15. +// Copyright (c) 2015 HicknHack Software GmbH. All rights reserved. // #import -NS_ASSUME_NONNULL_BEGIN +FOUNDATION_EXPORT NSString *const MPPluginHostWillLoadPlugin; +FOUNDATION_EXPORT NSString *const MPPluginHostDidLoadPlugin; -@class KPKNode; -@class KPKEntry; -@class KPKGroup; +FOUNDATION_EXPORT NSString *const MPPluginHostPluginBundleIdentifiyerKey; -typedef BOOL (^NodeMatchBlock)(KPKNode *aNode); +@class MPPlugin; @interface MPPluginHost : NSObject +@property (readonly, copy) NSArray *plugins; +@property (nonatomic, readonly) BOOL loadUnsecurePlugins; + + (instancetype)sharedHost; - (instancetype)init NS_UNAVAILABLE; -- (NSArray *)filteredEntriesUsingBlock:(NodeMatchBlock)matchBlock; -- (NSArray *)filteredGroupsUsingBlock:(NodeMatchBlock)matchBlock; - -- (void)presentError:(NSError *)error completionHandler:(void (^)(NSModalResponse response))completionHandler; - @end -NS_ASSUME_NONNULL_END diff --git a/MacPass/MPPluginHost.m b/MacPass/MPPluginHost.m index 36962761..00303dfd 100644 --- a/MacPass/MPPluginHost.m +++ b/MacPass/MPPluginHost.m @@ -2,54 +2,214 @@ // MPPluginHost.m // MacPass // -// Created by Michael Starke on 13/11/15. -// Copyright © 2015 HicknHack Software GmbH. All rights reserved. +// Created by Michael Starke on 16/07/15. +// Copyright (c) 2015 HicknHack Software GmbH. All rights reserved. // #import "MPPluginHost.h" -#import "MPDocument.h" + +#import "MPPlugin.h" +#import "NSApplication+MPAdditions.h" +#import "MPSettingsHelper.h" + +#import "KeePassKit/KeePassKit.h" + + +NSString *const MPPluginHostWillLoadPlugin = @"com.hicknhack.macpass.MPPluginHostWillLoadPlugin"; +NSString *const MPPluginHostDidLoadPlugin = @"comt.hicknhack.macpass.MPPluginHostDidLoadPlugin"; + +NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBundleIdentifiyerKey"; + + +@interface MPPluginHost () + +@property (strong) NSMutableArray *mutablePlugins; +@property (nonatomic) BOOL loadUnsecurePlugins; + +@end @implementation MPPluginHost -static MPPluginHost *_instance; ++ (NSSet *)keyPathsForValuesAffectingPlugins { + return [NSSet setWithObject:NSStringFromSelector(@selector(mutablePlugins))]; +} + (instancetype)sharedHost { + static MPPluginHost *instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _instance = [[MPPluginHost alloc] _init]; + instance = [[MPPluginHost alloc] _init]; }); - return _instance; + return instance; } - (instancetype)init { - return _instance; + return nil; } - (instancetype)_init { self = [super init]; if(self) { + _mutablePlugins = [[NSMutableArray alloc] init]; + _loadUnsecurePlugins = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyLoadUnsecurePlugins]; + [self _loadPlugins]; + + [self bind:NSStringFromSelector(@selector(loadUnsecurePlugins)) + toObject:[NSUserDefaultsController sharedUserDefaultsController] + withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyLoadUnsecurePlugins] + options:nil]; } return self; } -- (NSArray *)filteredEntriesUsingBlock:(NodeMatchBlock)matchBlock { - NSArray *currentDocuments = [NSDocumentController sharedDocumentController].documents; - NSMutableArray *entries = [[NSMutableArray alloc] initWithCapacity:200]; - for(MPDocument *document in currentDocuments) { - if(document.tree) { - [entries addObjectsFromArray:document.tree.allEntries]; +- (NSArray *)plugins { + return [self.mutablePlugins copy]; +} + +- (void)_loadPlugins { + NSURL *appSupportDir = [NSApp applicationSupportDirectoryURL:YES]; + NSError *error; + NSArray *externalPluginsURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:appSupportDir + includingPropertiesForKeys:@[] + options:NSDirectoryEnumerationSkipsHiddenFiles + error:&error]; + + NSArray *internalPluginsURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSBundle mainBundle].builtInPlugInsURL + includingPropertiesForKeys:@[] + options:NSDirectoryEnumerationSkipsHiddenFiles + error:&error]; + + + if(!externalPluginsURLs) { + // No external plugins + NSLog(@"No external plugins found!"); + } + if(!internalPluginsURLs) { + // No internal plugins + NSLog(@"No internal plugins found!"); + } + NSArray *pluginURLs = [externalPluginsURLs arrayByAddingObjectsFromArray:internalPluginsURLs]; + + for(NSURL *pluginURL in pluginURLs) { + + if(![self _validURL:pluginURL]) { + continue; + } + + if(![self _validSignature:pluginURL]) { + continue; + } + + NSBundle *pluginBundle = [NSBundle bundleWithURL:pluginURL]; + if(!pluginBundle) { + NSLog(@"Could not create bundle %@", pluginURL.path); + continue; + } + NSError *error; + if(![pluginBundle preflightAndReturnError:&error]) { + NSLog(@"Preflight Error %@ %@", error.localizedDescription, error.localizedFailureReason ); + continue; + }; + + if([self _validateUniqueBundle:pluginBundle]) { + NSLog(@"Plugin %@ already loaded!", pluginBundle.bundleIdentifier); + continue; + } + + if(![pluginBundle loadAndReturnError:&error]) { + NSLog(@"Bunlde Loading Error %@ %@", error.localizedDescription, error.localizedFailureReason); + continue; + } + + if(![self _validateClass:pluginBundle.principalClass]) { + NSLog(@"Wrong principal Class."); + continue; + } + if([pluginBundle.principalClass instancesRespondToSelector:NSSelectorFromString(@"initWithPluginManager:")]) { + NSLog(@"Plugin uses old interface. Update plugin to use initWithPluginHost: instead of initWithPluginManager:!"); + } + + MPPlugin *plugin = [[pluginBundle.principalClass alloc] initWithPluginHost:self]; + if(plugin) { + NSLog(@"Loaded plugin instance %@", pluginBundle.principalClass); + [[NSNotificationCenter defaultCenter] postNotificationName:MPPluginHostWillLoadPlugin + object:self + userInfo:@{ MPPluginHostPluginBundleIdentifiyerKey : plugin.identifier }]; + [self.mutablePlugins addObject:plugin]; + [[NSNotificationCenter defaultCenter] postNotificationName:MPPluginHostDidLoadPlugin + object:self + userInfo:@{ MPPluginHostPluginBundleIdentifiyerKey : plugin.identifier }]; + } + else { + NSLog(@"Unable to create instance of plugin class %@", pluginBundle.principalClass); } } - NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { return matchBlock(evaluatedObject); }]; - return [[NSArray alloc] initWithArray:[entries filteredArrayUsingPredicate:predicate] copyItems:YES]; } -- (NSArray *)filteredGroupsUsingBlock:(NodeMatchBlock)matchBlock { - NSAssert(NO, @"Not implemented"); - return nil; +- (BOOL)_validateUniqueBundle:(NSBundle *)bundle { + for(MPPlugin *plugin in self.mutablePlugins) { + NSBundle *pluginBundle = [NSBundle bundleForClass:plugin.class]; + if([pluginBundle.bundleIdentifier isEqualToString:bundle.bundleIdentifier]) { + return YES; + } + } + return NO; } -- (void)presentError:(NSError *)error completionHandler:(void (^)(NSModalResponse))completionHandler { +- (BOOL)_validURL:(NSURL *)url { + return (NSOrderedSame == [url.pathExtension compare:kMPPluginFileExtension options:NSCaseInsensitiveSearch]); } +- (BOOL)_validateClass:(Class)class { + return [class isSubclassOfClass:[MPPlugin class]]; +} + +/* Code by Jedda Wignall http://jedda.me/2012/03/verifying-plugin-bundles-using-code-signing/ */ +- (BOOL)_validSignature:(NSURL *)url { + if(!url.path) { + return NO; + } + + if(self.loadUnsecurePlugins) { + return YES; + } + + NSTask * task = [[NSTask alloc] init]; + NSPipe * pipe = [NSPipe pipe]; + NSArray* args = @[ @"--verify", + /*[NSString stringWithFormat:@"-R=anchor = \"%@\"", [[NSBundle mainBundle] pathForResource:@"BlargsoftCodeCA" ofType:@"cer"]],*/ + url.path ]; + task.launchPath = @"/usr/bin/codesign"; + task.standardOutput = pipe; + task.standardError = pipe; + task.arguments = args; + [task launch]; + [task waitUntilExit]; + + if(task.terminationStatus == 0) { + return YES; + } + NSString *pluginPath = url.path ? url.path : @""; + NSString * taskString = [[NSString alloc] initWithData:pipe.fileHandleForReading.readDataToEndOfFile encoding:NSASCIIStringEncoding]; + if ([taskString rangeOfString:@"modified"].length > 0 || [taskString rangeOfString:@"a sealed resource is missing or invalid"].length > 0) { + // The plugin has been modified or resources removed since being signed. You probably don't want to load this. + NSLog(@"Plugin %@ modified - not loaded", pluginPath); // log a real error here + } + else if ([taskString rangeOfString:@"failed to satisfy"].length > 0) { + // The plugin is missing resources since being signed. Don't load. + // throw an error + NSLog(@"Plugin %@ not signed by correct CA - not loaded", pluginPath); // log a real error here + } + else if ([taskString rangeOfString:@"not signed at all"].length > 0) { + // The plugin was not code signed at all. Don't load. + NSLog(@"Plugin %@ not signed at all - don't load.", pluginPath); // log a real error here + } + else { + NSLog(@"Unkown CodeSign Error!"); + } + + return NO; +} + + @end diff --git a/MacPass/MPPluginManager.h b/MacPass/MPPluginManager.h deleted file mode 100644 index 982e4d6c..00000000 --- a/MacPass/MPPluginManager.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// MPPluginManager.h -// MacPass -// -// Created by Michael Starke on 16/07/15. -// Copyright (c) 2015 HicknHack Software GmbH. All rights reserved. -// - -#import - -FOUNDATION_EXPORT NSString *const MPPluginManagerWillLoadPlugin; -FOUNDATION_EXPORT NSString *const MPPluginManagerDidLoadPlugin; -FOUNDATION_EXPORT NSString *const MPPluginManagerWillUnloadPlugin; -FOUNDATION_EXPORT NSString *const MPPluginManagerDidUnloadPlugin; - -FOUNDATION_EXPORT NSString *const MPPluginManagerPluginBundleIdentifiyerKey; - -@class MPPlugin; - -@interface MPPluginManager : NSObject - -@property (readonly, copy) NSArray *plugins; -@property (nonatomic, readonly) BOOL loadUnsecurePlugins; - -+ (instancetype)sharedManager; - -- (instancetype)init NS_UNAVAILABLE; - -@end diff --git a/MacPass/MPPluginManager.m b/MacPass/MPPluginManager.m deleted file mode 100644 index bebb6cd8..00000000 --- a/MacPass/MPPluginManager.m +++ /dev/null @@ -1,211 +0,0 @@ -// -// MPPluginManager.m -// MacPass -// -// Created by Michael Starke on 16/07/15. -// Copyright (c) 2015 HicknHack Software GmbH. All rights reserved. -// - -#import "MPPluginManager.h" - -#import "MPPlugin.h" -#import "NSApplication+MPAdditions.h" -#import "MPSettingsHelper.h" - -#import "KeePassKit/KeePassKit.h" - - -NSString *const MPPluginManagerWillLoadPlugin = @"com.hicknhack.macpass.MPPluginManagerWillLoadPlugin"; -NSString *const MPPluginManagerDidLoadPlugin = @"comt.hicknhack.macpass.MPPluginManagerDidLoadPlugin"; - -NSString *const MPPluginManagerPluginBundleIdentifiyerKey = @"MPPluginManagerPluginBundleIdentifiyerKey"; - - -@interface MPPluginManager () - -@property (strong) NSMutableArray *mutablePlugins; -@property (nonatomic) BOOL loadUnsecurePlugins; - -@end - -@implementation MPPluginManager - -+ (NSSet *)keyPathsForValuesAffectingPlugins { - return [NSSet setWithObject:NSStringFromSelector(@selector(mutablePlugins))]; -} - -+ (instancetype)sharedManager { - static MPPluginManager *instance; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[MPPluginManager alloc] _init]; - }); - return instance; -} - -- (instancetype)init { - return nil; -} - -- (instancetype)_init { - self = [super init]; - if(self) { - _mutablePlugins = [[NSMutableArray alloc] init]; - _loadUnsecurePlugins = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyLoadUnsecurePlugins]; - [self _loadPlugins]; - - [self bind:NSStringFromSelector(@selector(loadUnsecurePlugins)) - toObject:[NSUserDefaultsController sharedUserDefaultsController] - withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyLoadUnsecurePlugins] - options:nil]; - } - return self; -} - -- (NSArray *)plugins { - return [self.mutablePlugins copy]; -} - -- (void)_loadPlugins { - NSURL *appSupportDir = [NSApp applicationSupportDirectoryURL:YES]; - NSError *error; - NSArray *externalPluginsURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:appSupportDir - includingPropertiesForKeys:@[] - options:NSDirectoryEnumerationSkipsHiddenFiles - error:&error]; - - NSArray *internalPluginsURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSBundle mainBundle].builtInPlugInsURL - includingPropertiesForKeys:@[] - options:NSDirectoryEnumerationSkipsHiddenFiles - error:&error]; - - - if(!externalPluginsURLs) { - // No external plugins - NSLog(@"No external plugins found!"); - } - if(!internalPluginsURLs) { - // No internal plugins - NSLog(@"No internal plugins found!"); - } - NSArray *pluginURLs = [externalPluginsURLs arrayByAddingObjectsFromArray:internalPluginsURLs]; - - for(NSURL *pluginURL in pluginURLs) { - - if(![self _validURL:pluginURL]) { - continue; - } - - if(![self _validSignature:pluginURL]) { - continue; - } - - NSBundle *pluginBundle = [NSBundle bundleWithURL:pluginURL]; - if(!pluginBundle) { - NSLog(@"Could not create bundle %@", pluginURL.path); - continue; - } - NSError *error; - if(![pluginBundle preflightAndReturnError:&error]) { - NSLog(@"Preflight Error %@ %@", error.localizedDescription, error.localizedFailureReason ); - continue; - }; - - if([self _validateUniqueBundle:pluginBundle]) { - NSLog(@"Plugin %@ already loaded!", pluginBundle.bundleIdentifier); - continue; - } - - if(![pluginBundle loadAndReturnError:&error]) { - NSLog(@"Bunlde Loading Error %@ %@", error.localizedDescription, error.localizedFailureReason); - continue; - } - - if(![self _validateClass:pluginBundle.principalClass]) { - NSLog(@"Wrong principal Class."); - continue; - } - MPPlugin *plugin = [[pluginBundle.principalClass alloc] initWithPluginManager:self]; - if(plugin) { - NSLog(@"Loaded plugin instance %@", pluginBundle.principalClass); - [[NSNotificationCenter defaultCenter] postNotificationName:MPPluginManagerWillLoadPlugin - object:self - userInfo:@{ MPPluginManagerPluginBundleIdentifiyerKey : plugin.identifier }]; - [self.mutablePlugins addObject:plugin]; - [[NSNotificationCenter defaultCenter] postNotificationName:MPPluginManagerDidLoadPlugin - object:self - userInfo:@{ MPPluginManagerPluginBundleIdentifiyerKey : plugin.identifier }]; - } - else { - NSLog(@"Unable to create instance of plugin class %@", pluginBundle.principalClass); - } - } -} - -- (BOOL)_validateUniqueBundle:(NSBundle *)bundle { - for(MPPlugin *plugin in self.mutablePlugins) { - NSBundle *pluginBundle = [NSBundle bundleForClass:plugin.class]; - if([pluginBundle.bundleIdentifier isEqualToString:bundle.bundleIdentifier]) { - return YES; - } - } - return NO; -} - -- (BOOL)_validURL:(NSURL *)url { - return (NSOrderedSame == [url.pathExtension compare:kMPPluginFileExtension options:NSCaseInsensitiveSearch]); -} - -- (BOOL)_validateClass:(Class)class { - return [class isSubclassOfClass:[MPPlugin class]]; -} - -/* Code by Jedda Wignall http://jedda.me/2012/03/verifying-plugin-bundles-using-code-signing/ */ -- (BOOL)_validSignature:(NSURL *)url { - if(!url.path) { - return NO; - } - - if(self.loadUnsecurePlugins) { - return YES; - } - - NSTask * task = [[NSTask alloc] init]; - NSPipe * pipe = [NSPipe pipe]; - NSArray* args = @[ @"--verify", - /*[NSString stringWithFormat:@"-R=anchor = \"%@\"", [[NSBundle mainBundle] pathForResource:@"BlargsoftCodeCA" ofType:@"cer"]],*/ - url.path ]; - task.launchPath = @"/usr/bin/codesign"; - task.standardOutput = pipe; - task.standardError = pipe; - task.arguments = args; - [task launch]; - [task waitUntilExit]; - - if(task.terminationStatus == 0) { - return YES; - } - NSString *pluginPath = url.path ? url.path : @""; - NSString * taskString = [[NSString alloc] initWithData:pipe.fileHandleForReading.readDataToEndOfFile encoding:NSASCIIStringEncoding]; - if ([taskString rangeOfString:@"modified"].length > 0 || [taskString rangeOfString:@"a sealed resource is missing or invalid"].length > 0) { - // The plugin has been modified or resources removed since being signed. You probably don't want to load this. - NSLog(@"Plugin %@ modified - not loaded", pluginPath); // log a real error here - } - else if ([taskString rangeOfString:@"failed to satisfy"].length > 0) { - // The plugin is missing resources since being signed. Don't load. - // throw an error - NSLog(@"Plugin %@ not signed by correct CA - not loaded", pluginPath); // log a real error here - } - else if ([taskString rangeOfString:@"not signed at all"].length > 0) { - // The plugin was not code signed at all. Don't load. - NSLog(@"Plugin %@ not signed at all - don't load.", pluginPath); // log a real error here - } - else { - NSLog(@"Unkown CodeSign Error!"); - } - - return NO; -} - - -@end diff --git a/MacPass/MPPluginSettingsController.m b/MacPass/MPPluginSettingsController.m index 1036c6e3..5bbd09a4 100644 --- a/MacPass/MPPluginSettingsController.m +++ b/MacPass/MPPluginSettingsController.m @@ -7,7 +7,7 @@ // #import "MPPluginSettingsController.h" -#import "MPPluginManager.h" +#import "MPPluginHost.h" #import "MPPlugin.h" #import "MPSettingsHelper.h" @@ -50,7 +50,7 @@ } - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { - return [MPPluginManager sharedManager].plugins.count; + return [MPPluginHost sharedHost].plugins.count; } - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { @@ -75,7 +75,7 @@ } - (MPPlugin *)pluginForRow:(NSInteger)row { - NSArray *plugins = [MPPluginManager sharedManager].plugins; + NSArray *plugins = [MPPluginHost sharedHost].plugins; if(0 > row || row >= plugins.count) { return nil; }