EncodeDecodeTransforms.c [plain text]
#include "SecEncodeTransform.h"
#include "SecDecodeTransform.h"
#include "SecCustomTransform.h"
#include "CoreFoundation/CoreFoundation.h"
#include "misc.h"
#include "Utilities.h"
#include <zlib.h>
#include <malloc/malloc.h>
const static CFStringRef DecodeName = CFSTR("com.apple.security.Decoder");
const static CFStringRef EncodeName = CFSTR("com.apple.security.Encoder");
const CFStringRef kSecBase64Encoding = CFSTR("base64");
const CFStringRef kSecBase32Encoding = CFSTR("base32");
const CFStringRef kSecBase32FDEEncoding = CFSTR("base32FDE");
const CFStringRef kSecZLibEncoding = CFSTR("zlib");
const CFStringRef kSecEncodeTypeAttribute = CFSTR("EncodeType");
const CFStringRef kSecDecodeTypeAttribute = CFSTR("DecodeType");
const CFStringRef kSecEncodeLineLengthAttribute = CFSTR("LineLength");
const CFStringRef kSecCompressionRatio = CFSTR("CompressionRatio");
const CFStringRef kSecLineLength64 = CFSTR("64");
const CFStringRef kSecLineLength76 = CFSTR("76");
static unsigned char Base64Vals[] =
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
0x3c, 0x3d, 0xff, 0xff, 0xff, 0x40, 0xff, 0xff,
0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static char Base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"+/=";
static unsigned char Base32Vals[] = {0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static unsigned char Base32FDEVals[] = {0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x08, 0x12,
0xff, 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07, 0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0xff, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static SecTransformInstanceBlock DecodeTransform(CFStringRef name,
SecTransformRef newTransform,
SecTransformImplementationRef ref)
{
SecTransformInstanceBlock instanceBlock =
^{
CFErrorRef result = NULL;
SecTransformCustomSetAttribute(ref, kSecDecodeTypeAttribute,
kSecTransformMetaAttributeRequired, kCFBooleanTrue);
SecTransformSetAttributeAction(ref,
kSecTransformActionAttributeNotification,
kSecDecodeTypeAttribute,
^(SecTransformStringOrAttributeRef attribute, CFTypeRef value)
{
if (NULL == value || CFGetTypeID(value) != CFStringGetTypeID())
{
CFErrorRef errorResult = fancy_error(kSecTransformErrorDomain,
kSecTransformErrorInvalidInput,
CFSTR("Decode type was not a CFStringRef"));
return (CFTypeRef)errorResult;
}
if (kCFCompareEqualTo == CFStringCompare(value, kSecBase64Encoding, 0))
{
__block struct { unsigned char a[4]; } leftover;
static const short int in_chunk_size = 4;
static const short int out_chunk_size = 3;
__block int leftover_cnt = 0;
SecTransformSetDataAction(ref, kSecTransformActionProcessData,
^(CFTypeRef value)
{
CFDataRef d = value;
CFIndex enc_cnt = d ? CFDataGetLength(d) : 0;
const unsigned char *enc = d ? CFDataGetBytePtr(d) : NULL;
const unsigned char *enc_end = enc + enc_cnt;
long n_chunks = (leftover_cnt + enc_cnt) / out_chunk_size + 1;
unsigned char *out_base = malloc(n_chunks * out_chunk_size);
if (!out_base) {
return (CFTypeRef) GetNoMemoryError();
}
unsigned char *out_end = out_base + n_chunks * out_chunk_size;
unsigned char *out = out_base;
int chunk_i = leftover_cnt;
for(; enc < enc_end || !enc; chunk_i++) {
unsigned char ch, b;
if (enc) {
ch = *enc++;
} else {
ch = '=';
}
if (ch == ' ' || ch == '\n' || ch == '\r') {
chunk_i -= 1;
continue;
}
b = Base64Vals[ch];
if (b != 0xff) {
leftover.a[chunk_i] = b;
}
if (chunk_i == in_chunk_size-1 || ch == '=') {
*out = (leftover.a[0] & 0x3f) << 2;
*out++ |= ((leftover.a[1] & 0x3f) >> 4);
*out = (leftover.a[1] & 0x0f) << 4;
*out++ |= (leftover.a[2] & 0x3f) >> 2;
*out = (leftover.a[2] & 0x03) << 6;
*out++ |= (leftover.a[3] & 0x3f);
out -= 3 - chunk_i;
if (ch == '=') {
if (chunk_i != 0) {
out--;
}
chunk_i = -1;
break;
}
chunk_i = -1;
}
}
leftover_cnt = (chunk_i > 0) ? chunk_i : 0;
if (out > out_end) {
abort();
}
CFDataRef ret = CFDataCreateWithBytesNoCopy(NULL, out_base, out - out_base, kCFAllocatorMalloc);
if (!d) {
SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName,
kSecTransformMetaAttributeValue, ret);
CFRelease(ret);
ret = NULL;
}
return (CFTypeRef)ret;
});
}
else if (kCFCompareEqualTo == CFStringCompare(value, kSecBase32Encoding, 0) || kCFCompareEqualTo == CFStringCompare(value, kSecBase32FDEEncoding, 0))
{
__block struct { uint64_t a[2]; } accumulator = { .a = {0, 0}};
__block short int bits_accumulated = 0;
static const short int out_chunk_size = 8;
const short int full_accumulator = 80;
unsigned char *base32values = NULL;
if (kCFCompareEqualTo == CFStringCompare(value, kSecBase32Encoding, 0)) {
base32values = Base32Vals;
} else if (kCFCompareEqualTo == CFStringCompare(value, kSecBase32FDEEncoding, 0)) {
base32values = Base32FDEVals;
}
if (NULL == base32values) {
CFErrorRef bad_type = CreateSecTransformErrorRef(kSecTransformErrorInvalidInput, "Unknown base32 type '%@'", value);
SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, bad_type);
return (CFTypeRef)bad_type;
}
SecTransformSetDataAction(ref, kSecTransformActionProcessData,
^(CFTypeRef value)
{
CFDataRef d = value;
CFIndex enc_cnt = d ? CFDataGetLength(d) : 0;
const unsigned char *enc = d ? CFDataGetBytePtr(d) : NULL;
const unsigned char *enc_end = enc + enc_cnt;
long n_chunks = (bits_accumulated/8 + enc_cnt) / out_chunk_size + 1;
unsigned char *out_base = malloc(n_chunks * out_chunk_size);
if (!out_base) {
return (CFTypeRef)GetNoMemoryError();
}
unsigned char *out_end = out_base + n_chunks * out_chunk_size;
unsigned char *out = out_base;
for(; enc < enc_end || !d;) {
unsigned char ch, b;
if (enc) {
ch = *enc++;
} else {
ch = '=';
}
b = base32values[ch];
if (b == 0xff) {
continue;
}
if (ch != '=') {
accumulator.a[1] = accumulator.a[1] << 5 | (0x1f & (accumulator.a[0] >> (64 -5)));
accumulator.a[0] = accumulator.a[0] << 5 | b;
bits_accumulated += 5;
}
if (bits_accumulated == full_accumulator || ch == '=') {
short shifted = 0;
for(; shifted + bits_accumulated < full_accumulator; shifted += 5) {
accumulator.a[1] = accumulator.a[1] << 5 | (0x1f & accumulator.a[0] >> (64 -5));
accumulator.a[0] = accumulator.a[0] << 5;
}
for(; bits_accumulated >= 8; bits_accumulated -= 8) {
*out++ = accumulator.a[1] >> (80 - 64 - 8);
accumulator.a[1] = (accumulator.a[1] << 8 | accumulator.a[0] >> (64 - 8)) & 0xffff;
accumulator.a[0] = accumulator.a[0] << 8;
}
bits_accumulated = 0;
if (ch == '=') {
break;
}
}
}
if (out > out_end) {
abort();
}
CFDataRef ret = CFDataCreateWithBytesNoCopy(NULL, out_base, out - out_base, kCFAllocatorMalloc);
if (!d) {
SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName,
kSecTransformMetaAttributeValue, ret);
CFRelease(ret);
ret = NULL;
}
return (CFTypeRef)ret;
});
}
else if (kCFCompareEqualTo == CFStringCompare(value, kSecZLibEncoding, 0))
{
__block z_stream zs;
__block Boolean started = FALSE;
CFBooleanRef hasRatio = (CFBooleanRef)SecTranformCustomGetAttribute(ref,
kSecCompressionRatio, kSecTransformMetaAttributeHasOutboundConnections);
Boolean ratio_connected = (kCFBooleanTrue == hasRatio);
bzero(&zs, sizeof(zs));
SecTransformSetDataAction(ref, kSecTransformActionProcessData,
^(CFTypeRef value)
{
CFDataRef d = value;
if (!started) {
if (!d) {
return (CFTypeRef)NULL;
}
started = TRUE;
inflateInit(&zs);
}
if (d) {
zs.next_in = (UInt8 *)(CFDataGetBytePtr(d)); zs.avail_in = (uInt)CFDataGetLength(d);
} else {
zs.next_in = NULL;
zs.avail_in = 0;
}
int rc = Z_OK;
CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
while ((d && zs.avail_in) || (d == NULL && rc != Z_STREAM_END)) {
unsigned char *buf = malloc(buf_sz);
if (!buf) {
return (CFTypeRef)GetNoMemoryError();
}
zs.next_out = buf;
zs.avail_out = (uInt)buf_sz;
rc = inflate(&zs, d ? Z_NO_FLUSH : Z_FINISH);
CFIndex buf_used = buf_sz - zs.avail_out;
#ifdef DEBUG_ZLIB_MEMORY_USE
CFfprintf(stderr, ">>zavail_in %d buf_sz %d; d %p; ", zs.avail_in, buf_sz, d);
CFfprintf(stderr, "rc=%d %s", rc, (rc == Z_OK) ? "Z_OK" : (rc == Z_STREAM_END) ? "Z_STREAM_END" : (rc == Z_BUF_ERROR) ? "Z_BUF_ERROR" : "?");
CFfprintf(stderr, " (output used %d, input left %d)\n", buf_used, zs.avail_in);
#endif
if (rc == Z_OK || rc == Z_STREAM_END) {
CFDataRef d;
if ((4 * buf_used) / buf_sz <= 1) {
d = CFDataCreate(NULL, buf, buf_used);
free(buf);
} else {
d = CFDataCreateWithBytesNoCopy(NULL, buf, buf_used, kCFAllocatorMalloc);
}
SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName,
kSecTransformMetaAttributeValue, d);
CFRelease(d);
} else if (rc == Z_BUF_ERROR) {
free(buf);
if ((int)buf_sz > (1 << Z_BEST_COMPRESSION) && 0 == zs.avail_in) {
rc = Z_STREAM_END;
}
buf_sz = malloc_good_size(buf_sz * 2);
} else {
free(buf);
CFStringRef emsg = CFStringCreateWithFormat(NULL, NULL, CFSTR("Zlib error#%d"), rc);
CFErrorRef err = fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidInput, emsg);
CFRelease(emsg);
return (CFTypeRef)err;
}
}
if (ratio_connected && zs.total_in && zs.total_out) {
float r = (float)zs.total_in / zs.total_out;
CFNumberRef ratio = CFNumberCreate(NULL, kCFNumberFloatType, &r);
SecTransformCustomSetAttribute(ref, kSecCompressionRatio,
kSecTransformMetaAttributeValue, ratio);
CFRelease(ratio);
}
if (rc == Z_OK) {
return (CFTypeRef)SecTransformNoData();
} else if (rc == Z_STREAM_END) {
inflateEnd(&zs);
started = FALSE;
return (CFTypeRef)NULL;
}
CFStringRef emsg = CFStringCreateWithFormat(NULL, NULL, CFSTR("Zlib error#%d"), rc);
CFErrorRef err = fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidInput, emsg);
CFRelease(emsg);
return (CFTypeRef)err;
});
}
else
{
CFErrorRef bad_type = CreateSecTransformErrorRef(kSecTransformErrorInvalidInput, "Unsupported decode type '%@', supported types are kSecBase64Encoding, kSecBase32Encoding, and kSecGZipEncoding", value);
SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, bad_type);
return (CFTypeRef)bad_type;
}
return value;
});
return result;
};
return Block_copy(instanceBlock);
}
SecTransformRef SecDecodeTransformCreate(CFTypeRef DecodeType, CFErrorRef* error) {
static dispatch_once_t once;
__block Boolean ok = TRUE;
CFErrorRef localError = NULL;
dispatch_block_t aBlock = ^
{
ok = SecTransformRegister(DecodeName, &DecodeTransform, (CFErrorRef*)&localError);
};
dispatch_once(&once, aBlock);
if (!ok || NULL != localError)
{
if (NULL != error)
{
*error = localError;
}
return NULL;
}
SecTransformRef tr = SecTransformCreate(DecodeName, &localError);
if (!tr || NULL != localError)
{
if (NULL != error)
{
*error = localError;
}
return NULL;
}
SecTransformSetAttribute(tr, kSecDecodeTypeAttribute, DecodeType, &localError);
if (NULL != localError)
{
CFRelease(tr);
tr = NULL;
if (NULL != error)
{
*error = localError;
}
}
return tr;
}
static
unsigned char *encode_base64(const unsigned char *bin, unsigned char *base64, CFIndex bin_cnt) {
for(; bin_cnt > 0; bin_cnt -= 3, base64 += 4, bin += 3) {
switch (bin_cnt)
{
default:
case 3:
base64[0] = Base64Chars[((bin[0] >> 2) & 0x3f)];
base64[1] = Base64Chars[((bin[0] & 0x03) << 4) |
((bin[1] >> 4) & 0x0f)];
base64[2] = Base64Chars[((bin[1] & 0x0f) << 2) |
((bin[2] >> 6) & 0x03)];
base64[3] = Base64Chars[(bin[2] & 0x3f)];
break;
case 2:
base64[3] = '=';
base64[0] = Base64Chars[((bin[0] >> 2) & 0x3f)];
base64[1] = Base64Chars[((bin[0] & 0x03) << 4) |
((bin[1] >> 4) & 0x0f)];
base64[2] = Base64Chars[((bin[1] & 0x0f) << 2)];
break;
case 1:
base64[3] = base64[2] = '=';
base64[0] = Base64Chars[((bin[0] >> 2) & 0x3f)];
base64[1] = Base64Chars[((bin[0] & 0x03) << 4)];
break;
case 0:
base64[0] = base64[1] = base64[2] = base64[3] = '=';
break;
}
}
return base64;
}
static SecTransformInstanceBlock EncodeTransform(CFStringRef name,
SecTransformRef newTransform,
SecTransformImplementationRef ref)
{
SecTransformInstanceBlock instanceBlock =
^{
CFErrorRef result = NULL;
SecTransformCustomSetAttribute(ref, kSecEncodeTypeAttribute,
kSecTransformMetaAttributeRequired, kCFBooleanTrue);
__block int line_length = 0, target_line_length = 0;
SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification,
kSecEncodeLineLengthAttribute,
^(SecTransformStringOrAttributeRef attribute, CFTypeRef value)
{
SecTransformPushbackAttribute(ref, attribute, value);
return value;
});
CFTypeRef (^new_line_length)(int out_chunk_size, CFTypeRef value) = ^(int out_chunk_size, CFTypeRef value)
{
if (CFGetTypeID(value) == CFNumberGetTypeID()) {
CFNumberGetValue((CFNumberRef)value, kCFNumberIntType, &target_line_length);
} else if (CFGetTypeID(value) == CFStringGetTypeID()) {
int requested_length = CFStringGetIntValue(value);
if (requested_length == 0 && CFStringCompare(CFSTR("0"), value, kCFCompareAnchored)) {
SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, CreateSecTransformErrorRef(kSecTransformErrorInvalidInput, "Could not convert '%@' to a number, please set %@ to a numeric value", kSecEncodeLineLengthAttribute, value));
} else {
target_line_length = requested_length;
}
} else {
CFStringRef valueType = CFCopyTypeIDDescription(CFGetTypeID(value));
SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, CreateSecTransformErrorRef(kSecTransformErrorInvalidType, "%@ requires a CFNumber, but was set to a %@ (%@)", kSecEncodeLineLengthAttribute, valueType, value));
CFRelease(valueType);
}
target_line_length -= target_line_length % out_chunk_size;
if (target_line_length < 0) {
target_line_length = 0;
}
return value;
};
SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification,
kSecEncodeTypeAttribute,
^(SecTransformStringOrAttributeRef attribute, CFTypeRef value)
{
if (NULL == value || CFGetTypeID(value) != CFStringGetTypeID())
{
CFErrorRef errorResult = fancy_error(kSecTransformErrorDomain,
kSecTransformErrorInvalidInput,
CFSTR("Encode type was not a CFStringRef"));
return (CFTypeRef)errorResult;
}
if (kCFCompareEqualTo == CFStringCompare(value, kSecBase64Encoding, 0))
{
__block struct { unsigned char a[3]; } leftover;
static const short int in_chunk_size = 3, out_chunk_size = 4;
__block CFIndex leftover_cnt = 0;
SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification,
kSecEncodeLineLengthAttribute,
^(SecTransformStringOrAttributeRef attribute, CFTypeRef value)
{
return new_line_length(out_chunk_size, value);
});
SecTransformSetDataAction(ref, kSecTransformActionProcessData,
^(CFTypeRef value)
{
CFDataRef d = value;
CFIndex in_len = d ? CFDataGetLength(d) : 0;
const unsigned char *in = d ? CFDataGetBytePtr(d) : NULL;
CFIndex n_chunks = in_len / in_chunk_size + 3;
CFIndex buf_len = n_chunks * out_chunk_size;
CFIndex line_len=0;
if (target_line_length)
{
line_len=(n_chunks * out_chunk_size) / target_line_length;
}
if ( (in_len<0)
|| (leftover_cnt<0)
#if __LLP64__
|| (n_chunks > LONG_LONG_MAX/out_chunk_size)
|| (buf_len > LONG_LONG_MAX-line_len)
#else
|| (n_chunks > LONG_MAX/out_chunk_size)
|| (buf_len > LONG_MAX-line_len)
#endif
|| (buf_len+line_len<in_len))
{
CFErrorRef errorResult = fancy_error(kSecTransformErrorDomain,
kSecTransformErrorInvalidLength,
CFSTR("Invalid length"));
return (CFTypeRef)errorResult;
}
buf_len+=line_len;
unsigned char *out = malloc(buf_len);
unsigned char *out_end = out + buf_len, *out_base = out;
if (!out)
{
return (CFTypeRef)GetNoMemoryError();
}
if ((leftover_cnt) && (in_chunk_size>= leftover_cnt))
{
CFIndex copy_len = in_chunk_size - leftover_cnt;
copy_len = (copy_len > in_len) ? in_len : copy_len;
memcpy(leftover.a + leftover_cnt, in, copy_len);
if (copy_len + leftover_cnt == in_chunk_size || d == NULL)
{
out = encode_base64(leftover.a, out, copy_len + leftover_cnt);
if (in)
{
in += copy_len;
in_len -= copy_len;
}
}
else
{
free(out);
leftover_cnt += copy_len;
return (CFTypeRef)SecTransformNoData();
}
}
CFIndex chunked_in_len;
while (in_len >= in_chunk_size)
{
chunked_in_len = in_len - (in_len % in_chunk_size);
if (target_line_length)
{
if (target_line_length <= line_length + out_chunk_size)
{
*out++ = '\n';
line_length = 0;
}
int max_process = (((target_line_length - line_length) / out_chunk_size) * in_chunk_size);
chunked_in_len = (chunked_in_len < max_process) ? chunked_in_len : max_process;
}
unsigned char *old_out = out;
out = encode_base64(in, out, chunked_in_len);
line_length += out - old_out;
in += chunked_in_len;
in_len -= chunked_in_len;
}
leftover_cnt = in_len;
if (leftover_cnt)
{
memcpy(leftover.a, in, leftover_cnt);
}
if (out > out_end)
{
abort();
}
CFTypeRef ret = CFDataCreateWithBytesNoCopy(NULL, out_base, out - out_base, kCFAllocatorMalloc);
if (!d)
{
SecTransformCustomSetAttribute(ref,kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, ret);
CFRelease(ret);
ret = NULL;
}
return ret;
});
}
else if (kCFCompareEqualTo == CFStringCompare(value, kSecBase32Encoding, 0) || kCFCompareEqualTo == CFStringCompare(value, kSecBase32FDEEncoding, 0))
{
__block struct { uint64_t a[2]; } accumulator = { .a = {0, 0} };
__block short int bits_accumulated = 0;
static const short int in_chunk_size = 5;
static const short int out_chunk_size = 8;
char *base32alphabet = NULL;
if (kCFCompareEqualTo == CFStringCompare(value, kSecBase32Encoding, 0)) {
base32alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
} else if (kCFCompareEqualTo == CFStringCompare(value, kSecBase32FDEEncoding, 0)) {
base32alphabet = "ABCDEFGH8JKLMNOPQR9TUVWXYZ234567";
}
if (NULL == base32alphabet) {
CFErrorRef bad_type = CreateSecTransformErrorRef(kSecTransformErrorInvalidInput, "Unknown base32 type '%@'", value);
SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, bad_type);
return (CFTypeRef)bad_type;
}
SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification,
kSecEncodeLineLengthAttribute,
^(SecTransformStringOrAttributeRef attribute, CFTypeRef value)
{
return new_line_length(out_chunk_size, value);
});
SecTransformSetDataAction(ref, kSecTransformActionProcessData,
^(CFTypeRef value)
{
CFDataRef d = value;
CFIndex in_len = d ? CFDataGetLength(d) : 0;
const unsigned char *in = d ? CFDataGetBytePtr(d) : NULL;
const unsigned char *in_end = in + in_len;
CFIndex n_chunks = in_len / in_chunk_size + 3;
CFIndex buf_len = n_chunks * out_chunk_size;
if (target_line_length)
{
buf_len += (n_chunks * out_chunk_size) / target_line_length;
}
__block unsigned char *out = malloc(buf_len);
unsigned char *out_end = out + buf_len, *out_base = out;
if (!out) {
return (CFTypeRef)GetNoMemoryError();
}
void (^chunk)(void) = ^{
short int shift = 80 - bits_accumulated;
for(; shift > 0; shift -= 8) {
accumulator.a[1] = accumulator.a[1] << 8 | accumulator.a[0] >> (64 - 8);
accumulator.a[0] = accumulator.a[0] << 8;
}
for(; bits_accumulated > 0; bits_accumulated -= 5) {
*out++ = base32alphabet[(accumulator.a[1] >> 11) & 0x1f];
accumulator.a[1] = 0xffff & (accumulator.a[1] << 5 | (accumulator.a[0] >> (64 - 5)));
accumulator.a[0] = accumulator.a[0] << 5;
if (++line_length >= target_line_length && target_line_length) {
*out++ = '\n';
line_length = 0;
}
}
bits_accumulated = 0;
};
for (; in < in_end; in++)
{
accumulator.a[1] = accumulator.a[1] << 8 | accumulator.a[0] >> (64 - 8);
accumulator.a[0] = accumulator.a[0] << 8 | *in;
bits_accumulated += 8;
if (bits_accumulated == 8*in_chunk_size)
{
chunk();
}
}
if (!d && bits_accumulated) {
short int padding = 0;
switch(bits_accumulated) {
case 8:
padding = 6;
break;
case 16:
padding = 4;
break;
case 24:
padding = 3;
break;
case 32:
padding = 1;
break;
}
chunk();
int i;
for(i = 0; i < padding; i++) {
*out++ = '=';
}
}
if (out > out_end) {
abort();
}
CFTypeRef ret = NULL;
if (out - out_base) {
ret = CFDataCreateWithBytesNoCopy(NULL, out_base, out - out_base, kCFAllocatorMalloc);
} else {
ret = SecTransformNoData();
}
if (!d) {
if (ret != SecTransformNoData()) {
SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName,
kSecTransformMetaAttributeValue, ret);
CFRelease(ret);
}
ret = NULL;
}
return ret;
});
}
else if (kCFCompareEqualTo == CFStringCompare(value, kSecZLibEncoding, 0))
{
__block z_stream zs;
bzero(&zs, sizeof(zs));
__block int clevel = Z_DEFAULT_COMPRESSION;
__block Boolean started = FALSE;
CFBooleanRef hasRatio = (CFBooleanRef)SecTranformCustomGetAttribute(ref, kSecCompressionRatio,
kSecTransformMetaAttributeHasOutboundConnections);
Boolean ratio_connected = (kCFBooleanTrue == hasRatio);
SecTransformSetDataAction(ref, kSecTransformActionProcessData,
^(CFTypeRef value)
{
CFDataRef d = value;
if (!started) {
started = TRUE;
deflateInit(&zs, clevel);
}
if (d) {
zs.next_in = (UInt8 *)CFDataGetBytePtr(d); zs.avail_in = (uInt)CFDataGetLength(d);
} else {
zs.next_in = NULL;
zs.avail_in = 0;
}
int rc = Z_BUF_ERROR;
CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
while ((d && zs.avail_in) || (d == NULL && rc != Z_STREAM_END)) {
unsigned char *buf = malloc(buf_sz);
if (!buf) {
return (CFTypeRef)GetNoMemoryError();
}
zs.next_out = buf;
zs.avail_out = (uInt)buf_sz;
rc = deflate(&zs, d ? Z_NO_FLUSH : Z_FINISH);
CFIndex buf_used = buf_sz - zs.avail_out;
#ifdef DEBUG_ZLIB_MEMORY_USE
CFfprintf(stderr, "<<zavail_in %d buf_sz %d; d %p; ", zs.avail_in, buf_sz, d);
CFfprintf(stderr, "rc=%d %s", rc, (rc == Z_OK) ? "Z_OK" : (rc == Z_STREAM_END) ? "Z_FINISH" : (rc == Z_BUF_ERROR) ? "Z_BUF_ERROR" : "?");
CFfprintf(stderr, " (output used %d, input left %d)\n", buf_used, zs.avail_in);
#endif
if (rc == Z_OK || rc == Z_STREAM_END) {
CFDataRef d;
if ((4 * buf_used) / buf_sz <= 1) {
d = CFDataCreate(NULL, buf, buf_used);
free(buf);
} else {
d = CFDataCreateWithBytesNoCopy(NULL, buf, buf_used, kCFAllocatorMalloc);
}
SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName,
kSecTransformMetaAttributeValue, d);
CFRelease(d);
} else if (rc == Z_BUF_ERROR) {
free(buf);
buf_sz = malloc_good_size(buf_sz * 2);
} else {
free(buf);
CFStringRef emsg = CFStringCreateWithFormat(NULL, NULL, CFSTR("Zlib error#%d"), rc);
CFErrorRef err = fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidInput, emsg);
CFRelease(emsg);
return (CFTypeRef)err;
}
}
if (ratio_connected && zs.total_in && zs.total_out) {
float r = (float)zs.total_out / zs.total_in;
CFNumberRef ratio = CFNumberCreate(NULL, kCFNumberFloatType, &r);
SecTransformCustomSetAttribute(ref, kSecCompressionRatio,
kSecTransformMetaAttributeValue, ratio);
CFRelease(ratio);
}
if (d) {
return (CFTypeRef)SecTransformNoData();
} else {
deflateEnd(&zs);
started = FALSE;
return (CFTypeRef)NULL;
}
});
SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification,
kSecEncodeLineLengthAttribute, ^(SecTransformStringOrAttributeRef attribute, CFTypeRef value)
{
return value;
});
}
else
{
CFErrorRef bad_type = CreateSecTransformErrorRef(kSecTransformErrorInvalidInput, "Unsupported encode type '%@', supported types are kSecBase64Encoding, kSecBase32Encoding, and kSecGZipEncoding", value);
SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, bad_type);
return (CFTypeRef)bad_type;
}
return (CFTypeRef)value;
});
return result;
};
return Block_copy(instanceBlock);
}
SecTransformRef SecEncodeTransformCreate(CFTypeRef EncodeType, CFErrorRef* error)
{
static dispatch_once_t once;
__block Boolean ok = TRUE;
CFErrorRef localError = NULL;
dispatch_block_t aBlock = ^
{
ok = SecTransformRegister(EncodeName, &EncodeTransform, (CFErrorRef*)&localError);
};
dispatch_once(&once, aBlock);
if (!ok || NULL != localError)
{
if (NULL != error)
{
*error = localError;
}
return NULL;
}
SecTransformRef tr = SecTransformCreate(EncodeName, &localError);
if (!tr || NULL != localError)
{
if (NULL != error)
{
*error = localError;
}
return NULL;
}
SecTransformSetAttribute(tr, kSecEncodeTypeAttribute, EncodeType, &localError);
if (NULL != localError)
{
CFRelease(tr);
tr = NULL;
if (NULL != error)
{
*error = localError;
}
}
return tr;
}