Key hash -> key ID.
This commit is contained in:
parent
301366f1f1
commit
f7e64fe4b8
@ -24,23 +24,23 @@ static NSDictionary *keyQuery() {
|
|||||||
return MPKeyQuery;
|
return MPKeyQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSDictionary *keyHashQuery() {
|
static NSDictionary *keyIDQuery() {
|
||||||
|
|
||||||
static NSDictionary *MPKeyHashQuery = nil;
|
static NSDictionary *MPKeyIDQuery = nil;
|
||||||
if (!MPKeyHashQuery)
|
if (!MPKeyIDQuery)
|
||||||
MPKeyHashQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
MPKeyIDQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:[NSDictionary dictionaryWithObject:@"Master Password Verification"
|
attributes:[NSDictionary dictionaryWithObject:@"Master Password Verification"
|
||||||
forKey:(__bridge id)kSecAttrService]
|
forKey:(__bridge id)kSecAttrService]
|
||||||
matches:nil];
|
matches:nil];
|
||||||
|
|
||||||
return MPKeyHashQuery;
|
return MPKeyIDQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)forgetKey {
|
- (void)forgetKey {
|
||||||
|
|
||||||
inf(@"Deleting key and hash from keychain.");
|
inf(@"Deleting key and ID from keychain.");
|
||||||
[PearlKeyChain deleteItemForQuery:keyQuery()];
|
[PearlKeyChain deleteItemForQuery:keyQuery()];
|
||||||
[PearlKeyChain deleteItemForQuery:keyHashQuery()];
|
[PearlKeyChain deleteItemForQuery:keyIDQuery()];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
#ifdef TESTFLIGHT_SDK_VERSION
|
||||||
@ -75,14 +75,14 @@ static NSDictionary *keyHashQuery() {
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
NSData *tryKey = keyForPassword(tryPassword);
|
NSData *tryKey = keyForPassword(tryPassword);
|
||||||
NSData *tryKeyHash = keyHashForKey(tryKey);
|
NSData *tryKeyID = keyIDForKey(tryKey);
|
||||||
NSData *keyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
|
NSData *keyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
|
||||||
inf(@"Key hash known? %@.", keyHash? @"YES": @"NO");
|
inf(@"Key ID known? %@.", keyID? @"YES": @"NO");
|
||||||
if (keyHash)
|
if (keyID)
|
||||||
// A key hash is known -> a key is set.
|
// A key ID is known -> a password is set.
|
||||||
// Make sure the user's entered key matches it.
|
// Make sure the user's entered password matches it.
|
||||||
if (![keyHash isEqual:tryKeyHash]) {
|
if (![keyID isEqual:tryKeyID]) {
|
||||||
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [keyHash encodeHex], [tryKeyHash encodeHex]);
|
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [keyID encodeHex], [tryKeyID encodeHex]);
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
#ifdef TESTFLIGHT_SDK_VERSION
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||||
@ -110,15 +110,14 @@ static NSDictionary *keyHashQuery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.key) {
|
if (self.key) {
|
||||||
self.keyHash = keyHashForKey(self.key);
|
self.keyID = keyIDForKey(self.key);
|
||||||
self.keyID = [self.keyHash encodeHex];
|
|
||||||
|
|
||||||
NSData *existingKeyHash = [PearlKeyChain dataOfItemForQuery:keyHashQuery()];
|
NSData *existingKeyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
|
||||||
if (![existingKeyHash isEqualToData:self.keyHash]) {
|
if (![existingKeyID isEqualToData:self.keyID]) {
|
||||||
inf(@"Updating key ID in keychain.");
|
inf(@"Updating key ID in keychain.");
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:keyHashQuery()
|
[PearlKeyChain addOrUpdateItemForQuery:keyIDQuery()
|
||||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
self.keyHash, (__bridge id)kSecValueData,
|
self.keyID, (__bridge id)kSecValueData,
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||||
#endif
|
#endif
|
||||||
|
@ -9,8 +9,7 @@
|
|||||||
@interface MPAppDelegate_Shared : PearlAppDelegate
|
@interface MPAppDelegate_Shared : PearlAppDelegate
|
||||||
|
|
||||||
@property (strong, nonatomic) NSData *key;
|
@property (strong, nonatomic) NSData *key;
|
||||||
@property (strong, nonatomic) NSData *keyHash;
|
@property (strong, nonatomic) NSData *keyID;
|
||||||
@property (strong, nonatomic) NSString *keyID;
|
|
||||||
|
|
||||||
+ (MPAppDelegate_Shared *)get;
|
+ (MPAppDelegate_Shared *)get;
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
@implementation MPAppDelegate_Shared
|
@implementation MPAppDelegate_Shared
|
||||||
|
|
||||||
@synthesize key;
|
@synthesize key;
|
||||||
@synthesize keyHash;
|
|
||||||
@synthesize keyID;
|
@synthesize keyID;
|
||||||
|
|
||||||
+ (MPAppDelegate_Shared *)get {
|
+ (MPAppDelegate_Shared *)get {
|
||||||
|
@ -265,7 +265,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
|||||||
if (!headerPattern || !sitePattern)
|
if (!headerPattern || !sitePattern)
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
|
|
||||||
NSString *keyID = nil;
|
NSString *keyIDHex = nil;
|
||||||
BOOL headerStarted = NO, headerEnded = NO;
|
BOOL headerStarted = NO, headerEnded = NO;
|
||||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
||||||
@ -295,7 +295,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
|||||||
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||||
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||||
if ([key isEqualToString:@"Key ID"]) {
|
if ([key isEqualToString:@"Key ID"]) {
|
||||||
if (![(keyID = value) isEqualToString:[keyHashForPassword(password) encodeHex]])
|
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]])
|
||||||
return MPImportResultInvalidPassword;
|
return MPImportResultInvalidPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
|||||||
}
|
}
|
||||||
if (!headerEnded)
|
if (!headerEnded)
|
||||||
continue;
|
continue;
|
||||||
if (!keyID)
|
if (!keyIDHex)
|
||||||
return MPImportResultMalformedInput;
|
return MPImportResultMalformedInput;
|
||||||
if (![importedSiteLine length])
|
if (![importedSiteLine length])
|
||||||
continue;
|
continue;
|
||||||
@ -321,7 +321,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
|||||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||||
|
|
||||||
// Find existing site.
|
// Find existing site.
|
||||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND keyID == %@", name, keyID];
|
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND keyID == %@", name, keyIDHex];
|
||||||
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||||
if (error)
|
if (error)
|
||||||
err(@"Couldn't search existing sites: %@", error);
|
err(@"Couldn't search existing sites: %@", error);
|
||||||
@ -352,11 +352,11 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
|||||||
NSString *exportContent = [siteElements objectAtIndex:4];
|
NSString *exportContent = [siteElements objectAtIndex:4];
|
||||||
|
|
||||||
// Create new site.
|
// Create new site.
|
||||||
inf(@"Importing site: 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, keyIDHex);
|
||||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||||
inManagedObjectContext:self.managedObjectContext];
|
inManagedObjectContext:self.managedObjectContext];
|
||||||
element.name = name;
|
element.name = name;
|
||||||
element.keyID = keyID;
|
element.keyID = [keyIDHex decodeHex];
|
||||||
element.type = type;
|
element.type = type;
|
||||||
element.uses = uses;
|
element.uses = uses;
|
||||||
element.lastUsed = [lastUsed timeIntervalSinceReferenceDate];
|
element.lastUsed = [lastUsed timeIntervalSinceReferenceDate];
|
||||||
@ -386,7 +386,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
|||||||
[export appendFormat:@"# \n"];
|
[export appendFormat:@"# \n"];
|
||||||
[export appendFormat:@"##\n"];
|
[export appendFormat:@"##\n"];
|
||||||
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
|
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
|
||||||
[export appendFormat:@"# Key ID: %@\n", self.keyID];
|
[export appendFormat:@"# Key ID: %@\n", [self.keyID encodeHex]];
|
||||||
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]];
|
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]];
|
||||||
if (showPasswords)
|
if (showPasswords)
|
||||||
[export appendFormat:@"# Passwords: VISIBLE\n"];
|
[export appendFormat:@"# Passwords: VISIBLE\n"];
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
@interface MPElementEntity : NSManagedObject
|
@interface MPElementEntity : NSManagedObject
|
||||||
|
|
||||||
@property (nonatomic, retain) NSString *name;
|
@property (nonatomic, retain) NSString *name;
|
||||||
@property (nonatomic, retain) NSString *keyID;
|
@property (nonatomic, retain) NSData *keyID;
|
||||||
@property (nonatomic, assign) int16_t type;
|
@property (nonatomic, assign) int16_t type;
|
||||||
@property (nonatomic, assign) int16_t uses;
|
@property (nonatomic, assign) int16_t uses;
|
||||||
@property (nonatomic, assign) NSTimeInterval lastUsed;
|
@property (nonatomic, assign) NSTimeInterval lastUsed;
|
||||||
|
@ -77,8 +77,8 @@ typedef enum {
|
|||||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||||
|
|
||||||
NSData *keyForPassword(NSString *password);
|
NSData *keyForPassword(NSString *password);
|
||||||
NSData *keyHashForPassword(NSString *password);
|
NSData *keyIDForPassword(NSString *password);
|
||||||
NSData *keyHashForKey(NSData *key);
|
NSData *keyIDForKey(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);
|
||||||
|
@ -24,14 +24,14 @@ 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 ID: %@", password, [keyHashForKey(key) encodeHex]);
|
trc(@"Password: %@ derives to key ID: %@", password, [keyIDForKey(key) encodeHex]);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
NSData *keyHashForPassword(NSString *password) {
|
NSData *keyIDForPassword(NSString *password) {
|
||||||
|
|
||||||
return keyHashForKey(keyForPassword(password));
|
return keyIDForKey(keyForPassword(password));
|
||||||
}
|
}
|
||||||
NSData *keyHashForKey(NSData *key) {
|
NSData *keyIDForKey(NSData *key) {
|
||||||
|
|
||||||
return [key hashWith:MP_hash];
|
return [key hashWith:MP_hash];
|
||||||
}
|
}
|
||||||
@ -129,29 +129,29 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, in
|
|||||||
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 seed whose bytes will be used for calculating a password: sha1(name . '\0' . key . '\0' . salt)
|
||||||
uint32_t nsalt = htonl(salt);
|
uint32_t nsalt = htonl(salt);
|
||||||
trc(@"key hash from: %@-%@-%u", name, key, nsalt);
|
trc(@"seed from: sha1(%@, %@, %u)", name, key, nsalt);
|
||||||
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
|
NSData *seed = [[NSData dataByConcatenatingWithDelimitor:'\0' datas:
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
key,
|
key,
|
||||||
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)],
|
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)],
|
||||||
nil] hashWith:PearlDigestSHA1];
|
nil] hashWith:PearlDigestSHA1];
|
||||||
trc(@"key hash is: %@", keyHash);
|
trc(@"seed is: %@", seed);
|
||||||
const char *keyBytes = keyHash.bytes;
|
const char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
// Determine the cipher from the first hash byte.
|
// Determine the cipher from the first seed byte.
|
||||||
assert([keyHash length]);
|
assert([seed 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:htons(keyBytes[0]) % [typeCiphers count]];
|
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||||
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
|
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 seed bytes and the cipher.
|
||||||
assert([keyHash length] >= [cipher length] + 1);
|
assert([seed 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) {
|
||||||
uint16_t keyByte = htons(keyBytes[c + 1]);
|
uint16_t keyByte = htons(seedBytes[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)];
|
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)];
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
@synthesize passwordWindow;
|
@synthesize passwordWindow;
|
||||||
|
|
||||||
@synthesize key;
|
@synthesize key;
|
||||||
@synthesize keyHash;
|
|
||||||
@synthesize keyID;
|
@synthesize keyID;
|
||||||
|
|
||||||
#pragma GCC diagnostic ignored "-Wfour-char-constants"
|
#pragma GCC diagnostic ignored "-Wfour-char-constants"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||||
<attribute name="keyID" attributeType="String" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
<attribute name="keyID" attributeType="Binary" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
||||||
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
||||||
<attribute name="type" attributeType="Integer 16" defaultValueString="16" syncable="YES"/>
|
<attribute name="type" attributeType="Integer 16" defaultValueString="16" syncable="YES"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user