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