diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index 3cc3a63c..06286a7b 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -226,6 +226,7 @@ 4CD78AC016D155FF00768A1D /* 11_CameraTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 4CD78ABB16D155FF00768A1D /* 11_CameraTemplate.pdf */; }; 4CD820211A32173100399DBB /* ReferenceBuilderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4CD820231A32173100399DBB /* ReferenceBuilderView.xib */; }; 4CD884B715BD47080042BBF8 /* DocumentWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4CD884B615BD47080042BBF8 /* DocumentWindow.xib */; }; + 4CDA35751EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA35741EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.m */; }; 4CDF01A316D1B76700D0AC08 /* MPEntryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CDF01A216D1B76700D0AC08 /* MPEntryViewController.m */; }; 4CE2961518429AA5005F01CE /* MPAutotypeKeyPress.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE2961418429AA5005F01CE /* MPAutotypeKeyPress.m */; }; 4CE296191842A166005F01CE /* MPAutotypePaste.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE296181842A166005F01CE /* MPAutotypePaste.m */; }; @@ -662,6 +663,8 @@ 4CD78ABB16D155FF00768A1D /* 11_CameraTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = 11_CameraTemplate.pdf; sourceTree = ""; }; 4CD820221A32173100399DBB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ReferenceBuilderView.xib; sourceTree = ""; }; 4CD884B615BD47080042BBF8 /* DocumentWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DocumentWindow.xib; sourceTree = ""; }; + 4CDA35731EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MPComposedCharacterAdditions.h"; sourceTree = ""; }; + 4CDA35741EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MPComposedCharacterAdditions.m"; sourceTree = ""; }; 4CDF01A116D1B76700D0AC08 /* MPEntryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntryViewController.h; sourceTree = ""; }; 4CDF01A216D1B76700D0AC08 /* MPEntryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntryViewController.m; sourceTree = ""; }; 4CE2961318429AA5005F01CE /* MPAutotypeKeyPress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAutotypeKeyPress.h; sourceTree = ""; }; @@ -889,6 +892,8 @@ 4C77C84018E240E000D1C42B /* DDHotKey+MacPassAdditions.m */, 4C5807761C64F67000E7171F /* NSString+MPHash.h */, 4C5807771C64F67000E7171F /* NSString+MPHash.m */, + 4CDA35731EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.h */, + 4CDA35741EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.m */, ); name = Categories; sourceTree = ""; @@ -1731,6 +1736,7 @@ 4C888C9316EB6F5E003D34A1 /* MPToolbarItem.m in Sources */, 4C888C9716EB754B003D34A1 /* MPActionHelper.m in Sources */, 4C811C8316ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m in Sources */, + 4CDA35751EBA0CF2003CD59F /* NSString+MPComposedCharacterAdditions.m in Sources */, 4CE39ABF16ECE34A000FE29D /* MPIconSelectViewController.m in Sources */, 4CE39AC416ECE4F7000FE29D /* MPPopupImageView.m in Sources */, 4C46B88517063A070046109A /* NSString+MPPasswordCreation.m in Sources */, diff --git a/MacPass/NSString+MPComposedCharacterAdditions.h b/MacPass/NSString+MPComposedCharacterAdditions.h new file mode 100644 index 00000000..64546ae8 --- /dev/null +++ b/MacPass/NSString+MPComposedCharacterAdditions.h @@ -0,0 +1,18 @@ +// +// NSString+MPComposedCharacterLength.h +// MacPass +// +// Created by Michael Starke on 03.05.17. +// Copyright © 2017 HicknHack Software GmbH. All rights reserved. +// + +#import + +@interface NSString (MPComposedCharacterAdditions) + +@property (nonatomic, readonly) NSUInteger composedCharacterLength; +@property (nonatomic, readonly, copy) NSArray *composedCharacterRanges; // NSArray of NSValues of NSRanges + +- (NSString *)composedCharacterAtIndex:(NSUInteger)index; + +@end diff --git a/MacPass/NSString+MPComposedCharacterAdditions.m b/MacPass/NSString+MPComposedCharacterAdditions.m new file mode 100644 index 00000000..0d9fa4d9 --- /dev/null +++ b/MacPass/NSString+MPComposedCharacterAdditions.m @@ -0,0 +1,39 @@ +// +// NSString+MPComposedCharacterLength.m +// MacPass +// +// Created by Michael Starke on 03.05.17. +// Copyright © 2017 HicknHack Software GmbH. All rights reserved. +// + +#import "NSString+MPComposedCharacterAdditions.h" + +@implementation NSString (MPComposedCharacterAdditions) + +- (NSUInteger)composedCharacterLength { + NSUInteger __block actualLength = 0; + [self enumerateSubstringsInRange:NSMakeRange(0, self.length) + options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) { + actualLength++; + }]; + return actualLength; +} + +- (NSArray *)composedCharacterRanges { + __block NSMutableArray *ranges = [[NSMutableArray alloc] initWithCapacity:self.length]; + [self enumerateSubstringsInRange:NSMakeRange(0, self.length) + options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) { + [ranges addObject:[NSValue valueWithRange:substringRange]]; + }]; + return [ranges copy]; +} + +- (NSString *)composedCharacterAtIndex:(NSUInteger)index { + NSArray *ranges = self.composedCharacterRanges; + if(index < ranges.count) { + return [self substringWithRange:ranges[index].rangeValue]; + } + return nil; +} + +@end diff --git a/MacPass/NSString+MPPasswordCreation.h b/MacPass/NSString+MPPasswordCreation.h index 45f6f136..50b1a0f7 100644 --- a/MacPass/NSString+MPPasswordCreation.h +++ b/MacPass/NSString+MPPasswordCreation.h @@ -40,6 +40,12 @@ typedef NS_OPTIONS(NSUInteger, MPPasswordCharacterFlags) { + (NSString *)passwordFromString:(NSString *)source length:(NSUInteger)length; + (NSString *)passwordWithDefaultSettings; + +/** + * @return returns a random character from the string + */ +@property (nonatomic, readonly, copy) NSString *randomCharacter; + /** * * Creates a random password with only the characters of the receiver @@ -49,10 +55,6 @@ typedef NS_OPTIONS(NSUInteger, MPPasswordCharacterFlags) { * @return Password containing only the characters in receiver */ - (NSString *)passwordWithLength:(NSUInteger)length; -/** - * @return returns a random character from the string - */ -- (NSString *)randomCharacter; /** * Calculates the entropy of the receiver based on the allowed characters. The calculation considers the characters chosen randomly. * If the password supplied was not created randomly based on the full character set, the calculated entropy is NOT correct. diff --git a/MacPass/NSString+MPPasswordCreation.m b/MacPass/NSString+MPPasswordCreation.m index f9b5eb3f..efad79e8 100644 --- a/MacPass/NSString+MPPasswordCreation.m +++ b/MacPass/NSString+MPPasswordCreation.m @@ -9,6 +9,7 @@ #import "NSString+MPPasswordCreation.h" #import "KeePassKit/KeePassKit.h" +#import "NSString+MPComposedCharacterAdditions.h" #import "MPSettingsHelper.h" NSString *const kMPLowercaseLetterCharacters = @"abcdefghijklmnopqrstuvwxyz"; @@ -33,8 +34,7 @@ static NSString *allowedCharactersString(MPPasswordCharacterFlags flags) { } static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* customCharacters){ - NSInteger maxLength = baseCharacters.length + customCharacters.length; - NSMutableString* mergedCharacters = [NSMutableString stringWithCapacity: maxLength]; + NSMutableString* mergedCharacters = [[NSMutableString alloc] init]; [mergedCharacters appendString:baseCharacters]; [customCharacters enumerateSubstringsInRange: NSMakeRange(0, customCharacters.length) options: NSStringEnumerationByComposedCharacterSequences @@ -50,7 +50,7 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust + (NSString *)passwordFromString:(NSString *)source length:(NSUInteger)length { NSMutableString *password = [[NSMutableString alloc] initWithCapacity:length]; - while(password.length < length) { + while(password.composedCharacterLength < length) { [password appendString:[source randomCharacter]]; } return password; @@ -63,7 +63,7 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust NSString *characters = mergeWithoutDuplicates( allowedCharactersString(allowedCharacters), customCharacters); - while(password.length < length) { + while(password.composedCharacterLength < length) { NSString *randomCharacter = [characters randomCharacter]; if(randomCharacter.length > 0) { [password appendString:randomCharacter]; @@ -77,10 +77,10 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust + (NSString *)passwordWithDefaultSettings { /* generate and pre-fill password using default password creation settings */ - NSUInteger passwordLength = [[NSUserDefaults standardUserDefaults] integerForKey:kMPSettingsKeyDefaultPasswordLength]; - MPPasswordCharacterFlags characterFlags = [[NSUserDefaults standardUserDefaults] integerForKey:kMPSettingsKeyPasswordCharacterFlags]; - BOOL useCustomString = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyPasswordUseCustomString]; - NSString *customString = [[NSUserDefaults standardUserDefaults] stringForKey:kMPSettingsKeyPasswordCustomString]; + NSUInteger passwordLength = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyDefaultPasswordLength]; + MPPasswordCharacterFlags characterFlags = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyPasswordCharacterFlags]; + BOOL useCustomString = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyPasswordUseCustomString]; + NSString *customString = [NSUserDefaults.standardUserDefaults stringForKey:kMPSettingsKeyPasswordCustomString]; if(useCustomString && customString.length > 0) { return [customString passwordWithLength:passwordLength]; @@ -95,27 +95,25 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust } - (NSString *)randomCharacter { - if([self length] == 0) { + if(self.length == 0) { return nil; } NSData *data = [NSData kpk_dataWithRandomBytes:sizeof(NSUInteger)]; NSUInteger randomIndex; [data getBytes:&randomIndex length:data.length]; - return [self substringWithRange:NSMakeRange(randomIndex % self.length, 1)]; + return [self composedCharacterAtIndex:(randomIndex % self.composedCharacterLength)]; } - (CGFloat)entropyWhithPossibleCharacterSet:(MPPasswordCharacterFlags)allowedCharacters orCustomCharacters:(NSString *)customCharacters { NSString *characters = nil; - if([[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyPasswordUseCustomString] && nil != customCharacters) { - characters = mergeWithoutDuplicates( - allowedCharactersString(allowedCharacters), - customCharacters); + if([NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyPasswordUseCustomString] && nil != customCharacters) { + characters = mergeWithoutDuplicates(allowedCharactersString(allowedCharacters), customCharacters); } else { characters = allowedCharactersString(allowedCharacters); } - CGFloat alphabetCount = characters.length; - CGFloat passwordLength = self.length; + CGFloat alphabetCount = characters.composedCharacterLength; + CGFloat passwordLength = self.composedCharacterLength; return passwordLength * ( log10(alphabetCount) / log10(2) ); } @end