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:
parent
941b428cfc
commit
301366f1f1
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit d247edba08f70994bb8b0cb50413aedfa963cacd
|
||||
Subproject commit bbb92ad1957b17d50aaa44e124fb5bacef05da10
|
@ -38,7 +38,7 @@ static NSDictionary *keyHashQuery() {
|
||||
|
||||
- (void)forgetKey {
|
||||
|
||||
dbg(@"Deleting key and hash from key chain.");
|
||||
inf(@"Deleting key and hash from keychain.");
|
||||
[PearlKeyChain deleteItemForQuery:keyQuery()];
|
||||
[PearlKeyChain deleteItemForQuery:keyHashQuery()];
|
||||
|
||||
@ -57,13 +57,12 @@ static NSDictionary *keyHashQuery() {
|
||||
|
||||
if ([[MPConfig get].saveKey boolValue]) {
|
||||
// Key is stored in keychain. Load it.
|
||||
dbg(@"Loading key from key chain.");
|
||||
[self updateKey:[PearlKeyChain dataOfItemForQuery:keyQuery()]];
|
||||
dbg(@" -> Key %@.", self.key? @"found": @"NOT found");
|
||||
inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
|
||||
} else {
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
|
||||
dbg(@"Deleted key from key chain.");
|
||||
inf(@"Removed key from keychain.");
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
|
||||
#endif
|
||||
@ -72,19 +71,18 @@ static NSDictionary *keyHashQuery() {
|
||||
|
||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword {
|
||||
|
||||
NSData *keyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
|
||||
dbg(@"Key hash %@.", keyHash? @"known": @"NOT known");
|
||||
|
||||
if (![tryPassword length])
|
||||
return NO;
|
||||
|
||||
NSData *tryKey = keyForPassword(tryPassword);
|
||||
NSData *tryKeyHash = keyHashForKey(tryKey);
|
||||
NSData *keyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
|
||||
inf(@"Key hash known? %@.", keyHash? @"YES": @"NO");
|
||||
if (keyHash)
|
||||
// A key hash is known -> a key is set.
|
||||
// Make sure the user's entered key matches it.
|
||||
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
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||
@ -115,24 +113,30 @@ static NSDictionary *keyHashQuery() {
|
||||
self.keyHash = keyHashForKey(self.key);
|
||||
self.keyID = [self.keyHash encodeHex];
|
||||
|
||||
dbg(@"Updating key ID to: %@.", self.keyID);
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyHashQuery()
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.keyHash, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
if ([[MPConfig get].saveKey boolValue]) {
|
||||
dbg(@"Storing key in key chain.");
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery()
|
||||
NSData *existingKeyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
|
||||
if (![existingKeyHash isEqualToData:self.keyHash]) {
|
||||
inf(@"Updating key ID in keychain.");
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyHashQuery()
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id)kSecValueData,
|
||||
self.keyHash, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
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
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
|
||||
|
@ -120,7 +120,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
NSError *error = nil;
|
||||
if ([self.managedObjectContext hasChanges])
|
||||
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 {
|
||||
|
||||
dbg(@"StoreManager: %@", message);
|
||||
dbg(@"[StoreManager] %@", message);
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)didSwitch {
|
||||
@ -338,7 +338,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
|
||||
// Delete existing sites.
|
||||
[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 saveContext];
|
||||
@ -352,7 +352,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
NSString *exportContent = [siteElements objectAtIndex:4];
|
||||
|
||||
// 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)
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
element.name = name;
|
||||
|
@ -13,6 +13,6 @@
|
||||
|
||||
@interface MPElementGeneratedEntity : MPElementEntity
|
||||
|
||||
@property (nonatomic, assign) int16_t counter;
|
||||
@property (nonatomic, assign) int32_t counter;
|
||||
|
||||
@end
|
||||
|
@ -18,7 +18,7 @@
|
||||
- (id)content {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -82,4 +82,4 @@ NSData *keyHashForKey(NSData *key);
|
||||
NSString *NSStringFromMPElementType(MPElementType type);
|
||||
NSString *ClassNameFromMPElementType(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);
|
||||
|
@ -9,6 +9,8 @@
|
||||
#import "MPTypes.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPElementStoredEntity.h"
|
||||
#include <endian.h>
|
||||
|
||||
|
||||
#define MP_salt nil
|
||||
#define MP_N 16384
|
||||
@ -21,7 +23,8 @@ NSData *keyForPassword(NSString *password) {
|
||||
|
||||
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
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;
|
||||
}
|
||||
NSData *keyHashForPassword(NSString *password) {
|
||||
@ -102,32 +105,37 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
|
||||
}
|
||||
|
||||
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)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
|
||||
return nil;
|
||||
}
|
||||
if (!name) {
|
||||
err(@"Missing name.");
|
||||
return nil;
|
||||
}
|
||||
if (!key) {
|
||||
err(@"Key not set.");
|
||||
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)
|
||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
||||
withExtension:@"plist"]];
|
||||
|
||||
// Determine the hash whose bytes will be used for calculating a password: md4(name-key)
|
||||
uint16_t ncounter = htons(counter);
|
||||
trc(@"key hash from: %@-%@-%u", name, key, ncounter);
|
||||
uint32_t nsalt = htonl(salt);
|
||||
trc(@"key hash from: %@-%@-%u", name, key, nsalt);
|
||||
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
key,
|
||||
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
|
||||
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)],
|
||||
nil] hashWith:PearlDigestSHA1];
|
||||
trc(@"key hash is: %@", keyHash);
|
||||
const char *keyBytes = keyHash.bytes;
|
||||
|
@ -8,7 +8,7 @@
|
||||
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
</entity>
|
||||
<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 name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
|
@ -98,8 +98,8 @@ typedef enum {
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten
|
||||
object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[self.field becomeFirstResponder];
|
||||
}];
|
||||
[self.field becomeFirstResponder];
|
||||
}];
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
@ -200,7 +200,6 @@ typedef enum {
|
||||
|
||||
- (IBAction)changeMP {
|
||||
|
||||
dbg(@"Forgetting key phrase.");
|
||||
[PearlAlert showAlertWithTitle:@"Changing Master Password"
|
||||
message:
|
||||
@"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."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex != [alert cancelButtonIndex])
|
||||
[[MPAppDelegate get] forgetKey];
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[[MPAppDelegate get] forgetKey];
|
||||
[[MPAppDelegate get] loadKey:YES];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
|
||||
|
Loading…
Reference in New Issue
Block a user