2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * SecPasswordStrength.c
29 #include <CoreFoundation/CoreFoundation.h>
30 #include <Security/SecItem.h>
31 #include <Security/SecBase.h>
32 #include <Security/SecRandom.h>
33 #include "SecPasswordGenerate.h"
34 #include <AssertMacros.h>
37 #include <utilities/SecCFWrappers.h>
38 #include <utilities/SecCFRelease.h>
39 #include <utilities/SecCFError.h>
40 #include <corecrypto/ccdigest.h>
41 #include <corecrypto/ccsha2.h>
42 #include "SecCFAllocator.h"
45 // Keys for external dictionaries with password generation requirements we read from plist.
46 CFStringRef kSecPasswordMinLengthKey
= CFSTR("PasswordMinLength");
47 CFStringRef kSecPasswordMaxLengthKey
= CFSTR("PasswordMaxLength");
48 CFStringRef kSecPasswordAllowedCharactersKey
= CFSTR("PasswordAllowedCharacters");
49 CFStringRef kSecPasswordRequiredCharactersKey
= CFSTR("PasswordRequiredCharacters");
50 CFStringRef kSecPasswordDefaultForType
= CFSTR("PasswordDefaultForType");
52 CFStringRef kSecPasswordDisallowedCharacters
= CFSTR("PasswordDisallowedCharacters");
53 CFStringRef kSecPasswordCantStartWithChars
= CFSTR("PasswordCantStartWithChars");
54 CFStringRef kSecPasswordCantEndWithChars
= CFSTR("PasswordCantEndWithChars");
55 CFStringRef kSecPasswordContainsNoMoreThanNSpecificCharacters
= CFSTR("PasswordContainsNoMoreThanNSpecificCharacters");
56 CFStringRef kSecPasswordContainsAtLeastNSpecificCharacters
= CFSTR("PasswordContainsAtLeastNSpecificCharacters");
57 CFStringRef kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
= CFSTR("PasswordContainsNoMoreThanNConsecutiveIdenticalCharacters");
58 CFStringRef kSecPasswordCharacterCount
= CFSTR("PasswordCharacterCount");
59 CFStringRef kSecPasswordCharacters
= CFSTR("PasswordCharacters");
61 CFStringRef kSecPasswordGroupSize
= CFSTR("PasswordGroupSize");
62 CFStringRef kSecPasswordNumberOfGroups
= CFSTR("PasswordNumberOfGroups");
63 CFStringRef kSecPasswordSeparator
= CFSTR("SecPasswordSeparator");
65 // Keys for internally used dictionaries with password generation parameters (never exposed to external API).
66 static CFStringRef kSecUseDefaultPasswordFormatKey
= CFSTR("UseDefaultPasswordFormat");
67 static CFStringRef kSecNumberOfRequiredRandomCharactersKey
= CFSTR("NumberOfRequiredRandomCharacters");
68 static CFStringRef kSecNumberOfChecksumCharactersKey
= CFSTR("NumberOfChecksumCharacters");
69 static CFStringRef kSecAllowedCharactersKey
= CFSTR("AllowedCharacters");
70 static CFStringRef kSecRequiredCharacterSetsKey
= CFSTR("RequiredCharacterSets");
72 static CFIndex defaultNumberOfRandomCharacters
= 20;
73 static CFIndex defaultPINLength
= 4;
74 static CFIndex defaultiCloudPasswordLength
= 24;
75 static CFIndex defaultWifiPasswordLength
= 12;
77 static CFStringRef defaultWifiCharacters
= CFSTR("abcdefghijklmnopqrstuvwxyz1234567890");
78 static CFStringRef defaultPINCharacters
= CFSTR("0123456789");
79 static CFStringRef defaultiCloudCharacters
= CFSTR("ABCDEFGHJKLMNPQRSTUVWXYZ23456789");
80 static CFStringRef defaultCharacters
= CFSTR("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789");
82 static CFCharacterSetRef uppercaseLetterCharacterSet
;
83 static CFCharacterSetRef lowercaseLetterCharacterSet
;
84 static CFCharacterSetRef decimalDigitCharacterSet
;
85 static CFCharacterSetRef punctuationCharacterSet
;
87 static CFIndex alphabetSetSize
= 26;
88 static CFIndex decimalSetSize
= 10;
89 static CFIndex punctuationSetSize
= 33;
90 static double entropyStrengthThreshold
= 35.0;
93 generated with ruby badpins.rb | gperf
94 See this for PIN list:
95 A birthday present every eleven wallets? The security of customer-chosen banking PINs (2012), by Joseph Bonneau , Sören Preibusch , Ross Anderson
97 const char *in_word_set (const char *str
, unsigned int len
);
99 #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
100 && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
101 && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
102 && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
103 && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
104 && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
105 && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
106 && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
107 && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
108 && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
109 && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
110 && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
111 && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
112 && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
113 && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
114 && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
115 && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
116 && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
117 && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
118 && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
119 && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
120 && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
121 && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
122 /* The character set is not based on ISO-646. */
123 error
"gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
127 #define TOTAL_KEYWORDS 100
128 #define MIN_WORD_LENGTH 4
129 #define MAX_WORD_LENGTH 4
130 #define MIN_HASH_VALUE 21
131 #define MAX_HASH_VALUE 275
132 /* maximum key range = 255, duplicates = 0 */
141 static unsigned int pinhash (const char *str
, unsigned int len
)
143 static unsigned short asso_values
[] =
145 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
146 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
147 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
148 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
149 276, 276, 276, 276, 276, 276, 276, 276, 5, 0,
150 10, 10, 30, 50, 100, 120, 70, 25, 57, 85,
151 2, 4, 1, 19, 14, 11, 92, 276, 276, 276,
152 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
153 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
154 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
155 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
156 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
157 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
158 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
159 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
160 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
161 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
162 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
163 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
164 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
165 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
166 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
167 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
168 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
169 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
170 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
171 276, 276, 276, 276, 276
173 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];
176 ///<://problem/29089896> warn against using these common PINs
177 static bool isTopTenSixDigitPasscode(CFStringRef passcode
){
179 CFMutableArrayRef topTen
= CFArrayCreateMutableForCFTypesWith(kCFAllocatorDefault
,
191 CFSTR("789456"), NULL
);
193 for(CFIndex i
= 0; i
< CFArrayGetCount(topTen
); i
++){
194 if(CFEqualSafe(passcode
, CFArrayGetValueAtIndex(topTen
, i
))){
199 CFReleaseNull(topTen
);
203 CFStringRef
SecPasswordCreateWithRandomDigits(int n
, CFErrorRef
*error
){
207 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
208 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
209 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
210 punctuationCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetPunctuation
);
212 CFNumberRef minRef
= CFNumberCreate(NULL
, kCFNumberIntType
, &min
);
213 CFNumberRef maxRef
= CFNumberCreate(NULL
, kCFNumberIntType
, &max
);
215 CFMutableDictionaryRef passwordRequirements
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
216 CFDictionaryAddValue(passwordRequirements
, kSecPasswordMinLengthKey
, minRef
);
217 CFDictionaryAddValue(passwordRequirements
, kSecPasswordMaxLengthKey
, maxRef
);
218 CFStringRef allowedCharacters
= CFSTR("0123456789");
220 CFDictionaryAddValue(passwordRequirements
, kSecPasswordAllowedCharactersKey
, allowedCharacters
);
222 CFStringRef password
= SecPasswordGenerate(kSecPasswordTypePIN
, error
, passwordRequirements
);
224 CFReleaseNull(minRef
);
225 CFReleaseNull(maxRef
);
226 CFReleaseNull(passwordRequirements
);
233 //pins that reached the top 20 list
234 static const char *blacklist
[] = {"1234", "1004", "2000", "1122", "4321", "2001", "2580"};
235 bool SecPasswordIsPasswordWeak(CFStringRef passcode
)
237 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
238 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
239 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
240 punctuationCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetPunctuation
);
242 bool isNumber
= true;
246 if( CFStringGetLength(passcode
) < 4 ){
247 return true; //weak password
249 //check to see if passcode is a number
250 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
251 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), 0, NULL
))
258 //checking to see if it's a 4 digit pin
259 if(isNumber
&& CFStringGetLength(passcode
) == 4){
261 pin
= CFStringToCString(passcode
);
262 if(in_word_set(pin
, 4)){
267 CFIndex blacklistLength
= (CFIndex
)sizeof(blacklist
)/sizeof(blacklist
[0]);
269 //not all the same number
270 if(pin
[0] == pin
[1] == pin
[2] == pin
[3]){
272 return true; //weak password
274 //first two digits being the same and the last two digits being the same
275 if ( pin
[0] == pin
[1] && pin
[2] == pin
[3]){
277 return true; //weak password
279 //first two digits not being the same as the last two digits
280 if(pin
[0] == pin
[2] && pin
[1] == pin
[3]){
282 return true; //weak password
284 //check if PIN is a bunch of incrementing numbers
285 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
286 if(i
== CFStringGetLength(passcode
)-1){
290 else if ((pin
[i
] + 1) == pin
[i
+1])
295 //check if PIN is a bunch of decrementing numbers
296 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
297 if(i
== CFStringGetLength(passcode
)-1){
301 else if ((pin
[i
]) == (pin
[i
+1] +1))
303 else if ((i
== 0) && (pin
[i
] == '0') && (pin
[i
+1] == '9'))
310 for(CFIndex i
= 0; i
< blacklistLength
; i
++)
312 const char* blackCode
= blacklist
[i
];
313 if(0 == strcmp(blackCode
, pin
))
316 return true; //weak password
320 else if(isNumber
){ //dealing with a numeric PIN
321 pin
= CFStringToCString(passcode
);
322 //check if PIN is all the same number
323 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
324 if(i
+1 >= CFStringGetLength(passcode
)){
328 else if (pin
[i
] == pin
[i
+1])
333 //check if PIN is a bunch of incrementing numbers
334 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
335 if(i
== CFStringGetLength(passcode
)-1){
339 else if ((pin
[i
] + 1) == pin
[i
+1])
344 //check if PIN is a bunch of decrementing numbers
345 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
346 if(i
== CFStringGetLength(passcode
)-1){
350 else if ((pin
[i
]) == (pin
[i
+1] +1))
352 else if ((i
== 0) && (pin
[i
] == '0') && (pin
[i
+1] == '9'))
358 else{ // password is complex, evaluate entropy
363 int characterSet
= 0;
365 //calculate new entropy
366 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
368 if( CFStringFindCharacterFromSet(passcode
, uppercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
372 if( CFStringFindCharacterFromSet(passcode
, lowercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
376 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
380 if( CFStringFindCharacterFromSet(passcode
, punctuationCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
387 characterSet
+= alphabetSetSize
;
390 characterSet
+= alphabetSetSize
;
393 characterSet
+= decimalSetSize
;
396 characterSet
+= punctuationSetSize
;
399 double strength
= CFStringGetLength(passcode
)*log2(characterSet
);
401 if(strength
< entropyStrengthThreshold
){
405 return false; //strong
410 return false; //strong password
414 static bool SecPasswordIsPasscodeIncrementingOrDecrementingDigits(CFStringRef passcode
)
416 char* pin
= CFStringToCString(passcode
);
418 //check if PIN is a bunch of incrementing numbers
419 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
420 if(i
== CFStringGetLength(passcode
)-1){
424 else if ((pin
[i
] + 1) == pin
[i
+1])
429 //check if PIN is a bunch of decrementing numbers
430 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
431 if(i
== CFStringGetLength(passcode
)-1){
435 else if ((pin
[i
]) == (pin
[i
+1] +1))
437 else if ((i
== 0) && (pin
[i
] == '0') && (pin
[i
+1] == '9'))
446 static bool SecPasswordIsPasswordRepeatingTwoNumbers(CFStringRef passcode
){
447 char* pin
= CFStringToCString(passcode
);
449 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++)
451 if(i
+2 == CFStringGetLength(passcode
)-1){
455 else if(pin
[i
] == pin
[i
+2])
465 static int SecPasswordNumberOfRepeatedDigits(CFStringRef passcode
){
467 CFIndex length
= CFStringGetLength(passcode
);
468 CFNumberRef highest
= NULL
;
469 CFMutableArrayRef highestRepeatingcount
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
471 for(int i
= 0; i
< length
; i
++){
474 CFNumberRef newRepeatingAddition
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberIntType
, &repeating
);
475 CFArrayAppendValue(highestRepeatingcount
, newRepeatingAddition
);
476 CFReleaseNull(newRepeatingAddition
);
479 if(CFStringGetCharacterAtIndex(passcode
, i
) == CFStringGetCharacterAtIndex(passcode
,i
+1))
484 CFNumberRef newRepeatingAddition
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberIntType
, &repeating
);
485 CFArrayAppendValue(highestRepeatingcount
, newRepeatingAddition
);
486 CFReleaseNull(newRepeatingAddition
);
493 for(int i
=0; i
< CFArrayGetCount(highestRepeatingcount
); i
++){
495 highest
= CFArrayGetValueAtIndex(highestRepeatingcount
, i
);
499 CFNumberRef competitor
= CFArrayGetValueAtIndex(highestRepeatingcount
, i
);
500 if(CFNumberCompare(competitor
, highest
, NULL
) == kCFCompareGreaterThan
){
501 highest
= competitor
;
505 int finalRepeating
= 0;
507 CFNumberGetValue(highest
, kCFNumberIntType
, &finalRepeating
);
508 return finalRepeating
;
511 static bool SecPasswordIsPalindrome(CFStringRef passcode
){
512 char* pin
= CFStringToCString(passcode
);
513 long length
= CFStringGetLength(passcode
);
516 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++)
518 if(length%2
== 1 && i
== j
){
522 else if(length%2
== 0 && i
== j
-1){
523 if(pin
[i
] == pin
[j
]){
530 else if(pin
[i
] == pin
[j
]){
541 static bool SecPasswordHasRepeatingGroups(CFStringRef passcode
){
542 char* pin
= CFStringToCString(passcode
);
544 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++)
546 if(i
+4 == CFStringGetLength(passcode
)){
547 if(pin
[i
] == pin
[i
+3]){
554 else if(pin
[i
] == pin
[i
+3])
565 bool SecPasswordIsPasswordWeak2(bool isSimple
, CFStringRef passcode
)
567 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
568 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
569 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
570 punctuationCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetPunctuation
);
576 if( CFStringGetLength(passcode
) < 4 ){
577 return true; //weak password
580 bool isPasscodeNumber
= true;
581 //check to see if passcode is a number
582 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
583 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), 0, NULL
))
586 isPasscodeNumber
= false;
592 //checking to see if it's a 4 digit pin
593 if(isPasscodeNumber
&& CFStringGetLength(passcode
) == 4){
595 pin
= CFStringToCString(passcode
);
596 if(in_word_set(pin
, 4)){
601 CFIndex blacklistLength
= (CFIndex
)sizeof(blacklist
)/sizeof(blacklist
[0]);
603 //not all the same number
604 if(pin
[0] == pin
[1] == pin
[2] == pin
[3]){
606 return true; //weak password
608 //first two digits being the same and the last two digits being the same
609 if ( pin
[0] == pin
[1] && pin
[2] == pin
[3]){
611 return true; //weak password
613 //first two digits not being the same as the last two digits
614 if(pin
[0] == pin
[2] && pin
[1] == pin
[3]){
616 return true; //weak password
619 for(CFIndex i
= 0; i
< blacklistLength
; i
++)
621 const char* blackCode
= blacklist
[i
];
622 if(0 == strcmp(blackCode
, pin
))
625 return true; //weak password
629 else if(isPasscodeNumber
&& CFStringGetLength(passcode
) == 6){
630 pin
= CFStringToCString(passcode
);
632 //not all the same number
633 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
634 if(i
== CFStringGetLength(passcode
)-1){
638 else if ((pin
[i
]) == pin
[i
+1])
644 if(isTopTenSixDigitPasscode(passcode
)){
649 if(SecPasswordIsPalindrome(passcode
)){
655 if(SecPasswordHasRepeatingGroups(passcode
)){
659 //passcode is incrementing ex 123456 or 654321
660 if(SecPasswordIsPasscodeIncrementingOrDecrementingDigits(passcode
)) {
664 //passcode does not consist of 2 repeating digits
665 if(SecPasswordIsPasswordRepeatingTwoNumbers(passcode
)){
670 else//should be a 4 or 6digit number
673 else if(isPasscodeNumber
&& !isSimple
){ //dealing with a complex numeric passcode
674 pin
= CFStringToCString(passcode
);
675 //check if PIN is all the same number
676 int repeatingDigits
= SecPasswordNumberOfRepeatedDigits(passcode
);
677 if(repeatingDigits
>= (CFStringGetLength(passcode
)/2)){
682 if(SecPasswordIsPalindrome(passcode
)){
687 if(isTopTenSixDigitPasscode(passcode
)){
692 if(SecPasswordHasRepeatingGroups(passcode
) && CFStringGetLength(passcode
) >= 6){
697 if(SecPasswordIsPasscodeIncrementingOrDecrementingDigits(passcode
)) {
701 if(SecPasswordIsPasswordRepeatingTwoNumbers(passcode
)){
707 else{ // password is complex, evaluate entropy
712 int characterSet
= 0;
714 //calculate new entropy
715 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
717 if( CFStringFindCharacterFromSet(passcode
, uppercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
721 if( CFStringFindCharacterFromSet(passcode
, lowercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
725 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
729 if( CFStringFindCharacterFromSet(passcode
, punctuationCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
736 characterSet
+= alphabetSetSize
;
739 characterSet
+= alphabetSetSize
;
742 characterSet
+= decimalSetSize
;
745 characterSet
+= punctuationSetSize
;
748 double strength
= CFStringGetLength(passcode
)*log2(characterSet
);
750 if(strength
< entropyStrengthThreshold
){
754 return false; //strong
759 return false; //strong password
763 static void getUniformRandomNumbers(uint8_t* buffer
, size_t numberOfDesiredNumbers
, uint8_t upperBound
)
766 // The values returned by SecRandomCopyBytes are uniformly distributed in the range [0, 255]. If we try to map
767 // these values onto a smaller range using modulo we will introduce a bias towards lower numbers in situations
768 // where our smaller range doesn’t evenly divide in to [0, 255]. For example, with the desired range of [0, 54]
769 // the ranges 0..54, 55..109, 110..164, and 165..219 are uniformly distributed, but the range 220..255 modulo 55
770 // is only distributed over [0, 35], giving significant bias to these lower values. So, we ignore random numbers
771 // that would introduce this bias.
772 uint8_t limitAvoidingModuloBias
= UCHAR_MAX
- (UCHAR_MAX
% upperBound
);
774 for (size_t numberOfAcceptedNumbers
= 0; numberOfAcceptedNumbers
< numberOfDesiredNumbers
; ) {
775 if (SecRandomCopyBytes(kSecRandomDefault
, numberOfDesiredNumbers
- numberOfAcceptedNumbers
, buffer
+ numberOfAcceptedNumbers
) == -1)
777 for (size_t i
= numberOfAcceptedNumbers
; i
< numberOfDesiredNumbers
; ++i
) {
778 if (buffer
[i
] < limitAvoidingModuloBias
)
779 buffer
[numberOfAcceptedNumbers
++] = buffer
[i
] % upperBound
;
784 static bool passwordContainsRequiredCharacters(CFStringRef password
, CFArrayRef requiredCharacterSets
)
786 CFCharacterSetRef characterSet
;
788 for (CFIndex i
= 0; i
< CFArrayGetCount(requiredCharacterSets
); i
++) {
789 characterSet
= CFArrayGetValueAtIndex(requiredCharacterSets
, i
);
790 CFRange rangeToSearch
= CFRangeMake(0, CFStringGetLength(password
));
791 require_quiet(CFStringFindCharacterFromSet(password
, characterSet
, rangeToSearch
, 0, NULL
), fail
);
800 static bool passwordContainsLessThanNIdenticalCharacters(CFStringRef password
, CFIndex identicalCount
)
802 unsigned char Char
, nextChar
;
805 for(CFIndex i
= 0; i
< CFStringGetLength(password
); i
++){
806 Char
= CFStringGetCharacterAtIndex(password
, i
);
807 for(CFIndex j
= i
; j
< CFStringGetLength(password
); j
++){
808 nextChar
= CFStringGetCharacterAtIndex(password
, j
);
809 require_quiet(repeating
<= identicalCount
, fail
);
810 if(Char
== nextChar
){
823 static bool passwordContainsAtLeastNCharacters(CFStringRef password
, CFStringRef characters
, CFIndex N
)
825 CFCharacterSetRef characterSet
= NULL
;
826 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, characters
);
829 for(CFIndex i
= 0; i
< CFStringGetLength(password
); i
++){
830 if(CFStringFindCharacterFromSet(password
, characterSet
, CFRangeMake(i
, 1), 0, NULL
))
833 CFReleaseNull(characterSet
);
840 static bool passwordContainsLessThanNCharacters(CFStringRef password
, CFStringRef characters
, CFIndex N
)
842 CFCharacterSetRef characterSet
= NULL
;
843 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, characters
);
846 for(CFIndex i
= 0; i
< CFStringGetLength(password
); i
++){
847 if(CFStringFindCharacterFromSet(password
, characterSet
, CFRangeMake(i
, 1), 0, NULL
))
850 CFReleaseNull(characterSet
);
857 static bool passwordDoesNotContainCharacters(CFStringRef password
, CFStringRef prohibitedCharacters
)
859 CFCharacterSetRef characterSet
= NULL
;
860 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, prohibitedCharacters
);
861 CFRange rangeToSearch
= CFRangeMake(0, CFStringGetLength(password
));
863 require_quiet(!CFStringFindCharacterFromSet(password
, characterSet
, rangeToSearch
, 0, NULL
), fail
);
864 CFReleaseNull(characterSet
);
867 CFReleaseNull(characterSet
);
871 static void getPasswordRandomCharacters(CFStringRef
*returned
, CFDictionaryRef requirements
, CFIndex
*numberOfRandomCharacters
, CFStringRef allowedCharacters
)
873 uint8_t randomNumbers
[*numberOfRandomCharacters
];
874 unsigned char randomCharacters
[*numberOfRandomCharacters
];
875 getUniformRandomNumbers(randomNumbers
, *numberOfRandomCharacters
, CFStringGetLength(allowedCharacters
));
877 CFTypeRef prohibitedCharacters
= NULL
;
878 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordDisallowedCharacters
, &prohibitedCharacters
))
879 prohibitedCharacters
= NULL
;
881 //it's faster for long characters to check each character produced for these cases
882 for (CFIndex i
= 0; i
< *numberOfRandomCharacters
; ++i
){
883 //check prohibited characters
884 UniChar randomChar
[1];
885 randomChar
[0] = CFStringGetCharacterAtIndex(allowedCharacters
, randomNumbers
[i
]);
886 if (prohibitedCharacters
!= NULL
)
888 CFStringRef temp
= CFStringCreateWithCharacters(kCFAllocatorDefault
, randomChar
, 1);
889 bool pwdncc
= passwordDoesNotContainCharacters(temp
, prohibitedCharacters
);
892 //change up the random numbers so we don't get the same index into allowed
893 getUniformRandomNumbers(randomNumbers
, *numberOfRandomCharacters
, CFStringGetLength(allowedCharacters
));
898 randomCharacters
[i
] = (unsigned char)randomChar
[0];
901 *returned
= CFStringCreateWithBytes(kCFAllocatorDefault
, randomCharacters
, *numberOfRandomCharacters
, kCFStringEncodingUTF8
, false);
904 static bool doesPasswordEndWith(CFStringRef password
, CFStringRef prohibitedCharacters
)
906 CFCharacterSetRef characterSet
= NULL
;
907 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, prohibitedCharacters
);
909 CFRange rangeToSearch
= CFRangeMake(CFStringGetLength(password
) - CFStringGetLength(prohibitedCharacters
), CFStringGetLength(prohibitedCharacters
));
910 require_quiet(0 == CFStringCompareWithOptions(password
, prohibitedCharacters
, rangeToSearch
, 0), fail
);
911 CFReleaseNull(characterSet
);
914 CFReleaseNull(characterSet
);
918 static bool doesPasswordStartWith(CFStringRef password
, CFStringRef prohibitedCharacters
)
920 CFCharacterSetRef characterSet
= NULL
;
921 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, prohibitedCharacters
);
923 CFRange rangeToSearch
= CFRangeMake(0, CFStringGetLength(prohibitedCharacters
));
924 require_quiet(0 == CFStringCompareWithOptions(password
, prohibitedCharacters
, rangeToSearch
, 0), fail
);
925 CFReleaseNull(characterSet
);
926 return false; //does not start with prohibitedCharacters
928 CFReleaseNull(characterSet
);
932 static CFDictionaryRef
passwordGenerateCreateDefaultParametersDictionary(SecPasswordType type
, CFDictionaryRef requirements
){
934 CFMutableArrayRef requiredCharacterSets
= NULL
;
935 CFNumberRef numReqChars
= NULL
, checksumChars
= NULL
;
936 CFStringRef defaultPasswordFormat
= NULL
;
937 requiredCharacterSets
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
938 defaultPasswordFormat
= CFSTR("true");
939 CFTypeRef groupSizeRef
= NULL
, numberOfGroupsRef
= NULL
;
940 CFIndex groupSize
, numberOfGroups
, checksumSize
= 0;
941 CFDictionaryRef returned
= NULL
;
944 case kSecPasswordTypeiCloudRecoveryKey
:
948 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, (groupSize
* numberOfGroups
) - checksumSize
);
949 checksumChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, checksumSize
);
950 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
951 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
953 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
954 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
955 CFArrayAppendValue(requiredCharacterSets
, uppercaseLetterCharacterSet
);
956 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
957 returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
958 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
959 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
960 kSecNumberOfChecksumCharactersKey
, checksumChars
,
961 kSecAllowedCharactersKey
, defaultiCloudCharacters
,
962 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
963 kSecPasswordGroupSize
, groupSizeRef
,
964 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
967 case(kSecPasswordTypeiCloudRecovery
):
968 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultiCloudPasswordLength
);
971 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
972 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
974 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
975 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
976 CFArrayAppendValue(requiredCharacterSets
, uppercaseLetterCharacterSet
);
977 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
978 returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
979 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
980 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
981 kSecAllowedCharactersKey
, defaultiCloudCharacters
,
982 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
983 kSecPasswordGroupSize
, groupSizeRef
,
984 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
988 case(kSecPasswordTypePIN
):
989 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultPINLength
);
992 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
993 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
995 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
996 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
997 returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
998 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
999 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
1000 kSecAllowedCharactersKey
, defaultPINCharacters
,
1001 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
1002 kSecPasswordGroupSize
, groupSizeRef
,
1003 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
1007 case(kSecPasswordTypeWifi
):
1010 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
1011 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
1013 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
1014 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
1016 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultWifiPasswordLength
);
1017 CFArrayAppendValue(requiredCharacterSets
, lowercaseLetterCharacterSet
);
1018 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
1019 returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
1020 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
1021 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
1022 kSecAllowedCharactersKey
, defaultWifiCharacters
,
1023 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
1024 kSecPasswordGroupSize
, groupSizeRef
,
1025 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
1032 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
1033 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
1034 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
1035 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
1036 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
1037 CFArrayAppendValue(requiredCharacterSets
, uppercaseLetterCharacterSet
);
1038 CFArrayAppendValue(requiredCharacterSets
, lowercaseLetterCharacterSet
);
1039 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
1041 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultNumberOfRandomCharacters
);
1042 returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
1043 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
1044 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
1045 kSecAllowedCharactersKey
, defaultCharacters
,
1046 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
1047 kSecPasswordGroupSize
, groupSizeRef
,
1048 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
1056 CFReleaseNull(numReqChars
);
1057 CFReleaseNull(requiredCharacterSets
);
1058 CFReleaseNull(groupSizeRef
);
1059 CFReleaseNull(numberOfGroupsRef
);
1060 CFReleaseNull(checksumChars
);
1063 static CFDictionaryRef
passwordGenerationCreateParametersDictionary(SecPasswordType type
, CFDictionaryRef requirements
)
1065 CFMutableArrayRef requiredCharacterSets
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1066 CFNumberRef numReqChars
= NULL
;
1067 CFIndex numberOfRequiredRandomCharacters
;
1068 CFStringRef allowedCharacters
= NULL
, useDefaultPasswordFormat
= NULL
;
1070 CFTypeRef prohibitedCharacters
= NULL
, endWith
= NULL
, startWith
= NULL
,
1071 groupSizeRef
= NULL
, numberOfGroupsRef
= NULL
, separatorRef
= NULL
,
1072 atMostCharactersRef
= NULL
,atLeastCharactersRef
= NULL
, identicalRef
= NULL
;
1074 CFNumberRef min
= (CFNumberRef
)CFDictionaryGetValue(requirements
, kSecPasswordMinLengthKey
);
1075 CFNumberRef max
= (CFNumberRef
)CFDictionaryGetValue(requirements
, kSecPasswordMaxLengthKey
);
1077 CFNumberGetValue(min
, kCFNumberSInt64Type
, &valuePtr
);
1078 CFIndex minPasswordLength
= (long)valuePtr
;
1079 CFNumberGetValue(max
, kCFNumberSInt64Type
, &valuePtr
);
1080 CFIndex maxPasswordLength
= (long)valuePtr
;
1082 // If requirements allow, we will generate the password in default format.
1083 useDefaultPasswordFormat
= CFSTR("true");
1084 numberOfRequiredRandomCharacters
= defaultNumberOfRandomCharacters
;
1086 if(type
== kSecPasswordTypePIN
)
1088 if( maxPasswordLength
&& minPasswordLength
)
1089 numberOfRequiredRandomCharacters
= maxPasswordLength
;
1090 else if( !maxPasswordLength
&& minPasswordLength
)
1091 numberOfRequiredRandomCharacters
= minPasswordLength
;
1092 else if( !minPasswordLength
&& maxPasswordLength
)
1093 numberOfRequiredRandomCharacters
= maxPasswordLength
;
1095 numberOfRequiredRandomCharacters
= defaultPINLength
;
1097 allowedCharacters
= CFSTR("0123456789");
1098 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
1099 useDefaultPasswordFormat
= CFSTR("false");
1102 CFArrayRef requiredCharactersArray
= NULL
;
1104 if (minPasswordLength
&& minPasswordLength
> defaultNumberOfRandomCharacters
) {
1105 useDefaultPasswordFormat
= CFSTR("false");
1106 numberOfRequiredRandomCharacters
= minPasswordLength
;
1108 if (maxPasswordLength
&& maxPasswordLength
< defaultNumberOfRandomCharacters
) {
1109 useDefaultPasswordFormat
= CFSTR("false");
1110 numberOfRequiredRandomCharacters
= maxPasswordLength
;
1112 if (maxPasswordLength
&& minPasswordLength
&& maxPasswordLength
== minPasswordLength
&& maxPasswordLength
!= defaultNumberOfRandomCharacters
){
1113 useDefaultPasswordFormat
= CFSTR("false");
1114 numberOfRequiredRandomCharacters
= maxPasswordLength
;
1116 allowedCharacters
= (CFStringRef
)CFRetainSafe(CFDictionaryGetValue(requirements
, kSecPasswordAllowedCharactersKey
));
1117 requiredCharactersArray
= (CFArrayRef
)CFDictionaryGetValue(requirements
, kSecPasswordRequiredCharactersKey
);
1119 if (requiredCharactersArray
) {
1120 for (CFIndex i
= 0; i
< CFArrayGetCount(requiredCharactersArray
); i
++){
1121 CFCharacterSetRef stringWithRequiredCharacters
= CFArrayGetValueAtIndex(requiredCharactersArray
, i
);
1122 if(stringWithRequiredCharacters
&& CFStringFindCharacterFromSet(allowedCharacters
, stringWithRequiredCharacters
, CFRangeMake(0, CFStringGetLength(allowedCharacters
)), 0, NULL
)){
1123 CFArrayAppendValue(requiredCharacterSets
, stringWithRequiredCharacters
);
1127 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
1128 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
1129 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
1130 CFArrayAppendValue(requiredCharacterSets
, uppercaseLetterCharacterSet
);
1131 CFArrayAppendValue(requiredCharacterSets
, lowercaseLetterCharacterSet
);
1132 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
1136 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordDisallowedCharacters
, &prohibitedCharacters
))
1137 prohibitedCharacters
= NULL
;
1139 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantEndWithChars
, &endWith
))
1142 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantStartWithChars
, &startWith
))
1145 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordGroupSize
, &groupSizeRef
))
1146 groupSizeRef
= NULL
;
1148 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordNumberOfGroups
, &numberOfGroupsRef
))
1149 numberOfGroupsRef
= NULL
;
1151 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordSeparator
, &separatorRef
))
1152 separatorRef
= NULL
;
1154 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, &atMostCharactersRef
))
1155 atMostCharactersRef
= NULL
;
1157 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsAtLeastNSpecificCharacters
, &atLeastCharactersRef
))
1158 atLeastCharactersRef
= NULL
;
1160 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, &identicalRef
))
1161 identicalRef
= NULL
;
1163 if (allowedCharacters
) {
1164 if( false == CFStringFindWithOptions(allowedCharacters
, CFSTR("-"), CFRangeMake(0, CFStringGetLength(allowedCharacters
)), kCFCompareCaseInsensitive
, NULL
))
1165 useDefaultPasswordFormat
= CFSTR("false");
1167 allowedCharacters
= CFRetainSafe(defaultCharacters
);
1169 // In default password format, we use dashes only as separators, not as symbols you can encounter at a random position.
1170 if (useDefaultPasswordFormat
== CFSTR("false")){
1171 CFMutableStringRef mutatedAllowedCharacters
= CFStringCreateMutableCopy(kCFAllocatorDefault
, CFStringGetLength(allowedCharacters
), allowedCharacters
);
1172 CFStringFindAndReplace (mutatedAllowedCharacters
, CFSTR("-"), CFSTR(""), CFRangeMake(0, CFStringGetLength(allowedCharacters
)),kCFCompareCaseInsensitive
);
1173 CFReleaseSafe(allowedCharacters
);
1174 allowedCharacters
= mutatedAllowedCharacters
;
1177 if (CFArrayGetCount(requiredCharacterSets
) > numberOfRequiredRandomCharacters
) {
1178 CFReleaseNull(requiredCharacterSets
);
1179 requiredCharacterSets
= NULL
;
1181 //create new CFDictionary
1182 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, numberOfRequiredRandomCharacters
);
1183 CFMutableDictionaryRef updatedConstraints
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1184 CFDictionaryAddValue(updatedConstraints
, kSecUseDefaultPasswordFormatKey
, useDefaultPasswordFormat
);
1185 CFDictionarySetValue(updatedConstraints
, kSecNumberOfRequiredRandomCharactersKey
, numReqChars
);
1186 CFDictionaryAddValue(updatedConstraints
, kSecAllowedCharactersKey
, allowedCharacters
);
1187 if(requiredCharacterSets
)
1188 CFDictionaryAddValue(updatedConstraints
, kSecRequiredCharacterSetsKey
, requiredCharacterSets
);
1190 //add the prohibited characters string if it exists to the new dictionary
1191 if(prohibitedCharacters
)
1192 CFDictionaryAddValue(updatedConstraints
, kSecPasswordDisallowedCharacters
, prohibitedCharacters
);
1194 //add the characters the password can't end with if it exists to the new dictionary
1196 CFDictionaryAddValue(updatedConstraints
, kSecPasswordCantEndWithChars
, endWith
);
1198 //add the characters the password can't start with if it exists to the new dictionary
1200 CFDictionaryAddValue(updatedConstraints
, kSecPasswordCantStartWithChars
, startWith
);
1203 CFDictionaryAddValue(updatedConstraints
, kSecPasswordGroupSize
, groupSizeRef
);
1205 if(numberOfGroupsRef
)
1206 CFDictionaryAddValue(updatedConstraints
, kSecPasswordNumberOfGroups
, numberOfGroupsRef
);
1209 CFDictionaryAddValue(updatedConstraints
, kSecPasswordSeparator
, separatorRef
);
1211 if(atMostCharactersRef
)
1212 CFDictionaryAddValue(updatedConstraints
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, atMostCharactersRef
);
1214 if(atLeastCharactersRef
)
1215 CFDictionaryAddValue(updatedConstraints
, kSecPasswordContainsAtLeastNSpecificCharacters
, atLeastCharactersRef
);
1218 CFDictionaryAddValue(updatedConstraints
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, identicalRef
);
1220 CFReleaseNull(useDefaultPasswordFormat
);
1221 CFReleaseNull(numReqChars
);
1222 CFReleaseNull(allowedCharacters
);
1223 CFReleaseNull(requiredCharacterSets
);
1225 return updatedConstraints
;
1228 static bool isDictionaryFormattedProperly(SecPasswordType type
, CFDictionaryRef passwordRequirements
, CFErrorRef
*error
){
1230 CFTypeRef defaults
= NULL
;
1231 CFErrorRef tempError
= NULL
;
1232 if(passwordRequirements
== NULL
){
1236 if( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDefaultForType
, &defaults
) ){
1237 if(isString(defaults
) == true && 0 == CFStringCompare(defaults
, CFSTR("true"), 0)){
1241 //only need to check max and min pin length formatting
1242 if(type
== kSecPasswordTypePIN
){
1243 CFTypeRef minTest
= NULL
, maxTest
= NULL
;
1245 CFIndex minPasswordLength
= 0, maxPasswordLength
= 0;
1247 if( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDefaultForType
, &defaults
) ){
1248 if(isString(defaults
) == true && 0 == CFStringCompare(defaults
, CFSTR("true"), 0)){
1252 //check if the values exist!
1253 if( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMaxLengthKey
, &maxTest
) ){
1254 require_action_quiet(isNull(maxTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a max length"), (CFIndex
)errSecBadReq
, NULL
));
1255 require_action_quiet(isNumber(maxTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's max length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
1258 if (CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMinLengthKey
, &minTest
) ){
1259 require_action_quiet(isNull(minTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a min length"), (CFIndex
)errSecBadReq
, NULL
));
1260 require_action_quiet(isNumber(minTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's min length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
1262 //check if the values exist!
1264 CFNumberRef max
= (CFNumberRef
)maxTest
;
1265 CFNumberGetValue(max
, kCFNumberSInt64Type
, &valuePtr
);
1266 maxPasswordLength
= (long)valuePtr
;
1269 CFNumberRef min
= (CFNumberRef
)minTest
;
1270 CFNumberGetValue(min
, kCFNumberSInt64Type
, &valuePtr
);
1271 minPasswordLength
= (long)valuePtr
;
1273 //make sure min and max make sense respective to each other and that they aren't less than 4 digits.
1274 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
));
1275 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
));
1278 CFTypeRef allowedTest
, maxTest
, minTest
, requiredTest
, prohibitedCharacters
, endWith
, startWith
,
1279 groupSizeRef
, numberOfGroupsRef
, separatorRef
, atMostCharactersRef
,
1280 atLeastCharactersRef
, thresholdRef
, identicalRef
, characters
;
1283 //check if the values exist!
1284 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
));
1285 require_action_quiet( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMaxLengthKey
, &maxTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a max length"), (CFIndex
)errSecBadReq
, NULL
));
1286 require_action_quiet( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMinLengthKey
, &minTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a min length"), (CFIndex
)errSecBadReq
, NULL
));
1287 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
));
1289 //check if values are null?
1290 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
));
1291 require_action_quiet(isNull(maxTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a max length"), (CFIndex
)errSecBadReq
, NULL
));
1292 require_action_quiet(isNull(minTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a min length"), (CFIndex
)errSecBadReq
, NULL
));
1293 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
));
1295 //check if the values are correct
1296 require_action_quiet(isString(allowedTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's allowed characters must be a CFStringRef"), (CFIndex
)errSecBadReq
, NULL
));
1297 require_action_quiet(isNumber(maxTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's max length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
1298 require_action_quiet(isNumber(minTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's min length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
1299 require_action_quiet(isArray(requiredTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's required characters must be an array of CFCharacterSetRefs"), (CFIndex
)errSecBadReq
, NULL
));
1301 CFNumberGetValue(minTest
, kCFNumberSInt64Type
, &valuePtr
);
1302 CFIndex minPasswordLength
= (long)valuePtr
;
1303 CFNumberGetValue(maxTest
, kCFNumberSInt64Type
, &valuePtr
);
1304 CFIndex maxPasswordLength
= (long)valuePtr
;
1306 require_action_quiet(minPasswordLength
<= maxPasswordLength
, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's length parameters make no sense ( is max < min ?)"), (CFIndex
)errSecBadReq
, NULL
));
1308 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
));
1309 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
));
1311 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDisallowedCharacters
, &prohibitedCharacters
)){
1312 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
));
1313 require_action_quiet(isString(prohibitedCharacters
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("Disallowed Characters dictionary parameter is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1315 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordCantEndWithChars
, &endWith
)){
1316 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
));
1317 require_action_quiet(isString(endWith
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'EndWith' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1319 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordCantStartWithChars
, &startWith
)){
1320 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
));
1321 require_action_quiet(isString(startWith
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'StartWith' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1323 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordGroupSize
, &groupSizeRef
)){
1324 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
));
1325 require_action_quiet(isNumber(groupSizeRef
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'groupsize' is either null or not a number"), (CFIndex
)errSecBadReq
, NULL
));
1327 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordNumberOfGroups
, &numberOfGroupsRef
)){
1328 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
));
1329 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
));
1331 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordSeparator
, &separatorRef
)){
1332 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
));
1333 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
));
1336 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, &atMostCharactersRef
)){
1337 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
));
1338 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
));
1340 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
));
1341 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
));
1342 require_action_quiet(isNumber(thresholdRef
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex
)errSecBadReq
, NULL
));
1344 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
));
1345 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
));
1346 require_action_quiet(isString(characters
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1349 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordContainsAtLeastNSpecificCharacters
, &atLeastCharactersRef
)){
1350 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
));
1351 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
));
1353 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
));
1355 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
));
1356 require_action_quiet(isNumber(thresholdRef
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex
)errSecBadReq
, NULL
));
1358 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
));
1359 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
));
1360 require_action_quiet(isString(characters
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1363 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, &identicalRef
)){
1364 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
));
1365 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
));
1372 if (tempError
!= NULL
) {
1374 *error
= CFRetainSafe(tempError
);
1378 CFReleaseNull(tempError
);
1383 static bool doesFinalPasswordPass(bool isSimple
, CFStringRef password
, CFDictionaryRef requirements
){
1385 CFTypeRef characters
, identicalRef
= NULL
, NRef
= NULL
, endWith
= NULL
, startWith
= NULL
, atLeastCharacters
= NULL
, atMostCharacters
= NULL
;
1387 CFIndex N
, identicalCount
= 0;
1388 CFArrayRef requiredCharacterSet
= (CFArrayRef
)CFDictionaryGetValue(requirements
, kSecRequiredCharacterSetsKey
);
1390 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantEndWithChars
, &endWith
))
1393 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantStartWithChars
, &startWith
))
1396 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsAtLeastNSpecificCharacters
, &atLeastCharacters
))
1397 atLeastCharacters
= NULL
;
1399 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, &atMostCharacters
))
1400 atMostCharacters
= NULL
;
1402 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, &identicalRef
))
1403 identicalRef
= NULL
;
1405 CFNumberGetValue((CFNumberRef
)identicalRef
, kCFNumberSInt64Type
, &valuePtr
);
1406 identicalCount
= (long)valuePtr
;
1410 if(!doesPasswordEndWith(password
, endWith
))
1413 if(startWith
!= NULL
){
1414 if(!doesPasswordStartWith(password
, startWith
))
1417 if(atLeastCharacters
!= NULL
){
1418 NRef
= CFDictionaryGetValue(atLeastCharacters
, kSecPasswordCharacterCount
);
1419 characters
= CFDictionaryGetValue(atLeastCharacters
, kSecPasswordCharacters
);
1420 CFNumberGetValue((CFNumberRef
)NRef
, kCFNumberSInt64Type
, &valuePtr
);
1422 if(!passwordContainsAtLeastNCharacters(password
, characters
, N
))
1425 if(atMostCharacters
!= NULL
){
1426 NRef
= CFDictionaryGetValue(atMostCharacters
, kSecPasswordCharacterCount
);
1427 characters
= CFDictionaryGetValue(atMostCharacters
, kSecPasswordCharacters
);
1428 CFNumberGetValue((CFNumberRef
)NRef
, kCFNumberSInt64Type
, &valuePtr
);
1430 if(!passwordContainsLessThanNCharacters(password
, characters
, N
))
1433 if(identicalRef
!= NULL
){
1434 if(!passwordContainsLessThanNIdenticalCharacters(password
, identicalCount
))
1437 if (!passwordContainsRequiredCharacters(password
, requiredCharacterSet
))
1440 if(true == SecPasswordIsPasswordWeak2(isSimple
, password
))
1447 CreateChecksum(SecPasswordType type
, CFStringRef password
, CFIndex length
, CFStringRef allowedChars
)
1449 if (type
!= kSecPasswordTypeiCloudRecoveryKey
)
1452 CFMutableStringRef checksum
= NULL
;
1453 uint8_t digest
[CCSHA256_OUTPUT_SIZE
];
1454 if (length
> (CFIndex
)sizeof(digest
))
1457 CFDataRef data
= CFStringCreateExternalRepresentation(SecCFAllocatorZeroize(), password
, kCFStringEncodingUTF8
, 0);
1461 ccdigest(ccsha256_di(), CFDataGetLength(data
), CFDataGetBytePtr(data
), digest
);
1462 CFReleaseNull(data
);
1464 CFIndex allowedCharLength
= CFStringGetLength(allowedChars
);
1466 checksum
= CFStringCreateMutable(SecCFAllocatorZeroize(), 0);
1467 for (CFIndex n
= 0; n
< length
; n
++) {
1468 CFIndex selection
= digest
[n
] % allowedCharLength
;
1469 UniChar c
= CFStringGetCharacterAtIndex(allowedChars
, selection
);
1470 CFStringAppendCharacters(checksum
, &c
, 1);
1476 //entry point into password generation
1477 CF_RETURNS_RETAINED CFStringRef
SecPasswordGenerate(SecPasswordType type
, CFErrorRef
*error
, CFDictionaryRef passwordRequirements
){
1478 bool check
= false, isSimple
= false;
1479 CFTypeRef separator
= NULL
, defaults
= NULL
, groupSizeRef
= NULL
, numberOfGroupsRef
= NULL
;
1480 CFDictionaryRef properlyFormattedRequirements
= NULL
;
1481 CFErrorRef localError
= NULL
;
1482 uint64_t valuePtr
, groupSize
= 0, numberOfGroups
, checksumChars
= 0;
1483 CFNumberRef numberOfRequiredRandomCharacters
, checksumCharacters
;
1484 CFIndex requiredCharactersSize
= 0;
1485 CFStringRef randomCharacters
= NULL
, password
= NULL
, allowedChars
= NULL
;
1486 CFMutableStringRef finalPassword
= NULL
;
1488 if(type
== kSecPasswordTypePIN
)
1492 check
= isDictionaryFormattedProperly(type
, passwordRequirements
, &localError
);
1493 require_quiet(check
!= false, fail
);
1495 //should we generate defaults?
1496 if(passwordRequirements
== NULL
|| (CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDefaultForType
, &defaults
) && isString(defaults
) == true && 0 == CFStringCompare(defaults
, CFSTR("true"), 0) ))
1497 properlyFormattedRequirements
= passwordGenerateCreateDefaultParametersDictionary(type
, passwordRequirements
);
1499 properlyFormattedRequirements
= passwordGenerationCreateParametersDictionary(type
, passwordRequirements
);
1501 require_quiet(localError
== NULL
&& properlyFormattedRequirements
!= NULL
, fail
);
1503 numberOfRequiredRandomCharacters
= (CFNumberRef
)CFDictionaryGetValue(properlyFormattedRequirements
, kSecNumberOfRequiredRandomCharactersKey
);
1504 if (isNumber(numberOfRequiredRandomCharacters
) && CFNumberGetValue(numberOfRequiredRandomCharacters
, kCFNumberSInt64Type
, &valuePtr
))
1505 requiredCharactersSize
= (long)valuePtr
;
1507 checksumCharacters
= (CFNumberRef
)CFDictionaryGetValue(properlyFormattedRequirements
, kSecNumberOfChecksumCharactersKey
);
1508 if (isNumber(checksumCharacters
) && CFNumberGetValue(checksumCharacters
, kCFNumberSInt64Type
, &valuePtr
))
1509 checksumChars
= (long)valuePtr
;
1512 if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements
, kSecPasswordGroupSize
, &groupSizeRef
)){
1513 groupSizeRef
= NULL
;
1516 CFNumberGetValue((CFNumberRef
)groupSizeRef
, kCFNumberSInt64Type
, &groupSize
);
1518 if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements
, kSecPasswordNumberOfGroups
, &numberOfGroupsRef
)){
1519 numberOfGroupsRef
= NULL
;
1522 CFNumberGetValue((CFNumberRef
)numberOfGroupsRef
, kCFNumberSInt64Type
, &numberOfGroups
);
1524 require(requiredCharactersSize
, fail
);
1527 allowedChars
= CFDictionaryGetValue(properlyFormattedRequirements
, kSecAllowedCharactersKey
);
1528 getPasswordRandomCharacters(&randomCharacters
, properlyFormattedRequirements
, &requiredCharactersSize
, allowedChars
);
1530 if(numberOfGroupsRef
&& groupSizeRef
){
1531 finalPassword
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
1533 if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements
, kSecPasswordSeparator
, &separator
))
1536 if(separator
== NULL
)
1537 separator
= CFSTR("-");
1540 while( i
!= requiredCharactersSize
){
1541 if((i
+ (CFIndex
)groupSize
) < requiredCharactersSize
){
1542 CFStringRef subString
= CFStringCreateWithSubstring(kCFAllocatorDefault
, randomCharacters
, CFRangeMake(i
, (CFIndex
)groupSize
));
1543 CFStringAppend(finalPassword
, subString
);
1544 CFStringAppend(finalPassword
, separator
);
1545 CFReleaseSafe(subString
);
1548 else if((i
+(CFIndex
)groupSize
) == requiredCharactersSize
){
1549 CFStringRef subString
= CFStringCreateWithSubstring(kCFAllocatorDefault
, randomCharacters
, CFRangeMake(i
, (CFIndex
)groupSize
));
1550 CFStringAppend(finalPassword
, subString
);
1551 CFReleaseSafe(subString
);
1555 CFStringRef subString
= CFStringCreateWithSubstring(kCFAllocatorDefault
, randomCharacters
, CFRangeMake(i
, requiredCharactersSize
- i
));
1556 CFStringAppend(finalPassword
, subString
);
1557 CFReleaseSafe(subString
);
1558 i
+=(requiredCharactersSize
- i
);
1561 if (checksumChars
) {
1562 CFStringRef checksum
= CreateChecksum(type
, randomCharacters
, (CFIndex
)checksumChars
, allowedChars
);
1563 CFStringAppend(finalPassword
, checksum
);
1564 CFReleaseNull(checksum
);
1566 password
= CFStringCreateCopy(kCFAllocatorDefault
, finalPassword
);
1567 CFReleaseNull(finalPassword
);
1569 //no fancy formatting
1571 password
= CFStringCreateCopy(kCFAllocatorDefault
, randomCharacters
);
1574 CFReleaseNull(randomCharacters
);
1575 require_quiet(doesFinalPasswordPass(isSimple
, password
, properlyFormattedRequirements
), no_pass
);
1576 CFReleaseNull(properlyFormattedRequirements
);
1580 CFReleaseNull(password
);
1584 if (error
&& localError
) {
1585 *error
= localError
;
1589 CFReleaseSafe(localError
);
1590 CFReleaseNull(properlyFormattedRequirements
);
1594 const char *in_word_set (const char *str
, unsigned int len
){
1595 static const char * wordlist
[] =
1597 "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
1598 "", "", "0103", "", "", "", "", "0123", "", "", "", "", "0303", "", "", "",
1599 "", "", "", "", "0110", "", "1103", "", "", "", "", "1123", "", "", "0000",
1600 "", "1203", "", "0404", "", "", "", "", "1234", "1110", "2015", "2013", "",
1601 "2014", "1010", "2005", "2003", "", "2004", "1210", "0505", "0111", "", "",
1602 "", "2008", "0101", "", "2007", "", "", "", "", "2006", "2010", "1995", "1993",
1603 "", "1994", "2000", "", "1111", "", "", "", "1998", "1101", "", "1997", "",
1604 "0808", "1211", "", "1996", "0102", "", "1201", "", "", "1990", "", "", "",
1605 "", "0202", "", "2011", "", "", "1112", "1958", "2001", "", "1957", "1102",
1606 "", "3333", "", "1956", "1212", "1985", "1983", "", "1984", "1202", "", "0909",
1607 "", "0606", "", "1988", "1991", "", "1987", "2012", "", "", "", "1986", "2002",
1608 "", "", "", "0707", "1980", "", "2009", "", "", "2222", "1965", "1963", "",
1609 "1964", "", "", "2229", "", "", "1992", "1968", "", "", "1967", "", "", "1999",
1610 "", "1966", "", "1975", "1973", "", "1974", "1960", "", "1981", "", "4444",
1611 "", "1978", "", "7465", "1977", "", "", "", "", "1976", "2580", "", "1959",
1612 "", "", "1970", "", "", "", "", "", "", "", "", "", "1982", "", "1961", "",
1613 "", "5252", "", "1989", "", "", "", "", "", "", "", "", "", "", "", "", "",
1614 "", "1971", "", "", "", "", "", "", "", "1962", "", "5683", "", "6666", "",
1615 "", "1969", "", "", "", "", "", "", "", "", "", "", "", "", "1972", "", "",
1616 "", "", "", "", "1979", "", "", "", "7667"
1619 if (len
<= MAX_WORD_LENGTH
&& len
>= MIN_WORD_LENGTH
)
1621 register int key
= pinhash (str
, len
);
1623 if (key
<= MAX_HASH_VALUE
&& key
>= 0)
1625 register const char *s
= wordlist
[key
];
1626 if (*str
== *s
&& !strcmp (str
+ 1, s
+ 1))
1632 CFDictionaryRef
SecPasswordCopyDefaultPasswordLength(SecPasswordType type
, CFErrorRef
*error
){
1634 CFIndex tupleLengthInt
= 0, numOfTuplesInt
= 0;
1635 CFNumberRef tupleLength
= NULL
;
1636 CFNumberRef numOfTuples
= NULL
;
1638 CFMutableDictionaryRef passwordLengthDefaults
= NULL
;
1639 CFDictionaryRef result
= NULL
;
1642 case(kSecPasswordTypeiCloudRecoveryKey
):
1647 case(kSecPasswordTypeiCloudRecovery
):
1652 case(kSecPasswordTypePIN
):
1657 case(kSecPasswordTypeSafari
):
1662 case(kSecPasswordTypeWifi
):
1668 if(SecError(errSecBadReq
, error
, CFSTR("Password type does not exist.")) == false)
1670 secdebug("secpasswordcopydefaultpasswordlength", "could not create error!");
1674 if (tupleLengthInt
!= 0 && numOfTuplesInt
!= 0) {
1675 tupleLength
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberCFIndexType
, &tupleLengthInt
);
1676 numOfTuples
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberCFIndexType
, &numOfTuplesInt
);
1677 passwordLengthDefaults
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1678 CFDictionaryAddValue(passwordLengthDefaults
, kSecPasswordGroupSize
, tupleLength
);
1679 CFDictionaryAddValue(passwordLengthDefaults
, kSecPasswordNumberOfGroups
, numOfTuples
);
1680 result
= CFDictionaryCreateCopy(kCFAllocatorDefault
, passwordLengthDefaults
);
1683 CFReleaseSafe(tupleLength
);
1684 CFReleaseSafe(numOfTuples
);
1685 CFReleaseSafe(passwordLengthDefaults
);
1690 SecPasswordValidatePasswordFormat(SecPasswordType type
, CFStringRef password
, CFErrorRef
*error
)
1692 CFIndex tupleLengthInt
= 0, numOfTuplesInt
= 0, checkSumChars
= 3;
1693 CFStringRef checksum
= NULL
, madeChecksum
= NULL
, passwordNoChecksum
= NULL
;
1694 CFMutableStringRef randomChars
= NULL
;
1695 CFStringRef allowedChars
= NULL
;
1698 if (type
!= kSecPasswordTypeiCloudRecoveryKey
) {
1705 allowedChars
= defaultiCloudCharacters
;
1707 if (numOfTuplesInt
< 1)
1709 if (checkSumChars
> tupleLengthInt
)
1712 CFIndex numberOfChars
= numOfTuplesInt
* tupleLengthInt
+ (numOfTuplesInt
- 1);
1715 * First check expected length
1718 require(CFStringGetLength(password
) == numberOfChars
, out
); /* N groups of M with (N-1) seperator - in-between */
1720 randomChars
= CFStringCreateMutable(SecCFAllocatorZeroize(), 0);
1721 require(randomChars
, out
);
1724 * make sure dash-es are at the expected spots
1727 for (CFIndex n
= 0; n
< numOfTuplesInt
; n
++) {
1729 UniChar c
= CFStringGetCharacterAtIndex(password
, (n
* (tupleLengthInt
+ 1)) - 1);
1730 require(c
== '-', out
);
1732 CFStringRef substr
= CFStringCreateWithSubstring(SecCFAllocatorZeroize(), password
, CFRangeMake(n
* (tupleLengthInt
+ 1), tupleLengthInt
));
1733 CFStringAppend(randomChars
, substr
);
1734 CFReleaseNull(substr
);
1738 * Pull apart and password and checksum
1741 checksum
= CFStringCreateWithSubstring(SecCFAllocatorZeroize(), randomChars
, CFRangeMake((numOfTuplesInt
* tupleLengthInt
) - checkSumChars
, checkSumChars
));
1742 require(checksum
, out
);
1744 passwordNoChecksum
= CFStringCreateWithSubstring(SecCFAllocatorZeroize(), randomChars
, CFRangeMake(0, (numOfTuplesInt
* tupleLengthInt
) - checkSumChars
));
1745 require(passwordNoChecksum
, out
);
1751 madeChecksum
= CreateChecksum(type
, passwordNoChecksum
, checkSumChars
, allowedChars
);
1752 require(madeChecksum
, out
);
1754 require(CFEqual(madeChecksum
, checksum
), out
);
1758 CFReleaseNull(randomChars
);
1759 CFReleaseNull(madeChecksum
);
1760 CFReleaseNull(checksum
);
1761 CFReleaseNull(passwordNoChecksum
);