SecRecoveryPassword.c [plain text]
#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>
#include <utilities/SecCFRelease.h>
#include <utilities/debugging.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;
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);
}
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;
}
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);
}
#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;
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); if (theAnswer) {
if (theAnswerLen == CFStringGetBytes(answer, CFRangeMake(0, CFStringGetLength(answer)), kCFStringEncodingUTF8, '?', FALSE, (UInt8*)theAnswer, theAnswerSize, &theAnswerSize)) {
theAnswer[theAnswerSize] = 0; if (strlcat(concatenatedAnswers, theAnswer, concatenatedAnswersSize) < concatenatedAnswersSize) {
encodedAnswers += 1;
}
}
bzero(theAnswer, theAnswerSize);
free(theAnswer);
}
CFRelease(answer);
}
}
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;
}
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 CF_RETURNS_RETAINED
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 CF_RETURNS_RETAINED
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 encrypt = NULL;
SecTransformRef encode = NULL;
SecTransformRef group = NULL;
encrypt = SecEncryptTransformCreate(wrapKey, &error);
if (error) goto out;
SecTransformSetAttribute(encrypt, kSecEncryptionMode, kSecModeCBCKey, &error);
if (error) goto out;
SecTransformSetAttribute(encrypt, kSecPaddingKey, kSecPaddingPKCS7Key, &error);
if (error) goto out;
SecTransformSetAttribute(encrypt, kSecTransformInputAttributeName, inputString, &error);
if (error) goto out;
SecTransformSetAttribute(encrypt, kSecIVKey, iv, &error);
if (error) goto out;
encode = SecEncodeTransformCreate(kSecBase64Encoding, &error);
if (error) goto out;
group = SecTransformCreateGroupTransform();
SecTransformConnectTransforms(encrypt, kSecTransformOutputAttributeName, encode, kSecTransformInputAttributeName, group, &error);
if (error) goto out;
retval = SecTransformExecute(group, &error);
if (error) goto out;
out:
if (error) {
secerror("Failed to encrypt recovery password: %@", error);
}
CFReleaseNull(error);
CFReleaseNull(inputString);
CFReleaseNull(encrypt);
CFReleaseNull(encode);
CFReleaseNull(group);
return retval;
}
static CFStringRef CF_RETURNS_RETAINED
decryptString(SecKeyRef wrapKey, CFDataRef iv, CFDataRef wrappedPassword)
{
CFStringRef retval = NULL;
CFDataRef retData = NULL;
CFErrorRef error = NULL;
SecTransformRef decode = NULL;
SecTransformRef decrypt = NULL;
SecTransformRef group = NULL;
decode = SecDecodeTransformCreate(kSecBase64Encoding, &error);
if (error) goto out;
SecTransformSetAttribute(decode, kSecTransformInputAttributeName, wrappedPassword, &error);
if (error) goto out;
decrypt = SecDecryptTransformCreate(wrapKey, &error);
if (error) goto out;
SecTransformSetAttribute(decrypt, kSecEncryptionMode, kSecModeCBCKey, &error);
if (error) goto out;
SecTransformSetAttribute(decrypt, kSecPaddingKey, kSecPaddingPKCS7Key, &error);
if (error) goto out;
SecTransformSetAttribute(decrypt, kSecIVKey, iv, &error);
if (error) goto out;
group = SecTransformCreateGroupTransform();
SecTransformConnectTransforms(decode, kSecTransformOutputAttributeName, decrypt, kSecTransformInputAttributeName, group, &error);
if (error) goto out;
retData = SecTransformExecute(group, &error);
if (error) goto out;
retval = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, retData, kCFStringEncodingMacRoman);
out:
if (error) {
secerror("Failed to decrypt recovery password: %@", error);
}
CFReleaseNull(retData);
CFReleaseNull(error);
CFReleaseNull(decode);
CFReleaseNull(decrypt);
CFReleaseNull(group);
return retval;
}
#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;
}
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; 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;
}
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;
}
CFStringRef
SecCreateRecoveryPassword(void)
{
CFStringRef result = NULL;
CFErrorRef error = NULL;
CFDataRef encodedData = NULL;
CFDataRef randData = getRandomBytes(16);
int i;
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);
for(i = 4; i < 34; i += 5) CFStringInsert(encodedString, i, CFSTR("-"));
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;
}