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>
41 // Keys for external dictionaries with password generation requirements we read from plist.
42 CFStringRef kSecPasswordMinLengthKey
= CFSTR("PasswordMinLength");
43 CFStringRef kSecPasswordMaxLengthKey
= CFSTR("PasswordMaxLength");
44 CFStringRef kSecPasswordAllowedCharactersKey
= CFSTR("PasswordAllowedCharacters");
45 CFStringRef kSecPasswordRequiredCharactersKey
= CFSTR("PasswordRequiredCharacters");
46 CFStringRef kSecPasswordDefaultForType
= CFSTR("PasswordDefaultForType");
48 CFStringRef kSecPasswordDisallowedCharacters
= CFSTR("PasswordDisallowedCharacters");
49 CFStringRef kSecPasswordCantStartWithChars
= CFSTR("PasswordCantStartWithChars");
50 CFStringRef kSecPasswordCantEndWithChars
= CFSTR("PasswordCantEndWithChars");
51 CFStringRef kSecPasswordContainsNoMoreThanNSpecificCharacters
= CFSTR("PasswordContainsNoMoreThanNSpecificCharacters");
52 CFStringRef kSecPasswordContainsAtLeastNSpecificCharacters
= CFSTR("PasswordContainsAtLeastNSpecificCharacters");
53 CFStringRef kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
= CFSTR("PasswordContainsNoMoreThanNConsecutiveIdenticalCharacters");
54 CFStringRef kSecPasswordCharacterCount
= CFSTR("PasswordCharacterCount");
55 CFStringRef kSecPasswordCharacters
= CFSTR("PasswordCharacters");
57 CFStringRef kSecPasswordGroupSize
= CFSTR("PasswordGroupSize");
58 CFStringRef kSecPasswordNumberOfGroups
= CFSTR("PasswordNumberOfGroups");
59 CFStringRef kSecPasswordSeparator
= CFSTR("SecPasswordSeparator");
61 // Keys for internally used dictionaries with password generation parameters (never exposed to external API).
62 static CFStringRef kSecUseDefaultPasswordFormatKey
= CFSTR("UseDefaultPasswordFormat");
63 static CFStringRef kSecNumberOfRequiredRandomCharactersKey
= CFSTR("NumberOfRequiredRandomCharacters");
64 static CFStringRef kSecAllowedCharactersKey
= CFSTR("AllowedCharacters");
65 static CFStringRef kSecRequiredCharacterSetsKey
= CFSTR("RequiredCharacterSets");
67 static CFIndex defaultNumberOfRandomCharacters
= 20;
68 static CFIndex defaultPINLength
= 4;
69 static CFIndex defaultiCloudPasswordLength
= 24;
70 static CFIndex defaultWifiPasswordLength
= 12;
72 static CFStringRef defaultWifiCharacters
= CFSTR("abcdefghijklmnopqrstuvwxyz1234567890");
73 static CFStringRef defaultPINCharacters
= CFSTR("0123456789");
74 static CFStringRef defaultiCloudCharacters
= CFSTR("ABCDEFGHJKLMNPQRSTUVWXYZ23456789");
75 static CFStringRef defaultCharacters
= CFSTR("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789");
77 static CFCharacterSetRef uppercaseLetterCharacterSet
;
78 static CFCharacterSetRef lowercaseLetterCharacterSet
;
79 static CFCharacterSetRef decimalDigitCharacterSet
;
80 static CFCharacterSetRef punctuationCharacterSet
;
82 static CFIndex alphabetSetSize
= 26;
83 static CFIndex decimalSetSize
= 10;
84 static CFIndex punctuationSetSize
= 33;
85 static double entropyStrengthThreshold
= 35.0;
88 generated with ruby badpins.rb | gperf
89 See this for PIN list:
90 A birthday present every eleven wallets? The security of customer-chosen banking PINs (2012), by Joseph Bonneau , Sören Preibusch , Ross Anderson
92 const char *in_word_set (const char *str
, unsigned int len
);
94 #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
95 && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
96 && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
97 && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
98 && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
99 && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
100 && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
101 && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
102 && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
103 && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
104 && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
105 && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
106 && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
107 && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
108 && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
109 && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
110 && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
111 && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
112 && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
113 && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
114 && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
115 && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
116 && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
117 /* The character set is not based on ISO-646. */
118 error
"gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
122 #define TOTAL_KEYWORDS 100
123 #define MIN_WORD_LENGTH 4
124 #define MAX_WORD_LENGTH 4
125 #define MIN_HASH_VALUE 21
126 #define MAX_HASH_VALUE 275
127 /* maximum key range = 255, duplicates = 0 */
136 static unsigned int pinhash (const char *str
, unsigned int len
)
138 static unsigned short asso_values
[] =
140 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
141 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
142 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
143 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
144 276, 276, 276, 276, 276, 276, 276, 276, 5, 0,
145 10, 10, 30, 50, 100, 120, 70, 25, 57, 85,
146 2, 4, 1, 19, 14, 11, 92, 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, 276, 276,
150 276, 276, 276, 276, 276, 276, 276, 276, 276, 276,
151 276, 276, 276, 276, 276, 276, 276, 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
168 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];
171 //pins that reached the top 20 list
172 static const char *blacklist
[] = {"1234", "1004", "2000", "1122", "4321", "2001", "2580"};
173 bool SecPasswordIsPasswordWeak(CFStringRef passcode
)
175 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
176 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
177 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
178 punctuationCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetPunctuation
);
180 bool isNumber
= true;
184 if( CFStringGetLength(passcode
) < 4 ){
185 return true; //weak password
187 //check to see if passcode is a number
188 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
189 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), 0, NULL
))
196 //checking to see if it's a 4 digit pin
197 if(isNumber
&& CFStringGetLength(passcode
) == 4){
199 pin
= CFStringToCString(passcode
);
200 if(in_word_set(pin
, 4)){
205 CFIndex blacklistLength
= (CFIndex
)sizeof(blacklist
)/sizeof(blacklist
[0]);
207 //not all the same number
208 if(pin
[0] == pin
[1] == pin
[2] == pin
[3]){
210 return true; //weak password
212 //first two digits being the same and the last two digits being the same
213 if ( pin
[0] == pin
[1] && pin
[2] == pin
[3]){
215 return true; //weak password
217 //first two digits not being the same as the last two digits
218 if(pin
[0] == pin
[2] && pin
[1] == pin
[3]){
220 return true; //weak password
223 for(CFIndex i
= 0; i
< blacklistLength
; i
++)
225 const char* blackCode
= blacklist
[i
];
226 if(0 == strcmp(blackCode
, pin
))
229 return true; //weak password
233 else if(isNumber
){ //dealing with a numeric PIN
234 pin
= CFStringToCString(passcode
);
235 //check if PIN is all the same number
236 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
237 if(i
+1 >= CFStringGetLength(passcode
)){
241 else if (pin
[i
] == pin
[i
+1])
246 //check if PIN is a bunch of incrementing numbers
247 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
248 if(i
== CFStringGetLength(passcode
)-1){
252 else if ((pin
[i
] + 1) == pin
[i
+1])
257 //check if PIN is a bunch of decrementing numbers
258 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
259 if(i
== CFStringGetLength(passcode
)-1){
263 else if ((pin
[i
]) == (pin
[i
+1] +1))
269 else{ // password is complex, evaluate entropy
274 int characterSet
= 0;
276 //calculate new entropy
277 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
279 if( CFStringFindCharacterFromSet(passcode
, uppercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
283 if( CFStringFindCharacterFromSet(passcode
, lowercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
287 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
291 if( CFStringFindCharacterFromSet(passcode
, punctuationCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
298 characterSet
+= alphabetSetSize
;
301 characterSet
+= alphabetSetSize
;
304 characterSet
+= decimalSetSize
;
307 characterSet
+= punctuationSetSize
;
310 double strength
= CFStringGetLength(passcode
)*log2(characterSet
);
312 if(strength
< entropyStrengthThreshold
){
316 return false; //strong
321 return false; //strong password
325 bool SecPasswordIsPasswordWeak2(bool isSimple
, CFStringRef passcode
)
327 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
328 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
329 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
330 punctuationCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetPunctuation
);
336 if( CFStringGetLength(passcode
) < 4 ){
337 return true; //weak password
340 bool isPasscodeNumber
= true;
341 //check to see if passcode is a number
342 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
343 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), 0, NULL
))
346 isPasscodeNumber
= false;
352 //checking to see if it's a 4 digit pin
353 if(isPasscodeNumber
&& CFStringGetLength(passcode
) == 4){
355 pin
= CFStringToCString(passcode
);
356 if(in_word_set(pin
, 4)){
361 CFIndex blacklistLength
= (CFIndex
)sizeof(blacklist
)/sizeof(blacklist
[0]);
363 //not all the same number
364 if(pin
[0] == pin
[1] == pin
[2] == pin
[3]){
366 return true; //weak password
368 //first two digits being the same and the last two digits being the same
369 if ( pin
[0] == pin
[1] && pin
[2] == pin
[3]){
371 return true; //weak password
373 //first two digits not being the same as the last two digits
374 if(pin
[0] == pin
[2] && pin
[1] == pin
[3]){
376 return true; //weak password
379 for(CFIndex i
= 0; i
< blacklistLength
; i
++)
381 const char* blackCode
= blacklist
[i
];
382 if(0 == strcmp(blackCode
, pin
))
385 return true; //weak password
389 else //should be a 4 digit number
392 else if(isPasscodeNumber
&& !isSimple
){ //dealing with a complex numeric passcode
393 pin
= CFStringToCString(passcode
);
394 //check if PIN is all the same number
395 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
396 if(i
+1 >= CFStringGetLength(passcode
)){
400 else if (pin
[i
] == pin
[i
+1])
405 //check if PIN is a bunch of incrementing numbers
406 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
407 if(i
== CFStringGetLength(passcode
)-1){
411 else if ((pin
[i
] + 1) == pin
[i
+1])
416 //check if PIN is a bunch of decrementing numbers
417 for(int i
= 0; i
< CFStringGetLength(passcode
); i
++){
418 if(i
== CFStringGetLength(passcode
)-1){
422 else if ((pin
[i
]) == (pin
[i
+1] +1))
428 else{ // password is complex, evaluate entropy
433 int characterSet
= 0;
435 //calculate new entropy
436 for(CFIndex i
= 0; i
< CFStringGetLength(passcode
); i
++){
438 if( CFStringFindCharacterFromSet(passcode
, uppercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
442 if( CFStringFindCharacterFromSet(passcode
, lowercaseLetterCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
446 if( CFStringFindCharacterFromSet(passcode
, decimalDigitCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
450 if( CFStringFindCharacterFromSet(passcode
, punctuationCharacterSet
, CFRangeMake(i
,1), kCFCompareBackwards
, NULL
)){
457 characterSet
+= alphabetSetSize
;
460 characterSet
+= alphabetSetSize
;
463 characterSet
+= decimalSetSize
;
466 characterSet
+= punctuationSetSize
;
469 double strength
= CFStringGetLength(passcode
)*log2(characterSet
);
471 if(strength
< entropyStrengthThreshold
){
475 return false; //strong
480 return false; //strong password
484 static void getUniformRandomNumbers(uint8_t* buffer
, size_t numberOfDesiredNumbers
, uint8_t upperBound
)
487 // The values returned by SecRandomCopyBytes are uniformly distributed in the range [0, 255]. If we try to map
488 // these values onto a smaller range using modulo we will introduce a bias towards lower numbers in situations
489 // where our smaller range doesn’t evenly divide in to [0, 255]. For example, with the desired range of [0, 54]
490 // the ranges 0..54, 55..109, 110..164, and 165..219 are uniformly distributed, but the range 220..255 modulo 55
491 // is only distributed over [0, 35], giving significant bias to these lower values. So, we ignore random numbers
492 // that would introduce this bias.
493 uint8_t limitAvoidingModuloBias
= UCHAR_MAX
- (UCHAR_MAX
% upperBound
);
495 for (size_t numberOfAcceptedNumbers
= 0; numberOfAcceptedNumbers
< numberOfDesiredNumbers
; ) {
496 if (SecRandomCopyBytes(kSecRandomDefault
, numberOfDesiredNumbers
- numberOfAcceptedNumbers
, buffer
+ numberOfAcceptedNumbers
) == -1)
498 for (size_t i
= numberOfAcceptedNumbers
; i
< numberOfDesiredNumbers
; ++i
) {
499 if (buffer
[i
] < limitAvoidingModuloBias
)
500 buffer
[numberOfAcceptedNumbers
++] = buffer
[i
] % upperBound
;
505 static bool passwordContainsRequiredCharacters(CFStringRef password
, CFArrayRef requiredCharacterSets
)
507 CFCharacterSetRef characterSet
;
509 for (CFIndex i
= 0; i
< CFArrayGetCount(requiredCharacterSets
); i
++) {
510 characterSet
= CFArrayGetValueAtIndex(requiredCharacterSets
, i
);
511 CFRange rangeToSearch
= CFRangeMake(0, CFStringGetLength(password
));
512 require_quiet(CFStringFindCharacterFromSet(password
, characterSet
, rangeToSearch
, 0, NULL
), fail
);
521 static bool passwordContainsLessThanNIdenticalCharacters(CFStringRef password
, CFIndex identicalCount
)
523 unsigned char Char
, nextChar
;
526 for(CFIndex i
= 0; i
< CFStringGetLength(password
); i
++){
527 Char
= CFStringGetCharacterAtIndex(password
, i
);
528 for(CFIndex j
= i
; j
< CFStringGetLength(password
); j
++){
529 nextChar
= CFStringGetCharacterAtIndex(password
, j
);
530 require_quiet(repeating
<= identicalCount
, fail
);
531 if(Char
== nextChar
){
544 static bool passwordContainsAtLeastNCharacters(CFStringRef password
, CFStringRef characters
, CFIndex N
)
546 CFCharacterSetRef characterSet
= NULL
;
547 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, characters
);
550 for(CFIndex i
= 0; i
< CFStringGetLength(password
); i
++){
551 if(CFStringFindCharacterFromSet(password
, characterSet
, CFRangeMake(i
, 1), 0, NULL
))
554 CFReleaseNull(characterSet
);
561 static bool passwordContainsLessThanNCharacters(CFStringRef password
, CFStringRef characters
, CFIndex N
)
563 CFCharacterSetRef characterSet
= NULL
;
564 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, characters
);
567 for(CFIndex i
= 0; i
< CFStringGetLength(password
); i
++){
568 if(CFStringFindCharacterFromSet(password
, characterSet
, CFRangeMake(i
, 1), 0, NULL
))
571 CFReleaseNull(characterSet
);
578 static bool passwordDoesNotContainCharacters(CFStringRef password
, CFStringRef prohibitedCharacters
)
580 CFCharacterSetRef characterSet
= NULL
;
581 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, prohibitedCharacters
);
582 CFRange rangeToSearch
= CFRangeMake(0, CFStringGetLength(password
));
584 require_quiet(!CFStringFindCharacterFromSet(password
, characterSet
, rangeToSearch
, 0, NULL
), fail
);
585 CFReleaseNull(characterSet
);
588 CFReleaseNull(characterSet
);
592 static void getPasswordRandomCharacters(CFStringRef
*returned
, CFDictionaryRef requirements
, CFIndex
*numberOfRandomCharacters
, CFStringRef allowedCharacters
)
594 uint8_t randomNumbers
[*numberOfRandomCharacters
];
595 unsigned char randomCharacters
[*numberOfRandomCharacters
];
596 getUniformRandomNumbers(randomNumbers
, *numberOfRandomCharacters
, CFStringGetLength(allowedCharacters
));
598 CFTypeRef prohibitedCharacters
= NULL
;
599 CFStringRef temp
= NULL
;
601 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordDisallowedCharacters
, &prohibitedCharacters
))
602 prohibitedCharacters
= NULL
;
604 //it's faster for long characters to check each character produced for these cases
605 for (CFIndex i
= 0; i
< *numberOfRandomCharacters
; ++i
){
606 //check prohibited characters
607 UniChar randomChar
[1];
608 randomChar
[0] = CFStringGetCharacterAtIndex(allowedCharacters
, randomNumbers
[i
]);
609 temp
= CFStringCreateWithCharacters(kCFAllocatorDefault
, randomChar
, 1);
611 if(prohibitedCharacters
!= NULL
)
613 if(!passwordDoesNotContainCharacters(temp
, prohibitedCharacters
)){
614 //change up the random numbers so we don't get the same index into allowed
615 getUniformRandomNumbers(randomNumbers
, *numberOfRandomCharacters
, CFStringGetLength(allowedCharacters
));
620 randomCharacters
[i
] = (unsigned char)randomChar
[0];
625 *returned
= CFStringCreateWithBytes(kCFAllocatorDefault
, randomCharacters
, *numberOfRandomCharacters
, kCFStringEncodingUTF8
, false);
628 static bool doesPasswordEndWith(CFStringRef password
, CFStringRef prohibitedCharacters
)
630 CFCharacterSetRef characterSet
= NULL
;
631 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, prohibitedCharacters
);
633 CFRange rangeToSearch
= CFRangeMake(CFStringGetLength(password
) - CFStringGetLength(prohibitedCharacters
), CFStringGetLength(prohibitedCharacters
));
634 require_quiet(0 == CFStringCompareWithOptions(password
, prohibitedCharacters
, rangeToSearch
, 0), fail
);
635 CFReleaseNull(characterSet
);
638 CFReleaseNull(characterSet
);
642 static bool doesPasswordStartWith(CFStringRef password
, CFStringRef prohibitedCharacters
)
644 CFCharacterSetRef characterSet
= NULL
;
645 characterSet
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, prohibitedCharacters
);
647 CFRange rangeToSearch
= CFRangeMake(0, CFStringGetLength(prohibitedCharacters
));
648 require_quiet(0 == CFStringCompareWithOptions(password
, prohibitedCharacters
, rangeToSearch
, 0), fail
);
649 CFReleaseNull(characterSet
);
650 return false; //does not start with prohibitedCharacters
652 CFReleaseNull(characterSet
);
656 static void passwordGenerateDefaultParametersDictionary(CFDictionaryRef
*returned
, SecPasswordType type
, CFDictionaryRef requirements
){
658 CFMutableArrayRef requiredCharacterSets
= NULL
;
659 CFNumberRef numReqChars
= NULL
;
660 CFStringRef defaultPasswordFormat
= NULL
;
661 requiredCharacterSets
= CFArrayCreateMutable(NULL
, 0, NULL
);
662 defaultPasswordFormat
= CFSTR("true");
663 CFTypeRef groupSizeRef
= NULL
, numberOfGroupsRef
= NULL
;
664 CFIndex groupSize
, numberOfGroups
;
666 case(kSecPasswordTypeiCloudRecovery
):
667 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultiCloudPasswordLength
);
670 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
671 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
673 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
674 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
675 CFArrayAppendValue(requiredCharacterSets
, uppercaseLetterCharacterSet
);
676 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
677 *returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
678 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
679 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
680 kSecAllowedCharactersKey
, defaultiCloudCharacters
,
681 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
682 kSecPasswordGroupSize
, groupSizeRef
,
683 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
687 case(kSecPasswordTypePIN
):
688 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultPINLength
);
691 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
692 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
694 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
695 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
696 *returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
697 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
698 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
699 kSecAllowedCharactersKey
, defaultPINCharacters
,
700 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
701 kSecPasswordGroupSize
, groupSizeRef
,
702 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
706 case(kSecPasswordTypeWifi
):
709 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
710 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
712 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
713 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
715 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultWifiPasswordLength
);
716 CFArrayAppendValue(requiredCharacterSets
, lowercaseLetterCharacterSet
);
717 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
718 *returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
719 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
720 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
721 kSecAllowedCharactersKey
, defaultWifiCharacters
,
722 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
723 kSecPasswordGroupSize
, groupSizeRef
,
724 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
731 groupSizeRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &groupSize
);
732 numberOfGroupsRef
= CFNumberCreate(NULL
, kCFNumberCFIndexType
, &numberOfGroups
);
733 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
734 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
735 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
736 CFArrayAppendValue(requiredCharacterSets
, uppercaseLetterCharacterSet
);
737 CFArrayAppendValue(requiredCharacterSets
, lowercaseLetterCharacterSet
);
738 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
740 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, defaultNumberOfRandomCharacters
);
741 *returned
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
742 kSecUseDefaultPasswordFormatKey
, defaultPasswordFormat
,
743 kSecNumberOfRequiredRandomCharactersKey
, numReqChars
,
744 kSecAllowedCharactersKey
, defaultCharacters
,
745 kSecRequiredCharacterSetsKey
, requiredCharacterSets
,
746 kSecPasswordGroupSize
, groupSizeRef
,
747 kSecPasswordNumberOfGroups
, numberOfGroupsRef
,
755 CFReleaseNull(numReqChars
);
756 CFReleaseNull(requiredCharacterSets
);
757 CFReleaseNull(groupSizeRef
);
758 CFReleaseNull(numberOfGroupsRef
);
760 static void passwordGenerationParametersDictionary(CFDictionaryRef
*returned
, SecPasswordType type
, CFDictionaryRef requirements
)
762 CFMutableArrayRef requiredCharacterSets
= CFArrayCreateMutable(NULL
, 0, NULL
);
763 CFArrayRef requiredCharactersArray
= NULL
;
764 CFNumberRef numReqChars
= NULL
;
765 CFIndex numberOfRequiredRandomCharacters
;
766 CFStringRef allowedCharacters
= NULL
, useDefaultPasswordFormat
= NULL
;
768 CFTypeRef prohibitedCharacters
= NULL
, endWith
= NULL
, startWith
= NULL
,
769 groupSizeRef
= NULL
, numberOfGroupsRef
= NULL
, separatorRef
= NULL
,
770 atMostCharactersRef
= NULL
,atLeastCharactersRef
= NULL
, identicalRef
= NULL
;
772 CFNumberRef min
= (CFNumberRef
)CFDictionaryGetValue(requirements
, kSecPasswordMinLengthKey
);
773 CFNumberRef max
= (CFNumberRef
)CFDictionaryGetValue(requirements
, kSecPasswordMaxLengthKey
);
775 CFNumberGetValue(min
, kCFNumberSInt64Type
, &valuePtr
);
776 CFIndex minPasswordLength
= (long)valuePtr
;
777 CFNumberGetValue(max
, kCFNumberSInt64Type
, &valuePtr
);
778 CFIndex maxPasswordLength
= (long)valuePtr
;
780 // If requirements allow, we will generate the password in default format.
781 useDefaultPasswordFormat
= CFSTR("true");
782 numberOfRequiredRandomCharacters
= defaultNumberOfRandomCharacters
;
784 if(type
== kSecPasswordTypePIN
)
786 if( maxPasswordLength
&& minPasswordLength
)
787 numberOfRequiredRandomCharacters
= maxPasswordLength
;
788 else if( !maxPasswordLength
&& minPasswordLength
)
789 numberOfRequiredRandomCharacters
= minPasswordLength
;
790 else if( !minPasswordLength
&& maxPasswordLength
)
791 numberOfRequiredRandomCharacters
= maxPasswordLength
;
793 numberOfRequiredRandomCharacters
= defaultPINLength
;
795 allowedCharacters
= CFSTR("0123456789");
796 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
797 requiredCharactersArray
= CFArrayCreateCopy(NULL
, requiredCharacterSets
);
798 useDefaultPasswordFormat
= CFSTR("false");
801 if (minPasswordLength
&& minPasswordLength
> defaultNumberOfRandomCharacters
) {
802 useDefaultPasswordFormat
= CFSTR("false");
803 numberOfRequiredRandomCharacters
= minPasswordLength
;
805 if (maxPasswordLength
&& maxPasswordLength
< defaultNumberOfRandomCharacters
) {
806 useDefaultPasswordFormat
= CFSTR("false");
807 numberOfRequiredRandomCharacters
= maxPasswordLength
;
809 if (maxPasswordLength
&& minPasswordLength
&& maxPasswordLength
== minPasswordLength
&& maxPasswordLength
!= defaultNumberOfRandomCharacters
){
810 useDefaultPasswordFormat
= CFSTR("false");
811 numberOfRequiredRandomCharacters
= maxPasswordLength
;
813 allowedCharacters
= (CFStringRef
)CFDictionaryGetValue(requirements
, kSecPasswordAllowedCharactersKey
);
814 requiredCharactersArray
= (CFArrayRef
)CFDictionaryGetValue(requirements
, kSecPasswordRequiredCharactersKey
);
816 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordDisallowedCharacters
, &prohibitedCharacters
))
817 prohibitedCharacters
= NULL
;
819 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantEndWithChars
, &endWith
))
822 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantStartWithChars
, &startWith
))
825 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordGroupSize
, &groupSizeRef
))
828 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordNumberOfGroups
, &numberOfGroupsRef
))
829 numberOfGroupsRef
= NULL
;
831 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordSeparator
, &separatorRef
))
834 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, &atMostCharactersRef
))
835 atMostCharactersRef
= NULL
;
837 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsAtLeastNSpecificCharacters
, &atLeastCharactersRef
))
838 atLeastCharactersRef
= NULL
;
840 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, &identicalRef
))
843 if (allowedCharacters
) {
844 if( false == CFStringFindWithOptions(allowedCharacters
, CFSTR("-"), CFRangeMake(0, CFStringGetLength(allowedCharacters
)), kCFCompareCaseInsensitive
, NULL
))
845 useDefaultPasswordFormat
= CFSTR("false");
847 allowedCharacters
= defaultCharacters
;
849 // In default password format, we use dashes only as separators, not as symbols you can encounter at a random position.
850 if (useDefaultPasswordFormat
== CFSTR("false")){
851 CFMutableStringRef mutatedAllowedCharacters
= CFStringCreateMutableCopy(kCFAllocatorDefault
, CFStringGetLength(allowedCharacters
), allowedCharacters
);
852 CFStringFindAndReplace (mutatedAllowedCharacters
, CFSTR("-"), CFSTR(""), CFRangeMake(0, CFStringGetLength(allowedCharacters
)),kCFCompareCaseInsensitive
);
853 allowedCharacters
= CFStringCreateCopy(kCFAllocatorDefault
, mutatedAllowedCharacters
);
856 if (requiredCharactersArray
) {
857 for (CFIndex i
= 0; i
< CFArrayGetCount(requiredCharactersArray
); i
++){
858 CFCharacterSetRef stringWithRequiredCharacters
= CFArrayGetValueAtIndex(requiredCharactersArray
, i
);
859 if( CFStringFindCharacterFromSet(allowedCharacters
, stringWithRequiredCharacters
, CFRangeMake(0, CFStringGetLength(allowedCharacters
)), 0, NULL
)){
860 CFArrayAppendValue(requiredCharacterSets
, stringWithRequiredCharacters
);
864 uppercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter
);
865 lowercaseLetterCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetLowercaseLetter
);
866 decimalDigitCharacterSet
= CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit
);
867 CFArrayAppendValue(requiredCharacterSets
, uppercaseLetterCharacterSet
);
868 CFArrayAppendValue(requiredCharacterSets
, lowercaseLetterCharacterSet
);
869 CFArrayAppendValue(requiredCharacterSets
, decimalDigitCharacterSet
);
873 if (CFArrayGetCount(requiredCharacterSets
) > numberOfRequiredRandomCharacters
) {
874 CFReleaseNull(requiredCharacterSets
);
875 requiredCharacterSets
= NULL
;
877 //create new CFDictionary
878 numReqChars
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, numberOfRequiredRandomCharacters
);
879 CFMutableDictionaryRef updatedConstraints
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
880 CFDictionaryAddValue(updatedConstraints
, kSecUseDefaultPasswordFormatKey
, useDefaultPasswordFormat
);
881 CFDictionarySetValue(updatedConstraints
, kSecNumberOfRequiredRandomCharactersKey
, numReqChars
);
882 CFDictionaryAddValue(updatedConstraints
, kSecAllowedCharactersKey
, allowedCharacters
);
883 if(requiredCharacterSets
)
884 CFDictionaryAddValue(updatedConstraints
, kSecRequiredCharacterSetsKey
, requiredCharacterSets
);
886 //add the prohibited characters string if it exists to the new dictionary
887 if(prohibitedCharacters
)
888 CFDictionaryAddValue(updatedConstraints
, kSecPasswordDisallowedCharacters
, prohibitedCharacters
);
890 //add the characters the password can't end with if it exists to the new dictionary
892 CFDictionaryAddValue(updatedConstraints
, kSecPasswordCantEndWithChars
, endWith
);
894 //add the characters the password can't start with if it exists to the new dictionary
896 CFDictionaryAddValue(updatedConstraints
, kSecPasswordCantStartWithChars
, startWith
);
899 CFDictionaryAddValue(updatedConstraints
, kSecPasswordGroupSize
, groupSizeRef
);
901 if(numberOfGroupsRef
)
902 CFDictionaryAddValue(updatedConstraints
, kSecPasswordNumberOfGroups
, numberOfGroupsRef
);
905 CFDictionaryAddValue(updatedConstraints
, kSecPasswordSeparator
, separatorRef
);
907 if(atMostCharactersRef
)
908 CFDictionaryAddValue(updatedConstraints
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, atMostCharactersRef
);
910 if(atLeastCharactersRef
)
911 CFDictionaryAddValue(updatedConstraints
, kSecPasswordContainsAtLeastNSpecificCharacters
, atLeastCharactersRef
);
914 CFDictionaryAddValue(updatedConstraints
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, identicalRef
);
916 CFReleaseNull(useDefaultPasswordFormat
);
917 CFReleaseNull(numReqChars
);
918 CFReleaseNull(allowedCharacters
);
919 CFReleaseNull(requiredCharacterSets
);
921 *returned
= CFDictionaryCreateCopy(kCFAllocatorDefault
, updatedConstraints
);
924 static bool isDictionaryFormattedProperly(SecPasswordType type
, CFDictionaryRef passwordRequirements
, CFErrorRef
*error
){
926 CFTypeRef defaults
= NULL
;
927 CFErrorRef tempError
= NULL
;
928 if(passwordRequirements
== NULL
){
932 if( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDefaultForType
, &defaults
) ){
933 if(isString(defaults
) == true && 0 == CFStringCompare(defaults
, CFSTR("true"), 0)){
937 //only need to check max and min pin length formatting
938 if(type
== kSecPasswordTypePIN
){
939 CFTypeRef minTest
= NULL
, maxTest
= NULL
;
941 CFIndex minPasswordLength
= 0, maxPasswordLength
= 0;
943 if( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDefaultForType
, &defaults
) ){
944 if(isString(defaults
) == true && 0 == CFStringCompare(defaults
, CFSTR("true"), 0)){
948 //check if the values exist!
949 if( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMaxLengthKey
, &maxTest
) ){
950 require_action_quiet(isNull(maxTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a max length"), (CFIndex
)errSecBadReq
, NULL
));
951 require_action_quiet(isNumber(maxTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's max length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
954 if (CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMinLengthKey
, &minTest
) ){
955 require_action_quiet(isNull(minTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a min length"), (CFIndex
)errSecBadReq
, NULL
));
956 require_action_quiet(isNumber(minTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's min length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
958 //check if the values exist!
960 CFNumberRef max
= (CFNumberRef
)maxTest
;
961 CFNumberGetValue(max
, kCFNumberSInt64Type
, &valuePtr
);
962 maxPasswordLength
= (long)valuePtr
;
965 CFNumberRef min
= (CFNumberRef
)minTest
;
966 CFNumberGetValue(min
, kCFNumberSInt64Type
, &valuePtr
);
967 minPasswordLength
= (long)valuePtr
;
969 //make sure min and max make sense respective to each other and that they aren't less than 4 digits.
970 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
));
971 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
));
974 CFTypeRef allowedTest
, maxTest
, minTest
, requiredTest
, prohibitedCharacters
, endWith
, startWith
,
975 groupSizeRef
, numberOfGroupsRef
, separatorRef
, atMostCharactersRef
,
976 atLeastCharactersRef
, thresholdRef
, identicalRef
, characters
;
979 //check if the values exist!
980 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
));
981 require_action_quiet( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMaxLengthKey
, &maxTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a max length"), (CFIndex
)errSecBadReq
, NULL
));
982 require_action_quiet( CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordMinLengthKey
, &minTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a min length"), (CFIndex
)errSecBadReq
, NULL
));
983 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
));
985 //check if values are null?
986 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
));
987 require_action_quiet(isNull(maxTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a max length"), (CFIndex
)errSecBadReq
, NULL
));
988 require_action_quiet(isNull(minTest
)!= true, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("To generate a password, need a min length"), (CFIndex
)errSecBadReq
, NULL
));
989 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
));
991 //check if the values are correct
992 require_action_quiet(isString(allowedTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's allowed characters must be a CFStringRef"), (CFIndex
)errSecBadReq
, NULL
));
993 require_action_quiet(isNumber(maxTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's max length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
994 require_action_quiet(isNumber(minTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's min length must be a CFNumberRef"), (CFIndex
)errSecBadReq
, NULL
));
995 require_action_quiet(isArray(requiredTest
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's required characters must be an array of CFCharacterSetRefs"), (CFIndex
)errSecBadReq
, NULL
));
997 CFNumberGetValue(minTest
, kCFNumberSInt64Type
, &valuePtr
);
998 CFIndex minPasswordLength
= (long)valuePtr
;
999 CFNumberGetValue(maxTest
, kCFNumberSInt64Type
, &valuePtr
);
1000 CFIndex maxPasswordLength
= (long)valuePtr
;
1002 require_action_quiet(minPasswordLength
<= maxPasswordLength
, fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The password's length parameters make no sense ( is max < min ?)"), (CFIndex
)errSecBadReq
, NULL
));
1004 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
));
1005 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
));
1007 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDisallowedCharacters
, &prohibitedCharacters
)){
1008 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
));
1009 require_action_quiet(isString(prohibitedCharacters
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("Disallowed Characters dictionary parameter is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1011 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordCantEndWithChars
, &endWith
)){
1012 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
));
1013 require_action_quiet(isString(endWith
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'EndWith' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1015 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordCantStartWithChars
, &startWith
)){
1016 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
));
1017 require_action_quiet(isString(startWith
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'StartWith' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1019 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordGroupSize
, &groupSizeRef
)){
1020 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
));
1021 require_action_quiet(isNumber(groupSizeRef
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'groupsize' is either null or not a number"), (CFIndex
)errSecBadReq
, NULL
));
1023 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordNumberOfGroups
, &numberOfGroupsRef
)){
1024 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
));
1025 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
));
1027 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordSeparator
, &separatorRef
)){
1028 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
));
1029 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
));
1032 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, &atMostCharactersRef
)){
1033 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
));
1034 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
));
1036 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
));
1037 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
));
1038 require_action_quiet(isNumber(thresholdRef
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex
)errSecBadReq
, NULL
));
1040 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
));
1041 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
));
1042 require_action_quiet(isString(characters
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1045 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordContainsAtLeastNSpecificCharacters
, &atLeastCharactersRef
)){
1046 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
));
1047 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
));
1049 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
));
1051 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
));
1052 require_action_quiet(isNumber(thresholdRef
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'characters' is either null or not a number"), (CFIndex
)errSecBadReq
, NULL
));
1054 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
));
1055 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
));
1056 require_action_quiet(isString(characters
), fail
, tempError
= CFErrorCreate(kCFAllocatorDefault
, CFSTR("The dictionary parameter 'Characters' is either null or not a string"), (CFIndex
)errSecBadReq
, NULL
));
1059 if(CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, &identicalRef
)){
1060 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
));
1061 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
));
1068 if (tempError
!= NULL
) {
1070 *error
= CFRetainSafe(tempError
);
1074 CFReleaseNull(tempError
);
1079 static bool doesFinalPasswordPass(bool isSimple
, CFStringRef password
, CFDictionaryRef requirements
){
1081 CFTypeRef characters
, identicalRef
= NULL
, NRef
= NULL
, endWith
= NULL
, startWith
= NULL
, atLeastCharacters
= NULL
, atMostCharacters
= NULL
;
1083 CFIndex N
, identicalCount
;
1084 CFArrayRef requiredCharacterSet
= (CFArrayRef
)CFDictionaryGetValue(requirements
, kSecRequiredCharacterSetsKey
);
1086 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantEndWithChars
, &endWith
))
1089 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordCantStartWithChars
, &startWith
))
1092 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsAtLeastNSpecificCharacters
, &atLeastCharacters
))
1093 atLeastCharacters
= NULL
;
1095 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNSpecificCharacters
, &atMostCharacters
))
1096 atMostCharacters
= NULL
;
1098 if(!CFDictionaryGetValueIfPresent(requirements
, kSecPasswordContainsNoMoreThanNConsecutiveIdenticalCharacters
, &identicalRef
))
1099 identicalRef
= NULL
;
1101 CFNumberGetValue((CFNumberRef
)identicalRef
, kCFNumberSInt64Type
, &valuePtr
);
1102 identicalCount
= (long)valuePtr
;
1106 if(!doesPasswordEndWith(password
, endWith
))
1109 if(startWith
!= NULL
){
1110 if(!doesPasswordStartWith(password
, startWith
))
1113 if(atLeastCharacters
!= NULL
){
1114 NRef
= CFDictionaryGetValue(atLeastCharacters
, kSecPasswordCharacterCount
);
1115 characters
= CFDictionaryGetValue(atLeastCharacters
, kSecPasswordCharacters
);
1116 CFNumberGetValue((CFNumberRef
)NRef
, kCFNumberSInt64Type
, &valuePtr
);
1118 if(!passwordContainsAtLeastNCharacters(password
, characters
, N
))
1121 if(atMostCharacters
!= NULL
){
1122 NRef
= CFDictionaryGetValue(atMostCharacters
, kSecPasswordCharacterCount
);
1123 characters
= CFDictionaryGetValue(atMostCharacters
, kSecPasswordCharacters
);
1124 CFNumberGetValue((CFNumberRef
)NRef
, kCFNumberSInt64Type
, &valuePtr
);
1126 if(!passwordContainsLessThanNCharacters(password
, characters
, N
))
1129 if(identicalRef
!= NULL
){
1130 if(!passwordContainsLessThanNIdenticalCharacters(password
, identicalCount
))
1133 if (!passwordContainsRequiredCharacters(password
, requiredCharacterSet
))
1136 if(true == SecPasswordIsPasswordWeak2(isSimple
, password
))
1142 //entry point into password generation
1143 CF_RETURNS_RETAINED CFStringRef
SecPasswordGenerate(SecPasswordType type
, CFErrorRef
*error
, CFDictionaryRef passwordRequirements
){
1144 bool check
= false, isSimple
= false;
1145 CFTypeRef separator
= NULL
, defaults
= NULL
, groupSizeRef
= NULL
, numberOfGroupsRef
= NULL
;
1146 CFDictionaryRef properlyFormattedRequirements
= NULL
;
1147 CFErrorRef localError
= NULL
;
1148 uint64_t valuePtr
, groupSize
, numberOfGroups
;
1149 CFNumberRef numberOfRequiredRandomCharacters
;
1150 CFIndex requiredCharactersSize
;
1151 CFStringRef randomCharacters
= NULL
, password
= NULL
, allowedChars
= NULL
;
1152 CFMutableStringRef finalPassword
= NULL
;
1154 if(type
== kSecPasswordTypePIN
)
1158 check
= isDictionaryFormattedProperly(type
, passwordRequirements
, &localError
);
1159 require_quiet(check
!= false, fail
);
1161 //should we generate defaults?
1162 if(passwordRequirements
== NULL
|| (CFDictionaryGetValueIfPresent(passwordRequirements
, kSecPasswordDefaultForType
, &defaults
) && isString(defaults
) == true && 0 == CFStringCompare(defaults
, CFSTR("true"), 0) ))
1163 passwordGenerateDefaultParametersDictionary(&properlyFormattedRequirements
, type
, passwordRequirements
);
1165 passwordGenerationParametersDictionary(&properlyFormattedRequirements
, type
, passwordRequirements
);
1167 CFRetain(properlyFormattedRequirements
);
1169 require_quiet(localError
== NULL
&& properlyFormattedRequirements
!= NULL
, fail
);
1171 numberOfRequiredRandomCharacters
= (CFNumberRef
)CFDictionaryGetValue(properlyFormattedRequirements
, kSecNumberOfRequiredRandomCharactersKey
);
1172 CFNumberGetValue(numberOfRequiredRandomCharacters
, kCFNumberSInt64Type
, &valuePtr
);
1173 requiredCharactersSize
= (long)valuePtr
;
1175 if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements
, kSecPasswordGroupSize
, &groupSizeRef
)){
1176 groupSizeRef
= NULL
;
1179 CFNumberGetValue((CFNumberRef
)groupSizeRef
, kCFNumberSInt64Type
, &groupSize
);
1181 if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements
, kSecPasswordNumberOfGroups
, &numberOfGroupsRef
)){
1182 numberOfGroupsRef
= NULL
;
1185 CFNumberGetValue((CFNumberRef
)numberOfGroupsRef
, kCFNumberSInt64Type
, &numberOfGroups
);
1188 allowedChars
= CFDictionaryGetValue(properlyFormattedRequirements
, kSecAllowedCharactersKey
);
1189 getPasswordRandomCharacters(&randomCharacters
, properlyFormattedRequirements
, &requiredCharactersSize
, allowedChars
);
1191 if(numberOfGroupsRef
&& groupSizeRef
){
1192 finalPassword
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
1194 if(!CFDictionaryGetValueIfPresent(properlyFormattedRequirements
, kSecPasswordSeparator
, &separator
))
1197 if(separator
== NULL
)
1198 separator
= CFSTR("-");
1201 while( i
!= requiredCharactersSize
){
1202 if((i
+ (CFIndex
)groupSize
) < requiredCharactersSize
){
1203 CFStringAppend(finalPassword
, CFStringCreateWithSubstring(kCFAllocatorDefault
, randomCharacters
, CFRangeMake(i
, (CFIndex
)groupSize
)));
1204 CFStringAppend(finalPassword
, separator
);
1207 else if((i
+(CFIndex
)groupSize
) == requiredCharactersSize
){
1208 CFStringAppend(finalPassword
, CFStringCreateWithSubstring(kCFAllocatorDefault
, randomCharacters
, CFRangeMake(i
, (CFIndex
)groupSize
)));
1212 CFStringAppend(finalPassword
, CFStringCreateWithSubstring(kCFAllocatorDefault
, randomCharacters
, CFRangeMake(i
, requiredCharactersSize
- i
)));
1213 i
+=(requiredCharactersSize
- i
);
1216 password
= CFStringCreateCopy(kCFAllocatorDefault
, finalPassword
);
1217 CFReleaseNull(finalPassword
);
1219 //no fancy formatting
1221 password
= CFStringCreateCopy(kCFAllocatorDefault
, randomCharacters
);
1224 CFReleaseNull(randomCharacters
);
1225 require_quiet(doesFinalPasswordPass(isSimple
, password
, properlyFormattedRequirements
), no_pass
);
1229 CFReleaseNull(password
);
1233 if (error
&& localError
) {
1234 *error
= localError
;
1238 CFReleaseSafe(localError
);
1239 CFReleaseNull(properlyFormattedRequirements
);
1243 const char *in_word_set (const char *str
, unsigned int len
){
1244 static const char * wordlist
[] =
1246 "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
1247 "", "", "0103", "", "", "", "", "0123", "", "", "", "", "0303", "", "", "",
1248 "", "", "", "", "0110", "", "1103", "", "", "", "", "1123", "", "", "0000",
1249 "", "1203", "", "0404", "", "", "", "", "1234", "1110", "2015", "2013", "",
1250 "2014", "1010", "2005", "2003", "", "2004", "1210", "0505", "0111", "", "",
1251 "", "2008", "0101", "", "2007", "", "", "", "", "2006", "2010", "1995", "1993",
1252 "", "1994", "2000", "", "1111", "", "", "", "1998", "1101", "", "1997", "",
1253 "0808", "1211", "", "1996", "0102", "", "1201", "", "", "1990", "", "", "",
1254 "", "0202", "", "2011", "", "", "1112", "1958", "2001", "", "1957", "1102",
1255 "", "3333", "", "1956", "1212", "1985", "1983", "", "1984", "1202", "", "0909",
1256 "", "0606", "", "1988", "1991", "", "1987", "2012", "", "", "", "1986", "2002",
1257 "", "", "", "0707", "1980", "", "2009", "", "", "2222", "1965", "1963", "",
1258 "1964", "", "", "2229", "", "", "1992", "1968", "", "", "1967", "", "", "1999",
1259 "", "1966", "", "1975", "1973", "", "1974", "1960", "", "1981", "", "4444",
1260 "", "1978", "", "7465", "1977", "", "", "", "", "1976", "2580", "", "1959",
1261 "", "", "1970", "", "", "", "", "", "", "", "", "", "1982", "", "1961", "",
1262 "", "5252", "", "1989", "", "", "", "", "", "", "", "", "", "", "", "", "",
1263 "", "1971", "", "", "", "", "", "", "", "1962", "", "5683", "", "6666", "",
1264 "", "1969", "", "", "", "", "", "", "", "", "", "", "", "", "1972", "", "",
1265 "", "", "", "", "1979", "", "", "", "7667"
1268 if (len
<= MAX_WORD_LENGTH
&& len
>= MIN_WORD_LENGTH
)
1270 register int key
= pinhash (str
, len
);
1272 if (key
<= MAX_HASH_VALUE
&& key
>= 0)
1274 register const char *s
= wordlist
[key
];
1275 if (*str
== *s
&& !strcmp (str
+ 1, s
+ 1))
1281 CFDictionaryRef
SecPasswordCopyDefaultPasswordLength(SecPasswordType type
, CFErrorRef
*error
){
1283 CFIndex tupleLengthInt
= 0, numOfTuplesInt
= 0;
1284 CFNumberRef tupleLength
= NULL
;
1285 CFNumberRef numOfTuples
= NULL
;
1287 CFMutableDictionaryRef passwordLengthDefaults
= NULL
;
1288 CFDictionaryRef result
= NULL
;
1291 case(kSecPasswordTypeiCloudRecovery
):
1296 case(kSecPasswordTypePIN
):
1301 case(kSecPasswordTypeSafari
):
1306 case(kSecPasswordTypeWifi
):
1312 if(SecError(errSecBadReq
, error
, CFSTR("Password type does not exist.")) == false)
1314 secdebug("secpasswordcopydefaultpasswordlength", "could not create error!");
1318 if (tupleLengthInt
!= 0 && numOfTuplesInt
!= 0) {
1319 tupleLength
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberCFIndexType
, &tupleLengthInt
);
1320 numOfTuples
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberCFIndexType
, &numOfTuplesInt
);
1321 passwordLengthDefaults
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, 0, 0);
1322 CFDictionaryAddValue(passwordLengthDefaults
, kSecPasswordGroupSize
, tupleLength
);
1323 CFDictionaryAddValue(passwordLengthDefaults
, kSecPasswordNumberOfGroups
, numOfTuples
);
1324 result
= CFDictionaryCreateCopy(kCFAllocatorDefault
, passwordLengthDefaults
);
1327 CFReleaseSafe(tupleLength
);
1328 CFReleaseSafe(numOfTuples
);
1329 CFReleaseSafe(passwordLengthDefaults
);