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