Security Transforms Basics

Security transforms can perform many tasks, including encoding and decoding, encryption and decryption, and signing and verifying. In this chapter, Base64 encoding and decoding are used as an example of how to use transforms because they are the simplest transforms.

Although your specific use of the API may not involve Base64 encoding, the basic steps for any security transform are similar; only the transform-specific parameters (encryption keys, for example) are different.

To avoid confusion, this document uses Core Foundation data types throughout. However, because the Foundation and Core Foundation types used by this API are toll-free bridged, you can substitute NSData objects for CFDataRef, NSString for CFString, and so on.

Base64 transforms are demonstrated in two different forms in the sections that follow:

Performing Basic Transforms

The basic steps for performing a transform are relatively straightforward:

bullet
To perform a basic transform
  1. Create data objects or streams to use for your source data.

    For most transforms, these must be NSData or CFDataRef objects.

    If you are reading data from a file using a read transform, you must provide an NSInputStream or CFReadStreamRef object.

    This trivial example creates a CFData object from a C string as shown below:

    sourceData = CFDataCreate(
                        kCFAllocatorDefault,
                        (const unsigned char *)sourceCString,
                        (strlen(sourceCString) + 1));
  2. Create a transform object by calling one of the *Create* functions, including:

    This example creates Base64 encoder and decoder objects as follows:

    encoder = SecEncodeTransformCreate(kSecBase64Encoding, &error);
    if (error) { CFShow(error); exit(-1); }
    decoder = SecDecodeTransformCreate(kSecBase64Encoding, &error);
    if (error) { CFShow(error); exit(-1); }
  3. Set parameters for the transform by calling SecTransformSetAttribute.

    In general, all transforms except for SecTransformCreateReadTransformWithReadStream require a value for kSecTransformInputAttributeName. You can find a list of other required and optional attributes for each type of transform in the documentation for that transform.

    For example, this transform sets the input attribute to the encoder as follows:

    SecTransformSetAttribute(encoder, kSecTransformInputAttributeName,
                             sourceData, &error);
    if (error) { CFShow(error); exit(-1); }
  4. Execute the transform by calling SecTransformExecute or SecTransformExecuteAsync.

    For example:

    encodedData = SecTransformExecute(encoder, &error);
    if (error) { CFShow(error); exit(-1); }

Listing 1-1 demonstrates a very simple use of transforms to encode and subsequently decode a block of data using Base64 encoding.

Listing 1-1  Basic security transform

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
 
void ShowAsString(CFDataRef data);
 
char *sourceCString = "All these worlds are yours except Europa.";
 
/* Encrypts and decrypts a blob of data. */
 
main(int argc, char *argv[])
{
    SecTransformRef encoder, decoder;
    CFDataRef sourceData = NULL, encodedData = NULL, decodedData = NULL;
    CFErrorRef error = NULL;
 
    /* Create a CFData object for the source C string. */
    sourceData = CFDataCreate(
                        kCFAllocatorDefault,
                        (const unsigned char *)sourceCString,
                        (strlen(sourceCString) + 1));
 
    ShowAsString(sourceData);
 
    /* Create the transform objects */
    encoder = SecEncodeTransformCreate(kSecBase64Encoding, &error);
    if (error) { CFShow(error); exit(-1); }
    decoder = SecDecodeTransformCreate(kSecBase64Encoding, &error);
    if (error) { CFShow(error); exit(-1); }
 
    /* Tell the encode transform to get its input from the
       sourceData object. */
    SecTransformSetAttribute(encoder, kSecTransformInputAttributeName,
                             sourceData, &error);
    if (error) { CFShow(error); exit(-1); }
 
    /* Execute the encode transform. */
    encodedData = SecTransformExecute(encoder, &error);
    if (error) { CFShow(error); exit(-1); }
 
    ShowAsString(encodedData);
 
    CFRelease(encoder);
    CFRelease(sourceData);
 
    /* Tell the decode transform to get its input from the
       encodedData object. */
    SecTransformSetAttribute(decoder, kSecTransformInputAttributeName,
                             encodedData, &error);
    if (error) { CFShow(error); exit(-1); }
 
    /* Execute the decode transform. */
    decodedData = SecTransformExecute(decoder, &error);
    if (error) { CFShow(error); exit(-1); }
 
    ShowAsString(decodedData);
 
    CFRelease(decoder);
    CFRelease(encodedData);
    CFRelease(decodedData);
 
}
 
 
/* Creates a CFString from a CFData object, does a CFShow
   on that string, then releases the CFString.
 */
void ShowAsString(CFDataRef data)
{
    CFStringRef str = CFStringCreateFromExternalRepresentation(
                                kCFAllocatorDefault,
                                data,
                                kCFStringEncodingUTF8);
 
    CFShow(str);
    CFRelease(str);
}

Performing Chained Transforms

In addition to individual transforms, the security transforms API also provides a way to chain multiple transforms in sequence, with each transform operating on the results from the previous transform.

bullet
To chain multiple transforms
  1. Create the transforms and set their attributes appropriately, as described in Performing Basic Transforms.

  2. Create a group transform by calling SecTransformCreateGroupTransform.

  3. Wire the transforms together by calling SecTransformConnectTransforms.

    In this call, you tell the group which property of the source transform it should wire to which property of the destination transform. Typically, you connect the kSecTransformOutputAttributeName property of the first transform object in your pipeline to the kSecTransformInputAttributeName property of the second transform object. For example:

    SecTransformConnectTransforms(encoder, kSecTransformOutputAttributeName,
                decoder, kSecTransformInputAttributeName, group, &error);
  4. Start the transform by calling SecTransformExecute or SecTransformExecuteAsync on the group.

Listing 1-2 is a replacement for the contents of the main function in Listing 1-1. This version performs the Base64 encode and decode operations in a pipeline instead of as two separate tasks. (This is not a set of operations that is commonly performed together, but it is a good way to demonstrate the basic chaining process without having to first explain multiple transform types.)

Listing 1-2  Chained security transform

    SecTransformRef encoder, decoder;
    CFDataRef sourceData = NULL, decodedData = NULL;
    CFErrorRef error = NULL;
    SecGroupTransformRef group;
 
    sourceData = CFDataCreate(
                              kCFAllocatorDefault,
                              (unsigned char *)sourceCString,
                              (strlen(sourceCString) + 1));
 
    /* Create the transform objects */
    encoder = SecEncodeTransformCreate(kSecBase64Encoding, &error);
    if (error) { CFShow(error); exit(-1); }
    decoder = SecDecodeTransformCreate(kSecBase64Encoding, &error);
    if (error) { CFShow(error); exit(-1); }
 
 
    /* Tell the encode transform to get its input from the
       sourceData object. */
    SecTransformSetAttribute(encoder, kSecTransformInputAttributeName, sourceData, &error);
    if (error) { CFShow(error); exit(-1); }
 
    /* Tell the decode transform to get its input from the
       encode transform's output. */
    group = SecTransformCreateGroupTransform();
    SecTransformConnectTransforms(encoder, kSecTransformOutputAttributeName,
    decoder, kSecTransformInputAttributeName, group, &error);
 
    /* Execute both transforms. */
    decodedData = SecTransformExecute(group, &error);
    if (error) { CFShow(error); exit(-1); }
 
    ShowAsString(sourceData);
    ShowAsString(decodedData);
 
    CFRelease(sourceData);
    CFRelease(group);