-/*
- * 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 <Security/SecTransform.h>
-#include <Security/SecEncodeTransform.h>
-#include <Security/SecDecodeTransform.h>
-#include <Security/SecDigestTransform.h>
-#include <Security/SecEncryptTransform.h>
-#include <Security/SecItem.h>
-#include <Security/SecKey.h>
-#include <CommonCrypto/CommonKeyDerivation.h>
-#include <CommonCrypto/CommonCryptor.h>
-#include <CoreFoundation/CFBase.h>
-#include <fcntl.h>
-#include <asl.h>
-#include <stdarg.h>
-#include <string.h>
-#include <stdio.h>
-
-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<limit; ix++)
- {
- chkval = CFArrayGetValueAtIndex(answers, ix);
- if (!chkval || CFGetTypeID(chkval)!=CFStringGetTypeID() || CFEqual((CFStringRef)chkval, CFSTR("")))
- return NULL;
- chkval = CFArrayGetValueAtIndex(questions, ix);
- if (!chkval || CFGetTypeID(chkval)!=CFStringGetTypeID() || CFEqual((CFStringRef)chkval, CFSTR("")))
- return NULL;
- }
-
- iv = createIVFromPassword(password);
-
- SecKeyRef wrapKey = secDeriveKeyFromAnswers(answers, theLocale);
-
- if((wrappedPassword = encryptString(wrapKey, iv, password)) != NULL) {
- retval = CFDictionaryCreateMutable(kCFAllocatorDefault, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- CFDictionaryAddValue(retval, kSecRecVersionNumber, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vers));
- CFDictionaryAddValue(retval, kSecRecQuestions, questions);
- CFDictionaryAddValue(retval, kSecRecLocale, theLocaleString);
- CFDictionaryAddValue(retval, kSecRecIV, b64encode(iv));
- CFDictionaryAddValue(retval, kSecRecWrappedPassword, wrappedPassword);
- }
-
- if(wrappedPassword) CFRelease(wrappedPassword);
- CFRelease(iv);
- CFRelease(wrapKey);
- CFRelease(theLocale);
- CFRelease(theLocaleString);
-
- return retval;
-}
-
-/*
- * Function: SecUnwrapRecoveryPasswordWithAnswers
- * Description: This will unwrap a password contained in a recovery dictionary by using answers
- * to generate a key.
- */
-
-CFStringRef
-SecUnwrapRecoveryPasswordWithAnswers(CFDictionaryRef recref, CFArrayRef answers)
-{
- if(answers == NULL || CFArrayGetCount(answers) < 3) return NULL;
-
- CFStringRef theLocaleString = (CFStringRef) CFDictionaryGetValue(recref, kSecRecLocale);
- CFDataRef tmpIV = (CFDataRef) CFDictionaryGetValue(recref, kSecRecIV);
- CFDataRef wrappedPassword = (CFDataRef) CFDictionaryGetValue(recref, kSecRecWrappedPassword);
-
- if(theLocaleString == NULL || tmpIV == NULL || wrappedPassword == NULL) {
- return NULL;
- }
-
-
- CFLocaleRef theLocale = CFLocaleCreate(kCFAllocatorDefault, theLocaleString);
- SecKeyRef wrapKey = secDeriveKeyFromAnswers(answers, theLocale);
- CFRelease(theLocaleString);
- CFRelease(theLocale);
-
- CFDataRef iv = b64decode(tmpIV);
-
- CFStringRef recoveryPassword = decryptString(wrapKey, iv, wrappedPassword);
- CFRelease(wrapKey);
-
- if(recoveryPassword != NULL) {
- CFDataRef comphash = createIVFromPassword(recoveryPassword);
- if(!CFEqual(comphash, iv)) {
- secDebug(ASL_LEVEL_ERR, "Failed reconstitution of password for recovery\n", NULL);
- CFRelease(recoveryPassword);
- recoveryPassword = NULL;
- }
- CFRelease(comphash);
- }
- CFRelease(iv);
- return recoveryPassword;
-}
-
-/*
- * Function: SecCreateRecoveryPassword
- * Description: This function will get a random 128 bit number and base32
- * encode and format that value
- */
-
-CFStringRef
-SecCreateRecoveryPassword(void)
-{
- CFStringRef result = NULL;
- CFErrorRef error = NULL;
- CFDataRef encodedData = NULL;
- CFDataRef randData = getRandomBytes(16);
- int i;
-
- // base32FDE is a "private" base32 encoding, it has no 0/O or L/l/1 in it (it uses 8 and 9).
- SecTransformRef encodeTrans = SecEncodeTransformCreate(CFSTR("base32FDE"), &error);
- if(error == NULL) {
- SecTransformSetAttribute(encodeTrans, kSecTransformInputAttributeName, randData, &error);
- if(error == NULL) encodedData = SecTransformExecute(encodeTrans, &error);
- CFRelease(encodeTrans);
- }
- CFRelease(randData);
-
- if(encodedData != NULL && error == NULL) {
- CFStringRef b32string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, encodedData, kCFStringEncodingMacRoman);
- CFMutableStringRef encodedString = CFStringCreateMutableCopy(kCFAllocatorDefault, 64, b32string);
-
- // Add some hyphens to make the generated password easier to use
- for(i = 4; i < 34; i += 5) CFStringInsert(encodedString, i, CFSTR("-"));
- // Trim so the last section is 4 characters long
- CFStringDelete(encodedString, CFRangeMake(29,CFStringGetLength(encodedString)-29));
- result = CFStringCreateCopy(kCFAllocatorDefault, encodedString);
- CFRelease(encodedString);
- CFRelease(b32string);
- CFRelease(encodedData);
- } else {
- secDebug(ASL_LEVEL_ERR, "Failed to base32 encode random data for recovery password\n", NULL);
- }
-
- return result;
-
-}