]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_keychain/lib/SecRecoveryPassword.c
Security-57740.60.18.tar.gz
[apple/security.git] / OSX / libsecurity_keychain / lib / SecRecoveryPassword.c
1 /*
2 * Copyright (c) 2010-2012 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include "SecRecoveryPassword.h"
25 #include <Security/SecTransform.h>
26 #include <Security/SecEncodeTransform.h>
27 #include <Security/SecDecodeTransform.h>
28 #include <Security/SecDigestTransform.h>
29 #include <Security/SecEncryptTransform.h>
30 #include <Security/SecItem.h>
31 #include <Security/SecKey.h>
32 #include <CommonCrypto/CommonKeyDerivation.h>
33 #include <CommonCrypto/CommonCryptor.h>
34 #include <CoreFoundation/CFBase.h>
35 #include <fcntl.h>
36 #include <asl.h>
37 #include <stdarg.h>
38 #include <string.h>
39 #include <stdio.h>
40 #include <utilities/SecCFRelease.h>
41 #include <utilities/debugging.h>
42
43 CFStringRef kSecRecVersionNumber = CFSTR("SRVersionNumber");
44 CFStringRef kSecRecQuestions = CFSTR("SRQuestions");
45 CFStringRef kSecRecLocale = CFSTR("SRLocale");
46 CFStringRef kSecRecIV = CFSTR("SRiv");
47 CFStringRef kSecRecWrappedPassword = CFSTR("SRWrappedPassword");
48
49
50 static char *std_log_prefix = "###SecRecovery Function: %s - %s";
51 static const char *std_ident = "Security.framework";
52 static const char *std_facility = "InfoSec";
53 static uint32_t std_options = 0;
54
55 static aslclient aslhandle = NULL;
56 static aslmsg msgptr = NULL;
57
58 // Error Reporting
59
60 void ccdebug_imp(int level, char *funcname, char *format, ...);
61
62 #define secDebug(lvl,fmt,...) sec_debug_imp(lvl, __PRETTY_FUNCTION__, fmt, __VA_ARGS__)
63
64 static void
65 sec_debug_init() {
66 char *ccEnvStdErr = getenv("CC_STDERR");
67
68 if(ccEnvStdErr != NULL && strncmp(ccEnvStdErr, "yes", 3) == 0) std_options |= ASL_OPT_STDERR;
69 aslhandle = asl_open(std_ident, std_facility, std_options);
70
71 msgptr = asl_new(ASL_TYPE_MSG);
72 asl_set(msgptr, ASL_KEY_FACILITY, "com.apple.infosec");
73 }
74
75
76 static void
77 sec_debug_imp(int level, const char *funcname, char *format, ...) {
78 va_list argp;
79 char fmtbuffer[256];
80
81 if(aslhandle == NULL) sec_debug_init();
82
83 sprintf(fmtbuffer, std_log_prefix, funcname, format);
84 va_start(argp, format);
85 asl_vlog(aslhandle, msgptr, level, fmtbuffer, argp);
86 va_end(argp);
87 }
88
89 // Read /dev/random for random bytes
90
91 static CFDataRef
92 getRandomBytes(size_t len)
93 {
94 uint8_t *buffer;
95 CFDataRef randData = NULL;
96 int fdrand;
97
98 if((buffer = malloc(len)) == NULL) return NULL;
99 if((fdrand = open("/dev/random", O_RDONLY)) == -1) return NULL;
100 if(read(fdrand, buffer, len) == len) randData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *) buffer, len);
101 close(fdrand);
102
103 free(buffer);
104 return randData;
105 }
106
107 // This is the normalization routine - subject to change. We need to make sure that whitespace is removed and
108 // that upper/lower case is normalized, etc for all possible languages.
109
110 static void secNormalize(CFMutableStringRef theString, CFLocaleRef theLocale)
111 {
112 CFRange theRange;
113
114 CFStringFold(theString, kCFCompareCaseInsensitive | kCFCompareDiacriticInsensitive | kCFCompareWidthInsensitive, theLocale);
115 CFStringNormalize(theString, kCFStringNormalizationFormKC);
116 CFStringTrimWhitespace(theString);
117 while(CFStringFindCharacterFromSet(theString, CFCharacterSetGetPredefined(kCFCharacterSetWhitespace), CFRangeMake(0, CFStringGetLength(theString)), kCFCompareBackwards, &theRange))
118 CFStringDelete(theString, theRange);
119 }
120
121 /*
122 * This will derive a 128 bit (16 byte) key from a set of answers to questions in a CFArray of CFStrings.
123 * it normalizes each answer and concats them into a collector buffer. The resulting string is run through
124 * PBKDF2-HMAC-SHA256 to form a key.
125 *
126 * Todo: For version 2 it would be better to randomly generate the salt and make the iteration count flexible.
127 * This would require a different return value because that information would need to be returned up the stack
128 * to the callers. Given the time left in this release (Lion) we're going with set values for this.
129 */
130
131 #define RETURN_KEY_SIZE 16
132 #define MAXANSWERBUFF 4096
133 #define PBKDF_ROUNDS 100000
134
135 static SecKeyRef secDeriveKeyFromAnswers(CFArrayRef answers, CFLocaleRef theLocale)
136 {
137 static const uint8_t salt[16] = { 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F };
138 static const int saltLen = sizeof(salt);
139
140 SecKeyRef theKey = NULL;
141 uint8_t rawKeyData[RETURN_KEY_SIZE];
142
143 CFIndex encodedAnswers = 0;
144 CFIndex numAnswers = CFArrayGetCount(answers);
145 const size_t concatenatedAnswersSize = MAXANSWERBUFF * numAnswers;
146
147 char *concatenatedAnswers = (char *)malloc(concatenatedAnswersSize);
148 if (concatenatedAnswers == NULL) {
149 return NULL;
150 }
151
152 concatenatedAnswers[0] = 0; // NUL terminate
153
154 int i;
155 for (i = 0; i < numAnswers; i++) {
156 CFMutableStringRef answer = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFArrayGetValueAtIndex(answers, i));
157 if (answer) {
158 secNormalize(answer, theLocale);
159
160 CFIndex theAnswerLen = CFStringGetLength(answer);
161 CFIndex theAnswerSize = CFStringGetMaximumSizeForEncoding(theAnswerLen, kCFStringEncodingUTF8);
162 char *theAnswer = (char *)malloc(theAnswerSize + 1); // add space for NUL byte
163 if (theAnswer) {
164 if (theAnswerLen == CFStringGetBytes(answer, CFRangeMake(0, CFStringGetLength(answer)), kCFStringEncodingUTF8, '?', FALSE, (UInt8*)theAnswer, theAnswerSize, &theAnswerSize)) {
165 theAnswer[theAnswerSize] = 0; // NUL terminate
166 if (strlcat(concatenatedAnswers, theAnswer, concatenatedAnswersSize) < concatenatedAnswersSize) {
167 encodedAnswers += 1;
168 }
169 }
170 bzero(theAnswer, theAnswerSize);
171 free(theAnswer);
172 }
173 CFRelease(answer);
174 }
175 }
176
177 // one or more of the answers failed to encode
178 if (encodedAnswers != numAnswers) {
179 free(concatenatedAnswers);
180 return NULL;
181 }
182
183 if (CCKeyDerivationPBKDF(kCCPBKDF2, concatenatedAnswers, strlen(concatenatedAnswers), salt, saltLen, kCCPRFHmacAlgSHA256, PBKDF_ROUNDS, rawKeyData, RETURN_KEY_SIZE)) {
184 free(concatenatedAnswers);
185 return NULL;
186 }
187
188 CFDataRef keyData = CFDataCreate(kCFAllocatorDefault, rawKeyData, RETURN_KEY_SIZE);
189 if (keyData) {
190 CFMutableDictionaryRef params = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
191 if (params) {
192 CFErrorRef error = NULL;
193 CFDictionaryAddValue(params, kSecAttrKeyType, kSecAttrKeyTypeAES);
194 theKey = SecKeyCreateFromData(params, keyData, &error);
195 if (error) {
196 CFRelease(error);
197 }
198 CFRelease(params);
199 }
200 CFRelease(keyData);
201 }
202
203 bzero(rawKeyData, RETURN_KEY_SIZE);
204 bzero(concatenatedAnswers, concatenatedAnswersSize);
205 free(concatenatedAnswers);
206 return theKey;
207 }
208
209
210 // Single shot CFString processing routines for digests/encoding/encrypt/decrypt
211
212 static CFDataRef
213 digestString(CFStringRef str)
214 {
215 CFDataRef retval = NULL;
216 CFErrorRef error = NULL;
217
218 CFDataRef inputString = CFStringCreateExternalRepresentation(kCFAllocatorDefault, str, kCFStringEncodingUTF8, 0xff);
219
220 SecTransformRef digestTrans = SecDigestTransformCreate(kSecDigestSHA2, 256, &error);
221 if(error == NULL) {
222 SecTransformSetAttribute(digestTrans, kSecTransformInputAttributeName, inputString, &error);
223 if(error == NULL) {
224 retval = SecTransformExecute(digestTrans, &error);
225 if(retval == NULL) {
226 secDebug(ASL_LEVEL_ERR, "Couldn't create digest %s\n", CFStringGetCStringPtr(CFErrorCopyFailureReason(error), kCFStringEncodingUTF8));
227 }
228 }
229 CFRelease(digestTrans);
230 }
231 CFRelease(inputString);
232 return retval;
233 }
234
235 static CFDataRef CF_RETURNS_RETAINED
236 b64encode(CFDataRef input)
237 {
238 CFDataRef retval = NULL;
239 CFErrorRef error = NULL;
240 SecTransformRef encodeTrans = SecEncodeTransformCreate(kSecBase64Encoding, &error);
241 if(error == NULL) SecTransformSetAttribute(encodeTrans, kSecTransformInputAttributeName, input, &error);
242 if(error == NULL) retval = SecTransformExecute(encodeTrans, &error);
243 if(encodeTrans) CFRelease(encodeTrans);
244 return retval;
245 }
246
247 static CFDataRef CF_RETURNS_RETAINED
248 b64decode(CFDataRef input)
249 {
250 CFDataRef retval = NULL;
251 CFErrorRef error = NULL;
252 SecTransformRef decodeTrans = SecDecodeTransformCreate(kSecBase64Encoding, &error);
253 if(error == NULL) SecTransformSetAttribute(decodeTrans, kSecTransformInputAttributeName, input, &error);
254 if(error == NULL) retval = SecTransformExecute(decodeTrans, &error);
255 if(decodeTrans) CFRelease(decodeTrans);
256 return retval;
257 }
258
259 static CFDataRef
260 encryptString(SecKeyRef wrapKey, CFDataRef iv, CFStringRef str)
261 {
262 CFDataRef retval = NULL;
263 CFErrorRef error = NULL;
264 CFDataRef inputString = CFStringCreateExternalRepresentation(kCFAllocatorDefault, str, kCFStringEncodingMacRoman, 0xff);
265 SecTransformRef encrypt = NULL;
266 SecTransformRef encode = NULL;
267 SecTransformRef group = NULL;
268
269 encrypt = SecEncryptTransformCreate(wrapKey, &error);
270 if (error) goto out;
271 SecTransformSetAttribute(encrypt, kSecEncryptionMode, kSecModeCBCKey, &error);
272 if (error) goto out;
273 SecTransformSetAttribute(encrypt, kSecPaddingKey, kSecPaddingPKCS7Key, &error);
274 if (error) goto out;
275 SecTransformSetAttribute(encrypt, kSecTransformInputAttributeName, inputString, &error);
276 if (error) goto out;
277 SecTransformSetAttribute(encrypt, kSecIVKey, iv, &error);
278 if (error) goto out;
279
280 encode = SecEncodeTransformCreate(kSecBase64Encoding, &error);
281 if (error) goto out;
282
283 group = SecTransformCreateGroupTransform();
284 SecTransformConnectTransforms(encrypt, kSecTransformOutputAttributeName, encode, kSecTransformInputAttributeName, group, &error);
285 if (error) goto out;
286 retval = SecTransformExecute(group, &error);
287 if (error) goto out;
288
289 out:
290 if (error) {
291 secerror("Failed to encrypt recovery password: %@", error);
292 }
293
294 CFReleaseNull(error);
295 CFReleaseNull(inputString);
296 CFReleaseNull(encrypt);
297 CFReleaseNull(encode);
298 CFReleaseNull(group);
299
300 return retval;
301 }
302
303
304 static CFStringRef CF_RETURNS_RETAINED
305 decryptString(SecKeyRef wrapKey, CFDataRef iv, CFDataRef wrappedPassword)
306 {
307 CFStringRef retval = NULL;
308 CFDataRef retData = NULL;
309 CFErrorRef error = NULL;
310 SecTransformRef decode = NULL;
311 SecTransformRef decrypt = NULL;
312 SecTransformRef group = NULL;
313
314 decode = SecDecodeTransformCreate(kSecBase64Encoding, &error);
315 if (error) goto out;
316 SecTransformSetAttribute(decode, kSecTransformInputAttributeName, wrappedPassword, &error);
317 if (error) goto out;
318
319 decrypt = SecDecryptTransformCreate(wrapKey, &error);
320 if (error) goto out;
321 SecTransformSetAttribute(decrypt, kSecEncryptionMode, kSecModeCBCKey, &error);
322 if (error) goto out;
323 SecTransformSetAttribute(decrypt, kSecPaddingKey, kSecPaddingPKCS7Key, &error);
324 if (error) goto out;
325 SecTransformSetAttribute(decrypt, kSecIVKey, iv, &error);
326 if (error) goto out;
327
328 group = SecTransformCreateGroupTransform();
329 SecTransformConnectTransforms(decode, kSecTransformOutputAttributeName, decrypt, kSecTransformInputAttributeName, group, &error);
330 if (error) goto out;
331 retData = SecTransformExecute(group, &error);
332 if (error) goto out;
333 retval = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, retData, kCFStringEncodingMacRoman);
334
335 out:
336 if (error) {
337 secerror("Failed to decrypt recovery password: %@", error);
338 }
339
340 CFReleaseNull(retData);
341 CFReleaseNull(error);
342 CFReleaseNull(decode);
343 CFReleaseNull(decrypt);
344 CFReleaseNull(group);
345
346 return retval;
347 }
348
349 // IV for the recovery ref is currently the leftmost 16 bytes of the digest of the recovery password.
350 #define IVBYTECOUNT 16
351
352 static CFDataRef
353 createIVFromPassword(CFStringRef password)
354 {
355 CFDataRef hashedPassword, retval;
356 CFMutableDataRef iv;
357 if((hashedPassword = digestString(password)) == NULL) return NULL;
358 iv = CFDataCreateMutableCopy(kCFAllocatorDefault, CFDataGetLength(hashedPassword)+1, hashedPassword);
359 CFDataDeleteBytes(iv, CFRangeMake(IVBYTECOUNT, CFDataGetLength(iv)-IVBYTECOUNT));
360 retval = CFDataCreateCopy(kCFAllocatorDefault, iv);
361 CFRelease(hashedPassword);
362 CFRelease(iv);
363 return retval;
364 }
365
366
367 /*
368 * API functions
369 */
370
371 /*
372 * Function: SecWrapRecoveryPasswordWithAnswers
373 * Description: This will wrap a password by using answers to generate a key. The resulting
374 * wrapped password and the questions used to get the answers are saved in a
375 * recovery dictionary.
376 */
377
378 CFDictionaryRef
379 SecWrapRecoveryPasswordWithAnswers(CFStringRef password, CFArrayRef questions, CFArrayRef answers)
380 {
381 uint32_t vers = 1;
382 CFDataRef iv;
383 CFDataRef wrappedPassword;
384 CFMutableDictionaryRef retval = NULL;
385 CFLocaleRef theLocale = CFLocaleCopyCurrent();
386 CFStringRef theLocaleString = CFLocaleGetIdentifier(theLocale);
387
388 CFIndex ix, limit;
389
390 if (!password || !questions || !answers)
391 return NULL;
392
393 limit = CFArrayGetCount(answers);
394 if (limit != CFArrayGetCount(questions))
395 return NULL; // Error
396 CFTypeRef chkval;
397 for (ix=0; ix<limit; ix++)
398 {
399 chkval = CFArrayGetValueAtIndex(answers, ix);
400 if (!chkval || CFGetTypeID(chkval)!=CFStringGetTypeID() || CFEqual((CFStringRef)chkval, CFSTR("")))
401 return NULL;
402 chkval = CFArrayGetValueAtIndex(questions, ix);
403 if (!chkval || CFGetTypeID(chkval)!=CFStringGetTypeID() || CFEqual((CFStringRef)chkval, CFSTR("")))
404 return NULL;
405 }
406
407 iv = createIVFromPassword(password);
408
409 SecKeyRef wrapKey = secDeriveKeyFromAnswers(answers, theLocale);
410
411 if((wrappedPassword = encryptString(wrapKey, iv, password)) != NULL) {
412 retval = CFDictionaryCreateMutable(kCFAllocatorDefault, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
413 CFDictionaryAddValue(retval, kSecRecVersionNumber, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vers));
414 CFDictionaryAddValue(retval, kSecRecQuestions, questions);
415 CFDictionaryAddValue(retval, kSecRecLocale, theLocaleString);
416 CFDictionaryAddValue(retval, kSecRecIV, b64encode(iv));
417 CFDictionaryAddValue(retval, kSecRecWrappedPassword, wrappedPassword);
418 }
419
420 if(wrappedPassword) CFRelease(wrappedPassword);
421 CFRelease(iv);
422 CFRelease(wrapKey);
423 CFRelease(theLocale);
424 CFRelease(theLocaleString);
425
426 return retval;
427 }
428
429 /*
430 * Function: SecUnwrapRecoveryPasswordWithAnswers
431 * Description: This will unwrap a password contained in a recovery dictionary by using answers
432 * to generate a key.
433 */
434
435 CFStringRef
436 SecUnwrapRecoveryPasswordWithAnswers(CFDictionaryRef recref, CFArrayRef answers)
437 {
438 if(answers == NULL || CFArrayGetCount(answers) < 3) return NULL;
439
440 CFStringRef theLocaleString = (CFStringRef) CFDictionaryGetValue(recref, kSecRecLocale);
441 CFDataRef tmpIV = (CFDataRef) CFDictionaryGetValue(recref, kSecRecIV);
442 CFDataRef wrappedPassword = (CFDataRef) CFDictionaryGetValue(recref, kSecRecWrappedPassword);
443
444 if(theLocaleString == NULL || tmpIV == NULL || wrappedPassword == NULL) {
445 return NULL;
446 }
447
448
449 CFLocaleRef theLocale = CFLocaleCreate(kCFAllocatorDefault, theLocaleString);
450 SecKeyRef wrapKey = secDeriveKeyFromAnswers(answers, theLocale);
451 CFRelease(theLocaleString);
452 CFRelease(theLocale);
453
454 CFDataRef iv = b64decode(tmpIV);
455
456 CFStringRef recoveryPassword = decryptString(wrapKey, iv, wrappedPassword);
457 CFRelease(wrapKey);
458
459 if(recoveryPassword != NULL) {
460 CFDataRef comphash = createIVFromPassword(recoveryPassword);
461 if(!CFEqual(comphash, iv)) {
462 secDebug(ASL_LEVEL_ERR, "Failed reconstitution of password for recovery\n", NULL);
463 CFRelease(recoveryPassword);
464 recoveryPassword = NULL;
465 }
466 CFRelease(comphash);
467 }
468 CFRelease(iv);
469 return recoveryPassword;
470 }
471
472 /*
473 * Function: SecCreateRecoveryPassword
474 * Description: This function will get a random 128 bit number and base32
475 * encode and format that value
476 */
477
478 CFStringRef
479 SecCreateRecoveryPassword(void)
480 {
481 CFStringRef result = NULL;
482 CFErrorRef error = NULL;
483 CFDataRef encodedData = NULL;
484 CFDataRef randData = getRandomBytes(16);
485 int i;
486
487 // base32FDE is a "private" base32 encoding, it has no 0/O or L/l/1 in it (it uses 8 and 9).
488 SecTransformRef encodeTrans = SecEncodeTransformCreate(CFSTR("base32FDE"), &error);
489 if(error == NULL) {
490 SecTransformSetAttribute(encodeTrans, kSecTransformInputAttributeName, randData, &error);
491 if(error == NULL) encodedData = SecTransformExecute(encodeTrans, &error);
492 CFRelease(encodeTrans);
493 }
494 CFRelease(randData);
495
496 if(encodedData != NULL && error == NULL) {
497 CFStringRef b32string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, encodedData, kCFStringEncodingMacRoman);
498 CFMutableStringRef encodedString = CFStringCreateMutableCopy(kCFAllocatorDefault, 64, b32string);
499
500 // Add some hyphens to make the generated password easier to use
501 for(i = 4; i < 34; i += 5) CFStringInsert(encodedString, i, CFSTR("-"));
502 // Trim so the last section is 4 characters long
503 CFStringDelete(encodedString, CFRangeMake(29,CFStringGetLength(encodedString)-29));
504 result = CFStringCreateCopy(kCFAllocatorDefault, encodedString);
505 CFRelease(encodedString);
506 CFRelease(b32string);
507 CFRelease(encodedData);
508 } else {
509 secDebug(ASL_LEVEL_ERR, "Failed to base32 encode random data for recovery password\n", NULL);
510 }
511
512 return result;
513
514 }