X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/sec/Security/SecPasswordGenerate.c diff --git a/OSX/sec/Security/SecPasswordGenerate.c b/OSX/sec/Security/SecPasswordGenerate.c new file mode 100644 index 00000000..2d9afcea --- /dev/null +++ b/OSX/sec/Security/SecPasswordGenerate.c @@ -0,0 +1,1392 @@ +/* + * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * SecPasswordStrength.c + */ + +#include +#include +#include +#include +#include +#include "SecPasswordGenerate.h" +#include +#include +#include +#include +#include +#include + +// Keys for external dictionaries with password generation requirements we read from plist. +CFStringRef kSecPasswordMinLengthKey = CFSTR("PasswordMinLength"); +CFStringRef kSecPasswordMaxLengthKey = CFSTR("PasswordMaxLength"); +CFStringRef kSecPasswordAllowedCharactersKey = CFSTR("PasswordAllowedCharacters"); +CFStringRef kSecPasswordRequiredCharactersKey = CFSTR("PasswordRequiredCharacters"); +CFStringRef kSecPasswordDefaultForType = CFSTR("PasswordDefaultForType"); + +CFStringRef kSecPasswordDisallowedCharacters = CFSTR("PasswordDisallowedCharacters"); +CFStringRef kSecPasswordCantStartWithChars = CFSTR("PasswordCantStartWithChars"); +CFStringRef kSecPasswordCantEndWithChars = CFSTR("PasswordCantEndWithChars"); +CFStringRef kSecPasswordContainsNoMoreThanNSpecificCharacters = CFSTR("PasswordContainsNoMoreThanNSpecificCharacters"); +CFStringRef kSecPasswordContainsAtLeastNSpecificCharacters = CFSTR("PasswordContainsAtLeastNSpecificCharacters"); +CFStringRef kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters = CFSTR("PasswordContainsNoMoreThanNConsecutiveIdenticalCharacters"); +CFStringRef kSecPasswordCharacterCount = CFSTR("PasswordCharacterCount"); +CFStringRef kSecPasswordCharacters = CFSTR("PasswordCharacters"); + +CFStringRef kSecPasswordGroupSize = CFSTR("PasswordGroupSize"); +CFStringRef kSecPasswordNumberOfGroups = CFSTR("PasswordNumberOfGroups"); +CFStringRef kSecPasswordSeparator = CFSTR("SecPasswordSeparator"); + +// Keys for internally used dictionaries with password generation parameters (never exposed to external API). +static CFStringRef kSecUseDefaultPasswordFormatKey = CFSTR("UseDefaultPasswordFormat"); +static CFStringRef kSecNumberOfRequiredRandomCharactersKey = CFSTR("NumberOfRequiredRandomCharacters"); +static CFStringRef kSecAllowedCharactersKey = CFSTR("AllowedCharacters"); +static CFStringRef kSecRequiredCharacterSetsKey = CFSTR("RequiredCharacterSets"); + +static CFIndex defaultNumberOfRandomCharacters = 20; +static CFIndex defaultPINLength = 4; +static CFIndex defaultiCloudPasswordLength = 24; +static CFIndex defaultWifiPasswordLength = 12; + +static CFStringRef defaultWifiCharacters = CFSTR("abcdefghijklmnopqrstuvwxyz1234567890"); +static CFStringRef defaultPINCharacters = CFSTR("0123456789"); +static CFStringRef defaultiCloudCharacters = CFSTR("ABCDEFGHJKLMNPQRSTUVWXYZ23456789"); +static CFStringRef defaultCharacters = CFSTR("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789"); + +static CFCharacterSetRef uppercaseLetterCharacterSet; +static CFCharacterSetRef lowercaseLetterCharacterSet; +static CFCharacterSetRef decimalDigitCharacterSet; +static CFCharacterSetRef punctuationCharacterSet; + +static CFIndex alphabetSetSize = 26; +static CFIndex decimalSetSize = 10; +static CFIndex punctuationSetSize = 33; +static double entropyStrengthThreshold = 35.0; + +/* + generated with ruby badpins.rb | gperf + See this for PIN list: + A birthday present every eleven wallets? The security of customer-chosen banking PINs (2012), by Joseph Bonneau , Sören Preibusch , Ross Anderson + */ +const char *in_word_set (const char *str, unsigned int len); + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ +&& ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ +&& (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ +&& ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ +&& ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ +&& ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ +&& ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ +&& ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ +&& ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ +&& ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ +&& ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ +&& ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ +&& ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ +&& ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ +&& ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ +&& ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ +&& ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ +&& ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ +&& ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ +&& ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ +&& ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ +&& ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ +&& ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +error "gperf generated tables don't work with this execution character set. Please report a bug to ." +#endif + + +#define TOTAL_KEYWORDS 100 +#define MIN_WORD_LENGTH 4 +#define MAX_WORD_LENGTH 4 +#define MIN_HASH_VALUE 21 +#define MAX_HASH_VALUE 275 +/* maximum key range = 255, duplicates = 0 */ + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int pinhash (const char *str, unsigned int len) +{ + static unsigned short asso_values[] = + { + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 5, 0, + 10, 10, 30, 50, 100, 120, 70, 25, 57, 85, + 2, 4, 1, 19, 14, 11, 92, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276, 276, 276, 276, 276, 276, + 276, 276, 276, 276, 276 + }; + return len + asso_values[(unsigned char)str[3]+9] + asso_values[(unsigned char)str[2]] + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]+3]; +} + +CFStringRef SecPasswordCreateWithRandomDigits(int n, CFErrorRef *error){ + int min = n; + int max = n; + + uppercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter); + lowercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter); + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + punctuationCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetPunctuation); + + CFNumberRef minRef = CFNumberCreate(NULL, kCFNumberIntType, &min); + CFNumberRef maxRef = CFNumberCreate(NULL, kCFNumberIntType, &max); + + CFMutableDictionaryRef passwordRequirements = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); + CFDictionaryAddValue(passwordRequirements, kSecPasswordMinLengthKey, minRef); + CFDictionaryAddValue(passwordRequirements, kSecPasswordMaxLengthKey, maxRef); + CFStringRef allowedCharacters = CFSTR("0123456789"); + + CFDictionaryAddValue(passwordRequirements, kSecPasswordAllowedCharactersKey, allowedCharacters); + + CFStringRef password = SecPasswordGenerate(kSecPasswordTypePIN, error, passwordRequirements); + + CFReleaseNull(minRef); + CFReleaseNull(maxRef); + CFReleaseNull(passwordRequirements); + + return password; +} + + + +//pins that reached the top 20 list +static const char *blacklist[] = {"1234", "1004", "2000", "1122", "4321", "2001", "2580"}; +bool SecPasswordIsPasswordWeak(CFStringRef passcode) +{ + uppercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter); + lowercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter); + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + punctuationCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetPunctuation); + + bool isNumber = true; + char* pin = NULL; + + //length checks + if( CFStringGetLength(passcode) < 4 ){ + return true; //weak password + } + //check to see if passcode is a number + for(CFIndex i = 0; i < CFStringGetLength(passcode); i++){ + if( CFStringFindCharacterFromSet(passcode, decimalDigitCharacterSet, CFRangeMake(i,1), 0, NULL)) + continue; + else { + isNumber = false; + break; + } + } + //checking to see if it's a 4 digit pin + if(isNumber && CFStringGetLength(passcode) == 4){ + + pin = CFStringToCString(passcode); + if(in_word_set(pin, 4)){ + free(pin); + return true; + } + + CFIndex blacklistLength = (CFIndex)sizeof(blacklist)/sizeof(blacklist[0]); + + //not all the same number + if(pin[0] == pin[1] == pin[2] == pin[3]){ + free(pin); + return true; //weak password + } + //first two digits being the same and the last two digits being the same + if ( pin[0] == pin[1] && pin[2] == pin[3]){ + free(pin); + return true; //weak password + } + //first two digits not being the same as the last two digits + if(pin[0] == pin[2] && pin[1] == pin[3]){ + free(pin); + return true; //weak password + } + //not in this list + for(CFIndex i = 0; i < blacklistLength; i++) + { + const char* blackCode = blacklist[i]; + if(0 == strcmp(blackCode, pin)) + { + free(pin); + return true; //weak password + } + } + } + else if(isNumber){ //dealing with a numeric PIN + pin = CFStringToCString(passcode); + //check if PIN is all the same number + for(int i = 0; i < CFStringGetLength(passcode); i++){ + if(i+1 >= CFStringGetLength(passcode)){ + free(pin); + return true; + } + else if (pin[i] == pin[i+1]) + continue; + else + break; + } + //check if PIN is a bunch of incrementing numbers + for(int i = 0; i < CFStringGetLength(passcode); i++){ + if(i == CFStringGetLength(passcode)-1){ + free(pin); + return true; + } + else if ((pin[i] + 1) == pin[i+1]) + continue; + else + break; + } + //check if PIN is a bunch of decrementing numbers + for(int i = 0; i < CFStringGetLength(passcode); i++){ + if(i == CFStringGetLength(passcode)-1){ + free(pin); + return true; + } + else if ((pin[i]) == (pin[i+1] +1)) + continue; + else + break; + } + } + else{ // password is complex, evaluate entropy + int u = 0; + int l = 0; + int d = 0; + int p = 0; + int characterSet = 0; + + //calculate new entropy + for(CFIndex i = 0; i < CFStringGetLength(passcode); i++){ + + if( CFStringFindCharacterFromSet(passcode, uppercaseLetterCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + u++; + continue; + } + if( CFStringFindCharacterFromSet(passcode, lowercaseLetterCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + l++; + continue; + } + if( CFStringFindCharacterFromSet(passcode, decimalDigitCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + d++; + continue; + } + if( CFStringFindCharacterFromSet(passcode, punctuationCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + p++; + continue; + } + + } + if(u > 0){ + characterSet += alphabetSetSize; + } + if(l > 0){ + characterSet += alphabetSetSize; + } + if(d > 0){ + characterSet += decimalSetSize; + } + if(p > 0){ + characterSet += punctuationSetSize; + } + + double strength = CFStringGetLength(passcode)*log2(characterSet); + + if(strength < entropyStrengthThreshold){ + return true; //weak + } + else + return false; //strong + } + if(pin) + free(pin); + + return false; //strong password + +} + +static bool SecPasswordIsPasscodeIncrementingOrDecrementingDigits(CFStringRef passcode) +{ + char* pin = CFStringToCString(passcode); + + //check if PIN is a bunch of incrementing numbers + for(int i = 0; i < CFStringGetLength(passcode); i++){ + if(i == CFStringGetLength(passcode)-1){ + free(pin); + return true; + } + else if ((pin[i] + 1) == pin[i+1]) + continue; + else + break; + } + //check if PIN is a bunch of decrementing numbers + for(int i = 0; i < CFStringGetLength(passcode); i++){ + if(i == CFStringGetLength(passcode)-1){ + free(pin); + return true; + } + else if ((pin[i]) == (pin[i+1] +1)) + continue; + else + break; + } + free(pin); + return false; +} + +bool SecPasswordIsPasswordWeak2(bool isSimple, CFStringRef passcode) +{ + uppercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter); + lowercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter); + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + punctuationCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetPunctuation); + + + char* pin = NULL; + + //length checks + if( CFStringGetLength(passcode) < 4 ){ + return true; //weak password + } + + bool isPasscodeNumber = true; + //check to see if passcode is a number + for(CFIndex i = 0; i < CFStringGetLength(passcode); i++){ + if( CFStringFindCharacterFromSet(passcode, decimalDigitCharacterSet, CFRangeMake(i,1), 0, NULL)) + continue; + else { + isPasscodeNumber = false; + break; + } + } + + if(isSimple){ + //checking to see if it's a 4 digit pin + if(isPasscodeNumber && CFStringGetLength(passcode) == 4){ + + pin = CFStringToCString(passcode); + if(in_word_set(pin, 4)){ + free(pin); + return true; + } + + CFIndex blacklistLength = (CFIndex)sizeof(blacklist)/sizeof(blacklist[0]); + + //not all the same number + if(pin[0] == pin[1] == pin[2] == pin[3]){ + free(pin); + return true; //weak password + } + //first two digits being the same and the last two digits being the same + if ( pin[0] == pin[1] && pin[2] == pin[3]){ + free(pin); + return true; //weak password + } + //first two digits not being the same as the last two digits + if(pin[0] == pin[2] && pin[1] == pin[3]){ + free(pin); + return true; //weak password + } + //not in this list + for(CFIndex i = 0; i < blacklistLength; i++) + { + const char* blackCode = blacklist[i]; + if(0 == strcmp(blackCode, pin)) + { + free(pin); + return true; //weak password + } + } + } + else if(isPasscodeNumber && CFStringGetLength(passcode) == 6){ + pin = CFStringToCString(passcode); + + //not all the same number + for(int i = 0; i < CFStringGetLength(passcode); i++){ + if(i == CFStringGetLength(passcode)-1){ + free(pin); + return true; + } + else if ((pin[i]) == pin[i+1]) + continue; + else + break; + } + //passcode is incrementing ex 123456 or 654321 + if(SecPasswordIsPasscodeIncrementingOrDecrementingDigits(passcode)) { + free(pin); + return true; + } + } + else//should be a 4 or 6digit number + return false; + } + else if(isPasscodeNumber && !isSimple){ //dealing with a complex numeric passcode + pin = CFStringToCString(passcode); + //check if PIN is all the same number + for(int i = 0; i < CFStringGetLength(passcode); i++){ + if(i+1 >= CFStringGetLength(passcode)){ + free(pin); + return true; + } + else if (pin[i] == pin[i+1]) + continue; + else + break; + } + if(SecPasswordIsPasscodeIncrementingOrDecrementingDigits(passcode)) { + free(pin); + return true; + } + } + else{ // password is complex, evaluate entropy + int u = 0; + int l = 0; + int d = 0; + int p = 0; + int characterSet = 0; + + //calculate new entropy + for(CFIndex i = 0; i < CFStringGetLength(passcode); i++){ + + if( CFStringFindCharacterFromSet(passcode, uppercaseLetterCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + u++; + continue; + } + if( CFStringFindCharacterFromSet(passcode, lowercaseLetterCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + l++; + continue; + } + if( CFStringFindCharacterFromSet(passcode, decimalDigitCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + d++; + continue; + } + if( CFStringFindCharacterFromSet(passcode, punctuationCharacterSet, CFRangeMake(i,1), kCFCompareBackwards, NULL)){ + p++; + continue; + } + + } + if(u > 0){ + characterSet += alphabetSetSize; + } + if(l > 0){ + characterSet += alphabetSetSize; + } + if(d > 0){ + characterSet += decimalSetSize; + } + if(p > 0){ + characterSet += punctuationSetSize; + } + + double strength = CFStringGetLength(passcode)*log2(characterSet); + + if(strength < entropyStrengthThreshold){ + return true; //weak + } + else + return false; //strong + } + if(pin) + free(pin); + + return false; //strong password + +} + +static void getUniformRandomNumbers(uint8_t* buffer, size_t numberOfDesiredNumbers, uint8_t upperBound) +{ + + // The values returned by SecRandomCopyBytes are uniformly distributed in the range [0, 255]. If we try to map + // these values onto a smaller range using modulo we will introduce a bias towards lower numbers in situations + // where our smaller range doesn’t evenly divide in to [0, 255]. For example, with the desired range of [0, 54] + // the ranges 0..54, 55..109, 110..164, and 165..219 are uniformly distributed, but the range 220..255 modulo 55 + // is only distributed over [0, 35], giving significant bias to these lower values. So, we ignore random numbers + // that would introduce this bias. + uint8_t limitAvoidingModuloBias = UCHAR_MAX - (UCHAR_MAX % upperBound); + + for (size_t numberOfAcceptedNumbers = 0; numberOfAcceptedNumbers < numberOfDesiredNumbers; ) { + if (SecRandomCopyBytes(kSecRandomDefault, numberOfDesiredNumbers - numberOfAcceptedNumbers, buffer + numberOfAcceptedNumbers) == -1) + continue; + for (size_t i = numberOfAcceptedNumbers; i < numberOfDesiredNumbers; ++i) { + if (buffer[i] < limitAvoidingModuloBias) + buffer[numberOfAcceptedNumbers++] = buffer[i] % upperBound; + } + } +} + +static bool passwordContainsRequiredCharacters(CFStringRef password, CFArrayRef requiredCharacterSets) +{ + CFCharacterSetRef characterSet; + + for (CFIndex i = 0; i< CFArrayGetCount(requiredCharacterSets); i++) { + characterSet = CFArrayGetValueAtIndex(requiredCharacterSets, i); + CFRange rangeToSearch = CFRangeMake(0, CFStringGetLength(password)); + require_quiet(CFStringFindCharacterFromSet(password, characterSet, rangeToSearch, 0, NULL), fail); + } + return true; + +fail: + return false; + +} + +static bool passwordContainsLessThanNIdenticalCharacters(CFStringRef password, CFIndex identicalCount) +{ + unsigned char Char, nextChar; + int repeating = 0; + + for(CFIndex i = 0; i < CFStringGetLength(password); i++){ + Char = CFStringGetCharacterAtIndex(password, i); + for(CFIndex j = i; j< CFStringGetLength(password); j++){ + nextChar = CFStringGetCharacterAtIndex(password, j); + require_quiet(repeating <= identicalCount, fail); + if(Char == nextChar){ + repeating++; + }else{ + repeating = 0; + break; + } + } + } + return true; +fail: + return false; +} + +static bool passwordContainsAtLeastNCharacters(CFStringRef password, CFStringRef characters, CFIndex N) +{ + CFCharacterSetRef characterSet = NULL; + characterSet = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, characters); + CFIndex counter = 0; + + for(CFIndex i = 0; i < CFStringGetLength(password); i++){ + if(CFStringFindCharacterFromSet(password, characterSet, CFRangeMake(i, 1), 0, NULL)) + counter++; + } + CFReleaseNull(characterSet); + if(counter < N) + return false; + else + return true; +} + +static bool passwordContainsLessThanNCharacters(CFStringRef password, CFStringRef characters, CFIndex N) +{ + CFCharacterSetRef characterSet = NULL; + characterSet = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, characters); + CFIndex counter = 0; + + for(CFIndex i = 0; i < CFStringGetLength(password); i++){ + if(CFStringFindCharacterFromSet(password, characterSet, CFRangeMake(i, 1), 0, NULL)) + counter++; + } + CFReleaseNull(characterSet); + if(counter > N) + return false; + else + return true; +} + +static bool passwordDoesNotContainCharacters(CFStringRef password, CFStringRef prohibitedCharacters) +{ + CFCharacterSetRef characterSet = NULL; + characterSet = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, prohibitedCharacters); + CFRange rangeToSearch = CFRangeMake(0, CFStringGetLength(password)); + + require_quiet(!CFStringFindCharacterFromSet(password, characterSet, rangeToSearch, 0, NULL), fail); + CFReleaseNull(characterSet); + return true; +fail: + CFReleaseNull(characterSet); + return false; +} + +static void getPasswordRandomCharacters(CFStringRef *returned, CFDictionaryRef requirements, CFIndex *numberOfRandomCharacters, CFStringRef allowedCharacters) +{ + uint8_t randomNumbers[*numberOfRandomCharacters]; + unsigned char randomCharacters[*numberOfRandomCharacters]; + getUniformRandomNumbers(randomNumbers, *numberOfRandomCharacters, CFStringGetLength(allowedCharacters)); + + CFTypeRef prohibitedCharacters = NULL; + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordDisallowedCharacters, &prohibitedCharacters)) + prohibitedCharacters = NULL; + + //it's faster for long characters to check each character produced for these cases + for (CFIndex i = 0; i < *numberOfRandomCharacters; ++i){ + //check prohibited characters + UniChar randomChar[1]; + randomChar[0] = CFStringGetCharacterAtIndex(allowedCharacters, randomNumbers[i]); + if (prohibitedCharacters != NULL) + { + CFStringRef temp = CFStringCreateWithCharacters(kCFAllocatorDefault, randomChar, 1); + bool pwdncc = passwordDoesNotContainCharacters(temp, prohibitedCharacters); + CFReleaseSafe(temp); + if (!pwdncc) { + //change up the random numbers so we don't get the same index into allowed + getUniformRandomNumbers(randomNumbers, *numberOfRandomCharacters, CFStringGetLength(allowedCharacters)); + i--; + continue; + } + } + randomCharacters[i] = (unsigned char)randomChar[0]; + } + + *returned = CFStringCreateWithBytes(kCFAllocatorDefault, randomCharacters, *numberOfRandomCharacters, kCFStringEncodingUTF8, false); +} + +static bool doesPasswordEndWith(CFStringRef password, CFStringRef prohibitedCharacters) +{ + CFCharacterSetRef characterSet = NULL; + characterSet = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, prohibitedCharacters); + + CFRange rangeToSearch = CFRangeMake(CFStringGetLength(password) - CFStringGetLength(prohibitedCharacters), CFStringGetLength(prohibitedCharacters)); + require_quiet(0 == CFStringCompareWithOptions(password, prohibitedCharacters, rangeToSearch, 0), fail); + CFReleaseNull(characterSet); + return false; +fail: + CFReleaseNull(characterSet); + return true; +} + +static bool doesPasswordStartWith(CFStringRef password, CFStringRef prohibitedCharacters) +{ + CFCharacterSetRef characterSet = NULL; + characterSet = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, prohibitedCharacters); + + CFRange rangeToSearch = CFRangeMake(0, CFStringGetLength(prohibitedCharacters)); + require_quiet(0 == CFStringCompareWithOptions(password, prohibitedCharacters, rangeToSearch, 0), fail); + CFReleaseNull(characterSet); + return false; //does not start with prohibitedCharacters +fail: + CFReleaseNull(characterSet); + return true; +} + +static CFDictionaryRef passwordGenerateCreateDefaultParametersDictionary(SecPasswordType type, CFDictionaryRef requirements){ + + CFMutableArrayRef requiredCharacterSets = NULL; + CFNumberRef numReqChars = NULL; + CFStringRef defaultPasswordFormat = NULL; + requiredCharacterSets = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + defaultPasswordFormat = CFSTR("true"); + CFTypeRef groupSizeRef = NULL, numberOfGroupsRef = NULL; + CFIndex groupSize, numberOfGroups; + CFDictionaryRef returned = NULL; + + switch(type){ + case(kSecPasswordTypeiCloudRecovery): + numReqChars = CFNumberCreateWithCFIndex(kCFAllocatorDefault, defaultiCloudPasswordLength); + groupSize = 4; + numberOfGroups = 6; + groupSizeRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &groupSize); + numberOfGroupsRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &numberOfGroups); + + uppercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter); + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + CFArrayAppendValue(requiredCharacterSets, uppercaseLetterCharacterSet); + CFArrayAppendValue(requiredCharacterSets, decimalDigitCharacterSet); + returned = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecUseDefaultPasswordFormatKey, defaultPasswordFormat, + kSecNumberOfRequiredRandomCharactersKey, numReqChars, + kSecAllowedCharactersKey, defaultiCloudCharacters, + kSecRequiredCharacterSetsKey, requiredCharacterSets, + kSecPasswordGroupSize, groupSizeRef, + kSecPasswordNumberOfGroups, numberOfGroupsRef, + NULL); + break; + + case(kSecPasswordTypePIN): + numReqChars = CFNumberCreateWithCFIndex(kCFAllocatorDefault, defaultPINLength); + groupSize = 4; + numberOfGroups = 1; + groupSizeRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &groupSize); + numberOfGroupsRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &numberOfGroups); + + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + CFArrayAppendValue(requiredCharacterSets, decimalDigitCharacterSet); + returned = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecUseDefaultPasswordFormatKey, defaultPasswordFormat, + kSecNumberOfRequiredRandomCharactersKey, numReqChars, + kSecAllowedCharactersKey, defaultPINCharacters, + kSecRequiredCharacterSetsKey, requiredCharacterSets, + kSecPasswordGroupSize, groupSizeRef, + kSecPasswordNumberOfGroups, numberOfGroupsRef, + NULL); + break; + + case(kSecPasswordTypeWifi): + groupSize = 4; + numberOfGroups = 3; + groupSizeRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &groupSize); + numberOfGroupsRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &numberOfGroups); + + lowercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter); + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + + numReqChars = CFNumberCreateWithCFIndex(kCFAllocatorDefault, defaultWifiPasswordLength); + CFArrayAppendValue(requiredCharacterSets, lowercaseLetterCharacterSet); + CFArrayAppendValue(requiredCharacterSets, decimalDigitCharacterSet); + returned = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecUseDefaultPasswordFormatKey, defaultPasswordFormat, + kSecNumberOfRequiredRandomCharactersKey, numReqChars, + kSecAllowedCharactersKey, defaultWifiCharacters, + kSecRequiredCharacterSetsKey, requiredCharacterSets, + kSecPasswordGroupSize, groupSizeRef, + kSecPasswordNumberOfGroups, numberOfGroupsRef, + NULL); + break; + + default: + groupSize = 4; + numberOfGroups = 6; + groupSizeRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &groupSize); + numberOfGroupsRef = CFNumberCreate(NULL, kCFNumberCFIndexType, &numberOfGroups); + uppercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter); + lowercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter); + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + CFArrayAppendValue(requiredCharacterSets, uppercaseLetterCharacterSet); + CFArrayAppendValue(requiredCharacterSets, lowercaseLetterCharacterSet); + CFArrayAppendValue(requiredCharacterSets, decimalDigitCharacterSet); + + numReqChars = CFNumberCreateWithCFIndex(kCFAllocatorDefault, defaultNumberOfRandomCharacters); + returned = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecUseDefaultPasswordFormatKey, defaultPasswordFormat, + kSecNumberOfRequiredRandomCharactersKey, numReqChars, + kSecAllowedCharactersKey, defaultCharacters, + kSecRequiredCharacterSetsKey, requiredCharacterSets, + kSecPasswordGroupSize, groupSizeRef, + kSecPasswordNumberOfGroups, numberOfGroupsRef, + NULL); + + + + break; + } + + CFReleaseNull(numReqChars); + CFReleaseNull(requiredCharacterSets); + CFReleaseNull(groupSizeRef); + CFReleaseNull(numberOfGroupsRef); + return returned; +} +static CFDictionaryRef passwordGenerationCreateParametersDictionary(SecPasswordType type, CFDictionaryRef requirements) +{ + CFMutableArrayRef requiredCharacterSets = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + CFArrayRef requiredCharactersArray = NULL; + CFNumberRef numReqChars = NULL; + CFIndex numberOfRequiredRandomCharacters; + CFStringRef allowedCharacters = NULL, useDefaultPasswordFormat = NULL; + uint64_t valuePtr; + CFTypeRef prohibitedCharacters = NULL, endWith = NULL, startWith = NULL, + groupSizeRef = NULL, numberOfGroupsRef = NULL, separatorRef = NULL, + atMostCharactersRef = NULL,atLeastCharactersRef = NULL, identicalRef = NULL; + + CFNumberRef min = (CFNumberRef)CFDictionaryGetValue(requirements, kSecPasswordMinLengthKey); + CFNumberRef max = (CFNumberRef)CFDictionaryGetValue(requirements, kSecPasswordMaxLengthKey); + + CFNumberGetValue(min, kCFNumberSInt64Type, &valuePtr); + CFIndex minPasswordLength = (long)valuePtr; + CFNumberGetValue(max, kCFNumberSInt64Type, &valuePtr); + CFIndex maxPasswordLength = (long)valuePtr; + + // If requirements allow, we will generate the password in default format. + useDefaultPasswordFormat = CFSTR("true"); + numberOfRequiredRandomCharacters = defaultNumberOfRandomCharacters; + + if(type == kSecPasswordTypePIN) + { + if( maxPasswordLength && minPasswordLength ) + numberOfRequiredRandomCharacters = maxPasswordLength; + else if( !maxPasswordLength && minPasswordLength ) + numberOfRequiredRandomCharacters = minPasswordLength; + else if( !minPasswordLength && maxPasswordLength ) + numberOfRequiredRandomCharacters = maxPasswordLength; + else + numberOfRequiredRandomCharacters = defaultPINLength; + + allowedCharacters = CFSTR("0123456789"); + CFArrayAppendValue(requiredCharacterSets, decimalDigitCharacterSet); + requiredCharactersArray = CFArrayCreateCopy(NULL, requiredCharacterSets); + useDefaultPasswordFormat = CFSTR("false"); + } + else{ + if (minPasswordLength && minPasswordLength > defaultNumberOfRandomCharacters) { + useDefaultPasswordFormat = CFSTR("false"); + numberOfRequiredRandomCharacters = minPasswordLength; + } + if (maxPasswordLength && maxPasswordLength < defaultNumberOfRandomCharacters) { + useDefaultPasswordFormat = CFSTR("false"); + numberOfRequiredRandomCharacters = maxPasswordLength; + } + if (maxPasswordLength && minPasswordLength && maxPasswordLength == minPasswordLength && maxPasswordLength != defaultNumberOfRandomCharacters){ + useDefaultPasswordFormat = CFSTR("false"); + numberOfRequiredRandomCharacters = maxPasswordLength; + } + allowedCharacters = (CFStringRef)CFDictionaryGetValue(requirements, kSecPasswordAllowedCharactersKey); + requiredCharactersArray = (CFArrayRef)CFDictionaryGetValue(requirements, kSecPasswordRequiredCharactersKey); + } + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordDisallowedCharacters, &prohibitedCharacters)) + prohibitedCharacters = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordCantEndWithChars, &endWith)) + endWith = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordCantStartWithChars, &startWith)) + startWith = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordGroupSize, &groupSizeRef)) + groupSizeRef = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordNumberOfGroups, &numberOfGroupsRef)) + numberOfGroupsRef = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordSeparator, &separatorRef)) + separatorRef = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordContainsNoMoreThanNSpecificCharacters, &atMostCharactersRef)) + atMostCharactersRef = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordContainsAtLeastNSpecificCharacters, &atLeastCharactersRef)) + atLeastCharactersRef = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters, &identicalRef)) + identicalRef = NULL; + + if (allowedCharacters) { + if( false == CFStringFindWithOptions(allowedCharacters, CFSTR("-"), CFRangeMake(0, CFStringGetLength(allowedCharacters)), kCFCompareCaseInsensitive, NULL)) + useDefaultPasswordFormat = CFSTR("false"); + } else + allowedCharacters = defaultCharacters; + + // In default password format, we use dashes only as separators, not as symbols you can encounter at a random position. + if (useDefaultPasswordFormat == CFSTR("false")){ + CFMutableStringRef mutatedAllowedCharacters = CFStringCreateMutableCopy(kCFAllocatorDefault, CFStringGetLength(allowedCharacters), allowedCharacters); + CFStringFindAndReplace (mutatedAllowedCharacters, CFSTR("-"), CFSTR(""), CFRangeMake(0, CFStringGetLength(allowedCharacters)),kCFCompareCaseInsensitive); + allowedCharacters = CFStringCreateCopy(kCFAllocatorDefault, mutatedAllowedCharacters); + } + + if (requiredCharactersArray) { + for (CFIndex i = 0; i < CFArrayGetCount(requiredCharactersArray); i++){ + CFCharacterSetRef stringWithRequiredCharacters = CFArrayGetValueAtIndex(requiredCharactersArray, i); + if(stringWithRequiredCharacters && CFStringFindCharacterFromSet(allowedCharacters, stringWithRequiredCharacters, CFRangeMake(0, CFStringGetLength(allowedCharacters)), 0, NULL)){ + CFArrayAppendValue(requiredCharacterSets, stringWithRequiredCharacters); + } + } + } else{ + uppercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter); + lowercaseLetterCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter); + decimalDigitCharacterSet = CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit); + CFArrayAppendValue(requiredCharacterSets, uppercaseLetterCharacterSet); + CFArrayAppendValue(requiredCharacterSets, lowercaseLetterCharacterSet); + CFArrayAppendValue(requiredCharacterSets, decimalDigitCharacterSet); + } + + + if (CFArrayGetCount(requiredCharacterSets) > numberOfRequiredRandomCharacters) { + CFReleaseNull(requiredCharacterSets); + requiredCharacterSets = NULL; + } + //create new CFDictionary + numReqChars = CFNumberCreateWithCFIndex(kCFAllocatorDefault, numberOfRequiredRandomCharacters); + CFMutableDictionaryRef updatedConstraints = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); + CFDictionaryAddValue(updatedConstraints, kSecUseDefaultPasswordFormatKey, useDefaultPasswordFormat); + CFDictionarySetValue(updatedConstraints, kSecNumberOfRequiredRandomCharactersKey, numReqChars); + CFDictionaryAddValue(updatedConstraints, kSecAllowedCharactersKey, allowedCharacters); + if(requiredCharacterSets) + CFDictionaryAddValue(updatedConstraints, kSecRequiredCharacterSetsKey, requiredCharacterSets); + + //add the prohibited characters string if it exists to the new dictionary + if(prohibitedCharacters) + CFDictionaryAddValue(updatedConstraints, kSecPasswordDisallowedCharacters, prohibitedCharacters); + + //add the characters the password can't end with if it exists to the new dictionary + if(endWith) + CFDictionaryAddValue(updatedConstraints, kSecPasswordCantEndWithChars, endWith); + + //add the characters the password can't start with if it exists to the new dictionary + if(startWith) + CFDictionaryAddValue(updatedConstraints, kSecPasswordCantStartWithChars, startWith); + + if(groupSizeRef) + CFDictionaryAddValue(updatedConstraints, kSecPasswordGroupSize, groupSizeRef); + + if(numberOfGroupsRef) + CFDictionaryAddValue(updatedConstraints, kSecPasswordNumberOfGroups, numberOfGroupsRef); + + if(separatorRef) + CFDictionaryAddValue(updatedConstraints, kSecPasswordSeparator, separatorRef); + + if(atMostCharactersRef) + CFDictionaryAddValue(updatedConstraints, kSecPasswordContainsNoMoreThanNSpecificCharacters, atMostCharactersRef); + + if(atLeastCharactersRef) + CFDictionaryAddValue(updatedConstraints, kSecPasswordContainsAtLeastNSpecificCharacters, atLeastCharactersRef); + + if(identicalRef) + CFDictionaryAddValue(updatedConstraints, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters, identicalRef); + + CFReleaseNull(useDefaultPasswordFormat); + CFReleaseNull(numReqChars); + CFReleaseNull(allowedCharacters); + CFReleaseNull(requiredCharacterSets); + + return CFDictionaryCreateCopy(kCFAllocatorDefault, updatedConstraints); +} + +static bool isDictionaryFormattedProperly(SecPasswordType type, CFDictionaryRef passwordRequirements, CFErrorRef *error){ + + CFTypeRef defaults = NULL; + CFErrorRef tempError = NULL; + if(passwordRequirements == NULL){ + return true; + } + + if( CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordDefaultForType, &defaults) ){ + if(isString(defaults) == true && 0 == CFStringCompare(defaults, CFSTR("true"), 0)){ + return true; + } + } + //only need to check max and min pin length formatting + if(type == kSecPasswordTypePIN){ + CFTypeRef minTest = NULL, maxTest = NULL; + uint64_t valuePtr; + CFIndex minPasswordLength = 0, maxPasswordLength= 0; + + if( CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordDefaultForType, &defaults) ){ + if(isString(defaults) == true && 0 == CFStringCompare(defaults, CFSTR("true"), 0)){ + return true; + } + } + //check if the values exist! + if( CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordMaxLengthKey, &maxTest) ){ + require_action_quiet(isNull(maxTest)!= true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("To generate a password, need a max length"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(maxTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's max length must be a CFNumberRef"), (CFIndex)errSecBadReq, NULL)); + + } + if (CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordMinLengthKey, &minTest) ){ + require_action_quiet(isNull(minTest)!= true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("To generate a password, need a min length"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(minTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's min length must be a CFNumberRef"), (CFIndex)errSecBadReq, NULL)); + } + //check if the values exist! + if(maxTest){ + CFNumberRef max = (CFNumberRef)maxTest; + CFNumberGetValue(max, kCFNumberSInt64Type, &valuePtr); + maxPasswordLength = (long)valuePtr; + } + if(minTest){ + CFNumberRef min = (CFNumberRef)minTest; + CFNumberGetValue(min, kCFNumberSInt64Type, &valuePtr); + minPasswordLength = (long)valuePtr; + } + //make sure min and max make sense respective to each other and that they aren't less than 4 digits. + require_action_quiet(minPasswordLength && maxPasswordLength && minPasswordLength <= maxPasswordLength, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's length parameters make no sense ( is max < min ?)"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet((minPasswordLength && minPasswordLength >= 4) || (maxPasswordLength && maxPasswordLength >= 4), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's length parameters make no sense ( is max < min ?)"), (CFIndex)errSecBadReq, NULL)); + } + else{ + CFTypeRef allowedTest, maxTest, minTest, requiredTest, prohibitedCharacters, endWith, startWith, + groupSizeRef, numberOfGroupsRef, separatorRef, atMostCharactersRef, + atLeastCharactersRef, thresholdRef, identicalRef, characters; + uint64_t valuePtr; + + //check if the values exist! + require_action_quiet(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordAllowedCharactersKey, &allowedTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Need a string of characters; password must only contain characters in this string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet( CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordMaxLengthKey, &maxTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("To generate a password, need a max length"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet( CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordMinLengthKey, &minTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("To generate a password, need a min length"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordRequiredCharactersKey, &requiredTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Need an array of character sets, password must have at least 1 character from each set"), (CFIndex)errSecBadReq, NULL)); + + //check if values are null? + require_action_quiet(isNull(allowedTest) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Need a string of characters; password must only contain characters in this string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNull(maxTest)!= true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("To generate a password, need a max length"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNull(minTest)!= true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("To generate a password, need a min length"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNull(requiredTest)!= true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Need an array of character sets, password must have at least 1 character from each set"), (CFIndex)errSecBadReq, NULL)); + + //check if the values are correct + require_action_quiet(isString(allowedTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's allowed characters must be a CFStringRef"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(maxTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's max length must be a CFNumberRef"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(minTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's min length must be a CFNumberRef"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isArray(requiredTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's required characters must be an array of CFCharacterSetRefs"), (CFIndex)errSecBadReq, NULL)); + + CFNumberGetValue(minTest, kCFNumberSInt64Type, &valuePtr); + CFIndex minPasswordLength = (long)valuePtr; + CFNumberGetValue(maxTest, kCFNumberSInt64Type, &valuePtr); + CFIndex maxPasswordLength = (long)valuePtr; + + require_action_quiet(minPasswordLength <= maxPasswordLength, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The password's length parameters make no sense ( is max < min ?)"), (CFIndex)errSecBadReq, NULL)); + + require_action_quiet(CFStringGetLength((CFStringRef)allowedTest) != 0, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Need a string of characters; password must only contain characters in this string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(CFArrayGetCount((CFArrayRef)requiredTest), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Need an array of character sets, password must have at least 1 character from each set"), (CFIndex)errSecBadReq, NULL)); + + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordDisallowedCharacters, &prohibitedCharacters)){ + require_action_quiet(isNull(prohibitedCharacters) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Disallowed Characters dictionary parameter is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isString(prohibitedCharacters), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("Disallowed Characters dictionary parameter is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + } + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordCantEndWithChars, &endWith)){ + require_action_quiet(isNull(endWith) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'EndWith' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isString(endWith), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'EndWith' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + } + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordCantStartWithChars, &startWith)){ + require_action_quiet(isNull(startWith) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'StartWith' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isString(startWith), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'StartWith' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + } + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordGroupSize, &groupSizeRef)){ + require_action_quiet(isNull(groupSizeRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'groupsize' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(groupSizeRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'groupsize' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + } + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordNumberOfGroups, &numberOfGroupsRef)){ + require_action_quiet(isNull(numberOfGroupsRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'number of groupds' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(numberOfGroupsRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'number of groupds' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + } + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordSeparator, &separatorRef)){ + require_action_quiet(isNull(separatorRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'password separator character' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isString(separatorRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'password separator character' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + } + + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordContainsNoMoreThanNSpecificCharacters, &atMostCharactersRef)){ + require_action_quiet(isNull(atMostCharactersRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Most N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isDictionary(atMostCharactersRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Most N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + + require_action_quiet(CFDictionaryGetValueIfPresent(atMostCharactersRef, kSecPasswordCharacterCount, &thresholdRef) != false, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Most N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNull(thresholdRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(thresholdRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + + require_action_quiet(CFDictionaryGetValueIfPresent(atMostCharactersRef, kSecPasswordCharacters, &characters)!= false, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Most N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNull(characters) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isString(characters), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + } + + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordContainsAtLeastNSpecificCharacters, &atLeastCharactersRef)){ + require_action_quiet(isNull(atLeastCharactersRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Least N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isDictionary(atLeastCharactersRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Least N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + + require_action_quiet(CFDictionaryGetValueIfPresent(atLeastCharactersRef, kSecPasswordCharacterCount, &thresholdRef) != false, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Least N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + + require_action_quiet(isNull(thresholdRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(thresholdRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + + require_action_quiet(CFDictionaryGetValueIfPresent(atLeastCharactersRef, kSecPasswordCharacters, &characters) != false, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'At Least N Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNull(characters) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isString(characters), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex)errSecBadReq, NULL)); + } + + if(CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters, &identicalRef)){ + require_action_quiet(isNull(identicalRef) != true, fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'Identical Consecutive Characters' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + require_action_quiet(isNumber(identicalRef), fail, tempError = CFErrorCreate(kCFAllocatorDefault, CFSTR("The dictionary parameter 'Identical Consecutive Characters' is either null or not a number"), (CFIndex)errSecBadReq, NULL)); + } + } + +fail: + { + bool result = true; + if (tempError != NULL) { + if (error) + *error = CFRetainSafe(tempError); + result = false; + } + + CFReleaseNull(tempError); + return result; +} +} + +static bool doesFinalPasswordPass(bool isSimple, CFStringRef password, CFDictionaryRef requirements){ + + CFTypeRef characters, identicalRef = NULL, NRef = NULL, endWith= NULL, startWith= NULL, atLeastCharacters= NULL, atMostCharacters = NULL; + uint64_t valuePtr; + CFIndex N, identicalCount; + CFArrayRef requiredCharacterSet = (CFArrayRef)CFDictionaryGetValue(requirements, kSecRequiredCharacterSetsKey); + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordCantEndWithChars, &endWith)) + endWith = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordCantStartWithChars, &startWith)) + startWith = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordContainsAtLeastNSpecificCharacters, &atLeastCharacters)) + atLeastCharacters = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordContainsNoMoreThanNSpecificCharacters, &atMostCharacters)) + atMostCharacters = NULL; + + if(!CFDictionaryGetValueIfPresent(requirements, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters, &identicalRef)) + identicalRef = NULL; + else{ + CFNumberGetValue((CFNumberRef)identicalRef, kCFNumberSInt64Type, &valuePtr); + identicalCount = (long)valuePtr; + } + if(endWith != NULL) + { + if(!doesPasswordEndWith(password, endWith)) + return false; + } + if(startWith != NULL){ + if(!doesPasswordStartWith(password, startWith)) + return false; + } + if(atLeastCharacters != NULL){ + NRef = CFDictionaryGetValue(atLeastCharacters, kSecPasswordCharacterCount); + characters = CFDictionaryGetValue(atLeastCharacters, kSecPasswordCharacters); + CFNumberGetValue((CFNumberRef)NRef, kCFNumberSInt64Type, &valuePtr); + N = (long)valuePtr; + if(!passwordContainsAtLeastNCharacters(password, characters, N)) + return false; + } + if(atMostCharacters != NULL){ + NRef = CFDictionaryGetValue(atMostCharacters, kSecPasswordCharacterCount); + characters = CFDictionaryGetValue(atMostCharacters, kSecPasswordCharacters); + CFNumberGetValue((CFNumberRef)NRef, kCFNumberSInt64Type, &valuePtr); + N = (long)valuePtr; + if(!passwordContainsLessThanNCharacters(password, characters, N)) + return false; + } + if(identicalRef != NULL){ + if(!passwordContainsLessThanNIdenticalCharacters(password, identicalCount)) + return false; + } + if (!passwordContainsRequiredCharacters(password, requiredCharacterSet)) + return false; + + if(true == SecPasswordIsPasswordWeak2(isSimple, password)) + return false; + + return true; +} + +//entry point into password generation +CF_RETURNS_RETAINED CFStringRef SecPasswordGenerate(SecPasswordType type, CFErrorRef *error, CFDictionaryRef passwordRequirements){ + bool check = false, isSimple = false; + CFTypeRef separator = NULL, defaults = NULL, groupSizeRef = NULL, numberOfGroupsRef = NULL; + CFDictionaryRef properlyFormattedRequirements = NULL; + CFErrorRef localError = NULL; + uint64_t valuePtr, groupSize, numberOfGroups; + CFNumberRef numberOfRequiredRandomCharacters; + CFIndex requiredCharactersSize; + CFStringRef randomCharacters = NULL, password = NULL, allowedChars = NULL; + CFMutableStringRef finalPassword = NULL; + + if(type == kSecPasswordTypePIN) + isSimple = true; + else + isSimple = false; + check = isDictionaryFormattedProperly(type, passwordRequirements, &localError); + require_quiet(check != false, fail); + + //should we generate defaults? + if(passwordRequirements == NULL || (CFDictionaryGetValueIfPresent(passwordRequirements, kSecPasswordDefaultForType, &defaults) && isString(defaults) == true && 0 == CFStringCompare(defaults, CFSTR("true"), 0) )) + properlyFormattedRequirements = passwordGenerateCreateDefaultParametersDictionary(type, passwordRequirements); + else + properlyFormattedRequirements = passwordGenerationCreateParametersDictionary(type, passwordRequirements); + + require_quiet(localError == NULL && properlyFormattedRequirements != NULL, fail); + + numberOfRequiredRandomCharacters = (CFNumberRef)CFDictionaryGetValue(properlyFormattedRequirements, kSecNumberOfRequiredRandomCharactersKey); + CFNumberGetValue(numberOfRequiredRandomCharacters, kCFNumberSInt64Type, &valuePtr); + requiredCharactersSize = (long)valuePtr; + + if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements, kSecPasswordGroupSize, &groupSizeRef)){ + groupSizeRef = NULL; + } + else + CFNumberGetValue((CFNumberRef)groupSizeRef, kCFNumberSInt64Type, &groupSize); + + if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements, kSecPasswordNumberOfGroups, &numberOfGroupsRef)){ + numberOfGroupsRef = NULL; + } + else + CFNumberGetValue((CFNumberRef)numberOfGroupsRef, kCFNumberSInt64Type, &numberOfGroups); + + while (true) { + allowedChars = CFDictionaryGetValue(properlyFormattedRequirements, kSecAllowedCharactersKey); + getPasswordRandomCharacters(&randomCharacters, properlyFormattedRequirements, &requiredCharactersSize, allowedChars); + + if(numberOfGroupsRef && groupSizeRef){ + finalPassword = CFStringCreateMutable(kCFAllocatorDefault, 0); + + if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements, kSecPasswordSeparator, &separator)) + separator = NULL; + + if(separator == NULL) + separator = CFSTR("-"); + + CFIndex i = 0; + while( i != requiredCharactersSize){ + if((i + (CFIndex)groupSize) < requiredCharactersSize){ + CFStringAppend(finalPassword, CFStringCreateWithSubstring(kCFAllocatorDefault, randomCharacters, CFRangeMake(i, (CFIndex)groupSize))); + CFStringAppend(finalPassword, separator); + i+=groupSize; + } + else if((i+(CFIndex)groupSize) == requiredCharactersSize){ + CFStringAppend(finalPassword, CFStringCreateWithSubstring(kCFAllocatorDefault, randomCharacters, CFRangeMake(i, (CFIndex)groupSize))); + i+=groupSize; + } + else { + CFStringAppend(finalPassword, CFStringCreateWithSubstring(kCFAllocatorDefault, randomCharacters, CFRangeMake(i, requiredCharactersSize - i))); + i+=(requiredCharactersSize - i); + } + } + password = CFStringCreateCopy(kCFAllocatorDefault, finalPassword); + CFReleaseNull(finalPassword); + } + //no fancy formatting + else { + password = CFStringCreateCopy(kCFAllocatorDefault, randomCharacters); + } + + CFReleaseNull(randomCharacters); + require_quiet(doesFinalPasswordPass(isSimple, password, properlyFormattedRequirements), no_pass); + CFReleaseNull(properlyFormattedRequirements); + return password; + + no_pass: + CFReleaseNull(password); + } + +fail: + if (error && localError) { + *error = localError; + localError = NULL; + } + + CFReleaseSafe(localError); + CFReleaseNull(properlyFormattedRequirements); + return NULL; +} + +const char *in_word_set (const char *str, unsigned int len){ + static const char * wordlist[] = + { + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "0103", "", "", "", "", "0123", "", "", "", "", "0303", "", "", "", + "", "", "", "", "0110", "", "1103", "", "", "", "", "1123", "", "", "0000", + "", "1203", "", "0404", "", "", "", "", "1234", "1110", "2015", "2013", "", + "2014", "1010", "2005", "2003", "", "2004", "1210", "0505", "0111", "", "", + "", "2008", "0101", "", "2007", "", "", "", "", "2006", "2010", "1995", "1993", + "", "1994", "2000", "", "1111", "", "", "", "1998", "1101", "", "1997", "", + "0808", "1211", "", "1996", "0102", "", "1201", "", "", "1990", "", "", "", + "", "0202", "", "2011", "", "", "1112", "1958", "2001", "", "1957", "1102", + "", "3333", "", "1956", "1212", "1985", "1983", "", "1984", "1202", "", "0909", + "", "0606", "", "1988", "1991", "", "1987", "2012", "", "", "", "1986", "2002", + "", "", "", "0707", "1980", "", "2009", "", "", "2222", "1965", "1963", "", + "1964", "", "", "2229", "", "", "1992", "1968", "", "", "1967", "", "", "1999", + "", "1966", "", "1975", "1973", "", "1974", "1960", "", "1981", "", "4444", + "", "1978", "", "7465", "1977", "", "", "", "", "1976", "2580", "", "1959", + "", "", "1970", "", "", "", "", "", "", "", "", "", "1982", "", "1961", "", + "", "5252", "", "1989", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "1971", "", "", "", "", "", "", "", "1962", "", "5683", "", "6666", "", + "", "1969", "", "", "", "", "", "", "", "", "", "", "", "", "1972", "", "", + "", "", "", "", "1979", "", "", "", "7667" + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register int key = pinhash (str, len); + + if (key <= MAX_HASH_VALUE && key >= 0) + { + register const char *s = wordlist[key]; + if (*str == *s && !strcmp (str + 1, s + 1)) + return s; + } + } + return 0; +} +CFDictionaryRef SecPasswordCopyDefaultPasswordLength(SecPasswordType type, CFErrorRef *error){ + + CFIndex tupleLengthInt = 0, numOfTuplesInt = 0; + CFNumberRef tupleLength = NULL; + CFNumberRef numOfTuples = NULL; + + CFMutableDictionaryRef passwordLengthDefaults = NULL; + CFDictionaryRef result = NULL; + + switch(type){ + case(kSecPasswordTypeiCloudRecovery): + tupleLengthInt = 4; + numOfTuplesInt = 6; + break; + + case(kSecPasswordTypePIN): + tupleLengthInt = 4; + numOfTuplesInt = 1; + break; + + case(kSecPasswordTypeSafari): + tupleLengthInt = 4; + numOfTuplesInt = 5; + break; + + case(kSecPasswordTypeWifi): + tupleLengthInt = 4; + numOfTuplesInt = 3; + break; + + default: + if(SecError(errSecBadReq, error, CFSTR("Password type does not exist.")) == false) + { + secdebug("secpasswordcopydefaultpasswordlength", "could not create error!"); + } + } + + if (tupleLengthInt != 0 && numOfTuplesInt != 0) { + tupleLength = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &tupleLengthInt); + numOfTuples = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &numOfTuplesInt); + passwordLengthDefaults = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); + CFDictionaryAddValue(passwordLengthDefaults, kSecPasswordGroupSize, tupleLength); + CFDictionaryAddValue(passwordLengthDefaults, kSecPasswordNumberOfGroups, numOfTuples); + result = CFDictionaryCreateCopy(kCFAllocatorDefault, passwordLengthDefaults); +} + + CFReleaseSafe(tupleLength); + CFReleaseSafe(numOfTuples); + CFReleaseSafe(passwordLengthDefaults); + return result; +}