This chapter describes and illustrates the use of Certificate, Key, and Trust Services functions to import an identity, evaluate the trust of a certificate, determine the cause of a trust failure, and recover from a trust failure.
The sequence of operations illustrated in this chapter is:
Import an identity.
Obtain a certificate from the imported data.
Obtain a policy object for the policy used in evaluation of the certificate.
Validate the certificate and evaluate whether it can be trusted as specified by the policy.
Test for a recoverable trust error.
Determine whether the trust error is due to an expired certificate.
Change the evaluation criteria to ignore expired certificates.
Reevaluate the certificate.
“Chapter 2, Certificate, Key, and Trust Services Concepts,” provides an introduction to the concepts and terminology of Certificate, Key, and Trust Services. For detailed information about all Certificate, Key, and Trust Services functions, see Certificate, Key, and Trust Services Reference.
If you need a cryptographic identity (that is, a private key and its associated certificate) on an iOS-based device—for client-side authentication, for example—you can transfer it to the device securely as PKCS #12 data in a password-protected *.p12
file. This section shows how to extract the identity and trust objects from the PKCS #12 data and how to evaluate the trust.
Listing 2-1 shows sample code for using the SecPKCS12Import
function to extract identity and trust objects from a *.p12
file and how to evaluate the trust. Listing 2-2 shows how to get the certificate from the identity and display certificate information. Explanations for numbered lines of code follow each listing.
Be sure to add the Security framework to your Xcode project when compiling code with this snippet.
Listing 2-1 Extracting identity and trust objects from PKCS #12 Data
#import <UIKit/UIKit.h> |
#import <Security/Security.h> |
#import <CoreFoundation/CoreFoundation.h> |
NSString *thePath = [[NSBundle mainBundle] |
pathForResource:@"MyIdentity" ofType:@"p12"]; |
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath]; |
CFDataRef inPKCS12Data = (CFDataRef)PKCS12Data; // 1 |
OSStatus status = noErr; |
SecIdentityRef myIdentity; |
SecTrustRef myTrust; |
status = extractIdentityAndTrust( |
inPKCS12Data, |
&myIdentity, |
&myTrust); // 2 |
if status != 0 ... //Do some error checking here |
SecTrustResultType trustResult; |
if (status == noErr) { // 3 |
status = SecTrustEvaluate(myTrust, &trustResult); |
} |
... // 4 |
if (trustResult == kSecTrustResultRecoverableTrustFailure) { |
...; |
} |
OSStatus extractIdentityAndTrust(CFDataRef inPKCS12Data, // 5 |
SecIdentityRef *outIdentity, |
SecTrustRef *outTrust) |
{ |
OSStatus securityError = errSecSuccess; |
CFStringRef password = CFSTR("Password"); |
const void *keys[] = { kSecImportExportPassphrase }; |
const void *values[] = { password }; |
CFDictionaryRef optionsDictionary = CFDictionaryCreate( |
NULL, keys, |
values, 1, |
NULL, NULL); // 6 |
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); |
securityError = SecPKCS12Import(inPKCS12Data, |
optionsDictionary, |
&items); // 7 |
// |
if (securityError == 0) { // 8 |
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0); |
const void *tempIdentity = NULL; |
tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, |
kSecImportItemIdentity); |
*outIdentity = (SecIdentityRef)tempIdentity; |
const void *tempTrust = NULL; |
tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust); |
*outTrust = (SecTrustRef)tempTrust; |
} |
if (optionsDictionary) |
CFRelease(optionsDictionary); // 9 |
[PKCS12Data release]; |
Here’s what the code does:
Finds the PKCS #12 file and gets the data. In this example, the file is included in the application bundle. However, you can transfer the file to your application over a network if you prefer.
Calls the function that gets the identity and trust from the PKCS #12 file (see step #5).
Evaluates the trust. In this case, the trust object, containing the policy and other information needed to determine whether the certificate is trusted, is included in the PKCS data. To evaluate the trust of an isolated certificate, see Listing 2-6.
Handles the trust result. If the trust result is kSecTrustResultInvalid
, kSecTrustResultDeny
, kSecTrustResultFatalTrustFailure
, you cannot proceed and should fail gracefully. If the trust result is kSecTrustResultRecoverableTrustFailure
, you might be able to recover from the failure. See “Recovering From a Trust Failure.”
Implements the function called in step #2.
Sets up dictionary containing the password to pass to SecPKCS12Import
. Notice that core foundation dictionaries—as used here—and the NSDictionary
class are entirely equivalent. See Listing 2-9 for an example using NSDictionary
methods.
Extracts the certificate, key, and trust from the PKCS #12 data and puts them in an array.
Gets the first dictionary out of the array and gets the identity and trust out of the dictionary. The SecPKCS12Import
function returns one dictionary for each item (identity or certificate) in the PKCS #12 data. In this sample, the identity being extracted is the first one in the array (item #0).
Disposes of the options dictionary and releases the PKCS12Data, which are no longer needed.
The following listing shows how to get the certificate from the identity and how to display information from the certificate. Be sure to add the Security framework to your Xcode project when compiling code with this snippet.
Listing 2-2 Displaying information from the certificate
// Get the certificate from the identity. |
SecCertificateRef myReturnedCertificate = NULL; |
status = SecIdentityCopyCertificate (myReturnedIdentity, |
&myReturnedCertificate); // 1 |
CFStringRef certSummary = SecCertificateCopySubjectSummary |
(myReturnedCertificate); // 2 |
NSString* summaryString = [[NSString alloc] |
initWithString:(NSString*)certSummary]; // 3 |
//Display the string |
... |
[summaryString release]; // 4 |
Here’s what the code does:
Extracts the certificate from the identity.
Gets summary information from the certificate.
Converts the string to an NSString
object so it can be displayed.
Releases the NSString
object.
When you add an item to the keychain or find an item in the keychain, you can request a persistent reference. Because a persistent reference remains valid between invocations of your program and can be stored on disk, you can use one to make it easier to find a keychain item that you will need repeatedly. The following code sample shows how to obtain a persistent reference for the identity object obtained in Listing 2-1.
Listing 2-3 Getting a persistent reference for an identity
CFDataRef persistentRefForIdentity(SecIdentityRef identity) |
{ |
OSStatus status; |
CFTypeRef identity_handle = NULL; |
const void *keys[] = { kSecReturnPersistentRef, kSecValueRef }; |
const void *values[] = { kCFBooleanTrue, identity }; |
CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, |
2, NULL, NULL); |
status = SecItemAdd(dict, &persistent_ref); |
if (dict) |
CFRelease(dict); |
return (CFDataRef)persistent_ref; |
} |
The following sample shows how to retrieve the identity object from the keychain using the persistent reference.
Listing 2-4 Getting an identity using a persistent reference
SecIdentityRef identityForPersistentRef(CFDataRef persistent_ref) |
{ |
CFTypeRef identity_ref = NULL; |
const void *keys[] = { kSecReturnRef, kSecValuePersistentRef }; |
const void *values[] = { kCFBooleanTrue, persistent_ref }; |
CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, |
2, NULL, NULL); |
SecItemCopyMatching(dict, &identity_ref); |
if (dict) |
CFRelease(dict); |
return (SecIdentityRef)identity_ref; |
} |
The following code sample shows how to find a certificate in the keychain using the name of the certificate to identify it. To find a keychain item using a persistent reference, see Listing 2-4. To find a keychain item using an identifier string stored as a keychain item attribute, see “Encrypting and Decrypting Data.” An explanation for each numbered line of code follows the listing.
Listing 2-5 Finding a certificate In the Keychain
CFTypeRef certificateRef = NULL; // 1 |
const char *certLabelString = "Romeo Montegue"; |
CFStringRef certLabel = CFStringCreateWithCString( |
NULL, certLabelString, |
kCFStringEncodingUTF8); // 2 |
const void *keys[] = { kSecClass, kSecAttrLabel, kSecReturnRef }; |
const void *values[] = { kSecClassCertificate, certLabel, kCFBooleanTrue }; |
CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, |
values, 3, |
NULL, NULL); // 3 |
status = SecItemCopyMatching(dict, &certificateRef); // 4 |
if (dict) |
CFRelease(dict); |
Here’s what the code does:
Defines a variable to hold the certificate object.
Creates a string containing the name of the certificate.
Creates a dictionary of attributes to be used in the certificate search. The kSecReturnRef
key specifies that the function should return a reference to the keychain item when it’s found.
Searches for the certificate in the keychain.
Before you can evaluate the trust of a certificate, you must obtain a reference object for the certificate. You can obtain a certificate object by extracting it from an identity (see Listing 2-2), by creating one from DER certificate data using the SecCertificateCreateWithData
function (see the following sample: Listing 2-6), or by finding the certificate on a keychain (Listing 2-5).
The criteria for evaluation of trust are set by trust policies. Listing 3-2 shows how you can obtain a policy object for use in an evaluation. There are two policies available in iOS for this purpose: Basic X509 and SSL (see AppleX509TP Trust Policies). You use the SecPolicyCreateBasicX509
or SecPolicyCreateSSL
function to obtain the policy object.
The following code sample shows how to obtain a policy object and use it to evaluate trust of a certificate. An explanation for each numbered line of code follows the listing.
Listing 2-6 Obtaining a policy reference object and evaluating trust
NSString *thePath = [[NSBundle mainBundle] |
pathForResource:@"Romeo Montegue" ofType:@"cer"]; |
NSData *certData = [[NSData alloc] |
initWithContentsOfFile:thePath]; |
CFDataRef myCertData = (CFDataRef)certData; // 1 |
SecCertificateRef myCert; |
myCert = SecCertificateCreateWithData(NULL, myCertData); // 2 |
SecPolicyRef myPolicy = SecPolicyCreateBasicX509(); // 3 |
SecCertificateRef certArray[1] = { myCert }; |
CFArrayRef myCerts = CFArrayCreate( |
NULL, (void *)certArray, |
1, NULL); |
SecTrustRef myTrust; |
OSStatus status = SecTrustCreateWithCertificates( |
myCerts, |
myPolicy, |
&myTrust); // 4 |
SecTrustResultType trustResult; |
if (status == noErr) { |
status = SecTrustEvaluate(myTrust, &trustResult); // 5 |
} |
... // 6 |
if (trustResult == kSecTrustResultRecoverableTrustFailure) { |
...; |
} |
... |
if (myPolicy) |
CFRelease(myPolicy); // 7 |
Here’s what the code does:
Finds the certificate file and gets the data. In this example, the file is included in the application bundle. However, you can transfer the certificate to your application over a network if you prefer. If the certificate is already in the keychain, see “Finding a Certificate In the Keychain.”
Creates a certificate reference from the certificate data.
Creates a policy to be used in evaluating trust.
Creates a trust object using the certificate and the policy. If you have intermediate certificates or an anchor certificate for the certificate chain, you can include those in the certificate array passed to the SecTrustCreateWithCertificates
function. Doing so speeds up the trust evaluation.
Evaluates the trust.
Handles the trust result. If the trust result is kSecTrustResultInvalid
, kSecTrustResultDeny
, kSecTrustResultFatalTrustFailure
, you cannot proceed and should fail gracefully. If the trust result is kSecTrustResultRecoverableTrustFailure
, you might be able to recover from the failure. See “Recovering From a Trust Failure.”
Disposes of the policy object at the end of the routine, after it has been used to evaluate the trust.
There are several possible results of a trust evaluation, depending on such factors as whether all the certificates in the chain were found, whether they are all valid, and what the user trust settings are for the certificates. It is up to your application to determine the course of action based on the result of the evaluation. For example, if the result is kSecTrustResultConfirm
, you should display a dialog requesting that the user give permission to proceed.
The evaluation result kSecTrustResultRecoverableTrustFailure
indicates that trust was denied, but that it is possible to change settings to get a different result. For example, if the certificate used to sign a document has expired, you can change the date used for the evaluation to see whether the certificate was valid when the document was signed. The code in Listing 3-4 illustrates how to change the evaluation date. Note that the CFDateCreate
function takes an absolute time (the number of seconds since 1 January 2001); you can use the CFGregorianDateGetAbsoluteTime
function to convert a calendar date and time into an absolute time. An explanation for each numbered line of code follows the listing.
Listing 2-7 Setting an evaluation date
SecTrustResultType trustResult; |
status = SecTrustEvaluate(myTrust, &trustResult); // 1 |
//Get time used to verify trust |
CFAbsoluteTime trustTime,currentTime,timeIncrement,newTime; |
CFDateRef newDate; |
if (trustResult == kSecTrustResultRecoverableTrustFailure) {// 2 |
trustTime = SecTrustGetVerifyTime(myTrust); // 3 |
timeIncrement = 31536000; // 4 |
currentTime = CFAbsoluteTimeGetCurrent(); // 5 |
newTime = currentTime - timeIncrement; // 6 |
if (trustTime - newTime){ // 7 |
newDate = CFDateCreate(NULL, newTime); // 8 |
SecTrustSetVerifyDate(myTrust, newDate); // 9 |
status = SecTrustEvaluate(myTrust, &trustResult); // 10 |
} |
} |
if (trustResult != kSecTrustResultProceed) { // 11 |
... |
} |
Here’s what the code does:
Evaluates the trust of the certificate. See “Obtaining a Policy Object and Evaluating Trust.”
Checks whether the result of the trust evaluation was a recoverable trust failure.
Gets the absolute time that was used to evaluate the trust. If the certificate expired before this time, then it is considered invalid.
Sets a time increment equal to the number of seconds in a year.
Gets the current (absolute) time.
Subtracts a year from the current time.
Checks whether the time used to evaluate trust was more recent than one year before the current time. If it was, then the trust is evaluated again using the new time; that is, the certificate is checked to see if it failed verification because it expired sometime in the past year.
Converts the new time to a CFDateRef
. You can also use NSDate
to manipulate the dates; CFDateRef
and NSDate
are toll-free bridged, meaning in a method where you see an NSDate *
parameter, you can pass in a CFDateRef
, and in a function where you see a CFDateRef
parameter, you can pass in an instance of NSDate
or of a concrete subclass of NSDate
.
Sets the date used to verify trust to the new time (a year earlier).
Reevaluates the trust. If the reason the trust evaluation failed was because the certificate expired within a year of the current time, the evaluation should now succeed.
Checks whether the evaluation now succeeds. If not, you can try something else, such as asking the user to install an intermediate certificate, or you can tell the user that the certificate is not valid and fail gracefully.
The Certificate, Key, and Trust API includes functions for generating asymmetric key pairs and using them to encrypt and decrypt data. You might want to use this feature to encrypt data that you do not want to be accessible in backup data, for example. Or, you can use a private-public key pair shared between your iOS application and a desktop application to send encrypted data over a network. The code in Listing 2-8 shows how to generate a public-private key pair for use on the mobile device. Listing 2-9 shows how to use a public key to encrypt data using Certificate, Key, and Trust functions, and Listing 2-10 shows how to use a private key to decrypt data. Notice that these samples use Cocoa objects (such as NSMutableDictionary
) rather than the core foundation objects (such as CFMutableDictionaryRef
) used in other samples in this chapter. The Cocoa objects and their Core Foundation counterparts are completely equivalent and are toll-free bridged; for example, in a method where you see an NSMutableDictionary *
parameter, you can pass in a CFMutableDictionaryRef
, and in a function where you see a CFMutableDictionaryRef
parameter, you can pass in an instance of NSMutableDictionary
. Explanations for numbered lines of code follow each listing.
Listing 2-8 Generating a key pair
static const UInt8 publicKeyIdentifier[] = "com.apple.sample.publickey\0"; |
static const UInt8 privateKeyIdentifier[] = "com.apple.sample.privatekey\0"; |
// 1 |
- (void)generateKeyPairPlease |
{ |
OSStatus status = noErr; |
NSMutableDictionary *privateKeyAttr = [[NSMutableDictionary alloc] init]; |
NSMutableDictionary *publicKeyAttr = [[NSMutableDictionary alloc] init]; |
NSMutableDictionary *keyPairAttr = [[NSMutableDictionary alloc] init]; |
// 2 |
NSData * publicTag = [NSData dataWithBytes:publicKeyIdentifier |
length:strlen((const char *)publicKeyIdentifier)]; |
NSData * privateTag = [NSData dataWithBytes:privateKeyIdentifier |
length:strlen((const char *)privateKeyIdentifier)]; |
// 3 |
SecKeyRef publicKey = NULL; |
SecKeyRef privateKey = NULL; // 4 |
[keyPairAttr setObject:(id)kSecAttrKeyTypeRSA |
forKey:(id)kSecAttrKeyType]; // 5 |
[keyPairAttr setObject:[NSNumber numberWithInt:1024] |
forKey:(id)kSecAttrKeySizeInBits]; // 6 |
[privateKeyAttr setObject:[NSNumber numberWithBool:YES] |
forKey:(id)kSecAttrIsPermanent]; // 7 |
[privateKeyAttr setObject:privateTag |
forKey:(id)kSecAttrApplicationTag]; // 8 |
[publicKeyAttr setObject:[NSNumber numberWithBool:YES] |
forKey:(id)kSecAttrIsPermanent]; // 9 |
[publicKeyAttr setObject:publicTag |
forKey:(id)kSecAttrApplicationTag]; // 10 |
[keyPairAttr setObject:privateKeyAttr |
forKey:(id)kSecPrivateKeyAttrs]; // 11 |
[keyPairAttr setObject:publicKeyAttr |
forKey:(id)kSecPublicKeyAttrs]; // 12 |
status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr, |
&publicKey, &privateKey); // 13 |
// error handling... |
if(privateKeyAttr) [privateKeyAttr release]; |
if(publicKeyAttr) [publicKeyAttr release]; |
if(keyPairAttr) [keyPairAttr release]; |
if(publicKey) CFRelease(publicKey); |
if(privateKey) CFRelease(privateKey); // 14 |
} |
Here’s what the code does:
Defines unique strings to be added as attributes to the private and public key keychain items to make them easier to find later.
Allocates dictionaries to be used for attributes in the SecKeyGeneratePair
function.
Creates NSData
objects that contain the identifier strings defined in step 1.
Allocates SecKeyRef
objects for the public and private keys.
Sets the key-type attribute for the key pair to RSA.
Sets the key-size attribute for the key pair to 1024 bits.
Sets an attribute specifying that the private key is to be stored permanently (that is, put into the keychain).
Adds the identifier string defined in steps 1 and 3 to the dictionary for the private key.
Sets an attribute specifying that the public key is to be stored permanently (that is, put into the keychain).
Adds the identifier string defined in steps 1 and 3 to the dictionary for the public key.
Adds the dictionary of private key attributes to the key-pair dictionary.
Adds the dictionary of public key attributes to the key-pair dictionary.
Generates the key pair.
Releases memory that is no longer needed.
You can send your public key to anyone, who can then use it to encrypt data. Assuming you keep your private key secure, then only you will be able to decrypt the data. The following code sample shows how to encrypt data using a public key. This can be a public key that you generated on the device (see the preceding code sample) or a public key that you extracted from a certificate that was sent to you or that is in your keychain. You can use the SecTrustCopyPublicKey
function to extract a public key from a certificate. In the following code sample, the key is assumed to have been generated on the device and placed in the keychain. An explanation for each numbered line of code follows the listing.
Listing 2-9 Encrypting data with a public key
- (void)encryptWithPublicKey |
{ |
OSStatus status = noErr; |
size_t cipherBufferSize; |
uint8_t *cipherBuffer; // 1 |
// [cipherBufferSize] |
const uint8_t nonce[] = "the quick brown fox jumps |
over the lazy dog\0"; // 2 |
SecKeyRef publicKey = NULL; // 3 |
NSData * publicTag = [NSData dataWithBytes:publicKeyIdentifier |
length:strlen((const char *)publicKeyIdentifier)]; // 4 |
NSMutableDictionary *queryPublicKey = |
[[NSMutableDictionary alloc] init]; // 5 |
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass]; |
[queryPublicKey setObject:publicTag forKey:(id)kSecAttrApplicationTag]; |
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType]; |
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef]; |
// 6 |
status = SecItemCopyMatching |
((CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKey); // 7 |
// Allocate a buffer |
cipherBufferSize = cipherBufferSize(publicKey); |
cipherBuffer = malloc(cipherBufferSize); |
// Error handling |
if (cipherBufferSize < sizeof(nonce)) { |
// Ordinarily, you would split the data up into blocks |
// equal to cipherBufferSize, with the last block being |
// shorter. For simplicity, this example assumes that |
// the data is short enough to fit. |
printf("Could not decrypt. Packet too large.\n"); |
return; |
} |
// Encrypt using the public. |
status = SecKeyEncrypt( publicKey, |
kSecPaddingPKCS1, |
nonce, |
(size_t) sizeof(nonce)/sizeof(nonce[0]), |
cipherBuffer, |
&cipherBufferSize |
); // 8 |
// Error handling |
// Store or transmit the encrypted text |
if(publicKey) CFRelease(publicKey); |
if(queryPublicKey) [queryPublicKey release]; // 9 |
free(cipherBuffer); |
} |
Here’s what the code does:
Allocates a buffer to hold the encrypted text.
Specifies the text to be encrypted.
Allocates a SecKeyRef
object for the public key.
Creates an NSData
object containing the unique string used to identify the public key in the keychain (see steps 1, 3, and 8 in Listing 2-8).
Allocates the dictionary to be used to find the public key in the keychain.
Specifies the key-value attribute pairs for the dictionary to be used to find the public key in the keychain. The attributes specify that the keychain item is an encryption key; that the keychain item has an attribute containing the unique string specified in step 4; that the item is an RSA key; and that a reference to the keychain item is to be returned.
Calls the SecItemCopyMatching
function to find the key in the keychain.
Encrypts the data from step 2 using the key returned by the SecItemCopyMatching
function in step 7 using PKCS1 padding.
Releases memory that is no longer needed.
The following code sample shows how to decrypt data. This sample uses the private key corresponding to the public key used to encrypt the data, and assumes you already have the cipher text created in the preceding example. It gets the private key from the keychain using the same technique as used in the preceding example to get the public key.
Listing 2-10 Decrypting with a private key
- (void)decryptWithPrivateKey |
{ |
OSStatus status = noErr; |
size_t plainBufferSize;; |
uint8_t *plainBuffer; |
SecKeyRef privateKey = NULL; |
NSData * privateTag = [NSData dataWithBytes:privateKeyIdentifier |
length:strlen((const char *)privateKeyIdentifier)]; |
NSMutableDictionary *queryPrivateKey = [[NSMutableDictionary alloc] init]; |
// Set the private key query dictionary. |
[queryPrivateKey setObject:(id)kSecClassKey forKey:(id)kSecClass]; |
[queryPrivateKey setObject:privateTag forKey:(id)kSecAttrApplicationTag]; |
[queryPrivateKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType]; |
[queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef]; |
// 1 |
status = SecItemCopyMatching |
((CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKey); // 2 |
if (plainBufferSize < cipherBufferSize) { |
// Ordinarily, you would split the data up into blocks |
// equal to plainBufferSize, with the last block being |
// shorter. For simplicity, this example assumes that |
// the data is short enough to fit. |
printf("Could not decrypt. Packet too large.\n"); |
return; |
} |
// Allocate the buffer |
plainBufferSize = SecKeyGetBlockSize(privateKey); |
plainBuffer = malloc(plainBufferSize) |
// Error handling |
status = SecKeyDecrypt( privateKey, |
kSecPaddingPKCS1, |
cipherBuffer, |
cipherBufferSize, |
plainBuffer, |
&plainBufferSize |
); // 3 |
// Error handling |
// Store or display the decrypted text |
if(publicKey) CFRelease(publicKey); |
if(privateKey) CFRelease(privateKey); |
if(queryPublicKey) [queryPublicKey release]; |
if(queryPrivateKey) [queryPrivateKey release]; // 4 |
} |
Here’s what the code does:
Sets up the dictionary used to find the private key in the keychain.
Finds the private key in the keychain.
Decrypts the data.
Releases memory that is no longer needed.
Last updated: 2010-07-09