2
0

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:
Maarten Billemont 2012-03-06 01:04:19 +01:00
parent f3c24fd96f
commit d0ae954dbf
13 changed files with 2797 additions and 66 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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 */,

View File

@ -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];

View File

@ -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;

View File

@ -22,4 +22,6 @@
- (IBAction)saveAction:(id)sender; - (IBAction)saveAction:(id)sender;
- (void)loadKey;
@end @end

View File

@ -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]);

View File

@ -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: @""];
});
}];
} }
} }

View File

@ -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>

View File

@ -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]);

View File

@ -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;

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
iTunesArtwork.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB