SecMaskGenerationFunctionTransform.c   [plain text]


//
//  SecMaskGenerationFunctionTransform.c
//  libsecurity_transform
//
//  Created by Josh Osborne on 10/6/11.
//  Copyright 2011 Apple. All rights reserved.
//

#include <stdio.h>
#include <CoreFoundation/CoreFoundation.h>
#include "SecMaskGenerationFunctionTransform.h"
#include "SecCustomTransform.h"
#include "SecDigestTransform.h"
#include "misc.h"
#include "Utilities.h"

static const CFStringRef kMaskGenerationFunctionTransformName = CFSTR("com.apple.security.MGF1");
static const CFStringRef kLengthName = CFSTR("Length");

static SecTransformInstanceBlock MaskGenerationFunctionTransform(CFStringRef name, 
                                                  SecTransformRef newTransform, 
                                                  SecTransformImplementationRef ref)
{
    __block CFMutableDataRef accumulator = CFDataCreateMutable(NULL, 0);
    __block int32_t outputLength = 0;
    
    SecTransformInstanceBlock instanceBlock = ^{
        SecTransformSetTransformAction(ref, kSecTransformActionFinalize, ^{
            CFRelease(accumulator);
            
            return (CFTypeRef)NULL;
        });
        
        // XXX: be a good citizen, put a validator in for the types.
        
        SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kLengthName, ^CFTypeRef(SecTransformAttributeRef attribute, CFTypeRef value) {
            CFNumberGetValue((CFNumberRef)value, kCFNumberSInt32Type, &outputLength);
            if (outputLength <= 0) {
                SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, CreateSecTransformErrorRef(kSecTransformErrorInvalidLength, "MaskGenerationFunction Length must be one or more (not %@)", value));
            }

            return (CFTypeRef)NULL;
        });
        
        SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecTransformInputAttributeName, ^CFTypeRef(SecTransformAttributeRef attribute, CFTypeRef value) {
            if (value) {
                CFDataRef d = value;
                CFDataAppendBytes(accumulator, CFDataGetBytePtr(d), CFDataGetLength(d));
            } else {
                int32_t i = 0, l = 0;
                (void)transforms_assume(outputLength > 0);
                CFStringRef digestType = SecTranformCustomGetAttribute(ref, kSecDigestTypeAttribute, kSecTransformMetaAttributeValue);
                SecTransformRef digest0 = transforms_assume(SecDigestTransformCreate(digestType, 0, NULL));
                int32_t digestLength = 0;
                {
                    CFNumberRef digestLengthAsCFNumber = SecTransformGetAttribute(digest0, kSecDigestLengthAttribute);
                    CFNumberGetValue(transforms_assume(digestLengthAsCFNumber), kCFNumberSInt32Type, &digestLength);
                }
                (void)transforms_assume(digestLength >= 0);

                UInt8 *buffer = malloc(outputLength + digestLength);
                if (!buffer) {
                    SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, GetNoMemoryErrorAndRetain());
                    return (CFErrorRef)NULL;
                }

                dispatch_group_t all_hashed = dispatch_group_create();
                dispatch_group_enter(all_hashed);
                for(; l < outputLength; l += digestLength, i++) {
                    dispatch_group_enter(all_hashed);
                    CFErrorRef err = NULL;
                    SecTransformRef digest = NULL;
                    if (l == 0) {
                        digest = digest0;
                    } else {
                        digest = SecDigestTransformCreate(digestType, 0, &err);
                        if (digest == NULL) {
                            SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, err);
                            return (CFErrorRef)NULL;
                        }
                    }

                    // NOTE: we shuld be able to do this without the copy, make a transform that takes an
                    // array and outputs each item in the array followed by a NULL ought to be quicker.
                    CFMutableDataRef accumulatorPlusCounter = CFDataCreateMutableCopy(NULL, CFDataGetLength(accumulator) + sizeof(uint32_t), accumulator);
                    int32_t bigendian_i = htonl(i);
                    CFDataAppendBytes(accumulatorPlusCounter, (UInt8*)&bigendian_i, sizeof(bigendian_i));
                    SecTransformSetAttribute(digest, kSecTransformInputAttributeName, accumulatorPlusCounter, &err);
                    CFRelease(accumulatorPlusCounter);
                    
                    UInt8 *buf = buffer + l;
                    SecTransformExecuteAsync(digest, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFTypeRef message, CFErrorRef error, Boolean isFinal) {
                        if (message) {
                            CFIndex messageLen = CFDataGetLength(message);
                            CFDataGetBytes(message, CFRangeMake(0, messageLen), buf);
                        }
                        if (error) {
                            SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, error);
                        }
                        if (isFinal) {
                            dispatch_group_leave(all_hashed);
                        }
                    });
                    CFRelease(digest);
                }
                
                dispatch_group_leave(all_hashed);
                dispatch_group_wait(all_hashed, DISPATCH_TIME_FOREVER);
                CFDataRef out = CFDataCreateWithBytesNoCopy(NULL, buffer, outputLength, kCFAllocatorMalloc);
                SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, out);
                CFRelease(out);
                SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, NULL);
            }
            return (CFErrorRef)NULL;
        });
        
        return (CFErrorRef)NULL;
    };
    
    return Block_copy(instanceBlock);
}

SecTransformRef SecCreateMaskGenerationFunctionTransform(CFStringRef hashType, int length, CFErrorRef *error)
{
    static dispatch_once_t once;
	__block Boolean ok = TRUE;
    
    if (length <= 0) {
        if (error) {
            *error = CreateSecTransformErrorRef(kSecTransformErrorInvalidLength, "MaskGenerationFunction Length must be one or more (not %d)", length);
        }
        return NULL;
    }
    
    dispatch_once(&once, ^(void) {
        ok = SecTransformRegister(kMaskGenerationFunctionTransformName, MaskGenerationFunctionTransform, error);
    });
        
    if (!ok) {
        return NULL;
    }
    
    SecTransformRef ret = SecTransformCreate(kMaskGenerationFunctionTransformName, error);
    if (!ret) {
        return NULL;
    }
    
    if (!SecTransformSetAttribute(ret, kSecDigestTypeAttribute, hashType ? hashType : kSecDigestSHA1, error)) {
        CFRelease(ret);
        return NULL;
    }

    CFNumberRef len = CFNumberCreate(NULL, kCFNumberIntType, &length);
    ok = SecTransformSetAttribute(ret, kLengthName, len, error);
    CFRelease(len);
    if (!ok) {
        CFRelease(ret);
        return NULL;
    }

    return ret;
}