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