2
0

Log messages + OTP.

[IMPROVED]  MP-15: Audit and improve log messages.
[ADDED]     If an element's counter is 0, generate a time-based OTP
            instead.  The OTP changes every 5 minutes.
This commit is contained in:
Maarten Billemont 2012-05-12 18:31:05 +02:00
parent 941b428cfc
commit 301366f1f1
9 changed files with 61 additions and 49 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit d247edba08f70994bb8b0cb50413aedfa963cacd Subproject commit bbb92ad1957b17d50aaa44e124fb5bacef05da10

View File

@ -38,7 +38,7 @@ static NSDictionary *keyHashQuery() {
- (void)forgetKey { - (void)forgetKey {
dbg(@"Deleting key and hash from key chain."); inf(@"Deleting key and hash from keychain.");
[PearlKeyChain deleteItemForQuery:keyQuery()]; [PearlKeyChain deleteItemForQuery:keyQuery()];
[PearlKeyChain deleteItemForQuery:keyHashQuery()]; [PearlKeyChain deleteItemForQuery:keyHashQuery()];
@ -57,13 +57,12 @@ static NSDictionary *keyHashQuery() {
if ([[MPConfig get].saveKey boolValue]) { if ([[MPConfig get].saveKey boolValue]) {
// Key is stored in keychain. Load it. // Key is stored in keychain. Load it.
dbg(@"Loading key from key chain.");
[self updateKey:[PearlKeyChain dataOfItemForQuery:keyQuery()]]; [self updateKey:[PearlKeyChain dataOfItemForQuery:keyQuery()]];
dbg(@" -> Key %@.", self.key? @"found": @"NOT found"); inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
} else { } else {
// Key should not be stored in keychain. Delete it. // Key should not be stored in keychain. Delete it.
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound) if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
dbg(@"Deleted key from key chain."); inf(@"Removed key from keychain.");
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
#endif #endif
@ -72,19 +71,18 @@ static NSDictionary *keyHashQuery() {
- (BOOL)tryMasterPassword:(NSString *)tryPassword { - (BOOL)tryMasterPassword:(NSString *)tryPassword {
NSData *keyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
dbg(@"Key hash %@.", keyHash? @"known": @"NOT known");
if (![tryPassword length]) if (![tryPassword length])
return NO; return NO;
NSData *tryKey = keyForPassword(tryPassword); NSData *tryKey = keyForPassword(tryPassword);
NSData *tryKeyHash = keyHashForKey(tryKey); NSData *tryKeyHash = keyHashForKey(tryKey);
NSData *keyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
inf(@"Key hash known? %@.", keyHash? @"YES": @"NO");
if (keyHash) if (keyHash)
// A key hash is known -> a key is set. // A key hash is known -> a key is set.
// Make sure the user's entered key matches it. // Make sure the user's entered key matches it.
if (![keyHash isEqual:tryKeyHash]) { if (![keyHash isEqual:tryKeyHash]) {
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyHash, tryKeyHash); wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [keyHash encodeHex], [tryKeyHash encodeHex]);
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
@ -115,24 +113,30 @@ static NSDictionary *keyHashQuery() {
self.keyHash = keyHashForKey(self.key); self.keyHash = keyHashForKey(self.key);
self.keyID = [self.keyHash encodeHex]; self.keyID = [self.keyHash encodeHex];
dbg(@"Updating key ID to: %@.", self.keyID); NSData *existingKeyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
[PearlKeyChain addOrUpdateItemForQuery:keyHashQuery() if (![existingKeyHash isEqualToData:self.keyHash]) {
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: inf(@"Updating key ID in keychain.");
self.keyHash, (__bridge id)kSecValueData, [PearlKeyChain addOrUpdateItemForQuery:keyHashQuery()
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
#endif
nil]];
if ([[MPConfig get].saveKey boolValue]) {
dbg(@"Storing key in key chain.");
[PearlKeyChain addOrUpdateItemForQuery:keyQuery()
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData, self.keyHash, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
#endif #endif
nil]]; nil]];
} }
if ([[MPConfig get].saveKey boolValue]) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery()];
if (![existingKey isEqualToData:self.key]) {
inf(@"Updating key in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyQuery()
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
}
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey]; [TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];

View File

@ -120,7 +120,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
NSError *error = nil; NSError *error = nil;
if ([self.managedObjectContext hasChanges]) if ([self.managedObjectContext hasChanges])
if (![self.managedObjectContext save:&error]) if (![self.managedObjectContext save:&error])
err(@"Unresolved error %@", error); err(@"While saving context: %@", error);
}]; }];
} }
@ -178,7 +178,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
dbg(@"StoreManager: %@", message); dbg(@"[StoreManager] %@", message);
} }
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)didSwitch { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)didSwitch {
@ -192,7 +192,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
} }
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause context:(id)context { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause context:(id)context {
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:str(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)]; [TestFlight passCheckpoint:str(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)];
#endif #endif
@ -338,11 +338,11 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
// Delete existing sites. // Delete existing sites.
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
dbg(@"Deleting: %@", [obj name]); inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
[self.managedObjectContext deleteObject:obj]; [self.managedObjectContext deleteObject:obj];
}]; }];
[self saveContext]; [self saveContext];
// Import new sites. // Import new sites.
for (NSArray *siteElements in importedSiteElements) { for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]]; NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]];
@ -352,7 +352,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
NSString *exportContent = [siteElements objectAtIndex:4]; NSString *exportContent = [siteElements objectAtIndex:4];
// Create new site. // Create new site.
dbg(@"Creating: name=%@, lastUsed=%@, uses=%d, type=%u, keyID=%@", name, lastUsed, uses, type, keyID); inf(@"Importing site: name=%@, lastUsed=%@, uses=%d, type=%u, keyID=%@", name, lastUsed, uses, type, keyID);
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
element.name = name; element.name = name;
@ -364,11 +364,11 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[element importContent:exportContent]; [element importContent:exportContent];
} }
[self saveContext]; [self saveContext];
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported]; [TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported];
#endif #endif
return MPImportResultSuccess; return MPImportResultSuccess;
} }
@ -428,7 +428,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported]; [TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported];
#endif #endif
return export; return export;
} }

View File

@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity @interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) int16_t counter; @property (nonatomic, assign) int32_t counter;
@end @end

View File

@ -18,7 +18,7 @@
- (id)content { - (id)content {
if (!(self.type & MPElementTypeClassGenerated)) { if (!(self.type & MPElementTypeClassGenerated)) {
err(@"Corrupt element: %@, type: %d, does not match class: %@", self.name, self.type, [self class]); err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
return nil; return nil;
} }

View File

@ -82,4 +82,4 @@ NSData *keyHashForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type); NSString *NSStringFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type); Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int16_t counter); NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter);

View File

@ -9,6 +9,8 @@
#import "MPTypes.h" #import "MPTypes.h"
#import "MPElementGeneratedEntity.h" #import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h" #import "MPElementStoredEntity.h"
#include <endian.h>
#define MP_salt nil #define MP_salt nil
#define MP_N 16384 #define MP_N 16384
@ -21,7 +23,8 @@ NSData *keyForPassword(NSString *password) {
NSData *key = [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);
trc(@"Password: %@ derives to key ID: %@", password, [keyHashForKey(key) encodeHex]);
return key; return key;
} }
NSData *keyHashForPassword(NSString *password) { NSData *keyHashForPassword(NSString *password) {
@ -102,32 +105,37 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
} }
static NSDictionary *MPTypes_ciphers = nil; static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int16_t counter) { NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter) {
if (!name) {
err(@"Missing name.");
return nil;
}
if (!(type & MPElementTypeClassGenerated)) { if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name); err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
return nil; return nil;
} }
if (!name) {
err(@"Missing name.");
return nil;
}
if (!key) { if (!key) {
err(@"Key not set."); err(@"Key not set.");
return nil; return nil;
} }
uint32_t salt = (unsigned)counter;
if (!counter)
// Counter unset, go into OTP mode.
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
salt = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
if (MPTypes_ciphers == nil) if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers" MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]]; withExtension:@"plist"]];
// 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)
uint16_t ncounter = htons(counter); uint32_t nsalt = htonl(salt);
trc(@"key hash from: %@-%@-%u", name, key, ncounter); trc(@"key hash from: %@-%@-%u", name, key, nsalt);
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:&nsalt length:sizeof(nsalt)],
nil] hashWith:PearlDigestSHA1]; nil] hashWith:PearlDigestSHA1];
trc(@"key hash is: %@", keyHash); trc(@"key hash is: %@", keyHash);
const char *keyBytes = keyHash.bytes; const char *keyBytes = keyHash.bytes;

View File

@ -8,7 +8,7 @@
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/> <attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
</entity> </entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES"> <entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="counter" optional="YES" attributeType="Integer 16" defaultValueString="1" syncable="YES"/> <attribute name="counter" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity> </entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES"> <entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/> <attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>

View File

@ -98,8 +98,8 @@ typedef enum {
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten
object:nil queue:nil usingBlock:^(NSNotification *note) { object:nil queue:nil usingBlock:^(NSNotification *note) {
[self.field becomeFirstResponder]; [self.field becomeFirstResponder];
}]; }];
[super viewDidLoad]; [super viewDidLoad];
} }
@ -200,7 +200,6 @@ typedef enum {
- (IBAction)changeMP { - (IBAction)changeMP {
dbg(@"Forgetting key phrase.");
[PearlAlert showAlertWithTitle:@"Changing Master Password" [PearlAlert showAlertWithTitle:@"Changing Master Password"
message: message:
@"This will allow you to log in with a different master password.\n\n" @"This will allow you to log in with a different master password.\n\n"
@ -210,9 +209,10 @@ typedef enum {
@"Your current sites and passwords will then become available again." @"Your current sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex != [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
[[MPAppDelegate get] forgetKey]; return;
[[MPAppDelegate get] forgetKey];
[[MPAppDelegate get] loadKey:YES]; [[MPAppDelegate get] loadKey:YES];
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];