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