X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/SecRecoveryPassword.c?ds=inline diff --git a/Security/libsecurity_keychain/lib/SecRecoveryPassword.c b/Security/libsecurity_keychain/lib/SecRecoveryPassword.c new file mode 100644 index 00000000..0c93c311 --- /dev/null +++ b/Security/libsecurity_keychain/lib/SecRecoveryPassword.c @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2010-2012 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include "SecRecoveryPassword.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CFStringRef kSecRecVersionNumber = CFSTR("SRVersionNumber"); +CFStringRef kSecRecQuestions = CFSTR("SRQuestions"); +CFStringRef kSecRecLocale = CFSTR("SRLocale"); +CFStringRef kSecRecIV = CFSTR("SRiv"); +CFStringRef kSecRecWrappedPassword = CFSTR("SRWrappedPassword"); + + +static char *std_log_prefix = "###SecRecovery Function: %s - %s"; +static const char *std_ident = "Security.framework"; +static const char *std_facility = "InfoSec"; +static uint32_t std_options = 0; + +static aslclient aslhandle = NULL; +static aslmsg msgptr = NULL; + +// Error Reporting + +void ccdebug_imp(int level, char *funcname, char *format, ...); + +#define secDebug(lvl,fmt,...) sec_debug_imp(lvl, __PRETTY_FUNCTION__, fmt, __VA_ARGS__) + +static void +sec_debug_init() { + char *ccEnvStdErr = getenv("CC_STDERR"); + + if(ccEnvStdErr != NULL && strncmp(ccEnvStdErr, "yes", 3) == 0) std_options |= ASL_OPT_STDERR; + aslhandle = asl_open(std_ident, std_facility, std_options); + + msgptr = asl_new(ASL_TYPE_MSG); + asl_set(msgptr, ASL_KEY_FACILITY, "com.apple.infosec"); +} + + +static void +sec_debug_imp(int level, const char *funcname, char *format, ...) { + va_list argp; + char fmtbuffer[256]; + + if(aslhandle == NULL) sec_debug_init(); + + sprintf(fmtbuffer, std_log_prefix, funcname, format); + va_start(argp, format); + asl_vlog(aslhandle, msgptr, level, fmtbuffer, argp); + va_end(argp); +} + +// Read /dev/random for random bytes + +static CFDataRef +getRandomBytes(size_t len) +{ + uint8_t *buffer; + CFDataRef randData = NULL; + int fdrand; + + if((buffer = malloc(len)) == NULL) return NULL; + if((fdrand = open("/dev/random", O_RDONLY)) == -1) return NULL; + if(read(fdrand, buffer, len) == len) randData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *) buffer, len); + close(fdrand); + + free(buffer); + return randData; +} + +// This is the normalization routine - subject to change. We need to make sure that whitespace is removed and +// that upper/lower case is normalized, etc for all possible languages. + +static void secNormalize(CFMutableStringRef theString, CFLocaleRef theLocale) +{ + CFRange theRange; + + CFStringFold(theString, kCFCompareCaseInsensitive | kCFCompareDiacriticInsensitive | kCFCompareWidthInsensitive, theLocale); + CFStringNormalize(theString, kCFStringNormalizationFormKC); + CFStringTrimWhitespace(theString); + while(CFStringFindCharacterFromSet(theString, CFCharacterSetGetPredefined(kCFCharacterSetWhitespace), CFRangeMake(0, CFStringGetLength(theString)), kCFCompareBackwards, &theRange)) + CFStringDelete(theString, theRange); +} + +/* + * This will derive a 128 bit (16 byte) key from a set of answers to questions in a CFArray of CFStrings. + * it normalizes each answer and concats them into a collector buffer. The resulting string is run through + * PBKDF2-HMAC-SHA256 to form a key. + * + * Todo: For version 2 it would be better to randomly generate the salt and make the iteration count flexible. + * This would require a different return value because that information would need to be returned up the stack + * to the callers. Given the time left in this release (Lion) we're going with set values for this. + */ + +#define RETURN_KEY_SIZE 16 +#define MAXANSWERBUFF 4096 +#define PBKDF_ROUNDS 100000 + +static SecKeyRef secDeriveKeyFromAnswers(CFArrayRef answers, CFLocaleRef theLocale) +{ + static const uint8_t salt[16] = { 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x1F }; + static const int saltLen = sizeof(salt); + + SecKeyRef theKey = NULL; + uint8_t rawKeyData[RETURN_KEY_SIZE]; + + CFIndex encodedAnswers = 0; + CFIndex numAnswers = CFArrayGetCount(answers); + const size_t concatenatedAnswersSize = MAXANSWERBUFF * numAnswers; + + char *concatenatedAnswers = (char *)malloc(concatenatedAnswersSize); + if (concatenatedAnswers == NULL) { + return NULL; + } + + concatenatedAnswers[0] = 0; // NUL terminate + + int i; + for (i = 0; i < numAnswers; i++) { + CFMutableStringRef answer = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFArrayGetValueAtIndex(answers, i)); + if (answer) { + secNormalize(answer, theLocale); + + CFIndex theAnswerLen = CFStringGetLength(answer); + CFIndex theAnswerSize = CFStringGetMaximumSizeForEncoding(theAnswerLen, kCFStringEncodingUTF8); + char *theAnswer = (char *)malloc(theAnswerSize + 1); // add space for NUL byte + if (theAnswer) { + if (theAnswerLen == CFStringGetBytes(answer, CFRangeMake(0, CFStringGetLength(answer)), kCFStringEncodingUTF8, '?', FALSE, (UInt8*)theAnswer, theAnswerSize, &theAnswerSize)) { + theAnswer[theAnswerSize] = 0; // NUL terminate + if (strlcat(concatenatedAnswers, theAnswer, concatenatedAnswersSize) < concatenatedAnswersSize) { + encodedAnswers += 1; + } + } + bzero(theAnswer, theAnswerSize); + free(theAnswer); + } + CFRelease(answer); + } + } + + // one or more of the answers failed to encode + if (encodedAnswers != numAnswers) { + free(concatenatedAnswers); + return NULL; + } + + if (CCKeyDerivationPBKDF(kCCPBKDF2, concatenatedAnswers, strlen(concatenatedAnswers), salt, saltLen, kCCPRFHmacAlgSHA256, PBKDF_ROUNDS, rawKeyData, RETURN_KEY_SIZE)) { + free(concatenatedAnswers); + return NULL; + } + + CFDataRef keyData = CFDataCreate(kCFAllocatorDefault, rawKeyData, RETURN_KEY_SIZE); + if (keyData) { + CFMutableDictionaryRef params = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (params) { + CFErrorRef error = NULL; + CFDictionaryAddValue(params, kSecAttrKeyType, kSecAttrKeyTypeAES); + theKey = SecKeyCreateFromData(params, keyData, &error); + if (error) { + CFRelease(error); + } + CFRelease(params); + } + CFRelease(keyData); + } + + bzero(rawKeyData, RETURN_KEY_SIZE); + bzero(concatenatedAnswers, concatenatedAnswersSize); + free(concatenatedAnswers); + return theKey; +} + + +// Single shot CFString processing routines for digests/encoding/encrypt/decrypt + +static CFDataRef +digestString(CFStringRef str) +{ + CFDataRef retval = NULL; + CFErrorRef error = NULL; + + CFDataRef inputString = CFStringCreateExternalRepresentation(kCFAllocatorDefault, str, kCFStringEncodingUTF8, 0xff); + + SecTransformRef digestTrans = SecDigestTransformCreate(kSecDigestSHA2, 256, &error); + if(error == NULL) { + SecTransformSetAttribute(digestTrans, kSecTransformInputAttributeName, inputString, &error); + if(error == NULL) { + retval = SecTransformExecute(digestTrans, &error); + if(retval == NULL) { + secDebug(ASL_LEVEL_ERR, "Couldn't create digest %s\n", CFStringGetCStringPtr(CFErrorCopyFailureReason(error), kCFStringEncodingUTF8)); + } + } + CFRelease(digestTrans); + } + CFRelease(inputString); + return retval; +} + +static CFDataRef +b64encode(CFDataRef input) +{ + CFDataRef retval = NULL; + CFErrorRef error = NULL; + SecTransformRef encodeTrans = SecEncodeTransformCreate(kSecBase64Encoding, &error); + if(error == NULL) SecTransformSetAttribute(encodeTrans, kSecTransformInputAttributeName, input, &error); + if(error == NULL) retval = SecTransformExecute(encodeTrans, &error); + if(encodeTrans) CFRelease(encodeTrans); + return retval; +} + +static CFDataRef +b64decode(CFDataRef input) +{ + CFDataRef retval = NULL; + CFErrorRef error = NULL; + SecTransformRef decodeTrans = SecDecodeTransformCreate(kSecBase64Encoding, &error); + if(error == NULL) SecTransformSetAttribute(decodeTrans, kSecTransformInputAttributeName, input, &error); + if(error == NULL) retval = SecTransformExecute(decodeTrans, &error); + if(decodeTrans) CFRelease(decodeTrans); + return retval; +} + +static CFDataRef +encryptString(SecKeyRef wrapKey, CFDataRef iv, CFStringRef str) +{ + CFDataRef retval = NULL; + CFErrorRef error = NULL; + CFDataRef inputString = CFStringCreateExternalRepresentation(kCFAllocatorDefault, str, kCFStringEncodingMacRoman, 0xff); + + SecTransformRef encryptTrans = SecEncryptTransformCreate(wrapKey, &error); + if(error == NULL) { + SecTransformRef group = SecTransformCreateGroupTransform(); + + SecTransformSetAttribute(encryptTrans, kSecEncryptionMode, kSecModeCBCKey, &error); + if(error == NULL) SecTransformSetAttribute(encryptTrans, kSecPaddingKey, kSecPaddingPKCS7Key, &error); + if(error == NULL) SecTransformSetAttribute(encryptTrans, kSecTransformInputAttributeName, inputString, &error); + if(error == NULL) SecTransformSetAttribute(encryptTrans, kSecIVKey, iv, &error); + SecTransformRef encodeTrans = SecEncodeTransformCreate(kSecBase64Encoding, &error); + SecTransformConnectTransforms(encryptTrans, kSecTransformOutputAttributeName, encodeTrans, kSecTransformInputAttributeName, group, &error); + CFRelease(encodeTrans); + CFRelease(encryptTrans); + if(error == NULL) retval = SecTransformExecute(group, &error); + if(error != NULL) secDebug(ASL_LEVEL_ERR, "Failed to encrypt recovery password\n", NULL); + CFRelease(group); + } + return retval; +} + + +static CFStringRef +decryptString(SecKeyRef wrapKey, CFDataRef iv, CFDataRef wrappedPassword) +{ + CFStringRef retval = NULL; + CFDataRef retData = NULL; + CFErrorRef error = NULL; + + SecTransformRef decryptTrans = SecDecryptTransformCreate(wrapKey, &error); + if(error == NULL) { + SecTransformRef group = SecTransformCreateGroupTransform(); + + SecTransformRef decodeTrans = SecDecodeTransformCreate(kSecBase64Encoding, &error); + if(error == NULL) SecTransformSetAttribute(decodeTrans, kSecTransformInputAttributeName, wrappedPassword, &error); + + if(error == NULL) SecTransformSetAttribute(decryptTrans, kSecEncryptionMode, kSecModeCBCKey, &error); + if(error == NULL) SecTransformSetAttribute(decryptTrans, kSecPaddingKey, kSecPaddingPKCS7Key, &error); + if(error == NULL) SecTransformSetAttribute(decryptTrans, kSecIVKey, iv, &error); + SecTransformConnectTransforms(decodeTrans, kSecTransformOutputAttributeName, decryptTrans, kSecTransformInputAttributeName, group, &error); + CFRelease(decodeTrans); + CFRelease(decryptTrans); + if(error == NULL) retData = SecTransformExecute(group, &error); + + if(error == NULL) retval = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, retData, kCFStringEncodingMacRoman); + else secDebug(ASL_LEVEL_ERR, "Failed to decrypt recovery password\n", NULL); + CFRelease(group); + } + return retval; +} + +// IV for the recovery ref is currently the leftmost 16 bytes of the digest of the recovery password. +#define IVBYTECOUNT 16 + +static CFDataRef +createIVFromPassword(CFStringRef password) +{ + CFDataRef hashedPassword, retval; + CFMutableDataRef iv; + if((hashedPassword = digestString(password)) == NULL) return NULL; + iv = CFDataCreateMutableCopy(kCFAllocatorDefault, CFDataGetLength(hashedPassword)+1, hashedPassword); + CFDataDeleteBytes(iv, CFRangeMake(IVBYTECOUNT, CFDataGetLength(iv)-IVBYTECOUNT)); + retval = CFDataCreateCopy(kCFAllocatorDefault, iv); + CFRelease(hashedPassword); + CFRelease(iv); + return retval; +} + + +/* + * API functions + */ + +/* + * Function: SecWrapRecoveryPasswordWithAnswers + * Description: This will wrap a password by using answers to generate a key. The resulting + * wrapped password and the questions used to get the answers are saved in a + * recovery dictionary. + */ + +CFDictionaryRef +SecWrapRecoveryPasswordWithAnswers(CFStringRef password, CFArrayRef questions, CFArrayRef answers) +{ + uint32_t vers = 1; + CFDataRef iv; + CFDataRef wrappedPassword; + CFMutableDictionaryRef retval = NULL; + CFLocaleRef theLocale = CFLocaleCopyCurrent(); + CFStringRef theLocaleString = CFLocaleGetIdentifier(theLocale); + + CFIndex ix, limit; + + if (!password || !questions || !answers) + return NULL; + + limit = CFArrayGetCount(answers); + if (limit != CFArrayGetCount(questions)) + return NULL; // Error + CFTypeRef chkval; + for (ix=0; ix