2012-07-17 20:57:11 +00:00
|
|
|
/**
|
|
|
|
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
|
|
|
*
|
|
|
|
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
|
|
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
|
|
*
|
|
|
|
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
|
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
|
|
*/
|
|
|
|
|
2012-01-05 00:44:15 +00:00
|
|
|
//
|
2012-07-17 20:57:11 +00:00
|
|
|
// MPAlgorithmV0
|
2012-01-05 00:44:15 +00:00
|
|
|
//
|
2012-07-17 20:57:11 +00:00
|
|
|
// Created by Maarten Billemont on 16/07/12.
|
|
|
|
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
2012-01-05 00:44:15 +00:00
|
|
|
//
|
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
#import "MPAlgorithmV0.h"
|
2012-06-10 06:21:41 +00:00
|
|
|
#import "MPEntities.h"
|
2012-05-12 16:31:05 +00:00
|
|
|
|
2012-06-27 21:44:37 +00:00
|
|
|
#define MP_N 32768
|
2012-02-25 14:30:23 +00:00
|
|
|
#define MP_r 8
|
2012-06-27 21:44:37 +00:00
|
|
|
#define MP_p 2
|
2012-02-25 14:30:23 +00:00
|
|
|
#define MP_dkLen 64
|
2012-06-06 22:12:38 +00:00
|
|
|
#define MP_hash PearlHashSHA256
|
2012-01-05 00:44:15 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
@implementation MPAlgorithmV0
|
2012-06-06 22:12:38 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (NSUInteger)version {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
return 0;
|
2012-02-13 09:27:08 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-04-30 05:49:53 +00:00
|
|
|
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
2013-04-20 18:11:19 +00:00
|
|
|
|
|
|
|
NSError *error = nil;
|
|
|
|
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
2013-04-13 17:40:17 +00:00
|
|
|
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
2013-04-30 05:49:53 +00:00
|
|
|
NSArray *migrationElements = [moc executeFetchRequest:migrationRequest error:&error];
|
2013-01-31 05:42:32 +00:00
|
|
|
if (!migrationElements) {
|
|
|
|
err(@"While looking for elements to migrate: %@", error);
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2013-04-20 18:11:19 +00:00
|
|
|
BOOL requiresExplicitMigration = NO;
|
2013-01-31 05:42:32 +00:00
|
|
|
for (MPElementEntity *migrationElement in migrationElements)
|
|
|
|
if (![migrationElement migrateExplicitly:NO])
|
|
|
|
requiresExplicitMigration = YES;
|
|
|
|
|
|
|
|
return requiresExplicitMigration;
|
2013-01-17 05:37:20 +00:00
|
|
|
}
|
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
2012-06-22 14:52:33 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
if (element.version != [self version] - 1)
|
2013-04-20 18:11:19 +00:00
|
|
|
// Only migrate from previous version.
|
2012-07-17 20:57:11 +00:00
|
|
|
return NO;
|
2012-06-22 14:52:33 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
if (!explicit) {
|
|
|
|
// This migration requires explicit permission.
|
|
|
|
element.requiresExplicitMigration = YES;
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply migration.
|
|
|
|
element.requiresExplicitMigration = NO;
|
2013-04-20 18:11:19 +00:00
|
|
|
element.version = [self version];
|
2012-07-17 20:57:11 +00:00
|
|
|
return YES;
|
2012-06-22 14:52:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
|
|
|
|
|
|
|
|
uint32_t nuserNameLength = htonl(userName.length);
|
2013-04-20 18:11:19 +00:00
|
|
|
NSDate *start = [NSDate date];
|
2012-07-17 20:57:11 +00:00
|
|
|
NSData *keyData = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
2013-04-20 18:11:19 +00:00
|
|
|
usingSalt:[NSData dataByConcatenatingDatas:
|
|
|
|
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
|
|
|
[NSData dataWithBytes:&nuserNameLength
|
|
|
|
length:sizeof(nuserNameLength)],
|
|
|
|
[userName dataUsingEncoding:NSUTF8StringEncoding],
|
|
|
|
nil] N:MP_N r:MP_r p:MP_p];
|
2012-06-22 14:52:33 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
MPKey *key = [self keyFromKeyData:keyData];
|
|
|
|
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex], -[start timeIntervalSinceNow]);
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
return key;
|
2012-02-13 09:27:08 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (MPKey *)keyFromKeyData:(NSData *)keyData {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
return [[MPKey alloc] initWithKeyData:keyData algorithm:self];
|
2012-02-13 09:27:08 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (NSData *)keyIDForKeyData:(NSData *)keyData {
|
|
|
|
|
|
|
|
return [keyData hashWith:MP_hash];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)nameOfType:(MPElementType)type {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-01-16 08:51:08 +00:00
|
|
|
if (!type)
|
|
|
|
return nil;
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-01-05 00:44:15 +00:00
|
|
|
switch (type) {
|
2012-06-11 14:15:49 +00:00
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
return @"Maximum Security Password";
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
case MPElementTypeGeneratedLong:
|
2012-01-24 23:30:43 +00:00
|
|
|
return @"Long Password";
|
2012-06-06 20:38:43 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
case MPElementTypeGeneratedMedium:
|
2012-01-24 23:30:43 +00:00
|
|
|
return @"Medium Password";
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
case MPElementTypeGeneratedBasic:
|
2012-01-24 23:30:43 +00:00
|
|
|
return @"Basic Password";
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-02-08 23:11:24 +00:00
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
return @"Short Password";
|
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
case MPElementTypeGeneratedPIN:
|
2012-01-05 00:44:15 +00:00
|
|
|
return @"PIN";
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-02-05 21:18:38 +00:00
|
|
|
case MPElementTypeStoredPersonal:
|
2012-01-24 23:30:43 +00:00
|
|
|
return @"Personal Password";
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-02-05 21:18:38 +00:00
|
|
|
case MPElementTypeStoredDevicePrivate:
|
2012-01-24 23:30:43 +00:00
|
|
|
return @"Device Private Password";
|
2012-01-05 00:44:15 +00:00
|
|
|
}
|
2012-07-17 20:57:11 +00:00
|
|
|
|
2014-02-22 23:27:14 +00:00
|
|
|
Throw(@"Type not supported: %lu", (long)type);
|
2012-01-05 00:44:15 +00:00
|
|
|
}
|
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (NSString *)shortNameOfType:(MPElementType)type {
|
2012-06-11 14:15:49 +00:00
|
|
|
|
|
|
|
if (!type)
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
return @"Maximum";
|
|
|
|
|
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
return @"Long";
|
|
|
|
|
|
|
|
case MPElementTypeGeneratedMedium:
|
|
|
|
return @"Medium";
|
|
|
|
|
|
|
|
case MPElementTypeGeneratedBasic:
|
|
|
|
return @"Basic";
|
|
|
|
|
2013-02-08 23:11:24 +00:00
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
return @"Short";
|
|
|
|
|
2012-06-11 14:15:49 +00:00
|
|
|
case MPElementTypeGeneratedPIN:
|
|
|
|
return @"PIN";
|
|
|
|
|
|
|
|
case MPElementTypeStoredPersonal:
|
|
|
|
return @"Personal";
|
|
|
|
|
|
|
|
case MPElementTypeStoredDevicePrivate:
|
|
|
|
return @"Device";
|
|
|
|
}
|
2012-07-17 20:57:11 +00:00
|
|
|
|
2014-02-22 23:27:14 +00:00
|
|
|
Throw(@"Type not supported: %lu", (long)type);
|
2012-07-17 20:57:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)classNameOfType:(MPElementType)type {
|
|
|
|
|
2013-04-20 18:11:19 +00:00
|
|
|
return NSStringFromClass( [self classOfType:type] );
|
2012-06-11 14:15:49 +00:00
|
|
|
}
|
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (Class)classOfType:(MPElementType)type {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-01-19 16:40:39 +00:00
|
|
|
if (!type)
|
2013-04-20 18:11:19 +00:00
|
|
|
Throw(@"No type given.");
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-01-19 16:40:39 +00:00
|
|
|
switch (type) {
|
2012-06-11 14:15:49 +00:00
|
|
|
case MPElementTypeGeneratedMaximum:
|
2012-02-05 21:18:38 +00:00
|
|
|
return [MPElementGeneratedEntity class];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-06-06 20:38:43 +00:00
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
return [MPElementGeneratedEntity class];
|
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
case MPElementTypeGeneratedMedium:
|
2012-02-05 21:18:38 +00:00
|
|
|
return [MPElementGeneratedEntity class];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-02-08 23:11:24 +00:00
|
|
|
case MPElementTypeGeneratedBasic:
|
2012-02-05 21:18:38 +00:00
|
|
|
return [MPElementGeneratedEntity class];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-02-08 23:11:24 +00:00
|
|
|
case MPElementTypeGeneratedShort:
|
2012-02-05 21:18:38 +00:00
|
|
|
return [MPElementGeneratedEntity class];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
case MPElementTypeGeneratedPIN:
|
2012-02-05 21:18:38 +00:00
|
|
|
return [MPElementGeneratedEntity class];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-02-05 21:18:38 +00:00
|
|
|
case MPElementTypeStoredPersonal:
|
|
|
|
return [MPElementStoredEntity class];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-02-05 21:18:38 +00:00
|
|
|
case MPElementTypeStoredDevicePrivate:
|
|
|
|
return [MPElementStoredEntity class];
|
2012-01-19 16:40:39 +00:00
|
|
|
}
|
|
|
|
|
2014-02-22 23:27:14 +00:00
|
|
|
Throw(@"Type not supported: %lu", (long)type);
|
2014-02-19 05:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (MPElementType)nextType:(MPElementType)type {
|
|
|
|
|
|
|
|
if (!type)
|
|
|
|
Throw(@"No type given.");
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
return MPElementTypeStoredDevicePrivate;
|
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
return MPElementTypeGeneratedMaximum;
|
|
|
|
case MPElementTypeGeneratedMedium:
|
|
|
|
return MPElementTypeGeneratedLong;
|
|
|
|
case MPElementTypeGeneratedBasic:
|
|
|
|
return MPElementTypeGeneratedMedium;
|
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
return MPElementTypeGeneratedBasic;
|
|
|
|
case MPElementTypeGeneratedPIN:
|
|
|
|
return MPElementTypeGeneratedShort;
|
|
|
|
case MPElementTypeStoredPersonal:
|
|
|
|
return MPElementTypeGeneratedPIN;
|
|
|
|
case MPElementTypeStoredDevicePrivate:
|
|
|
|
return MPElementTypeStoredPersonal;
|
|
|
|
}
|
|
|
|
|
2014-02-22 23:27:14 +00:00
|
|
|
Throw(@"Type not supported: %lu", (long)type);
|
2014-02-19 05:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (MPElementType)previousType:(MPElementType)type {
|
|
|
|
|
|
|
|
MPElementType previousType = type, nextType = type;
|
|
|
|
while ((nextType = [self nextType:nextType]) != type)
|
|
|
|
previousType = nextType;
|
|
|
|
|
|
|
|
return previousType;
|
2012-01-24 23:30:43 +00:00
|
|
|
}
|
|
|
|
|
2013-04-19 04:43:01 +00:00
|
|
|
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
|
|
|
|
|
|
|
static NSDictionary *MPTypes_ciphers = nil;
|
2012-02-05 21:18:38 +00:00
|
|
|
if (MPTypes_ciphers == nil)
|
2013-04-19 04:43:01 +00:00
|
|
|
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:
|
|
|
|
[[NSBundle mainBundle] URLForResource:@"ciphers" withExtension:@"plist"]];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-06-06 22:12:38 +00:00
|
|
|
// Determine the seed whose bytes will be used for calculating a password
|
2013-04-19 04:43:01 +00:00
|
|
|
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
|
2013-04-20 18:11:19 +00:00
|
|
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
|
2012-07-05 11:47:50 +00:00
|
|
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
|
2013-04-19 04:43:01 +00:00
|
|
|
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], name, [counterBytes encodeHex]);
|
2012-06-06 22:12:38 +00:00
|
|
|
NSData *seed = [[NSData dataByConcatenatingDatas:
|
2013-04-20 18:11:19 +00:00
|
|
|
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
|
|
|
nameLengthBytes,
|
|
|
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
|
|
|
counterBytes,
|
|
|
|
nil]
|
|
|
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
2012-07-05 11:47:50 +00:00
|
|
|
trc(@"seed is: %@", [seed encodeBase64]);
|
2012-05-12 17:24:29 +00:00
|
|
|
const char *seedBytes = seed.bytes;
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-12 17:24:29 +00:00
|
|
|
// Determine the cipher from the first seed byte.
|
2013-09-13 12:14:58 +00:00
|
|
|
NSAssert([seed length], @"Missing seed.");
|
2013-04-20 18:11:19 +00:00
|
|
|
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]]
|
|
|
|
valueForKey:[self nameOfType:type]];
|
2013-09-28 01:23:52 +00:00
|
|
|
NSString *cipher = typeCiphers[htons(seedBytes[0]) % [typeCiphers count]];
|
2013-04-19 04:43:01 +00:00
|
|
|
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-12 17:24:29 +00:00
|
|
|
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
2013-09-13 12:14:58 +00:00
|
|
|
NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
|
2012-01-05 00:44:15 +00:00
|
|
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
|
|
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
2012-05-12 17:24:29 +00:00
|
|
|
uint16_t keyByte = htons(seedBytes[c + 1]);
|
2013-04-20 18:11:19 +00:00
|
|
|
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
|
2012-02-05 21:18:38 +00:00
|
|
|
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
2013-04-20 18:11:19 +00:00
|
|
|
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length],
|
|
|
|
1 )];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-07-05 11:47:50 +00:00
|
|
|
trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
|
2012-03-06 00:04:19 +00:00
|
|
|
[content appendString:character];
|
2012-01-05 00:44:15 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-01-05 00:44:15 +00:00
|
|
|
return content;
|
2012-01-30 00:02:45 +00:00
|
|
|
}
|
2012-07-12 06:41:18 +00:00
|
|
|
|
2013-09-13 12:14:58 +00:00
|
|
|
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
|
|
|
|
|
|
|
|
return [self decryptContent:element.contentObject usingKey:key];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
|
|
|
|
|
|
|
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
|
|
|
switch (element.type) {
|
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
case MPElementTypeGeneratedMedium:
|
|
|
|
case MPElementTypeGeneratedBasic:
|
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
case MPElementTypeGeneratedPIN: {
|
2014-02-22 23:27:14 +00:00
|
|
|
NSAssert(NO, @"Cannot save content to element with generated type %lu.", (long)element.type);
|
2013-09-13 12:14:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MPElementTypeStoredPersonal: {
|
|
|
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
2014-02-22 23:27:14 +00:00
|
|
|
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
2013-09-13 12:14:58 +00:00
|
|
|
|
|
|
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
|
|
|
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
|
|
|
((MPElementStoredEntity *)element).contentObject = encryptedContent;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MPElementTypeStoredDevicePrivate: {
|
|
|
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
2014-02-22 23:27:14 +00:00
|
|
|
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
2013-09-13 12:14:58 +00:00
|
|
|
|
|
|
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
|
|
|
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
|
|
|
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
|
|
|
if (!encryptedContent)
|
|
|
|
[PearlKeyChain deleteItemForQuery:elementQuery];
|
|
|
|
else
|
|
|
|
[PearlKeyChain addOrUpdateItemForQuery:elementQuery withAttributes:@{
|
|
|
|
(__bridge id)kSecValueData : encryptedContent,
|
|
|
|
#if TARGET_OS_IPHONE
|
|
|
|
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
|
|
#endif
|
|
|
|
}];
|
|
|
|
((MPElementStoredEntity *)element).contentObject = nil;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
|
|
|
|
|
|
|
dispatch_group_t group = dispatch_group_create();
|
|
|
|
dispatch_group_enter( group );
|
|
|
|
__block NSString *result = nil;
|
|
|
|
[self resolveContentForElement:element usingKey:elementKey result:^(NSString *result_) {
|
|
|
|
result = result_;
|
|
|
|
dispatch_group_leave( group );
|
|
|
|
}];
|
|
|
|
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void (^)(NSString *result))resultBlock {
|
|
|
|
|
|
|
|
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
|
|
|
switch (element.type) {
|
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
case MPElementTypeGeneratedMedium:
|
|
|
|
case MPElementTypeGeneratedBasic:
|
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
case MPElementTypeGeneratedPIN: {
|
|
|
|
NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]],
|
2014-02-22 23:27:14 +00:00
|
|
|
@"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", (long)element.type, [element class]);
|
2013-09-13 12:14:58 +00:00
|
|
|
|
|
|
|
NSString *name = element.name;
|
|
|
|
MPElementType type = element.type;
|
|
|
|
NSUInteger counter = ((MPElementGeneratedEntity *)element).counter;
|
|
|
|
id<MPAlgorithm> algorithm = nil;
|
|
|
|
if (!element.name.length)
|
|
|
|
err(@"Missing name.");
|
|
|
|
else if (!elementKey.keyData.length)
|
|
|
|
err(@"Missing key.");
|
|
|
|
else
|
|
|
|
algorithm = element.algorithm;
|
|
|
|
|
|
|
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
|
|
|
NSString *result = [algorithm generateContentNamed:name ofType:type withCounter:counter usingKey:elementKey];
|
|
|
|
resultBlock( result );
|
|
|
|
} );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MPElementTypeStoredPersonal: {
|
|
|
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
2014-02-22 23:27:14 +00:00
|
|
|
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
2013-09-13 12:14:58 +00:00
|
|
|
|
|
|
|
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
|
|
|
|
|
|
|
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
|
|
|
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
|
|
|
resultBlock( result );
|
|
|
|
} );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MPElementTypeStoredDevicePrivate: {
|
|
|
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
2014-02-22 23:27:14 +00:00
|
|
|
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
2013-09-13 12:14:58 +00:00
|
|
|
|
|
|
|
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
|
|
|
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
|
|
|
|
|
|
|
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
|
|
|
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
|
|
|
resultBlock( result );
|
|
|
|
} );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
|
|
|
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
|
|
|
|
|
|
|
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
|
|
|
switch (element.type) {
|
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
case MPElementTypeGeneratedMedium:
|
|
|
|
case MPElementTypeGeneratedBasic:
|
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
case MPElementTypeGeneratedPIN:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MPElementTypeStoredPersonal: {
|
|
|
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
2014-02-22 23:27:14 +00:00
|
|
|
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
2013-09-13 12:14:58 +00:00
|
|
|
if ([importKey.keyID isEqualToData:elementKey.keyID])
|
|
|
|
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
|
|
|
|
|
|
|
|
else {
|
|
|
|
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
|
|
|
[self importClearTextContent:clearContent intoElement:element usingKey:elementKey];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MPElementTypeStoredDevicePrivate:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
|
|
|
|
|
|
|
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
|
|
|
switch (element.type) {
|
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
case MPElementTypeGeneratedMedium:
|
|
|
|
case MPElementTypeGeneratedBasic:
|
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
case MPElementTypeGeneratedPIN:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MPElementTypeStoredPersonal: {
|
|
|
|
[self saveContent:clearContent toElement:element usingKey:elementKey];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MPElementTypeStoredDevicePrivate:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
|
|
|
|
|
|
|
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
|
|
|
if (!(element.type & MPElementFeatureExportContent))
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
NSString *result = nil;
|
|
|
|
switch (element.type) {
|
|
|
|
case MPElementTypeGeneratedMaximum:
|
|
|
|
case MPElementTypeGeneratedLong:
|
|
|
|
case MPElementTypeGeneratedMedium:
|
|
|
|
case MPElementTypeGeneratedBasic:
|
|
|
|
case MPElementTypeGeneratedShort:
|
|
|
|
case MPElementTypeGeneratedPIN: {
|
|
|
|
result = nil;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MPElementTypeStoredPersonal: {
|
|
|
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
2014-02-22 23:27:14 +00:00
|
|
|
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
2013-09-13 12:14:58 +00:00
|
|
|
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MPElementTypeStoredDevicePrivate: {
|
|
|
|
result = nil;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
|
|
|
|
|
|
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
|
|
|
attributes:@{
|
|
|
|
(__bridge id)kSecAttrService : @"DevicePrivate",
|
|
|
|
(__bridge id)kSecAttrAccount : name
|
|
|
|
}
|
|
|
|
matches:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
|
|
|
|
|
|
|
|
NSData *decryptedContent = nil;
|
|
|
|
if ([encryptedContent length])
|
|
|
|
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
|
|
|
if (!decryptedContent)
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
|
|
|
}
|
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
@end
|