Certificate, Key, and Trust Services Tasks for OS X

This chapter describes and illustrates the use of Certificate, Key, and Trust Services functions to 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:

  1. Find a certificate in a keychain.

  2. Obtain a policy object for the policy used in evaluation of the certificate.

  3. Validate the certificate and evaluate whether it can be trusted as specified by the policy.

  4. Test for a recoverable trust error.

  5. Determine whether the trust error is due to an expired certificate.

  6. Change the evaluation criteria to ignore expired certificates.

  7. Reevaluate the certificate.

“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.

Finding a Certificate on 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 using the Secure Transport API SSLGetPeerCertificates function, by creating one from certificate data using the SecCertificateCreateFromData function, or by finding the certificate on a keychain.

Listing 3-1 shows sample code for obtaining a certificate object by finding the certificate on a keychain. In this sample, the certificate is identified by the email address of the certificate owner. You can use other certificate attributes for this purpose, such as the label of the keychain item or its modification date. A detailed explanation for each numbered line of code follows the listing.

Listing 3-1  Finding a certificate on the keychain

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <CoreServices/CoreServices.h>
OSStatus GetCertRef (SecKeychainAttributeList *attrList,
                                    SecKeychainItemRef *itemRef)
{
    OSStatus status;
    SecKeychainSearchRef searchReference = nil;
 
    status = SecKeychainSearchCreateFromAttributes (    // 1
        NULL,                                           // 2
        kSecCertificateItemClass,                       // 3
        attrList,                                       // 4
        &searchReference
    );
 
    status = SecKeychainSearchCopyNext (                // 5
        searchReference,
        itemRef
        );
    if (searchReference)
        CFRelease(searchReference);                     // 6
 
    return (status);
}
int main (int argc, const char * argv[]) {
    OSStatus status;
    SecKeychainItemRef itemRef = nil;
    SecKeychainAttributeList attrList;
    SecKeychainAttribute attrib;
    attrList.count = 1;                                 // 7
    attrList.attr  = &attrib;
    attrib.tag = kSecAlias;                             // 8
    attrib.data = "emailname@domain.com";               // 9
    attrib.length = strlen(attrib.data);
 
    status = GetCertRef (&attrList, &itemRef);
    .
    .
    .
    if (itemRef)
        CFRelease(itemRef);                             // 10
    return (status);
    }

Here’s what the code does:

  1. Sets up keychain item search criteria and gets a search reference object. This object must be disposed of when it’s no longer needed.

  2. Passes NULL to use the default keychain search list.

  3. Specifies that the search is for a certificate.

  4. Provides the list of attributes to match. The attributes are defined in the main routine (see steps 7 through 9).

  5. Finds the certificate on the keychain and retrieves the keychain item reference object. This object must be disposed of when no longer needed. Note that a keychain item object for a certificate can be cast to a certificate object.

  6. Disposes of the search reference object, which is no longer needed.

  7. Specifies that there is only one attribute in the attribute list.

  8. Specifies that the attribute is to be of type kSecAlias. In the case of a certificate, this indicates that the attribute is the email address of the certificate owner.

  9. Specifies the email address to search for.

  10. Disposes of the keychain item object at the end of the routine, after it has been used to evaluate the trust (Listing 3-3).

Obtaining a Policy Object

The criteria for evaluation of trust are set by trust policies. Trust policies can specify, for example, whether each certificate in the chain must be checked against a certificate revocation list, or that certificates’ expiration dates should be ignored.

Listing 3-2 shows how you can obtain a policy object for use in an evaluation. To use this procedure, you must know the object identifier (OID) of the policy. OIDs of policies implemented by the AppleX509TP CDSA module are shown in “AppleX509TP Trust Policies” of Certificate, Key, and Trust Services Reference. A detailed explanation for each numbered line of code follows the listing.

Listing 3-2  Obtaining a policy reference object

OSStatus FindPolicy (const CSSM_OID *policyOID, SecPolicyRef *policyRef)
{
    OSStatus status1;
    OSStatus status2;
    SecPolicySearchRef searchRef;
 
    status1 = SecPolicySearchCreate (               // 1
        CSSM_CERT_X_509v3,                          // 2
        policyOID,                                  // 3
        NULL,
        &searchRef
        );
 
    status2 = SecPolicySearchCopyNext (             // 4
        searchRef,
        policyRef
        );
 
    if (searchRef)
        CFRelease(searchRef);                       // 5
    return (status2);
}
int main (int argc, const char * argv[]) {
    OSStatus status;
    const CSSM_OID *myPolicyOID = &CSSMOID_APPLE_X509_BASIC;
    SecPolicyRef policyRef = nil;
    status = FindPolicy (myPolicyOID, &policyRef);
    .
    .
    .
    if (policyRef)
        CFRelease(policyRef);                        // 6
    return (status);
    }

Here’s what the code does:

  1. Sets up policy search criteria and gets a policy search reference object. This object must be disposed of when no longer needed.

  2. Specifies the type of certificates used by the policy. Specify CSSM_CERT_X_509v3 if you are uncertain of the certificate type.

  3. Specifies the OID of the policy.

  4. Finds the policy and retrieves the policy reference object. This object must be disposed of when no longer needed.

  5. Disposes of the search reference object, which is no longer needed.

  6. Disposes of the policy object at the end of the routine, after it has been used to evaluate the trust (Listing 3-3).

Evaluating Trust

Having obtained a certificate object (Listing 3-1) and a policy object (Listing 3-2), you can evaluate the trust of a certificate. If you know—or have a good guess for—the intermediate and root certificates needed to verify the certificate, you can include them in the array of certificates passed to the SecTrustCreateWithCertificates function. Whereas the intermediate and root certificates can be in any order, the leaf certificate—the one you want to evaluate—must be the first in the array. You can include certificates not needed for the evaluation with no ill effects. Any certificates in the certificate chain that you do not pass in to the function are sought in keychains on the system. Certificates and certificate chains are discussed in Security Overview.

Listing 3-3 illustrates how you use Certificate, Key, and Trust functions to evaluate trust of a certificate. A detailed explanation for each numbered line of code follows the listing.

Listing 3-3  Evaluating trust

OSStatus EvaluateCert (SecCertificateRef cert, CFTypeRef policyRef,
                            SecTrustResultType *result,
                            SecTrustRef *pTrustRef)
{
    OSStatus status1;
    OSStatus status2;
 
    SecCertificateRef evalCertArray[1] = { cert };  // 1
    CFArrayRef cfCertRef = CFArrayCreate ((CFAllocatorRef) NULL,
                                        (void *)evalCertArray, 1,
                                        &kCFTypeArrayCallBacks);
    if (!cfCertRef)
        return memFullErr;                          // 2
 
    status1 = SecTrustCreateWithCertificates (      // 3
        cfCertRef,                                  // 4
        policyRef,                                  // 5
        pTrustRef
        );
    if (status1)
        return status1;
 
    status2 = SecTrustEvaluate (                    // 6
        *pTrustRef,
        result                                      // 7
        );
    // Release the objects we allocated
    if (cfCertRef)
        CFRelease(cfCertRef);
    if (cfDate)
        CFRelease(cfDate);
 
    return (status2);
}
int main (int argc, const char * argv[]) {
    OSStatus status;
    OSStatus status1;
    OSStatus status2;
 
    SecKeychainItemRef itemRef = nil;
    SecKeychainAttributeList attrList;
    SecKeychainAttribute attrib;
    attrList.count = 1;
    attrList.attr  = &attrib;
    attrib.tag = kSecAlias;
    attrib.data = "emailname@domain.com";
    attrib.length = strlen(attrib.data);
 
    const CSSM_OID *myPolicyOID = &CSSMOID_APPLE_X509_BASIC;
    SecPolicyRef policyRef = nil;
 
    SecTrustRef trustRef = nil;
    SecTrustResultType result;
 
    CFArrayRef certChain;
    CSSM_TP_APPLE_EVIDENCE_INFO *statusChain = nil;
 
    status = GetCertRef (&attrList, &itemRef);      // 8
    status1 = FindPolicy (myPolicyOID, &policyRef); // 9
 
    status2 = EvaluateCert (
                            (SecCertificateRef)itemRef,
                            (CFTypeRef) policyRef,
                             &result, &trustRef);   // 10
    .
    .
    .
    if (itemRef)
        CFRelease(itemRef);
    if (policyRef)
        CFRelease(policyRef);
    if (trustRef)
        CFRelease(trustRef);                        // 11
    return (status2);
    }

Here’s what the code does:

  1. Makes a CFArray of one element from the provided certificate. See Listing 3-1 for code to find a certificate on a keychain.

  2. Returns with an error if it can’t allocate the array.

  3. Sets up trust evaluation criteria and gets a trust management object. This object must be disposed of when no longer needed.

  4. Provides an array of certificates, containing the certificate to be evaluated and possibly intermediate and root certificates that might be needed in the evaluation. In this sample, the array contains only one certificate.

  5. Specifies the policy reference object of the policy to used in evaluating this certificate. See Listing 3-2 for code to obtain a policy reference object.

  6. Evaluates the certificate according to the specified policy.

  7. Returns a result object that is used to obtain information about the result of the evaluation; see Listing 3-5.

  8. Gets a certificate reference object; see Listing 3-1.

  9. Gets a policy reference object; see Listing 3-2.

  10. Evaluates the certificate and obtains a trust reference object. This object must be disposed of when no longer needed. The keychain item object is cast to a certificate object, which is possible because the certificate is on the keychain.

  11. Disposes of the trust reference object, after it has been used to recover from a trust failure (Listing 3-5).

Recovering From a Trust Failure

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.

Listing 3-4  Setting an evaluation date

    OSStatus status;
 
    CFAbsoluteTime expDate;
    CFDateRef cfDate = nil;
    expDate = 157680000;  //seconds since 1 Jan 2001
    cfDate = CFDateCreate (NULL, expDate);
    status = SecTrustSetVerifyDate (*pTrustRef, cfDate);

Listing 3-5 illustrates recovery from a trust failure caused by an expired certificate. In this case, the evaluation criteria are changed to ignore expiration dates. A detailed explanation for each numbered line of code follows the listing.

Listing 3-5  Recovering from a trust failure

     int n;
     CSSM_TP_APPLE_CERT_STATUS AllStatusBits = 0;
 
status3 = EvaluateCert (
                        (SecCertificateRef)itemRef,
                        (CFTypeRef) policyRef,
                        &result, &trustRef);                // 1
 
if (status3 == noErr)
    {
    if (result ==  kSecTrustResultRecoverableTrustFailure)  // 2
        {
            status3 = SecTrustGetResult (
                                        trustRef,           // 3
                                        &result,            // 4
                                        &certChain,         // 5
                                        &statusChain        // 6
                                        );
            if (!status3 && statusChain)
            {                                               // 7
                for (n = 0; n <                            // 8
                        CFArrayGetCount(certChain); n++)
                AllStatusBits =
                    AllStatusBits | statusChain[n].StatusBits;
            if (AllStatusBits & CSSM_CERT_STATUS_EXPIRED)
                {
                    CSSM_APPLE_TP_ACTION_DATA actionData;   // 9
                    actionData.Version =
                            CSSM_APPLE_TP_ACTION_VERSION;   // 10
                    actionData.ActionFlags =
                        CSSM_TP_ACTION_ALLOW_EXPIRED |
                        CSSM_TP_ACTION_ALLOW_EXPIRED_ROOT;  // 11
 
                    CFDataRef myActionData =                // 12
                        CFDataCreateWithBytesNoCopy
                            (NULL, (UInt8*) &actionData,
                             sizeof(actionData),
                             kCFAllocatorNull);             // 13
 
                    if (myActionData)
                        {
                            status2 = SecTrustSetParameters (
                                        trustRef,
                                        CSSM_TP_ACTION_DEFAULT,
                                        myActionData);      // 14
                            status3 = SecTrustEvaluate (
                                        trustRef,
                                        &result
                                        );                  // 15
                            status3 = SecTrustGetResult (
                                        trustRef,
                                        &result,
                                        &certChain,
                                        &statusChain);      // 16
 
                            CFRelease(myActionData);
                    }
                }
                }
            }
    }

Here’s what the code does:

  1. Evaluates trust for a specific certificate and policy (see Listing 3-3).

  2. Checks the result of the evaluation. If there was a recoverable trust failure, the routine goes on to obtain more information.

  3. Passes the trust management object obtained earlier.

  4. Passes a pointer to the result object obtained earlier.

  5. Returns the certificate chain used to verify the evaluated certificate.

  6. Returns an array of structures, each of which contains information about the status of one certificate in the chain.

  7. If the function succeeds, checks for the validity of statusChain. The statusChain pointer is left uninitialized if (result != kSecTrustResultRecoverableTrustFailure) or if (status3 != noErr).

  8. Checks to see if the status bits of any certificates in the chain indicate an expired certificate. If so, you can recover from this condition.

    Before proceeding, you should prompt the user to ask permission to use expired certificates. You can check through the status chain to determine which certificate has expired and give that information to the user. If the user approves of using expired certificates, continue with the rest of this sample. If not, quit here.

  9. Allocates space on the stack for an action data structure.

  10. Fills in the Version field of the action data structure.

  11. Fills in the ActionFlags field of the action data structure to allow expired certificates and root certificates.

  12. Creates a CFDataRef from the action data.

  13. Passes kCFAllocatorNull as the last parameter (bytesDeallocator) so that the bytes the CFDataRef points to aren’t freed automatically.

  14. Uses the action data to set the parameters for the trust object so that the next time it is evaluated, it will allow expired certificates.

  15. Reevaluates the trust.

  16. Checks the results of the evaluation.