Working OS X prototype application.
[ADDED] OS X: Master password input and changing. [ADDED] OS X: Autocompletion by searching Core Data for previously used sites. [ADDED] OS X: Working password generation for sites. [FIXED] Bad ciphering on little endian machines: Convert bytes to network endian before using them as numbers. [FIXED] Opening of Core Data store when iCloud is unavailable.
This commit is contained in:
parent
f3c24fd96f
commit
d0ae954dbf
File diff suppressed because it is too large
Load Diff
@ -1870,10 +1870,10 @@
|
|||||||
children = (
|
children = (
|
||||||
DAB8D9B11503757D00CED3BC /* Supporting Files */,
|
DAB8D9B11503757D00CED3BC /* Supporting Files */,
|
||||||
DAB8D44215036BCF00CED3BC /* MainStoryboard_iPhone.storyboard */,
|
DAB8D44215036BCF00CED3BC /* MainStoryboard_iPhone.storyboard */,
|
||||||
DAB8D44615036BCF00CED3BC /* MPAppDelegate.h */,
|
|
||||||
DAB8D44715036BCF00CED3BC /* MPAppDelegate.m */,
|
|
||||||
DAB8D44815036BCF00CED3BC /* MPiOSConfig.h */,
|
DAB8D44815036BCF00CED3BC /* MPiOSConfig.h */,
|
||||||
DAB8D44915036BCF00CED3BC /* MPiOSConfig.m */,
|
DAB8D44915036BCF00CED3BC /* MPiOSConfig.m */,
|
||||||
|
DAB8D44615036BCF00CED3BC /* MPAppDelegate.h */,
|
||||||
|
DAB8D44715036BCF00CED3BC /* MPAppDelegate.m */,
|
||||||
DAB8D44A15036BCF00CED3BC /* MPGuideViewController.h */,
|
DAB8D44A15036BCF00CED3BC /* MPGuideViewController.h */,
|
||||||
DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */,
|
DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */,
|
||||||
DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */,
|
DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */,
|
||||||
|
@ -16,7 +16,7 @@ static NSDictionary *keyQuery() {
|
|||||||
static NSDictionary *MPKeyQuery = nil;
|
static NSDictionary *MPKeyQuery = nil;
|
||||||
if (!MPKeyQuery)
|
if (!MPKeyQuery)
|
||||||
MPKeyQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
MPKeyQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:[NSDictionary dictionaryWithObject:@"Master Password Key"
|
attributes:[NSDictionary dictionaryWithObject:@"Stored Master Password"
|
||||||
forKey:(__bridge id)kSecAttrService]
|
forKey:(__bridge id)kSecAttrService]
|
||||||
matches:nil];
|
matches:nil];
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ static NSDictionary *keyHashQuery() {
|
|||||||
static NSDictionary *MPKeyHashQuery = nil;
|
static NSDictionary *MPKeyHashQuery = nil;
|
||||||
if (!MPKeyHashQuery)
|
if (!MPKeyHashQuery)
|
||||||
MPKeyHashQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
MPKeyHashQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:[NSDictionary dictionaryWithObject:@"Master Password Key Hash"
|
attributes:[NSDictionary dictionaryWithObject:@"Master Password Verification"
|
||||||
forKey:(__bridge id)kSecAttrService]
|
forKey:(__bridge id)kSecAttrService]
|
||||||
matches:nil];
|
matches:nil];
|
||||||
|
|
||||||
|
@ -19,8 +19,10 @@
|
|||||||
|
|
||||||
NSData *keyForPassword(NSString *password) {
|
NSData *keyForPassword(NSString *password) {
|
||||||
|
|
||||||
return [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
|
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
|
||||||
|
trc(@"password: %@ derives to key: %@", password, key);
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
NSData *keyHashForPassword(NSString *password) {
|
NSData *keyHashForPassword(NSString *password) {
|
||||||
|
|
||||||
@ -111,28 +113,33 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
|
|||||||
// Determine the hash whose bytes will be used for calculating a password: md4(name-key)
|
// Determine the hash whose bytes will be used for calculating a password: md4(name-key)
|
||||||
assert(name && key);
|
assert(name && key);
|
||||||
uint16_t ncounter = htons(counter);
|
uint16_t ncounter = htons(counter);
|
||||||
|
trc(@"key hash from: %@-%@-%u", name, key, ncounter);
|
||||||
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
|
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
key,
|
key,
|
||||||
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
|
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
|
||||||
nil] hashWith:PearlDigestSHA1];
|
nil] hashWith:PearlDigestSHA1];
|
||||||
|
trc(@"key hash is: %@", keyHash);
|
||||||
const char *keyBytes = keyHash.bytes;
|
const char *keyBytes = keyHash.bytes;
|
||||||
|
|
||||||
// Determine the cipher from the first hash byte.
|
// Determine the cipher from the first hash byte.
|
||||||
assert([keyHash length]);
|
assert([keyHash length]);
|
||||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
||||||
valueForKey:NSStringFromMPElementType(type)];
|
valueForKey:NSStringFromMPElementType(type)];
|
||||||
NSString *cipher = [typeCiphers objectAtIndex:keyBytes[0] % [typeCiphers count]];
|
NSString *cipher = [typeCiphers objectAtIndex:htons(keyBytes[0]) % [typeCiphers count]];
|
||||||
|
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent hash bytes and the cipher.
|
// Encode the content, character by character, using subsequent hash bytes and the cipher.
|
||||||
assert([keyHash length] >= [cipher length] + 1);
|
assert([keyHash length] >= [cipher length] + 1);
|
||||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||||
const char keyByte = keyBytes[c + 1];
|
uint16_t keyByte = htons(keyBytes[c + 1]);
|
||||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
||||||
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
||||||
|
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)];
|
||||||
|
|
||||||
[content appendString:[cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)]];
|
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
|
||||||
|
[content appendString:character];
|
||||||
}
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
|
@ -22,4 +22,6 @@
|
|||||||
|
|
||||||
- (IBAction)saveAction:(id)sender;
|
- (IBAction)saveAction:(id)sender;
|
||||||
|
|
||||||
|
- (void)loadKey;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
|
#import "MPConfig.h"
|
||||||
|
|
||||||
@interface MPAppDelegate ()
|
@interface MPAppDelegate ()
|
||||||
|
|
||||||
@ -25,6 +26,15 @@
|
|||||||
@synthesize keyHash;
|
@synthesize keyHash;
|
||||||
@synthesize keyHashHex;
|
@synthesize keyHashHex;
|
||||||
|
|
||||||
|
+ (void)initialize {
|
||||||
|
|
||||||
|
[MPConfig get];
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
+ (NSManagedObjectContext *)managedObjectContext {
|
+ (NSManagedObjectContext *)managedObjectContext {
|
||||||
|
|
||||||
return [[self get] managedObjectContext];
|
return [[self get] managedObjectContext];
|
||||||
@ -44,12 +54,61 @@
|
|||||||
if (!self.passwordWindow)
|
if (!self.passwordWindow)
|
||||||
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
||||||
[self.passwordWindow showWindow:self];
|
[self.passwordWindow showWindow:self];
|
||||||
|
|
||||||
|
[self loadKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)loadKey {
|
||||||
|
|
||||||
|
if (!self.key)
|
||||||
|
// Try and load the key from the keychain.
|
||||||
|
[self loadStoredKey];
|
||||||
|
|
||||||
|
if (!self.key)
|
||||||
|
// Ask the user to set the key through his master password.
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
|
||||||
|
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
|
||||||
|
informativeTextWithFormat:@"Your master password is required to unlock the application."];
|
||||||
|
NSTextField *passwordField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)];
|
||||||
|
[alert setAccessoryView:passwordField];
|
||||||
|
[alert layout];
|
||||||
|
do {
|
||||||
|
NSInteger button = [alert runModal];
|
||||||
|
|
||||||
|
if (button == 0)
|
||||||
|
// "Change" button.
|
||||||
|
if ([[NSAlert alertWithMessageText:@"Changing Master Password"
|
||||||
|
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
|
||||||
|
informativeTextWithFormat:
|
||||||
|
@"This will allow you to log in with a different master password.\n\n"
|
||||||
|
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
|
||||||
|
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
|
||||||
|
@"You can always change back to your current master password later.\n"
|
||||||
|
@"Your current sites and passwords will then become available again."] runModal] == 1) {
|
||||||
|
[self forgetKey];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (button == -1) {
|
||||||
|
// "Quit" button.
|
||||||
|
[[NSApplication sharedApplication] terminate:self];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (![self tryMasterPassword:[passwordField stringValue]]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSURL *)applicationFilesDirectory {
|
- (NSURL *)applicationFilesDirectory {
|
||||||
|
|
||||||
NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
|
NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
|
||||||
return [appSupportURL URLByAppendingPathComponent:@"com.lyndir.lhunath.MasterPassword"];
|
NSURL *applicationFilesDirectory = [appSupportURL URLByAppendingPathComponent:@"com.lyndir.lhunath.MasterPassword"];
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
[[NSFileManager defaultManager] createDirectoryAtURL:applicationFilesDirectory withIntermediateDirectories:YES attributes:nil error:&error];
|
||||||
|
if (error)
|
||||||
|
[[NSApplication sharedApplication] presentError:error];
|
||||||
|
|
||||||
|
return applicationFilesDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Core Data stack
|
#pragma mark - Core Data stack
|
||||||
@ -97,20 +156,20 @@
|
|||||||
if (__persistentStoreCoordinator)
|
if (__persistentStoreCoordinator)
|
||||||
return __persistentStoreCoordinator;
|
return __persistentStoreCoordinator;
|
||||||
|
|
||||||
NSURL *storeURL = [[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.storedata"];
|
NSURL *storeURL = [[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"];
|
||||||
|
|
||||||
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
||||||
[__persistentStoreCoordinator lock];
|
[__persistentStoreCoordinator lock];
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:storeURL
|
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL
|
||||||
options:[NSDictionary dictionaryWithObjectsAndKeys:
|
options:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
|
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
|
||||||
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
||||||
@"MasterPassword.store", NSPersistentStoreUbiquitousContentNameKey,
|
|
||||||
[[[NSFileManager defaultManager]
|
[[[NSFileManager defaultManager]
|
||||||
URLForUbiquityContainerIdentifier:nil]
|
URLForUbiquityContainerIdentifier:nil]
|
||||||
URLByAppendingPathComponent:@"store"
|
URLByAppendingPathComponent:@"store"
|
||||||
isDirectory:YES], NSPersistentStoreUbiquitousContentURLKey,
|
isDirectory:YES], NSPersistentStoreUbiquitousContentURLKey,
|
||||||
|
@"MasterPassword.store", NSPersistentStoreUbiquitousContentNameKey,
|
||||||
nil]
|
nil]
|
||||||
error:&error]) {
|
error:&error]) {
|
||||||
err(@"Unresolved error %@, %@", error, [error userInfo]);
|
err(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||||
|
@ -8,15 +8,18 @@
|
|||||||
|
|
||||||
#import "MPPasswordWindowController.h"
|
#import "MPPasswordWindowController.h"
|
||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
|
#import "MPElementEntity.h"
|
||||||
|
#import "MPElementGeneratedEntity.h"
|
||||||
|
|
||||||
@interface MPPasswordWindowController ()
|
@interface MPPasswordWindowController ()
|
||||||
|
|
||||||
@property (nonatomic, assign) BOOL completingSiteName;
|
@property (nonatomic, strong) NSString *oldSiteName;
|
||||||
|
@property (nonatomic, strong) NSArray /* MPElementEntity */ *siteResults;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPPasswordWindowController
|
@implementation MPPasswordWindowController
|
||||||
@synthesize completingSiteName;
|
@synthesize oldSiteName, siteResults;
|
||||||
@synthesize siteField;
|
@synthesize siteField;
|
||||||
@synthesize contentField;
|
@synthesize contentField;
|
||||||
|
|
||||||
@ -29,11 +32,11 @@
|
|||||||
}];
|
}];
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSControlTextDidChangeNotification object:self.siteField queue:nil
|
[[NSNotificationCenter defaultCenter] addObserverForName:NSControlTextDidChangeNotification object:self.siteField queue:nil
|
||||||
usingBlock:^(NSNotification *note) {
|
usingBlock:^(NSNotification *note) {
|
||||||
if (!self.completingSiteName) {
|
NSString *newSiteName = [self.siteField stringValue];
|
||||||
self.completingSiteName = YES;
|
BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length];
|
||||||
|
self.oldSiteName = newSiteName;
|
||||||
|
if (shouldComplete)
|
||||||
[[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil];
|
[[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil];
|
||||||
self.completingSiteName = NO;
|
|
||||||
}
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[super windowDidLoad];
|
[super windowDidLoad];
|
||||||
@ -42,22 +45,70 @@
|
|||||||
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
|
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
|
||||||
|
|
||||||
NSString *query = [[control stringValue] substringWithRange:charRange];
|
NSString *query = [[control stringValue] substringWithRange:charRange];
|
||||||
|
|
||||||
|
assert(query);
|
||||||
|
assert([MPAppDelegate get].keyHashHex);
|
||||||
NSFetchRequest *fetchRequest = [MPAppDelegate.managedObjectModel
|
NSFetchRequest *fetchRequest = [MPAppDelegate.managedObjectModel
|
||||||
fetchRequestFromTemplateWithName:@"MPElements"
|
fetchRequestFromTemplateWithName:@"MPElements"
|
||||||
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
|
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
query, @"query",
|
query, @"query",
|
||||||
[MPAppDelegate get].keyHashHex, @"mpHashHex",
|
[MPAppDelegate get].keyHashHex, @"mpHashHex",
|
||||||
nil]];
|
nil]];
|
||||||
|
[fetchRequest setSortDescriptors:
|
||||||
|
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
|
||||||
|
|
||||||
return [NSArray arrayWithObjects:@"cow", @"milk", @"hippopotamus", nil];
|
NSError *error = nil;
|
||||||
|
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
|
||||||
|
if (error)
|
||||||
|
err(@"Couldn't fetch elements: %@", error);
|
||||||
|
|
||||||
|
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
|
||||||
|
if (self.siteResults)
|
||||||
|
for (MPElementEntity *element in self.siteResults)
|
||||||
|
[mutableResults addObject:element.name];
|
||||||
|
[mutableResults addObject:query];
|
||||||
|
return mutableResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)controlTextDidEndEditing:(NSNotification *)obj {
|
- (void)controlTextDidEndEditing:(NSNotification *)obj {
|
||||||
|
|
||||||
if (obj.object == self.siteField) {
|
if (obj.object == self.siteField) {
|
||||||
// NSString *siteName = [self.siteField stringValue];
|
NSString *siteName = [self.siteField stringValue];
|
||||||
|
|
||||||
// [self.contentField setStringValue:];
|
MPElementEntity *result = nil;
|
||||||
|
for (MPElementEntity *element in self.siteResults)
|
||||||
|
if ([element.name isEqualToString:siteName]) {
|
||||||
|
result = element;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||||
|
NSString *description = [result description];
|
||||||
|
[result use];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.contentField setStringValue:description];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
else
|
||||||
|
[[MPAppDelegate get].managedObjectContext performBlock:^{
|
||||||
|
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
|
||||||
|
inManagedObjectContext:[MPAppDelegate get].managedObjectContext];
|
||||||
|
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
|
||||||
|
assert([MPAppDelegate get].keyHashHex);
|
||||||
|
|
||||||
|
element.name = siteName;
|
||||||
|
element.mpHashHex = [MPAppDelegate get].keyHashHex;
|
||||||
|
|
||||||
|
NSString *description = [element description];
|
||||||
|
[element use];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.contentField setStringValue:description? description: @""];
|
||||||
|
});
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string></string>
|
<string>iTunesArtwork-Rounded.png</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.lyndir.lhunath.${PRODUCT_NAME:rfc1034identifier}</string>
|
<string>com.lyndir.lhunath.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
@ -12,12 +12,6 @@
|
|||||||
#import "MPMainViewController.h"
|
#import "MPMainViewController.h"
|
||||||
#import "IASKSettingsReader.h"
|
#import "IASKSettingsReader.h"
|
||||||
|
|
||||||
@interface MPAppDelegate ()
|
|
||||||
|
|
||||||
- (void)askKey:(BOOL)animated;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation MPAppDelegate
|
@implementation MPAppDelegate
|
||||||
|
|
||||||
@synthesize managedObjectModel = __managedObjectModel;
|
@synthesize managedObjectModel = __managedObjectModel;
|
||||||
@ -165,20 +159,12 @@
|
|||||||
|
|
||||||
- (void)loadKey:(BOOL)animated {
|
- (void)loadKey:(BOOL)animated {
|
||||||
|
|
||||||
if (self.key)
|
if (!self.key)
|
||||||
return;
|
// Try and load the key from the keychain.
|
||||||
|
|
||||||
[self loadStoredKey];
|
[self loadStoredKey];
|
||||||
if (!self.key) {
|
|
||||||
// Key is not known. Ask user to set/specify it.
|
|
||||||
dbg(@"Key not known. Will ask user.");
|
|
||||||
[self askKey:animated];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)askKey:(BOOL)animated {
|
|
||||||
|
|
||||||
|
if (!self.key)
|
||||||
|
// Ask the user to set the key through his master password.
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self.navigationController presentViewController:
|
[self.navigationController presentViewController:
|
||||||
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||||
@ -207,11 +193,6 @@
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (MPAppDelegate *)get {
|
|
||||||
|
|
||||||
return (MPAppDelegate *)[super get];
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (NSManagedObjectContext *)managedObjectContext {
|
+ (NSManagedObjectContext *)managedObjectContext {
|
||||||
|
|
||||||
return [[self get] managedObjectContext];
|
return [[self get] managedObjectContext];
|
||||||
@ -285,11 +266,11 @@
|
|||||||
options:[NSDictionary dictionaryWithObjectsAndKeys:
|
options:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
|
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
|
||||||
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
||||||
@"MasterPassword.store", NSPersistentStoreUbiquitousContentNameKey,
|
|
||||||
[[[NSFileManager defaultManager]
|
[[[NSFileManager defaultManager]
|
||||||
URLForUbiquityContainerIdentifier:nil]
|
URLForUbiquityContainerIdentifier:nil]
|
||||||
URLByAppendingPathComponent:@"store"
|
URLByAppendingPathComponent:@"store"
|
||||||
isDirectory:YES], NSPersistentStoreUbiquitousContentURLKey,
|
isDirectory:YES], NSPersistentStoreUbiquitousContentURLKey,
|
||||||
|
@"MasterPassword.store", NSPersistentStoreUbiquitousContentNameKey,
|
||||||
nil]
|
nil]
|
||||||
error:&error]) {
|
error:&error]) {
|
||||||
err(@"Unresolved error %@, %@", error, [error userInfo]);
|
err(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||||
|
@ -181,7 +181,7 @@
|
|||||||
self.passwordCounter.text = [NSString stringWithFormat:@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter];
|
self.passwordCounter.text = [NSString stringWithFormat:@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter];
|
||||||
|
|
||||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||||
NSString *description = self.activeElement.description;
|
NSString *description = [self.activeElement description];
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
self.contentField.text = description;
|
self.contentField.text = description;
|
||||||
|
@ -264,6 +264,7 @@
|
|||||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
|
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
|
||||||
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
||||||
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
|
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
|
||||||
|
assert([MPAppDelegate get].keyHashHex);
|
||||||
|
|
||||||
element.name = siteName;
|
element.name = siteName;
|
||||||
element.mpHashHex = [MPAppDelegate get].keyHashHex;
|
element.mpHashHex = [MPAppDelegate get].keyHashHex;
|
||||||
|
BIN
Resources/iTunesArtwork-Rounded.png
Normal file
BIN
Resources/iTunesArtwork-Rounded.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 109 KiB |
BIN
iTunesArtwork.png
Normal file
BIN
iTunesArtwork.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 152 KiB |
Loading…
Reference in New Issue
Block a user