]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2010-2012 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
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> | |
866f8763 | 32 | #include <Security/SecRandom.h> |
b1ab9ed8 A |
33 | #include <CommonCrypto/CommonKeyDerivation.h> |
34 | #include <CommonCrypto/CommonCryptor.h> | |
35 | #include <CoreFoundation/CFBase.h> | |
36 | #include <fcntl.h> | |
37 | #include <asl.h> | |
38 | #include <stdarg.h> | |
39 | #include <string.h> | |
40 | #include <stdio.h> | |
6b200bc3 | 41 | #include <utilities/SecCFRelease.h> |
914fc88e | 42 | #include <utilities/debugging.h> |
b1ab9ed8 A |
43 | |
44 | CFStringRef kSecRecVersionNumber = CFSTR("SRVersionNumber"); | |
45 | CFStringRef kSecRecQuestions = CFSTR("SRQuestions"); | |
46 | CFStringRef kSecRecLocale = CFSTR("SRLocale"); | |
47 | CFStringRef kSecRecIV = CFSTR("SRiv"); | |
48 | CFStringRef kSecRecWrappedPassword = CFSTR("SRWrappedPassword"); | |
49 | ||
50 | ||
51 | static char *std_log_prefix = "###SecRecovery Function: %s - %s"; | |
52 | static const char *std_ident = "Security.framework"; | |
53 | static const char *std_facility = "InfoSec"; | |
54 | static uint32_t std_options = 0; | |
55 | ||
56 | static aslclient aslhandle = NULL; | |
57 | static aslmsg msgptr = NULL; | |
58 | ||
59 | // Error Reporting | |
60 | ||
61 | void ccdebug_imp(int level, char *funcname, char *format, ...); | |
62 | ||
63 | #define secDebug(lvl,fmt,...) sec_debug_imp(lvl, __PRETTY_FUNCTION__, fmt, __VA_ARGS__) | |
64 | ||
65 | static void | |
66 | sec_debug_init() { | |
67 | char *ccEnvStdErr = getenv("CC_STDERR"); | |
68 | ||
69 | if(ccEnvStdErr != NULL && strncmp(ccEnvStdErr, "yes", 3) == 0) std_options |= ASL_OPT_STDERR; | |
70 | aslhandle = asl_open(std_ident, std_facility, std_options); | |
71 | ||
72 | msgptr = asl_new(ASL_TYPE_MSG); | |
73 | asl_set(msgptr, ASL_KEY_FACILITY, "com.apple.infosec"); | |
74 | } | |
75 | ||
76 | ||
77 | static void | |
78 | sec_debug_imp(int level, const char *funcname, char *format, ...) { | |
79 | va_list argp; | |
80 | char fmtbuffer[256]; | |
81 | ||
82 | if(aslhandle == NULL) sec_debug_init(); | |
83 | ||
84 | sprintf(fmtbuffer, std_log_prefix, funcname, format); | |
85 | va_start(argp, format); | |
86 | asl_vlog(aslhandle, msgptr, level, fmtbuffer, argp); | |
87 | va_end(argp); | |
88 | } | |
89 | ||
90 | // Read /dev/random for random bytes | |
91 | ||
92 | static CFDataRef | |
866f8763 | 93 | createRandomBytes(size_t len) |
b1ab9ed8 | 94 | { |
866f8763 A |
95 | CFMutableDataRef data = CFDataCreateMutable(NULL, len); |
96 | if (data == NULL) | |
97 | return NULL; | |
98 | CFDataSetLength(data, len); | |
99 | if (SecRandomCopyBytes(kSecRandomDefault, len, CFDataGetMutableBytePtr(data)) != noErr) { | |
100 | CFRelease(data); | |
101 | return NULL; | |
102 | } | |
103 | return data; | |
b1ab9ed8 A |
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 | |
b1ab9ed8 | 133 | |
866f8763 A |
134 | static SecKeyRef CF_RETURNS_RETAINED |
135 | secDeriveKeyFromAnswers(CFArrayRef answers, CFLocaleRef theLocale) | |
b1ab9ed8 A |
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 | |
866f8763 | 213 | createDigestString(CFStringRef str) |
b1ab9ed8 A |
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) { | |
866f8763 A |
226 | CFStringRef errorReason = CFErrorCopyFailureReason(error); |
227 | secDebug(ASL_LEVEL_ERR, "Couldn't create digest %s\n", CFStringGetCStringPtr(errorReason, kCFStringEncodingUTF8)); | |
228 | CFReleaseNull(errorReason); | |
b1ab9ed8 A |
229 | } |
230 | } | |
b1ab9ed8 | 231 | } |
866f8763 A |
232 | CFReleaseNull(digestTrans); |
233 | CFReleaseNull(inputString); | |
b1ab9ed8 A |
234 | return retval; |
235 | } | |
236 | ||
6b200bc3 | 237 | static CFDataRef CF_RETURNS_RETAINED |
b1ab9ed8 A |
238 | b64encode(CFDataRef input) |
239 | { | |
427c49bc | 240 | CFDataRef retval = NULL; |
b1ab9ed8 A |
241 | CFErrorRef error = NULL; |
242 | SecTransformRef encodeTrans = SecEncodeTransformCreate(kSecBase64Encoding, &error); | |
243 | if(error == NULL) SecTransformSetAttribute(encodeTrans, kSecTransformInputAttributeName, input, &error); | |
244 | if(error == NULL) retval = SecTransformExecute(encodeTrans, &error); | |
245 | if(encodeTrans) CFRelease(encodeTrans); | |
246 | return retval; | |
247 | } | |
248 | ||
6b200bc3 | 249 | static CFDataRef CF_RETURNS_RETAINED |
b1ab9ed8 A |
250 | b64decode(CFDataRef input) |
251 | { | |
427c49bc | 252 | CFDataRef retval = NULL; |
b1ab9ed8 A |
253 | CFErrorRef error = NULL; |
254 | SecTransformRef decodeTrans = SecDecodeTransformCreate(kSecBase64Encoding, &error); | |
255 | if(error == NULL) SecTransformSetAttribute(decodeTrans, kSecTransformInputAttributeName, input, &error); | |
256 | if(error == NULL) retval = SecTransformExecute(decodeTrans, &error); | |
257 | if(decodeTrans) CFRelease(decodeTrans); | |
258 | return retval; | |
259 | } | |
260 | ||
866f8763 | 261 | static CFDataRef CF_RETURNS_RETAINED |
b1ab9ed8 A |
262 | encryptString(SecKeyRef wrapKey, CFDataRef iv, CFStringRef str) |
263 | { | |
264 | CFDataRef retval = NULL; | |
265 | CFErrorRef error = NULL; | |
266 | CFDataRef inputString = CFStringCreateExternalRepresentation(kCFAllocatorDefault, str, kCFStringEncodingMacRoman, 0xff); | |
914fc88e A |
267 | SecTransformRef encrypt = NULL; |
268 | SecTransformRef encode = NULL; | |
269 | SecTransformRef group = NULL; | |
270 | ||
271 | encrypt = SecEncryptTransformCreate(wrapKey, &error); | |
272 | if (error) goto out; | |
273 | SecTransformSetAttribute(encrypt, kSecEncryptionMode, kSecModeCBCKey, &error); | |
274 | if (error) goto out; | |
275 | SecTransformSetAttribute(encrypt, kSecPaddingKey, kSecPaddingPKCS7Key, &error); | |
276 | if (error) goto out; | |
277 | SecTransformSetAttribute(encrypt, kSecTransformInputAttributeName, inputString, &error); | |
278 | if (error) goto out; | |
279 | SecTransformSetAttribute(encrypt, kSecIVKey, iv, &error); | |
280 | if (error) goto out; | |
281 | ||
282 | encode = SecEncodeTransformCreate(kSecBase64Encoding, &error); | |
283 | if (error) goto out; | |
284 | ||
285 | group = SecTransformCreateGroupTransform(); | |
286 | SecTransformConnectTransforms(encrypt, kSecTransformOutputAttributeName, encode, kSecTransformInputAttributeName, group, &error); | |
287 | if (error) goto out; | |
288 | retval = SecTransformExecute(group, &error); | |
289 | if (error) goto out; | |
290 | ||
291 | out: | |
292 | if (error) { | |
293 | secerror("Failed to encrypt recovery password: %@", error); | |
b1ab9ed8 | 294 | } |
914fc88e A |
295 | |
296 | CFReleaseNull(error); | |
297 | CFReleaseNull(inputString); | |
298 | CFReleaseNull(encrypt); | |
299 | CFReleaseNull(encode); | |
300 | CFReleaseNull(group); | |
301 | ||
b1ab9ed8 A |
302 | return retval; |
303 | } | |
304 | ||
305 | ||
6b200bc3 | 306 | static CFStringRef CF_RETURNS_RETAINED |
b1ab9ed8 A |
307 | decryptString(SecKeyRef wrapKey, CFDataRef iv, CFDataRef wrappedPassword) |
308 | { | |
309 | CFStringRef retval = NULL; | |
310 | CFDataRef retData = NULL; | |
914fc88e A |
311 | CFErrorRef error = NULL; |
312 | SecTransformRef decode = NULL; | |
313 | SecTransformRef decrypt = NULL; | |
314 | SecTransformRef group = NULL; | |
315 | ||
316 | decode = SecDecodeTransformCreate(kSecBase64Encoding, &error); | |
317 | if (error) goto out; | |
318 | SecTransformSetAttribute(decode, kSecTransformInputAttributeName, wrappedPassword, &error); | |
319 | if (error) goto out; | |
320 | ||
321 | decrypt = SecDecryptTransformCreate(wrapKey, &error); | |
322 | if (error) goto out; | |
323 | SecTransformSetAttribute(decrypt, kSecEncryptionMode, kSecModeCBCKey, &error); | |
324 | if (error) goto out; | |
325 | SecTransformSetAttribute(decrypt, kSecPaddingKey, kSecPaddingPKCS7Key, &error); | |
326 | if (error) goto out; | |
327 | SecTransformSetAttribute(decrypt, kSecIVKey, iv, &error); | |
328 | if (error) goto out; | |
329 | ||
330 | group = SecTransformCreateGroupTransform(); | |
331 | SecTransformConnectTransforms(decode, kSecTransformOutputAttributeName, decrypt, kSecTransformInputAttributeName, group, &error); | |
332 | if (error) goto out; | |
333 | retData = SecTransformExecute(group, &error); | |
334 | if (error) goto out; | |
335 | retval = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, retData, kCFStringEncodingMacRoman); | |
336 | ||
337 | out: | |
338 | if (error) { | |
339 | secerror("Failed to decrypt recovery password: %@", error); | |
6b200bc3 | 340 | } |
914fc88e A |
341 | |
342 | CFReleaseNull(retData); | |
343 | CFReleaseNull(error); | |
344 | CFReleaseNull(decode); | |
345 | CFReleaseNull(decrypt); | |
346 | CFReleaseNull(group); | |
347 | ||
6b200bc3 | 348 | return retval; |
b1ab9ed8 A |
349 | } |
350 | ||
351 | // IV for the recovery ref is currently the leftmost 16 bytes of the digest of the recovery password. | |
352 | #define IVBYTECOUNT 16 | |
353 | ||
354 | static CFDataRef | |
355 | createIVFromPassword(CFStringRef password) | |
356 | { | |
357 | CFDataRef hashedPassword, retval; | |
358 | CFMutableDataRef iv; | |
866f8763 | 359 | if((hashedPassword = createDigestString(password)) == NULL) return NULL; |
b1ab9ed8 A |
360 | iv = CFDataCreateMutableCopy(kCFAllocatorDefault, CFDataGetLength(hashedPassword)+1, hashedPassword); |
361 | CFDataDeleteBytes(iv, CFRangeMake(IVBYTECOUNT, CFDataGetLength(iv)-IVBYTECOUNT)); | |
362 | retval = CFDataCreateCopy(kCFAllocatorDefault, iv); | |
363 | CFRelease(hashedPassword); | |
364 | CFRelease(iv); | |
365 | return retval; | |
366 | } | |
367 | ||
368 | ||
369 | /* | |
370 | * API functions | |
371 | */ | |
372 | ||
373 | /* | |
374 | * Function: SecWrapRecoveryPasswordWithAnswers | |
375 | * Description: This will wrap a password by using answers to generate a key. The resulting | |
376 | * wrapped password and the questions used to get the answers are saved in a | |
377 | * recovery dictionary. | |
378 | */ | |
379 | ||
866f8763 | 380 | CFDictionaryRef CF_RETURNS_RETAINED |
b1ab9ed8 A |
381 | SecWrapRecoveryPasswordWithAnswers(CFStringRef password, CFArrayRef questions, CFArrayRef answers) |
382 | { | |
383 | uint32_t vers = 1; | |
866f8763 A |
384 | CFDataRef iv = NULL; |
385 | CFDataRef wrappedPassword = NULL; | |
b1ab9ed8 A |
386 | CFMutableDictionaryRef retval = NULL; |
387 | CFLocaleRef theLocale = CFLocaleCopyCurrent(); | |
388 | CFStringRef theLocaleString = CFLocaleGetIdentifier(theLocale); | |
866f8763 | 389 | SecKeyRef wrapKey = NULL; |
b1ab9ed8 | 390 | |
427c49bc | 391 | CFIndex ix, limit; |
b1ab9ed8 | 392 | |
866f8763 A |
393 | if (!password || !questions || !answers) { |
394 | goto error; | |
395 | } | |
b1ab9ed8 A |
396 | |
397 | limit = CFArrayGetCount(answers); | |
866f8763 A |
398 | if (limit != CFArrayGetCount(questions)) { |
399 | goto error; | |
400 | } | |
b1ab9ed8 A |
401 | CFTypeRef chkval; |
402 | for (ix=0; ix<limit; ix++) | |
403 | { | |
404 | chkval = CFArrayGetValueAtIndex(answers, ix); | |
866f8763 A |
405 | if (!chkval || CFGetTypeID(chkval)!=CFStringGetTypeID() || CFEqual((CFStringRef)chkval, CFSTR(""))) { |
406 | goto error; | |
407 | } | |
b1ab9ed8 | 408 | chkval = CFArrayGetValueAtIndex(questions, ix); |
866f8763 A |
409 | if (!chkval || CFGetTypeID(chkval)!=CFStringGetTypeID() || CFEqual((CFStringRef)chkval, CFSTR(""))) { |
410 | goto error; | |
411 | } | |
b1ab9ed8 A |
412 | } |
413 | ||
414 | iv = createIVFromPassword(password); | |
415 | ||
866f8763 | 416 | wrapKey = secDeriveKeyFromAnswers(answers, theLocale); |
b1ab9ed8 A |
417 | |
418 | if((wrappedPassword = encryptString(wrapKey, iv, password)) != NULL) { | |
419 | retval = CFDictionaryCreateMutable(kCFAllocatorDefault, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
866f8763 A |
420 | CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vers); |
421 | CFDictionaryAddValue(retval, kSecRecVersionNumber, num); | |
422 | CFReleaseNull(num); | |
b1ab9ed8 A |
423 | CFDictionaryAddValue(retval, kSecRecQuestions, questions); |
424 | CFDictionaryAddValue(retval, kSecRecLocale, theLocaleString); | |
866f8763 A |
425 | CFDataRef ivdata = b64encode(iv); |
426 | CFDictionaryAddValue(retval, kSecRecIV, ivdata); | |
427 | CFReleaseNull(ivdata); | |
b1ab9ed8 A |
428 | CFDictionaryAddValue(retval, kSecRecWrappedPassword, wrappedPassword); |
429 | } | |
430 | ||
866f8763 A |
431 | CFReleaseNull(wrappedPassword); |
432 | goto out; | |
433 | error: | |
434 | CFReleaseNull(retval); | |
435 | out: | |
436 | CFReleaseNull(iv); | |
437 | CFReleaseNull(wrapKey); | |
438 | CFReleaseNull(theLocale); | |
439 | ||
b1ab9ed8 A |
440 | return retval; |
441 | } | |
442 | ||
443 | /* | |
444 | * Function: SecUnwrapRecoveryPasswordWithAnswers | |
445 | * Description: This will unwrap a password contained in a recovery dictionary by using answers | |
446 | * to generate a key. | |
447 | */ | |
448 | ||
866f8763 | 449 | CFStringRef CF_RETURNS_RETAINED |
b1ab9ed8 A |
450 | SecUnwrapRecoveryPasswordWithAnswers(CFDictionaryRef recref, CFArrayRef answers) |
451 | { | |
452 | if(answers == NULL || CFArrayGetCount(answers) < 3) return NULL; | |
453 | ||
454 | CFStringRef theLocaleString = (CFStringRef) CFDictionaryGetValue(recref, kSecRecLocale); | |
455 | CFDataRef tmpIV = (CFDataRef) CFDictionaryGetValue(recref, kSecRecIV); | |
456 | CFDataRef wrappedPassword = (CFDataRef) CFDictionaryGetValue(recref, kSecRecWrappedPassword); | |
457 | ||
458 | if(theLocaleString == NULL || tmpIV == NULL || wrappedPassword == NULL) { | |
459 | return NULL; | |
460 | } | |
461 | ||
462 | ||
463 | CFLocaleRef theLocale = CFLocaleCreate(kCFAllocatorDefault, theLocaleString); | |
464 | SecKeyRef wrapKey = secDeriveKeyFromAnswers(answers, theLocale); | |
b1ab9ed8 A |
465 | CFRelease(theLocale); |
466 | ||
467 | CFDataRef iv = b64decode(tmpIV); | |
468 | ||
469 | CFStringRef recoveryPassword = decryptString(wrapKey, iv, wrappedPassword); | |
470 | CFRelease(wrapKey); | |
471 | ||
472 | if(recoveryPassword != NULL) { | |
473 | CFDataRef comphash = createIVFromPassword(recoveryPassword); | |
474 | if(!CFEqual(comphash, iv)) { | |
475 | secDebug(ASL_LEVEL_ERR, "Failed reconstitution of password for recovery\n", NULL); | |
476 | CFRelease(recoveryPassword); | |
477 | recoveryPassword = NULL; | |
478 | } | |
479 | CFRelease(comphash); | |
480 | } | |
481 | CFRelease(iv); | |
482 | return recoveryPassword; | |
483 | } | |
484 | ||
485 | /* | |
486 | * Function: SecCreateRecoveryPassword | |
487 | * Description: This function will get a random 128 bit number and base32 | |
488 | * encode and format that value | |
489 | */ | |
490 | ||
491 | CFStringRef | |
427c49bc | 492 | SecCreateRecoveryPassword(void) |
b1ab9ed8 A |
493 | { |
494 | CFStringRef result = NULL; | |
495 | CFErrorRef error = NULL; | |
496 | CFDataRef encodedData = NULL; | |
866f8763 | 497 | CFDataRef randData = createRandomBytes(16); |
b1ab9ed8 A |
498 | int i; |
499 | ||
500 | // base32FDE is a "private" base32 encoding, it has no 0/O or L/l/1 in it (it uses 8 and 9). | |
501 | SecTransformRef encodeTrans = SecEncodeTransformCreate(CFSTR("base32FDE"), &error); | |
502 | if(error == NULL) { | |
503 | SecTransformSetAttribute(encodeTrans, kSecTransformInputAttributeName, randData, &error); | |
504 | if(error == NULL) encodedData = SecTransformExecute(encodeTrans, &error); | |
b1ab9ed8 | 505 | } |
866f8763 | 506 | CFReleaseNull(encodeTrans); |
b1ab9ed8 A |
507 | CFRelease(randData); |
508 | ||
509 | if(encodedData != NULL && error == NULL) { | |
510 | CFStringRef b32string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, encodedData, kCFStringEncodingMacRoman); | |
511 | CFMutableStringRef encodedString = CFStringCreateMutableCopy(kCFAllocatorDefault, 64, b32string); | |
512 | ||
513 | // Add some hyphens to make the generated password easier to use | |
514 | for(i = 4; i < 34; i += 5) CFStringInsert(encodedString, i, CFSTR("-")); | |
515 | // Trim so the last section is 4 characters long | |
516 | CFStringDelete(encodedString, CFRangeMake(29,CFStringGetLength(encodedString)-29)); | |
517 | result = CFStringCreateCopy(kCFAllocatorDefault, encodedString); | |
518 | CFRelease(encodedString); | |
519 | CFRelease(b32string); | |
b1ab9ed8 A |
520 | } else { |
521 | secDebug(ASL_LEVEL_ERR, "Failed to base32 encode random data for recovery password\n", NULL); | |
522 | } | |
866f8763 | 523 | CFReleaseNull(encodedData); |
b1ab9ed8 A |
524 | |
525 | return result; | |
526 | ||
527 | } |