]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_keychain/lib/SecCertificateP.c
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_keychain / lib / SecCertificateP.c
diff --git a/Security/libsecurity_keychain/lib/SecCertificateP.c b/Security/libsecurity_keychain/lib/SecCertificateP.c
new file mode 100644 (file)
index 0000000..0fe9d52
--- /dev/null
@@ -0,0 +1,4742 @@
+/*
+ * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+/*
+ * SecCertificate.c - CoreFoundation based certificate object
+ */
+
+
+//#include <Security/SecCertificateInternal.h>
+#include "SecCertificateInternalP.h"
+
+#include <CommonCrypto/CommonDigest.h>
+#include <CoreFoundation/CFRuntime.h>
+#include <CoreFoundation/CFString.h>
+#include <CoreFoundation/CFBundle.h>
+#include <CoreFoundation/CFDictionary.h>
+#include <CoreFoundation/CFNumber.h>
+#include <CoreFoundation/CFTimeZone.h>
+#include <pthread.h>
+#include <string.h>
+#include <AssertMacros.h>
+#include <libDER/libDER.h>
+#include <libDER/DER_CertCrl.h>
+#include <libDER/DER_Encode.h>
+#include <libDER/DER_Keys.h>
+#include <libDER/asn1Types.h>
+#include <libDER/oids.h>
+
+#include "SecBasePriv.h"
+
+#include "SecRSAKeyP.h"
+#include "SecFrameworkP.h"
+#include "SecItem.h"
+#include "SecItemPriv.h"
+#include <stdbool.h>
+#include <stdlib.h>
+#include <libkern/OSByteOrder.h>
+#include <ctype.h>
+#include "SecInternalP.h"
+#include "SecBase64P.h"
+
+#include <security_utilities/debugging.h>
+
+typedef struct SecCertificateExtension {
+       DERItem extnID;
+    bool critical;
+    DERItem extnValue;
+} SecCertificateExtension;
+
+#if 0
+typedef struct KnownExtension {
+    bool critical;
+    DERItem extnValue;
+} KnownExtension;
+
+enum {
+    kSecSelfSignedUnknown = 0,
+    kSecSelfSignedFalse,
+    kSecSelfSignedTrue,
+};
+#endif
+
+struct __SecCertificate {
+    CFRuntimeBase              _base;
+
+       DERItem                         _der;                   /* Entire certificate in DER form. */
+       DERItem                         _tbs;                   /* To Be Signed cert DER bytes. */
+    DERAlgorithmId      _sigAlg;               /* Top level signature algorithm. */
+    DERItem                            _signature;             /* The content of the sig bit string. */
+
+    UInt8               _version;
+       DERItem                         _serialNum;             /* Integer. */
+    DERAlgorithmId      _tbsSigAlg;            /* sig alg MUST be same as _sigAlg. */
+       DERItem                         _issuer;                /* Sequence of RDN. */
+       CFAbsoluteTime      _notBefore;
+       CFAbsoluteTime      _notAfter;
+       DERItem                         _subject;               /* Sequence of RDN. */
+       DERAlgorithmId          _algId;                 /* oid and params of _pubKeyDER. */
+    DERItem             _pubKeyDER;            /* contents of bit string */
+       DERItem                         _issuerUniqueID;                /* bit string, optional */
+       DERItem                         _subjectUniqueID;               /* bit string, optional */
+
+#if 0
+    /* Known extensions if the certificate contains them,
+       extnValue.length will be > 0. */
+    KnownExtension      _authorityKeyID;
+
+    /* This extension is used to uniquely identify a certificate from among
+       several that have the same subject name. If the extension is not
+       present, its value is calculated by performing a SHA-1 hash of the
+       certificate's DER encoded subjectPublicKeyInfo, as recommended by
+       PKIX. */
+    KnownExtension      _subjectKeyID;
+    KnownExtension      _keyUsage;
+    KnownExtension      _extendedKeyUsage;
+    KnownExtension      _basicConstraints;
+    KnownExtension      _netscapeCertType;
+    KnownExtension      _subjectAltName;
+    KnownExtension      _qualCertStatements;
+
+#endif
+    bool                _foundUnknownCriticalExtension;
+
+       /* Well known certificate extensions. */
+    SecCEBasicConstraints       _basicConstraints;
+    SecCEPolicyConstraints      _policyConstraints;
+    CFDictionaryRef             _policyMappings;
+    SecCECertificatePolicies    _certificatePolicies;
+
+    /* If InhibitAnyPolicy extension is not present or invalid UINT32_MAX,
+       value of the SkipCerts field of the InhibitAnyPolicy extension
+       otherwise. */
+    uint32_t _inhibitAnyPolicySkipCerts;
+
+    /* If KeyUsage extension is not present this is 0, otherwise it's
+       the value of the extension. */
+    SecKeyUsage _keyUsage;
+
+       /* OCTECTS of SubjectKeyIdentifier extensions KeyIdentifier.
+          Length = 0 if not present. */
+    DERItem                            _subjectKeyIdentifier;
+
+       /* OCTECTS of AuthorityKeyIdentifier extensions KeyIdentifier.
+          Length = 0 if not present. */
+    DERItem                            _authorityKeyIdentifier;
+       /* AuthorityKeyIdentifier extension _authorityKeyIdentifierIssuer and
+          _authorityKeyIdentifierSerialNumber have non zero length if present.
+          Both are either present or absent together.  */
+    DERItem                            _authorityKeyIdentifierIssuer;
+    DERItem                            _authorityKeyIdentifierSerialNumber;
+
+       /* Subject alt name extension, if present.  Not malloced, it's just a
+          pointer to an element in the _extensions array. */
+       const SecCertificateExtension   *_subjectAltName;
+
+    /* Parsed extension values. */
+
+    /* Array of CFURLRefs containing the URI values of crlDistributionPoints. */
+    CFMutableArrayRef   _crlDistributionPoints;
+
+    /* Array of CFURLRefs containing the URI values of accessLocations of each
+       id-ad-ocsp AccessDescription in the Authority Information Access
+       extension. */
+    CFMutableArrayRef   _ocspResponders;
+
+    /* Array of CFURLRefs containing the URI values of accessLocations of each
+       id-ad-caIssuers AccessDescription in the Authority Information Access
+       extension. */
+    CFMutableArrayRef   _caIssuers;
+
+    /* All other (non known) extensions.   The _extensions array is malloced. */
+    CFIndex             _extensionCount;
+    SecCertificateExtension *_extensions;
+
+       /* Optional cached fields. */
+       SecKeyRef                       _pubKey;
+       CFDataRef                       _der_data;
+       CFArrayRef                      _properties;
+    CFDataRef                  _serialNumber;
+    CFDataRef                  _normalizedIssuer;
+       CFDataRef                       _normalizedSubject;
+       CFDataRef                       _authorityKeyID;
+       CFDataRef                       _subjectKeyID;
+
+       CFDataRef                       _sha1Digest;
+    uint8_t             _isSelfSigned;
+
+};
+
+/* Public Constants for property list keys. */
+CFStringRef kSecPropertyKeyType             = CFSTR("type");
+CFStringRef kSecPropertyKeyLabel            = CFSTR("label");
+CFStringRef kSecPropertyKeyLocalizedLabel   = CFSTR("localized label");
+CFStringRef kSecPropertyKeyValue            = CFSTR("value");
+
+/* Public Constants for property list values. */
+CFStringRef kSecPropertyTypeWarning         = CFSTR("warning");
+CFStringRef kSecPropertyTypeError           = CFSTR("error");
+CFStringRef kSecPropertyTypeSuccess         = CFSTR("success");
+CFStringRef kSecPropertyTypeTitle           = CFSTR("title");
+CFStringRef kSecPropertyTypeSection         = CFSTR("section");
+CFStringRef kSecPropertyTypeData            = CFSTR("data");
+CFStringRef kSecPropertyTypeString          = CFSTR("string");
+CFStringRef kSecPropertyTypeURL             = CFSTR("url");
+CFStringRef kSecPropertyTypeDate            = CFSTR("date");
+
+/* Extension parsing routine. */
+typedef void (*SecCertificateExtensionParser)(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn);
+
+/* CFRuntime regsitration data. */
+static pthread_once_t kSecCertificateRegisterClass = PTHREAD_ONCE_INIT;
+static CFTypeID kSecCertificateTypeID = _kCFRuntimeNotATypeID;
+
+/* Mapping from extension OIDs (as a DERItem *) to
+   SecCertificateExtensionParser extension parsing routines. */
+static CFDictionaryRef gExtensionParsers;
+
+/* Forward declartions of static functions. */
+static CFStringRef SecCertificateDescribe(CFTypeRef cf);
+static void SecCertificateDestroy(CFTypeRef cf);
+static bool derDateGetAbsoluteTime(const DERItem *dateChoice,
+    CFAbsoluteTime *absTime);
+
+/* Static functions. */
+static CFStringRef SecCertificateDescribe(CFTypeRef cf) {
+    SecCertificateRefP certificate = (SecCertificateRefP)cf;
+    return CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
+        CFSTR("<cert(%p) s: %@ i: %@>"), certificate,
+        SecCertificateCopySubjectSummaryP(certificate),
+        SecCertificateCopyIssuerSummaryP(certificate));
+}
+
+static void SecCertificateDestroy(CFTypeRef cf) {
+    SecCertificateRefP certificate = (SecCertificateRefP)cf;
+    if (certificate->_certificatePolicies.policies)
+        free(certificate->_certificatePolicies.policies);
+    CFReleaseSafe(certificate->_policyMappings);
+    CFReleaseSafe(certificate->_crlDistributionPoints);
+    CFReleaseSafe(certificate->_ocspResponders);
+    CFReleaseSafe(certificate->_caIssuers);
+    if (certificate->_extensions) {
+        free(certificate->_extensions);
+    }
+    CFReleaseSafe(certificate->_pubKey);
+    CFReleaseSafe(certificate->_der_data);
+    CFReleaseSafe(certificate->_properties);
+    CFReleaseSafe(certificate->_serialNumber);
+    CFReleaseSafe(certificate->_normalizedIssuer);
+    CFReleaseSafe(certificate->_normalizedSubject);
+    CFReleaseSafe(certificate->_authorityKeyID);
+    CFReleaseSafe(certificate->_subjectKeyID);
+    CFReleaseSafe(certificate->_sha1Digest);
+}
+
+static Boolean SecCertificateEqual(CFTypeRef cf1, CFTypeRef cf2) {
+    SecCertificateRefP cert1 = (SecCertificateRefP)cf1;
+    SecCertificateRefP cert2 = (SecCertificateRefP)cf2;
+    if (cert1 == cert2)
+        return true;
+    if (!cert2 || cert1->_der.length != cert2->_der.length)
+        return false;
+    return !memcmp(cert1->_der.data, cert2->_der.data, cert1->_der.length);
+}
+
+/* Hash of the certificate is der length + signature length + last 4 bytes
+   of signature. */
+static CFHashCode SecCertificateHash(CFTypeRef cf) {
+    SecCertificateRefP certificate = (SecCertificateRefP)cf;
+       DERSize der_length = certificate->_der.length;
+       DERSize sig_length = certificate->_signature.length;
+       DERSize ix = (sig_length > 4) ? sig_length - 4 : 0;
+       CFHashCode hashCode = 0;
+       for (; ix < sig_length; ++ix)
+               hashCode = (hashCode << 8) + certificate->_signature.data[ix];
+
+       return (hashCode + der_length + sig_length);
+}
+
+#if 1
+
+/************************************************************************/
+/************************* General Name Parsing *************************/
+/************************************************************************/
+
+typedef OSStatus (*parseGeneralNameCallback)(void *context,
+       SecCEGeneralNameType type, const DERItem *value);
+
+
+/*
+      GeneralName ::= CHOICE {
+           otherName                       [0]     OtherName,
+           rfc822Name                      [1]     IA5String,
+           dNSName                         [2]     IA5String,
+           x400Address                     [3]     ORAddress,
+           directoryName                   [4]     Name,
+           ediPartyName                    [5]     EDIPartyName,
+           uniformResourceIdentifier       [6]     IA5String,
+           iPAddress                       [7]     OCTET STRING,
+           registeredID                    [8]     OBJECT IDENTIFIER}
+
+      OtherName ::= SEQUENCE {
+           type-id    OBJECT IDENTIFIER,
+           value      [0] EXPLICIT ANY DEFINED BY type-id }
+
+      EDIPartyName ::= SEQUENCE {
+           nameAssigner            [0]     DirectoryString OPTIONAL,
+           partyName               [1]     DirectoryString }
+ */
+static OSStatus parseGeneralNameContentProperty(DERTag tag,
+       const DERItem *generalNameContent,
+       void *context, parseGeneralNameCallback callback) {
+       switch (tag) {
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 0:
+               return callback(context, GNT_OtherName, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | 1:
+               return callback(context, GNT_RFC822Name, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | 2:
+               return callback(context, GNT_DNSName, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 3:
+               return callback(context, GNT_X400Address, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 4:
+               return callback(context, GNT_DirectoryName, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 5:
+               return callback(context, GNT_EdiPartyName, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 6:
+       {
+               /* Technically I don't think this is valid, but there are certs out
+                  in the wild that use a constructed IA5String.   In particular the
+                  VeriSign Time Stamping Authority CA.cer does this.  */
+               DERDecodedInfo uriContent;
+               require_noerr(DERDecodeItem(generalNameContent, &uriContent), badDER);
+               require(uriContent.tag == ASN1_IA5_STRING, badDER);
+               return callback(context, GNT_URI, &uriContent.content);
+       }
+       case ASN1_CONTEXT_SPECIFIC | 6:
+               return callback(context, GNT_URI, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | 7:
+               return callback(context, GNT_IPAddress, generalNameContent);
+       case ASN1_CONTEXT_SPECIFIC | 8:
+               return callback(context, GNT_RegisteredID, generalNameContent);
+       default:
+               goto badDER;
+       }
+badDER:
+       return errSecInvalidCertificate;
+}
+
+static OSStatus parseGeneralNamesContent(const DERItem *generalNamesContent,
+       void *context, parseGeneralNameCallback callback) {
+    DERSequence gnSeq;
+    DERReturn drtn = DERDecodeSeqContentInit(generalNamesContent, &gnSeq);
+    require_noerr_quiet(drtn, badDER);
+    DERDecodedInfo generalNameContent;
+    while ((drtn = DERDecodeSeqNext(&gnSeq, &generalNameContent)) ==
+               DR_Success) {
+               OSStatus status = parseGeneralNameContentProperty(
+                       generalNameContent.tag, &generalNameContent.content, context,
+                               callback);
+               if (status)
+                       return status;
+       }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+       return errSecSuccess;
+
+badDER:
+       return errSecInvalidCertificate;
+}
+
+static OSStatus parseGeneralNames(const DERItem *generalNames, void *context,
+       parseGeneralNameCallback callback) {
+    DERDecodedInfo generalNamesContent;
+    DERReturn drtn = DERDecodeItem(generalNames, &generalNamesContent);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(generalNamesContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+    return parseGeneralNamesContent(&generalNamesContent.content, context,
+               callback);
+badDER:
+       return errSecInvalidCertificate;
+}
+
+#else
+
+/*
+      GeneralName ::= CHOICE {
+           otherName                       [0]     OtherName,
+           rfc822Name                      [1]     IA5String,
+           dNSName                         [2]     IA5String,
+           x400Address                     [3]     ORAddress,
+           directoryName                   [4]     Name,
+           ediPartyName                    [5]     EDIPartyName,
+           uniformResourceIdentifier       [6]     IA5String,
+           iPAddress                       [7]     OCTET STRING,
+           registeredID                    [8]     OBJECT IDENTIFIER}
+
+      EDIPartyName ::= SEQUENCE {
+           nameAssigner            [0]     DirectoryString OPTIONAL,
+           partyName               [1]     DirectoryString }
+ */
+static OSStatus parseGeneralNameContentProperty(DERTag tag,
+       const DERItem *generalNameContent, SecCEGeneralName *generalName) {
+       switch (tag) {
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 0:
+               generalName->nameType = GNT_OtherName;
+               generalName->berEncoded = true;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 1:
+               /* IA5String. */
+               generalName->nameType = GNT_RFC822Name;
+               generalName->berEncoded = false;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 2:
+               /* IA5String. */
+               generalName->nameType = GNT_DNSName;
+               generalName->berEncoded = false;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 3:
+               generalName->nameType = GNT_X400Address;
+               generalName->berEncoded = true;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 4:
+               generalName->nameType = GNT_DirectoryName;
+               generalName->berEncoded = true;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 5:
+               generalName->nameType = GNT_EdiPartyName;
+               generalName->berEncoded = true;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 6:
+       {
+               /* Technically I don't think this is valid, but there are certs out
+                  in the wild that use a constructed IA5String.   In particular the
+                  VeriSign Time Stamping Authority CA.cer does this.  */
+               DERDecodedInfo decoded;
+               require_noerr(DERDecodeItem(generalNameContent, &decoded), badDER);
+               require(decoded.tag == ASN1_IA5_STRING, badDER);
+               generalName->nameType = GNT_URI;
+               generalName->berEncoded = false;
+               generalName->name = decoded.content;
+               break;
+       }
+       case ASN1_CONTEXT_SPECIFIC | 6:
+               generalName->nameType = GNT_URI;
+               generalName->berEncoded = false;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 7:
+               /* @@@ This is the IP Address as an OCTECT STRING. For IPv4 it's
+                  8 octects, addr/mask for ipv6 it's 32.  */
+               generalName->nameType = GNT_IPAddress;
+               generalName->berEncoded = false;
+               generalName->name = *generalNameContent;
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 8:
+               /* name is the content of an OID. */
+               generalName->nameType = GNT_RegisteredID;
+               generalName->berEncoded = false;
+               generalName->name = *generalNameContent;
+               break;
+       default:
+               goto badDER;
+               break;
+       }
+       return errSecSuccess;
+badDER:
+       return errSecInvalidCertificate;
+}
+
+/*
+      GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ */
+static OSStatus parseGeneralNamesContent(const DERItem *generalNamesContent,
+       CFIndex *count, SecCEGeneralName **name) {
+       SecCEGeneralName *generalNames = NULL;
+    DERSequence gnSeq;
+    DERReturn drtn = DERDecodeSeqContentInit(generalNamesContent, &gnSeq);
+    require_noerr_quiet(drtn, badDER);
+    DERDecodedInfo generalNameContent;
+       CFIndex generalNamesCount = 0;
+    while ((drtn = DERDecodeSeqNext(&gnSeq, &generalNameContent)) ==
+               DR_Success) {
+               ++generalNamesCount;
+       }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+
+       require(generalNames = calloc(generalNamesCount, sizeof(SecCEGeneralName)),
+               badDER);
+    DERDecodeSeqContentInit(generalNamesContent, &gnSeq);
+       CFIndex ix = 0;
+    while ((drtn = DERDecodeSeqNext(&gnSeq, &generalNameContent)) ==
+               DR_Success) {
+               if (!parseGeneralNameContentProperty(generalNameContent.tag,
+                       &generalNameContent.content, &generalNames[ix])) {
+                       goto badDER;
+               }
+               ++ix;
+    }
+       *count = generalNamesCount;
+       *name = generalNames;
+       return errSecSuccess;
+
+badDER:
+       if (generalNames)
+               free(generalNames);
+       return errSecInvalidCertificate;
+}
+
+static OSStatus parseGeneralNames(const DERItem *generalNames,
+       CFIndex *count, SecCEGeneralName **name) {
+    DERDecodedInfo generalNamesContent;
+    DERReturn drtn = DERDecodeItem(generalNames, &generalNamesContent);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(generalNamesContent.tag == ASN1_CONSTR_SEQUENCE,
+        badDER);
+    parseGeneralNamesContent(&generalNamesContent.content, count, name);
+    return errSecSuccess;
+badDER:
+       return errSecInvalidCertificate;
+}
+#endif
+
+/************************************************************************/
+/************************** X.509 Name Parsing **************************/
+/************************************************************************/
+
+typedef OSStatus (*parseX501NameCallback)(void *context, const DERItem *type,
+       const DERItem *value, CFIndex rdnIX);
+
+static OSStatus parseRDNContent(const DERItem *rdnSetContent, void *context,
+       parseX501NameCallback callback) {
+       DERSequence rdn;
+       DERReturn drtn = DERDecodeSeqContentInit(rdnSetContent, &rdn);
+       require_noerr_quiet(drtn, badDER);
+       DERDecodedInfo atvContent;
+       CFIndex rdnIX = 0;
+       while ((drtn = DERDecodeSeqNext(&rdn, &atvContent)) == DR_Success) {
+               require_quiet(atvContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+               DERAttributeTypeAndValue atv;
+               drtn = DERParseSequenceContent(&atvContent.content,
+                       DERNumAttributeTypeAndValueItemSpecs,
+                       DERAttributeTypeAndValueItemSpecs,
+                       &atv, sizeof(atv));
+               require_noerr_quiet(drtn, badDER);
+               require_quiet(atv.type.length != 0, badDER);
+               OSStatus status = callback(context, &atv.type, &atv.value, rdnIX++);
+               if (status)
+                       return status;
+       }
+       require_quiet(drtn == DR_EndOfSequence, badDER);
+
+       return errSecSuccess;
+badDER:
+       return errSecInvalidCertificate;
+}
+
+static OSStatus parseX501NameContent(const DERItem *x501NameContent, void *context,
+       parseX501NameCallback callback) {
+       DERSequence derSeq;
+       DERReturn drtn = DERDecodeSeqContentInit(x501NameContent, &derSeq);
+       require_noerr_quiet(drtn, badDER);
+       DERDecodedInfo currDecoded;
+       while ((drtn = DERDecodeSeqNext(&derSeq, &currDecoded)) == DR_Success) {
+               require_quiet(currDecoded.tag == ASN1_CONSTR_SET, badDER);
+               OSStatus status = parseRDNContent(&currDecoded.content, context,
+                       callback);
+               if (status)
+                       return status;
+       }
+       require_quiet(drtn == DR_EndOfSequence, badDER);
+
+       return errSecSuccess;
+
+badDER:
+       return errSecInvalidCertificate;
+}
+
+static OSStatus parseX501Name(const DERItem *x501Name, void *context,
+       parseX501NameCallback callback) {
+       DERDecodedInfo x501NameContent;
+       if (DERDecodeItem(x501Name, &x501NameContent) ||
+        x501NameContent.tag != ASN1_CONSTR_SEQUENCE) {
+               return errSecInvalidCertificate;
+    } else {
+        return parseX501NameContent(&x501NameContent.content, context,
+                       callback);
+    }
+}
+
+/************************************************************************/
+/********************** Extension Parsing Routines **********************/
+/************************************************************************/
+
+static void SecCEPSubjectKeyIdentifier(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+    DERDecodedInfo keyIdentifier;
+       DERReturn drtn = DERDecodeItem(&extn->extnValue, &keyIdentifier);
+       require_noerr_quiet(drtn, badDER);
+       require_quiet(keyIdentifier.tag == ASN1_OCTET_STRING, badDER);
+       certificate->_subjectKeyIdentifier = keyIdentifier.content;
+
+       return;
+badDER:
+       secdebug("cert", "Invalid SubjectKeyIdentifier Extension");
+}
+
+static void SecCEPKeyUsage(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+    SecKeyUsage keyUsage = extn->critical ? kSecKeyUsageCritical : 0;
+    DERDecodedInfo bitStringContent;
+    DERReturn drtn = DERDecodeItem(&extn->extnValue, &bitStringContent);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(bitStringContent.tag == ASN1_BIT_STRING, badDER);
+    DERSize len = bitStringContent.content.length - 1;
+    require_quiet(len == 1 || len == 2, badDER);
+    DERByte numUnusedBits = bitStringContent.content.data[0];
+    require_quiet(numUnusedBits < 8, badDER);
+    /* Flip the bits in the bit string so the first bit in the lsb. */
+    uint_fast16_t bits = 8 * len - numUnusedBits;
+    uint_fast16_t value = bitStringContent.content.data[1];
+    uint_fast16_t mask;
+    if (len > 1) {
+        value = (value << 8) + bitStringContent.content.data[2];
+        mask = 0x8000;
+    } else {
+        mask = 0x80;
+    }
+    uint_fast16_t ix;
+    for (ix = 0; ix < bits; ++ix) {
+        if (value & mask) {
+            keyUsage |= 1 << ix;
+        }
+        mask >>= 1;
+    }
+    certificate->_keyUsage = keyUsage;
+    return;
+badDER:
+    certificate->_keyUsage = kSecKeyUsageUnspecified;
+}
+
+static void SecCEPPrivateKeyUsagePeriod(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+}
+
+static void SecCEPSubjectAltName(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+       certificate->_subjectAltName = extn;
+}
+
+static void SecCEPIssuerAltName(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+}
+
+static void SecCEPBasicConstraints(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+       DERBasicConstraints basicConstraints;
+       require_noerr_quiet(DERParseSequence(&extn->extnValue,
+        DERNumBasicConstraintsItemSpecs, DERBasicConstraintsItemSpecs,
+        &basicConstraints, sizeof(basicConstraints)), badDER);
+    require_noerr_quiet(DERParseBoolean(&basicConstraints.cA, false,
+               &certificate->_basicConstraints.isCA), badDER);
+    if (basicConstraints.pathLenConstraint.length != 0) {
+        require_noerr_quiet(DERParseInteger(
+            &basicConstraints.pathLenConstraint,
+            &certificate->_basicConstraints.pathLenConstraint), badDER);
+               certificate->_basicConstraints.pathLenConstraintPresent = true;
+       }
+    certificate->_basicConstraints.present = true;
+       certificate->_basicConstraints.critical = extn->critical;
+    return;
+badDER:
+    certificate->_basicConstraints.present = false;
+       secdebug("cert", "Invalid BasicConstraints Extension");
+}
+
+static void SecCEPCrlDistributionPoints(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+}
+
+/*
+   certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
+
+   PolicyInformation ::= SEQUENCE {
+        policyIdentifier   CertPolicyId,
+        policyQualifiers   SEQUENCE SIZE (1..MAX) OF
+                                PolicyQualifierInfo OPTIONAL }
+
+   CertPolicyId ::= OBJECT IDENTIFIER
+
+   PolicyQualifierInfo ::= SEQUENCE {
+        policyQualifierId  PolicyQualifierId,
+        qualifier          ANY DEFINED BY policyQualifierId }
+*/
+static void SecCEPCertificatePolicies(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+    DERTag tag;
+    DERSequence piSeq;
+    SecCEPolicyInformation *policies = NULL;
+    DERReturn drtn = DERDecodeSeqInit(&extn->extnValue, &tag, &piSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo piContent;
+    DERSize policy_count = 0;
+    while ((drtn = DERDecodeSeqNext(&piSeq, &piContent)) == DR_Success) {
+        require_quiet(piContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+        policy_count++;
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+    policies = (SecCEPolicyInformation *)malloc(sizeof(SecCEPolicyInformation)
+        * policy_count);
+    DERDecodeSeqInit(&extn->extnValue, &tag, &piSeq);
+    DERSize policy_ix = 0;
+    while ((drtn = DERDecodeSeqNext(&piSeq, &piContent)) == DR_Success) {
+        DERPolicyInformation pi;
+        drtn = DERParseSequenceContent(&piContent.content,
+            DERNumPolicyInformationItemSpecs,
+            DERPolicyInformationItemSpecs,
+            &pi, sizeof(pi));
+        require_noerr_quiet(drtn, badDER);
+        policies[policy_ix].policyIdentifier = pi.policyIdentifier;
+        policies[policy_ix++].policyQualifiers = pi.policyQualifiers;
+    }
+    certificate->_certificatePolicies.present = true;
+    certificate->_certificatePolicies.critical = extn->critical;
+    certificate->_certificatePolicies.numPolicies = (uint32_t)policy_count;
+    certificate->_certificatePolicies.policies = policies;
+       return;
+badDER:
+    if (policies)
+        free(policies);
+    certificate->_certificatePolicies.present = false;
+       secdebug("cert", "Invalid CertificatePolicies Extension");
+}
+
+/*
+   id-ce-policyMappings OBJECT IDENTIFIER ::=  { id-ce 33 }
+
+   PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
+        issuerDomainPolicy      CertPolicyId,
+        subjectDomainPolicy     CertPolicyId }
+*/
+#if 0
+static void SecCEPPolicyMappings(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+    DERTag tag;
+    DERSequence pmSeq;
+    SecCEPolicyMapping *mappings = NULL;
+    DERReturn drtn = DERDecodeSeqInit(&extn->extnValue, &tag, &pmSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo pmContent;
+    DERSize mapping_count = 0;
+    while ((drtn = DERDecodeSeqNext(&pmSeq, &pmContent)) == DR_Success) {
+        require_quiet(pmContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+        mapping_count++;
+    }
+    mappings = (SecCEPolicyMapping *)malloc(sizeof(SecCEPolicyMapping)
+        * mapping_count);
+    DERDecodeSeqInit(&extn->extnValue, &tag, &pmSeq);
+    DERSize mapping_ix = 0;
+    while ((drtn = DERDecodeSeqNext(&pmSeq, &pmContent)) == DR_Success) {
+        DERPolicyMapping pm;
+        drtn = DERParseSequenceContent(&pmContent.content,
+            DERNumPolicyMappingItemSpecs,
+            DERPolicyMappingItemSpecs,
+            &pm, sizeof(pm));
+        require_noerr_quiet(drtn, badDER);
+        mappings[mapping_ix].issuerDomainPolicy = pm.issuerDomainPolicy;
+        mappings[mapping_ix++].subjectDomainPolicy = pm.subjectDomainPolicy;
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+    certificate->_policyMappings.present = true;
+    certificate->_policyMappings.critical = extn->critical;
+    certificate->_policyMappings.numMappings = mapping_count;
+    certificate->_policyMappings.mappings = mappings;
+       return;
+badDER:
+    if (mappings)
+        free(mappings);
+    CFReleaseSafe(mappings);
+    certificate->_policyMappings.present = false;
+       secdebug("cert", "Invalid CertificatePolicies Extension");
+}
+#else
+static void SecCEPPolicyMappings(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+    DERTag tag;
+    DERSequence pmSeq;
+    CFMutableDictionaryRef mappings = NULL;
+    DERReturn drtn = DERDecodeSeqInit(&extn->extnValue, &tag, &pmSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo pmContent;
+    require_quiet(mappings = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks),
+        badDER);;
+    while ((drtn = DERDecodeSeqNext(&pmSeq, &pmContent)) == DR_Success) {
+        require_quiet(pmContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+        DERPolicyMapping pm;
+        drtn = DERParseSequenceContent(&pmContent.content,
+            DERNumPolicyMappingItemSpecs,
+            DERPolicyMappingItemSpecs,
+            &pm, sizeof(pm));
+        require_noerr_quiet(drtn, badDER);
+        CFDataRef idp, sdp;
+        require_quiet(idp = CFDataCreate(kCFAllocatorDefault,
+            pm.issuerDomainPolicy.data, pm.issuerDomainPolicy.length), badDER);
+        require_quiet(sdp = CFDataCreate(kCFAllocatorDefault,
+            pm.subjectDomainPolicy.data, pm.subjectDomainPolicy.length), badDER);
+        CFMutableArrayRef sdps =
+            (CFMutableArrayRef)CFDictionaryGetValue(mappings, idp);
+        if (sdps) {
+            CFArrayAppendValue(sdps, sdp);
+        } else {
+            require_quiet(sdps = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+                &kCFTypeArrayCallBacks), badDER);
+            CFDictionarySetValue(mappings, idp, sdps);
+            CFRelease(sdps);
+        }
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+    certificate->_policyMappings = mappings;
+       return;
+badDER:
+    CFReleaseSafe(mappings);
+    certificate->_policyMappings = NULL;
+       secdebug("cert", "Invalid CertificatePolicies Extension");
+}
+#endif
+
+/*
+AuthorityKeyIdentifier ::= SEQUENCE {
+    keyIdentifier             [0] KeyIdentifier            OPTIONAL,
+    authorityCertIssuer       [1] GeneralNames             OPTIONAL,
+    authorityCertSerialNumber [2] CertificateSerialNumber  OPTIONAL }
+    -- authorityCertIssuer and authorityCertSerialNumber MUST both
+    -- be present or both be absent
+
+KeyIdentifier ::= OCTET STRING
+*/
+static void SecCEPAuthorityKeyIdentifier(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+       DERAuthorityKeyIdentifier akid;
+       DERReturn drtn;
+       drtn = DERParseSequence(&extn->extnValue,
+               DERNumAuthorityKeyIdentifierItemSpecs,
+               DERAuthorityKeyIdentifierItemSpecs,
+               &akid, sizeof(akid));
+       require_noerr_quiet(drtn, badDER);
+       if (akid.keyIdentifier.length) {
+               certificate->_authorityKeyIdentifier = akid.keyIdentifier;
+       }
+       if (akid.authorityCertIssuer.length ||
+               akid.authorityCertSerialNumber.length) {
+               require_quiet(akid.authorityCertIssuer.length &&
+                       akid.authorityCertSerialNumber.length, badDER);
+               /* Perhaps put in a subsection called Authority Certificate Issuer. */
+               certificate->_authorityKeyIdentifierIssuer = akid.authorityCertIssuer;
+               certificate->_authorityKeyIdentifierSerialNumber = akid.authorityCertSerialNumber;
+       }
+
+       return;
+badDER:
+       secdebug("cert", "Invalid AuthorityKeyIdentifier Extension");
+}
+
+static void SecCEPPolicyConstraints(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+       DERPolicyConstraints pc;
+       DERReturn drtn;
+       drtn = DERParseSequence(&extn->extnValue,
+               DERNumPolicyConstraintsItemSpecs,
+               DERPolicyConstraintsItemSpecs,
+               &pc, sizeof(pc));
+       require_noerr_quiet(drtn, badDER);
+       if (pc.requireExplicitPolicy.length) {
+        require_noerr_quiet(DERParseInteger(
+            &pc.requireExplicitPolicy,
+            &certificate->_policyConstraints.requireExplicitPolicy), badDER);
+        certificate->_policyConstraints.requireExplicitPolicyPresent = true;
+       }
+       if (pc.inhibitPolicyMapping.length) {
+        require_noerr_quiet(DERParseInteger(
+            &pc.inhibitPolicyMapping,
+            &certificate->_policyConstraints.inhibitPolicyMapping), badDER);
+        certificate->_policyConstraints.inhibitPolicyMappingPresent = true;
+       }
+
+    certificate->_policyConstraints.present = true;
+    certificate->_policyConstraints.critical = extn->critical;
+
+    return;
+badDER:
+    certificate->_policyConstraints.present = false;
+       secdebug("cert", "Invalid PolicyConstraints Extension");
+}
+
+static void SecCEPExtendedKeyUsage(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+}
+
+/*
+   InhibitAnyPolicy ::= SkipCerts
+
+   SkipCerts ::= INTEGER (0..MAX)
+*/
+static void SecCEPInhibitAnyPolicy(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+    require_noerr_quiet(DERParseInteger(
+        &extn->extnValue,
+        &certificate->_inhibitAnyPolicySkipCerts), badDER);
+    return;
+badDER:
+    certificate->_inhibitAnyPolicySkipCerts = UINT32_MAX;
+       secdebug("cert", "Invalid InhibitAnyPolicy Extension");
+}
+
+/*
+   id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
+
+   AuthorityInfoAccessSyntax  ::=
+           SEQUENCE SIZE (1..MAX) OF AccessDescription
+
+   AccessDescription  ::=  SEQUENCE {
+           accessMethod          OBJECT IDENTIFIER,
+           accessLocation        GeneralName  }
+
+   id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
+
+   id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
+
+   id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
+ */
+static void SecCEPAuthorityInfoAccess(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+    DERTag tag;
+    DERSequence adSeq;
+    DERReturn drtn = DERDecodeSeqInit(&extn->extnValue, &tag, &adSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo adContent;
+    while ((drtn = DERDecodeSeqNext(&adSeq, &adContent)) == DR_Success) {
+        require_quiet(adContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+               DERAccessDescription ad;
+               drtn = DERParseSequenceContent(&adContent.content,
+                       DERNumAccessDescriptionItemSpecs,
+                       DERAccessDescriptionItemSpecs,
+                       &ad, sizeof(ad));
+               require_noerr_quiet(drtn, badDER);
+        CFMutableArrayRef *urls;
+        if (DEROidCompare(&ad.accessMethod, &oidAdOCSP))
+            urls = &certificate->_ocspResponders;
+        else if (DEROidCompare(&ad.accessMethod, &oidAdCAIssuer))
+            urls = &certificate->_caIssuers;
+        else
+            continue;
+
+        DERDecodedInfo generalNameContent;
+        drtn = DERDecodeItem(&ad.accessLocation, &generalNameContent);
+        require_noerr_quiet(drtn, badDER);
+        switch (generalNameContent.tag) {
+#if 0
+        case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 6:
+            /* Technically I don't think this is valid, but there are certs out
+               in the wild that use a constructed IA5String.   In particular the
+               VeriSign Time Stamping Authority CA.cer does this.  */
+#endif
+        case ASN1_CONTEXT_SPECIFIC | 6:
+        {
+            CFURLRef url = CFURLCreateWithBytes(kCFAllocatorDefault,
+                generalNameContent.content.data, generalNameContent.content.length,
+                kCFStringEncodingASCII, NULL);
+            if (url) {
+                if (!*urls)
+                    *urls = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+                CFArrayAppendValue(*urls, url);
+                CFRelease(url);
+            }
+            break;
+        }
+        default:
+            secdebug("cert", "bad general name for id-ad-ocsp AccessDescription t: 0x%02x v: %.*s",
+                generalNameContent.tag, (int)generalNameContent.content.length, generalNameContent.content.data);
+            goto badDER;
+            break;
+        }
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+       return;
+badDER:
+    secdebug("cert", "failed to parse Authority Information Access extension");
+}
+
+static void SecCEPSubjectInfoAccess(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+}
+
+static void SecCEPNetscapeCertType(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+}
+
+static void SecCEPEntrustVersInfo(SecCertificateRefP certificate,
+       const SecCertificateExtension *extn) {
+       secdebug("cert", "critical: %s", extn->critical ? "yes" : "no");
+}
+
+/* Dictionary key callback for comparing to DERItems. */
+static Boolean SecDERItemEqual(const void *value1, const void *value2) {
+       return DEROidCompare((const DERItem *)value1, (const DERItem *)value2);
+}
+
+/* Dictionary key callback calculating the hash of a DERItem. */
+static CFHashCode SecDERItemHash(const void *value) {
+       const DERItem *derItem = (const DERItem *)value;
+       CFHashCode hash = derItem->length;
+       DERSize ix = derItem->length > 8 ? derItem->length - 8 : 0;
+       for (; ix < derItem->length; ++ix) {
+               hash = (hash << 9) + (hash >> 23) + derItem->data[ix];
+       }
+
+       return hash;
+}
+
+/* Dictionary key callbacks using the above 2 functions. */
+static const CFDictionaryKeyCallBacks SecDERItemKeyCallBacks = {
+       0,                                      /* version */
+       NULL,                           /* retain */
+       NULL,                           /* release */
+       NULL,                           /* copyDescription */
+       SecDERItemEqual,        /* equal */
+       SecDERItemHash          /* hash */
+};
+
+static void SecCertificateRegisterClass(void) {
+       static const CFRuntimeClass kSecCertificateClass = {
+               0,                                                                                              /* version */
+        "SecCertificate",                                                      /* class name */
+               NULL,                                                                                   /* init */
+               NULL,                                                                                   /* copy */
+               SecCertificateDestroy,                          /* dealloc */
+               SecCertificateEqual,                                                    /* equal */
+               SecCertificateHash,                                                             /* hash */
+               NULL,                                                                                   /* copyFormattingDesc */
+               SecCertificateDescribe                          /* copyDebugDesc */
+       };
+
+    kSecCertificateTypeID = _CFRuntimeRegisterClass(&kSecCertificateClass);
+
+       /* Build a dictionary that maps from extension OIDs to callback functions
+          which can parse the extension of the type given. */
+       static const void *extnOIDs[] = {
+               &oidSubjectKeyIdentifier,
+               &oidKeyUsage,
+               &oidPrivateKeyUsagePeriod,
+               &oidSubjectAltName,
+               &oidIssuerAltName,
+               &oidBasicConstraints,
+               &oidCrlDistributionPoints,
+               &oidCertificatePolicies,
+               &oidPolicyMappings,
+               &oidAuthorityKeyIdentifier,
+               &oidPolicyConstraints,
+               &oidExtendedKeyUsage,
+               &oidInhibitAnyPolicy,
+               &oidAuthorityInfoAccess,
+               &oidSubjectInfoAccess,
+               &oidNetscapeCertType,
+               &oidEntrustVersInfo
+       };
+       static const void *extnParsers[] = {
+               SecCEPSubjectKeyIdentifier,
+               SecCEPKeyUsage,
+               SecCEPPrivateKeyUsagePeriod,
+               SecCEPSubjectAltName,
+               SecCEPIssuerAltName,
+               SecCEPBasicConstraints,
+               SecCEPCrlDistributionPoints,
+               SecCEPCertificatePolicies,
+               SecCEPPolicyMappings,
+               SecCEPAuthorityKeyIdentifier,
+               SecCEPPolicyConstraints,
+               SecCEPExtendedKeyUsage,
+        SecCEPInhibitAnyPolicy,
+               SecCEPAuthorityInfoAccess,
+               SecCEPSubjectInfoAccess,
+               SecCEPNetscapeCertType,
+               SecCEPEntrustVersInfo
+       };
+       gExtensionParsers = CFDictionaryCreate(kCFAllocatorDefault, extnOIDs,
+               extnParsers, sizeof(extnOIDs) / sizeof(*extnOIDs),
+               &SecDERItemKeyCallBacks, NULL);
+}
+
+/* Given the contents of an X.501 Name return the contents of a normalized
+   X.501 name. */
+CFDataRef createNormalizedX501Name(CFAllocatorRef allocator,
+       const DERItem *x501name) {
+    CFMutableDataRef result = CFDataCreateMutable(allocator, x501name->length);
+    CFIndex length = x501name->length;
+    CFDataSetLength(result, length);
+    UInt8 *base = CFDataGetMutableBytePtr(result);
+
+       DERSequence rdnSeq;
+       DERReturn drtn = DERDecodeSeqContentInit(x501name, &rdnSeq);
+
+       require_noerr_quiet(drtn, badDER);
+       DERDecodedInfo rdn;
+
+    /* Always points to last rdn tag. */
+    const DERByte *rdnTag = rdnSeq.nextItem;
+    /* Offset relative to base of current rdn set tag. */
+    CFIndex rdnTagLocation = 0;
+       while ((drtn = DERDecodeSeqNext(&rdnSeq, &rdn)) == DR_Success) {
+               require_quiet(rdn.tag == ASN1_CONSTR_SET, badDER);
+               /* We don't allow empty RDNs. */
+               require_quiet(rdn.content.length != 0, badDER);
+        /* Length of the tag and length of the current rdn. */
+        CFIndex rdnTLLength = rdn.content.data - rdnTag;
+        CFIndex rdnContentLength = rdn.content.length;
+        /* Copy the tag and length of the RDN. */
+        memcpy(base + rdnTagLocation, rdnTag, rdnTLLength);
+
+               DERSequence atvSeq;
+               drtn = DERDecodeSeqContentInit(&rdn.content, &atvSeq);
+        DERDecodedInfo atv;
+        /* Always points to tag of current atv sequence. */
+        const DERByte *atvTag = atvSeq.nextItem;
+        /* Offset relative to base of current atv sequence tag. */
+        CFIndex atvTagLocation = rdnTagLocation + rdnTLLength;
+               while ((drtn = DERDecodeSeqNext(&atvSeq, &atv)) == DR_Success) {
+                       require_quiet(atv.tag == ASN1_CONSTR_SEQUENCE, badDER);
+            /* Length of the tag and length of the current atv. */
+            CFIndex atvTLLength = atv.content.data - atvTag;
+            CFIndex atvContentLength = atv.content.length;
+            /* Copy the tag and length of the atv and the atv itself. */
+            memcpy(base + atvTagLocation, atvTag,
+                atvTLLength + atv.content.length);
+
+            /* Now decode the atv sequence. */
+                       DERAttributeTypeAndValue atvPair;
+                       drtn = DERParseSequenceContent(&atv.content,
+                               DERNumAttributeTypeAndValueItemSpecs,
+                               DERAttributeTypeAndValueItemSpecs,
+                               &atvPair, sizeof(atvPair));
+                       require_noerr_quiet(drtn, badDER);
+                       require_quiet(atvPair.type.length != 0, badDER);
+            DERDecodedInfo value;
+            drtn = DERDecodeItem(&atvPair.value, &value);
+                       require_noerr_quiet(drtn, badDER);
+
+            /* (c) attribute values in PrintableString are not case sensitive
+               (e.g., "Marianne Swanson" is the same as "MARIANNE SWANSON"); and
+
+               (d) attribute values in PrintableString are compared after
+               removing leading and trailing white space and converting internal
+               substrings of one or more consecutive white space characters to a
+               single space. */
+            if (value.tag == ASN1_PRINTABLE_STRING) {
+                /* Offset relative to base of current value tag. */
+                CFIndex valueTagLocation = atvTagLocation + atvPair.value.data - atvTag;
+                CFIndex valueTLLength = value.content.data - atvPair.value.data;
+                CFIndex valueContentLength = value.content.length;
+
+                /* Now copy all the bytes, but convert to upper case while
+                   doing so and convert multiple whitespace chars into a
+                   single space. */
+                bool lastWasBlank = false;
+                CFIndex valueLocation = valueTagLocation + valueTLLength;
+                CFIndex valueCurrentLocation = valueLocation;
+                CFIndex ix;
+                for (ix = 0; ix < valueContentLength; ++ix) {
+                    UInt8 ch = value.content.data[ix];
+                    if (isblank(ch)) {
+                        if (lastWasBlank) {
+                            continue;
+                        } else {
+                            /* Don't insert a space for first character
+                               we encounter. */
+                            if (valueCurrentLocation > valueLocation) {
+                                base[valueCurrentLocation++] = ' ';
+                            }
+                            lastWasBlank = true;
+                        }
+                    } else {
+                        lastWasBlank = false;
+                        if ('a' <= ch && ch <= 'z') {
+                            base[valueCurrentLocation++] = ch + 'A' - 'a';
+                        } else {
+                            base[valueCurrentLocation++] = ch;
+                        }
+                    }
+                }
+                /* Finally if lastWasBlank remove the trailing space. */
+                if (lastWasBlank && valueCurrentLocation > valueLocation) {
+                    valueCurrentLocation--;
+                }
+                /* Adjust content length to normalized length. */
+                valueContentLength = valueCurrentLocation - valueLocation;
+
+                /* Number of bytes by which the length should be shorted. */
+                CFIndex lengthDiff = value.content.length - valueContentLength;
+                if (lengthDiff == 0) {
+                    /* Easy case no need to adjust lengths. */
+                } else {
+                    /* Hard work we need to go back and fix up length fields
+                       for:
+                           1) The value itself.
+                           2) The ATV Sequence containing type/value
+                           3) The RDN Set containing one or more atv pairs.
+                           4) The result.
+                       */
+
+                    /* Step 1 fix up length of value. */
+                    /* Length of value tag and length minus the tag. */
+                    DERSize newValueTLLength = valueTLLength - 1;
+                    drtn = DEREncodeLength(valueContentLength,
+                        base + valueTagLocation + 1, &newValueTLLength);
+                    /* Add the length of the tag back in. */
+                    newValueTLLength++;
+                    CFIndex valueLLDiff = valueTLLength - newValueTLLength;
+                    if (valueLLDiff) {
+                        /* The size of the length field changed, let's slide
+                           the value back by valueLLDiff bytes. */
+                        memmove(base + valueTagLocation + newValueTLLength,
+                            base + valueTagLocation + valueTLLength,
+                            valueContentLength);
+                        /* The length diff for the enclosing object. */
+                        lengthDiff += valueLLDiff;
+                    }
+
+                    /* Step 2 fix up length of the enclosing ATV Sequence. */
+                    atvContentLength -= lengthDiff;
+                    DERSize newATVTLLength = atvTLLength - 1;
+                    drtn = DEREncodeLength(atvContentLength,
+                        base + atvTagLocation + 1, &newATVTLLength);
+                    /* Add the length of the tag back in. */
+                    newATVTLLength++;
+                    CFIndex atvLLDiff = atvTLLength - newATVTLLength;
+                    if (atvLLDiff) {
+                        /* The size of the length field changed, let's slide
+                           the value back by valueLLDiff bytes. */
+                        memmove(base + atvTagLocation + newATVTLLength,
+                            base + atvTagLocation + atvTLLength,
+                            atvContentLength);
+                        /* The length diff for the enclosing object. */
+                        lengthDiff += atvLLDiff;
+                        atvTLLength = newATVTLLength;
+                    }
+
+                    /* Step 3 fix up length of enclosing RDN Set. */
+                    rdnContentLength -= lengthDiff;
+                    DERSize newRDNTLLength = rdnTLLength - 1;
+                    drtn = DEREncodeLength(rdnContentLength,
+                        base + rdnTagLocation + 1, &newRDNTLLength);
+                    /* Add the length of the tag back in. */
+                    newRDNTLLength++;
+                    CFIndex rdnLLDiff = rdnTLLength - newRDNTLLength;
+                    if (rdnLLDiff) {
+                        /* The size of the length field changed, let's slide
+                           the value back by valueLLDiff bytes. */
+                        memmove(base + rdnTagLocation + newRDNTLLength,
+                            base + rdnTagLocation + rdnTLLength,
+                            rdnContentLength);
+                        /* The length diff for the enclosing object. */
+                        lengthDiff += rdnLLDiff;
+                        rdnTLLength = newRDNTLLength;
+
+                        /* Adjust the locations that might have changed due to
+                           this slide. */
+                        atvTagLocation -= rdnLLDiff;
+                    }
+                }
+            }
+            atvTagLocation += atvTLLength + atvContentLength;
+            atvTag = atvSeq.nextItem;
+               }
+        rdnTagLocation += rdnTLLength + rdnContentLength;
+        rdnTag = rdnSeq.nextItem;
+       }
+       require_quiet(drtn == DR_EndOfSequence, badDER);
+    /* Truncate the result to the proper length. */
+    CFDataSetLength(result, rdnTagLocation);
+
+       return result;
+
+badDER:
+    CFRelease(result);
+    return NULL;
+}
+
+/* AUDIT[securityd]:
+   certificate->_der is a caller provided data of any length (might be 0).
+
+   Top level certificate decode.
+ */
+static bool SecCertificateParse(SecCertificateRefP certificate)
+{
+       DERReturn drtn;
+
+    check(certificate);
+    CFAllocatorRef allocator = CFGetAllocator(certificate);
+
+       /* top level decode */
+       DERSignedCertCrl signedCert;
+       drtn = DERParseSequence(&certificate->_der, DERNumSignedCertCrlItemSpecs,
+               DERSignedCertCrlItemSpecs, &signedCert,
+               sizeof(signedCert));
+       require_noerr_quiet(drtn, badCert);
+       /* Store tbs since we need to digest it for verification later on. */
+       certificate->_tbs = signedCert.tbs;
+
+       /* decode the TBSCert - it was saved in full DER form */
+    DERTBSCert tbsCert;
+       drtn = DERParseSequence(&signedCert.tbs,
+               DERNumTBSCertItemSpecs, DERTBSCertItemSpecs,
+               &tbsCert, sizeof(tbsCert));
+       require_noerr_quiet(drtn, badCert);
+
+       /* sequence we're given: decode the signedCerts Signature Algorithm. */
+       /* This MUST be the same as the certificate->_tbsSigAlg with the exception
+          of the params field. */
+       drtn = DERParseSequenceContent(&signedCert.sigAlg,
+               DERNumAlgorithmIdItemSpecs, DERAlgorithmIdItemSpecs,
+               &certificate->_sigAlg, sizeof(certificate->_sigAlg));
+       require_noerr_quiet(drtn, badCert);
+
+       /* The contents of signedCert.sig is a bit string whose contents
+          are the signature itself. */
+    DERByte numUnusedBits;
+       drtn = DERParseBitString(&signedCert.sig,
+        &certificate->_signature, &numUnusedBits);
+       require_noerr_quiet(drtn, badCert);
+
+    /* Now decode the tbsCert. */
+
+    /* First we turn the optional version into an int. */
+    if (tbsCert.version.length) {
+        DERDecodedInfo decoded;
+        drtn = DERDecodeItem(&tbsCert.version, &decoded);
+        require_noerr_quiet(drtn, badCert);
+        require_quiet(decoded.tag == ASN1_INTEGER, badCert);
+        require_quiet(decoded.content.length == 1, badCert);
+        certificate->_version = decoded.content.data[0];
+        require_quiet(certificate->_version > 0, badCert);
+        require_quiet(certificate->_version < 3, badCert);
+    } else {
+        certificate->_version = 0;
+    }
+
+       /* The serial number is in the tbsCert.serialNum - it was saved in
+       INTEGER form without the tag and length. */
+       certificate->_serialNum = tbsCert.serialNum;
+       certificate->_serialNumber = CFDataCreate(allocator,
+               tbsCert.serialNum.data, tbsCert.serialNum.length);
+
+       /* sequence we're given: decode the tbsCerts TBS Signature Algorithm. */
+       drtn = DERParseSequenceContent(&tbsCert.tbsSigAlg,
+               DERNumAlgorithmIdItemSpecs, DERAlgorithmIdItemSpecs,
+               &certificate->_tbsSigAlg, sizeof(certificate->_tbsSigAlg));
+       require_noerr_quiet(drtn, badCert);
+
+       /* The issuer is in the tbsCert.issuer - it's a sequence without the tag
+       and length fields. */
+       certificate->_issuer = tbsCert.issuer;
+    certificate->_normalizedIssuer = createNormalizedX501Name(allocator,
+        &tbsCert.issuer);
+
+       /* sequence we're given: decode the tbsCerts Validity sequence. */
+    DERValidity validity;
+       drtn = DERParseSequenceContent(&tbsCert.validity,
+               DERNumValidityItemSpecs, DERValidityItemSpecs,
+               &validity, sizeof(validity));
+       require_noerr_quiet(drtn, badCert);
+    require_quiet(derDateGetAbsoluteTime(&validity.notBefore,
+        &certificate->_notBefore), badCert);
+    require_quiet(derDateGetAbsoluteTime(&validity.notAfter,
+        &certificate->_notAfter), badCert);
+
+       /* The subject is in the tbsCert.subject - it's a sequence without the tag
+       and length fields. */
+       certificate->_subject = tbsCert.subject;
+    certificate->_normalizedSubject = createNormalizedX501Name(allocator,
+        &tbsCert.subject);
+
+       /* sequence we're given: encoded DERSubjPubKeyInfo */
+       DERSubjPubKeyInfo pubKeyInfo;
+       drtn = DERParseSequenceContent(&tbsCert.subjectPubKey,
+               DERNumSubjPubKeyInfoItemSpecs, DERSubjPubKeyInfoItemSpecs,
+               &pubKeyInfo, sizeof(pubKeyInfo));
+       require_noerr_quiet(drtn, badCert);
+
+       /* sequence we're given: decode the pubKeyInfos DERAlgorithmId */
+       drtn = DERParseSequenceContent(&pubKeyInfo.algId,
+               DERNumAlgorithmIdItemSpecs, DERAlgorithmIdItemSpecs,
+               &certificate->_algId, sizeof(certificate->_algId));
+       require_noerr_quiet(drtn, badCert);
+
+       /* Now we can figure out the key's algorithm id and params based on
+          certificate->_algId.oid. */
+
+       /* The contents of pubKeyInfo.pubKey is a bit string whose contents
+          are a PKCS1 format RSA key. */
+       drtn = DERParseBitString(&pubKeyInfo.pubKey,
+        &certificate->_pubKeyDER, &numUnusedBits);
+       require_noerr_quiet(drtn, badCert);
+
+       /* The contents of tbsCert.issuerID is a bit string. */
+       certificate->_issuerUniqueID = tbsCert.issuerID;
+
+       /* The contents of tbsCert.subjectID is a bit string. */
+       certificate->_subjectUniqueID = tbsCert.subjectID;
+
+       /* Extensions. */
+    if (tbsCert.extensions.length) {
+        CFIndex extensionCount = 0;
+        DERSequence derSeq;
+        DERTag tag;
+        drtn = DERDecodeSeqInit(&tbsCert.extensions, &tag,
+            &derSeq);
+        require_noerr_quiet(drtn, badCert);
+        require_quiet(tag == ASN1_CONSTR_SEQUENCE, badCert);
+        DERDecodedInfo currDecoded;
+        while ((drtn = DERDecodeSeqNext(&derSeq, &currDecoded)) == DR_Success) {
+#if 0
+/* ! = MUST recognize ? = SHOULD recognize
+*/
+
+    KnownExtension      _subjectKeyID;          /* ?SubjectKeyIdentifier     id-ce 14 */
+    KnownExtension      _keyUsage;              /* !KeyUsage                 id-ce 15 */
+    KnownExtension      _subjectAltName;        /* !SubjectAltName           id-ce 17 */
+    KnownExtension      _basicConstraints;      /* !BasicConstraints         id-ce 19 */
+    KnownExtension      _authorityKeyID;        /* ?AuthorityKeyIdentifier   id-ce 35 */
+    KnownExtension      _extKeyUsage;           /* !ExtKeyUsage              id-ce 37 */
+    KnownExtension      _netscapeCertType;      /* 2.16.840.1.113730.1.1 netscape 1 1 */
+    KnownExtension      _qualCertStatements;    /* QCStatements             id-pe 3 */
+
+    KnownExtension      _issuerAltName;         /* IssuerAltName            id-ce 18 */
+    KnownExtension      _nameConstraints;       /* !NameConstraints          id-ce 30 */
+    KnownExtension      _cRLDistributionPoints; /* CRLDistributionPoints    id-ce 31 */
+    KnownExtension      _certificatePolicies;   /* !CertificatePolicies      id-ce 32 */
+    KnownExtension      _policyMappings;        /* ?PolicyMappings           id-ce 33 */
+    KnownExtension      _policyConstraints;     /* !PolicyConstraints        id-ce 36 */
+    KnownExtension      _freshestCRL;           /* FreshestCRL              id-ce 46 */
+    KnownExtension      _inhibitAnyPolicy;      /* !InhibitAnyPolicy         id-ce 54 */
+
+    KnownExtension      _authorityInfoAccess;   /* AuthorityInfoAccess      id-pe 1 */
+    KnownExtension      _subjectInfoAccess;     /* SubjectInfoAccess        id-pe 11 */
+#endif
+
+            extensionCount++;
+        }
+        require_quiet(drtn == DR_EndOfSequence, badCert);
+
+        /* Put some upper limit on the number of extentions allowed. */
+        require_quiet(extensionCount < 10000, badCert);
+        certificate->_extensionCount = extensionCount;
+        certificate->_extensions =
+            malloc(sizeof(SecCertificateExtension) * extensionCount);
+
+        CFIndex ix = 0;
+        drtn = DERDecodeSeqInit(&tbsCert.extensions, &tag, &derSeq);
+        require_noerr_quiet(drtn, badCert);
+        for (ix = 0; ix < extensionCount; ++ix) {
+            drtn = DERDecodeSeqNext(&derSeq, &currDecoded);
+            require_quiet(drtn == DR_Success ||
+                (ix == extensionCount - 1 && drtn == DR_EndOfSequence), badCert);
+            require_quiet(currDecoded.tag == ASN1_CONSTR_SEQUENCE, badCert);
+            DERExtension extn;
+            drtn = DERParseSequenceContent(&currDecoded.content,
+                DERNumExtensionItemSpecs, DERExtensionItemSpecs,
+                &extn, sizeof(extn));
+            require_noerr_quiet(drtn, badCert);
+            /* Copy stuff into certificate->extensions[ix]. */
+            certificate->_extensions[ix].extnID = extn.extnID;
+            require_noerr_quiet(drtn = DERParseBoolean(&extn.critical, false,
+                &certificate->_extensions[ix].critical), badCert);
+            certificate->_extensions[ix].extnValue = extn.extnValue;
+
+                       SecCertificateExtensionParser parser =
+                               (SecCertificateExtensionParser)CFDictionaryGetValue(
+                               gExtensionParsers, &certificate->_extensions[ix].extnID);
+                       if (parser) {
+                               /* Invoke the parser. */
+                               parser(certificate, &certificate->_extensions[ix]);
+                       } else if (certificate->_extensions[ix].critical) {
+                               secdebug("cert", "Found unknown critical extension");
+                               certificate->_foundUnknownCriticalExtension = true;
+                       } else {
+                               secdebug("cert", "Found unknown non critical extension");
+                       }
+        }
+    }
+
+       return true;
+
+badCert:
+       return false;
+}
+
+
+/* Public API functions. */
+CFTypeID SecCertificateGetTypeIDP(void) {
+    pthread_once(&kSecCertificateRegisterClass, SecCertificateRegisterClass);
+    return kSecCertificateTypeID;
+}
+
+SecCertificateRefP SecCertificateCreateWithBytesP(CFAllocatorRef allocator,
+       const UInt8 *der_bytes, CFIndex der_length) {
+       check(der_bytes);
+       check(der_length);
+    CFIndex size = sizeof(struct __SecCertificate) + der_length;
+    SecCertificateRefP result = (SecCertificateRefP)_CFRuntimeCreateInstance(
+               allocator, SecCertificateGetTypeIDP(), size - sizeof(CFRuntimeBase), 0);
+       if (result) {
+               memset((char*)result + sizeof(result->_base), 0,
+                       sizeof(*result) - sizeof(result->_base));
+               result->_der.data = ((DERByte *)result + sizeof(*result));
+               result->_der.length = der_length;
+               memcpy(result->_der.data, der_bytes, der_length);
+               if (!SecCertificateParse(result)) {
+                       CFRelease(result);
+                       return NULL;
+               }
+    }
+    return result;
+}
+
+/* @@@ Placeholder until <rdar://problem/5701851> iap submits a binary is fixed. */
+SecCertificateRefP SecCertificateCreate(CFAllocatorRef allocator,
+       const UInt8 *der_bytes, CFIndex der_length);
+
+SecCertificateRefP SecCertificateCreate(CFAllocatorRef allocator,
+       const UInt8 *der_bytes, CFIndex der_length) {
+    return SecCertificateCreateWithBytesP(allocator, der_bytes, der_length);
+}
+/* @@@ End of placeholder. */
+
+/* AUDIT[securityd](done):
+   der_certificate is a caller provided data of any length (might be 0), only
+   its cf type has been checked.
+ */
+SecCertificateRefP SecCertificateCreateWithDataP(CFAllocatorRef allocator,
+       CFDataRef der_certificate) {
+       check(der_certificate);
+    CFIndex size = sizeof(struct __SecCertificate);
+    SecCertificateRefP result = (SecCertificateRefP)_CFRuntimeCreateInstance(
+               allocator, SecCertificateGetTypeIDP(), size - sizeof(CFRuntimeBase), 0);
+       if (result) {
+               memset((char*)result + sizeof(result->_base), 0, size - sizeof(result->_base));
+        result->_der_data = CFDataCreateCopy(allocator, der_certificate);
+               result->_der.data = (DERByte *)CFDataGetBytePtr(result->_der_data);
+               result->_der.length = CFDataGetLength(result->_der_data);
+               if (!SecCertificateParse(result)) {
+                       CFRelease(result);
+                       return NULL;
+               }
+    }
+    return result;
+}
+
+CFDataRef SecCertificateCopyDataP(SecCertificateRefP certificate) {
+       check(certificate);
+    CFDataRef result;
+       if (certificate->_der_data) {
+        CFRetain(certificate->_der_data);
+        result = certificate->_der_data;
+    } else {
+               result = CFDataCreate(CFGetAllocator(certificate),
+            certificate->_der.data, certificate->_der.length);
+#if 0
+               /* FIXME: If we wish to cache result we need to lock the certificate.
+           Also this create 2 copies of the certificate data which is somewhat
+           suboptimal. */
+        CFRetain(result);
+        certificate->_der_data = result;
+#endif
+       }
+
+       return result;
+}
+
+CFIndex SecCertificateGetLengthP(SecCertificateRefP certificate) {
+       return certificate->_der.length;
+}
+
+const UInt8 *SecCertificateGetBytePtrP(SecCertificateRefP certificate) {
+       return certificate->_der.data;
+}
+
+/* From rfc3280 - Appendix B.  ASN.1 Notes
+
+   Object Identifiers (OIDs) are used throughout this specification to
+   identify certificate policies, public key and signature algorithms,
+   certificate extensions, etc.  There is no maximum size for OIDs.
+   This specification mandates support for OIDs which have arc elements
+   with values that are less than 2^28, that is, they MUST be between 0
+   and 268,435,455, inclusive.  This allows each arc element to be
+   represented within a single 32 bit word.  Implementations MUST also
+   support OIDs where the length of the dotted decimal (see [RFC 2252],
+   section 4.1) string representation can be up to 100 bytes
+   (inclusive).  Implementations MUST be able to handle OIDs with up to
+   20 elements (inclusive).  CAs SHOULD NOT issue certificates which
+   contain OIDs that exceed these requirements.  Likewise, CRL issuers
+   SHOULD NOT issue CRLs which contain OIDs that exceed these
+   requirements.
+*/
+
+/* Oids longer than this are considered invalid. */
+#define MAX_OID_SIZE                           32
+
+CFStringRef SecDERItemCopyOIDDecimalRepresentation(CFAllocatorRef allocator,
+    const DERItem *oid) {
+
+       if (oid->length == 0) {
+        return SecFrameworkCopyLocalizedString(CFSTR("<NULL>"),
+            CFSTR("SecCertificate"));
+    }
+       if (oid->length > MAX_OID_SIZE) {
+        return SecFrameworkCopyLocalizedString(CFSTR("Oid too long"),
+            CFSTR("SecCertificate"));
+    }
+
+    CFMutableStringRef result = CFStringCreateMutable(allocator, 0);
+
+       // The first two levels are encoded into one byte, since the root level
+       // has only 3 nodes (40*x + y).  However if x = joint-iso-itu-t(2) then
+       // y may be > 39, so we have to add special-case handling for this.
+       uint32_t x = oid->data[0] / 40;
+       uint32_t y = oid->data[0] % 40;
+       if (x > 2)
+       {
+               // Handle special case for large y if x = 2
+               y += (x - 2) * 40;
+               x = 2;
+       }
+    CFStringAppendFormat(result, NULL, CFSTR("%u.%u"), x, y);
+
+       uint32_t value = 0;
+       for (x = 1; x < oid->length; ++x)
+       {
+               value = (value << 7) | (oid->data[x] & 0x7F);
+        /* @@@ value may not span more than 4 bytes. */
+        /* A max number of 20 values is allowed. */
+               if (!(oid->data[x] & 0x80))
+               {
+            CFStringAppendFormat(result, NULL, CFSTR(".%lu"), (unsigned long)value);
+                       value = 0;
+               }
+       }
+       return result;
+}
+
+static CFStringRef copyLocalizedOidDescription(CFAllocatorRef allocator,
+    const DERItem *oid) {
+       if (oid->length == 0) {
+        return SecFrameworkCopyLocalizedString(CFSTR("<NULL>"),
+            CFSTR("SecCertificate"));
+    }
+
+    /* Build the key we use to lookup the localized OID description. */
+    CFMutableStringRef oidKey = CFStringCreateMutable(allocator,
+        oid->length * 3 + 5);
+    CFStringAppendFormat(oidKey, NULL, CFSTR("06 %02lX"), (unsigned long)oid->length);
+    DERSize ix;
+    for (ix = 0; ix < oid->length; ++ix)
+        CFStringAppendFormat(oidKey, NULL, CFSTR(" %02X"), oid->data[ix]);
+
+    CFStringRef name = SecFrameworkCopyLocalizedString(oidKey, CFSTR("OID"));
+    if (CFEqual(oidKey, name)) {
+        CFRelease(name);
+        name = SecDERItemCopyOIDDecimalRepresentation(allocator, oid);
+    }
+    CFRelease(oidKey);
+
+    return name;
+}
+
+/* Return the ipAddress as a dotted quad for ipv4 or as 8 colon separated
+   4 digit hex strings for ipv6.  Return NULL if the passed in IP doesn't
+   have a length of exactly 4 or 16 octects.  */
+static CFStringRef copyIPAddressContentDescription(CFAllocatorRef allocator,
+       const DERItem *ip) {
+       /* @@@ This is the IP Address as an OCTECT STRING. For IPv4 it's
+          4 octects addr, or 8 octects, addr/mask for ipv6 it's
+          16 octects addr, or 32 octects addr/mask.  */
+       CFStringRef value = NULL;
+       if (ip->length == 4) {
+               value = CFStringCreateWithFormat(allocator, NULL,
+                       CFSTR("%u.%u.%u.%u"),
+                       ip->data[0], ip->data[1], ip->data[2], ip->data[3]);
+       } else if (ip->length == 16) {
+               value = CFStringCreateWithFormat(allocator, NULL,
+                       CFSTR("%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
+                       "%02x%02x:%02x%02x:%02x%02x:%02x%02x"),
+                       ip->data[0], ip->data[1], ip->data[2], ip->data[3],
+                       ip->data[4], ip->data[5], ip->data[6], ip->data[7],
+                       ip->data[8], ip->data[9], ip->data[10], ip->data[11],
+                       ip->data[12], ip->data[13], ip->data[14], ip->data[15]);
+       }
+
+       return value;
+}
+
+#if 0
+static CFStringRef copyFullOidDescription(CFAllocatorRef allocator,
+    const DERItem *oid) {
+    CFStringRef decimal = SecDERItemCopyOIDDecimalRepresentation(allocator, oid);
+    CFStringRef name = copyLocalizedOidDescription(allocator, oid);
+    CFStringRef oid_string = CFStringCreateWithFormat(allocator, NULL,
+        CFSTR("%@ (%@)"), name, decimal);
+    CFRelease(name);
+    CFRelease(decimal);
+    return oid_string;
+}
+#endif
+
+void appendProperty(CFMutableArrayRef properties,
+    CFStringRef propertyType, CFStringRef label, CFTypeRef value) {
+    CFDictionaryRef property;
+    if (label) {
+        CFStringRef localizedLabel = SecFrameworkCopyLocalizedString(label,
+            CFSTR("SecCertificate"));
+        const void *all_keys[4];
+        all_keys[0] = kSecPropertyKeyType;
+        all_keys[1] = kSecPropertyKeyLabel;
+        all_keys[2] = kSecPropertyKeyLocalizedLabel;
+        all_keys[3] = kSecPropertyKeyValue;
+        const void *property_values[] = {
+            propertyType,
+            label,
+            localizedLabel,
+            value,
+        };
+        property = CFDictionaryCreate(CFGetAllocator(properties),
+            all_keys, property_values, value ? 4 : 3,
+            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+        CFRelease(localizedLabel);
+    } else {
+        const void *nolabel_keys[2];
+        nolabel_keys[0] = kSecPropertyKeyType;
+        nolabel_keys[1] = kSecPropertyKeyValue;
+        const void *property_values[] = {
+            propertyType,
+            value,
+        };
+        property = CFDictionaryCreate(CFGetAllocator(properties),
+            nolabel_keys, property_values, 2,
+            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+    }
+
+    CFArrayAppendValue(properties, property);
+    CFRelease(property);
+}
+
+/* YYMMDDhhmmZ */
+#define UTC_TIME_NOSEC_ZULU_LEN                        11
+/* YYMMDDhhmmssZ */
+#define UTC_TIME_ZULU_LEN                              13
+/* YYMMDDhhmmssThhmm */
+#define UTC_TIME_LOCALIZED_LEN                 17
+/* YYYYMMDDhhmmssZ */
+#define GENERALIZED_TIME_ZULU_LEN              15
+/* YYYYMMDDhhmmssThhmm */
+#define GENERALIZED_TIME_LOCALIZED_LEN 19
+
+/* Parse 2 digits at (*p)[0] and (*p)[1] and return the result.  Also
+   advance *p by 2. */
+static inline SInt32 parseDecimalPair(const DERByte **p) {
+    const DERByte *cp = *p;
+    *p += 2;
+    return 10 * (cp[0] - '0') + cp[1] - '0';
+}
+
+/* Decode a choice of UTCTime or GeneralizedTime to a CFAbsoluteTime. Return
+   true if the date was valid and properly decoded, also return the result in
+   absTime.  Return false otherwise. */
+CFAbsoluteTime SecAbsoluteTimeFromDateContent(DERTag tag, const uint8_t *bytes,
+    size_t length) {
+       check(bytes);
+       if (length == 0)
+               return NULL_TIME;
+
+       bool isUtcLength = false;
+       bool isLocalized = false;
+       bool noSeconds = false;
+       switch (length) {
+               case UTC_TIME_NOSEC_ZULU_LEN:           /* YYMMDDhhmmZ */
+                       isUtcLength = true;
+                       noSeconds = true;
+                       break;
+               case UTC_TIME_ZULU_LEN:                         /* YYMMDDhhmmssZ */
+                       isUtcLength = true;
+                       break;
+               case GENERALIZED_TIME_ZULU_LEN:         /* YYYYMMDDhhmmssZ */
+                       break;
+               case UTC_TIME_LOCALIZED_LEN:            /* YYMMDDhhmmssThhmm (where T=[+,-]) */
+                       isUtcLength = true;
+                       /*DROPTHROUGH*/
+               case GENERALIZED_TIME_LOCALIZED_LEN:/* YYYYMMDDhhmmssThhmm (where T=[+,-]) */
+                       isLocalized = true;
+                       break;
+               default:                                                        /* unknown format */
+            return NULL_TIME;
+       }
+
+       /* Make sure the der tag fits the thing inside it. */
+       if (tag == ASN1_UTC_TIME) {
+               if (!isUtcLength)
+            return NULL_TIME;
+       } else if (tag == ASN1_GENERALIZED_TIME) {
+               if (isUtcLength)
+            return NULL_TIME;
+       } else {
+               return NULL_TIME;
+       }
+
+    const DERByte *cp = bytes;
+       /* Check that all characters are digits, except if localized the timezone
+          indicator or if not localized the 'Z' at the end.  */
+       DERSize ix;
+       for (ix = 0; ix < length; ++ix) {
+               if (!(isdigit(cp[ix]))) {
+                       if ((isLocalized && ix == length - 5 &&
+                                (cp[ix] == '+' || cp[ix] == '-')) ||
+                               (!isLocalized && ix == length - 1 && cp[ix] == 'Z')) {
+                               continue;
+                       }
+                       return NULL_TIME;
+               }
+       }
+
+       /* Initialize the fields in a gregorian date struct. */
+    CFGregorianDate gdate;
+       if (isUtcLength) {
+               SInt32 year = parseDecimalPair(&cp);
+               if (year < 50) {
+                       /* 0  <= year <  50 : assume century 21 */
+                       gdate.year = 2000 + year;
+               } else if (year < 70) {
+                       /* 50 <= year <  70 : illegal per PKIX */
+                       return false;
+               } else {
+                       /* 70 <  year <= 99 : assume century 20 */
+                       gdate.year = 1900 + year;
+               }
+       } else {
+        gdate.year = 100 * parseDecimalPair(&cp) + parseDecimalPair(&cp);
+       }
+       gdate.month = parseDecimalPair(&cp);
+       gdate.day = parseDecimalPair(&cp);
+       gdate.hour = parseDecimalPair(&cp);
+       gdate.minute = parseDecimalPair(&cp);
+       if (noSeconds) {
+               gdate.second = 0;
+       } else {
+               gdate.second = parseDecimalPair(&cp);
+       }
+
+       CFTimeInterval timeZoneOffset = 0;
+       if (isLocalized) {
+               /* ZONE INDICATOR */
+        SInt32 multiplier = *cp++ == '+' ? 60 : -60;
+        timeZoneOffset = multiplier *
+            (parseDecimalPair(&cp) + 60 * parseDecimalPair(&cp));
+       } else {
+               timeZoneOffset = 0;
+       }
+
+    secdebug("dateparse",
+        "date %.*s year: %04d-%02d-%02d %02d:%02d:%02.f %+05.f",
+        (int)length, bytes, (int)gdate.year, gdate.month,
+        gdate.day, gdate.hour, gdate.minute, gdate.second,
+        timeZoneOffset / 60);
+
+    if (!CFGregorianDateIsValid(gdate, kCFGregorianAllUnits))
+               return false;
+       CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL,
+               timeZoneOffset);
+       if (!timeZone)
+               return NULL_TIME;
+       CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(gdate, timeZone);
+       CFRelease(timeZone);
+       return absTime;
+}
+
+static bool derDateContentGetAbsoluteTime(DERTag tag, const DERItem *date,
+    CFAbsoluteTime *pabsTime) {
+    CFAbsoluteTime absTime = SecAbsoluteTimeFromDateContent(tag, date->data,
+        date->length);
+    if (absTime == NULL_TIME)
+        return false;
+
+    *pabsTime = absTime;
+    return true;
+}
+
+/* Decode a choice of UTCTime or GeneralizedTime to a CFAbsoluteTime. Return
+   true if the date was valid and properly decoded, also return the result in
+   absTime.  Return false otherwise. */
+static bool derDateGetAbsoluteTime(const DERItem *dateChoice,
+       CFAbsoluteTime *absTime) {
+       check(dateChoice);
+       check(absTime);
+       if (dateChoice->length == 0)
+               return false;
+
+       DERDecodedInfo decoded;
+       if (DERDecodeItem(dateChoice, &decoded))
+               return false;
+
+    return derDateContentGetAbsoluteTime(decoded.tag, &decoded.content,
+        absTime);
+}
+
+static void appendDataProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *der_data) {
+    CFDataRef data = CFDataCreate(CFGetAllocator(properties),
+        der_data->data, der_data->length);
+    appendProperty(properties, kSecPropertyTypeData, label, data);
+    CFRelease(data);
+}
+
+static void appendUnparsedProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *der_data) {
+    CFStringRef newLabel = CFStringCreateWithFormat(CFGetAllocator(properties),
+               NULL, CFSTR("Unparsed %@"), label);
+    appendDataProperty(properties, newLabel, der_data);
+    CFRelease(newLabel);
+}
+
+static void appendInvalidProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *der_data) {
+    CFStringRef newLabel = CFStringCreateWithFormat(CFGetAllocator(properties),
+               NULL, CFSTR("Invalid %@"), label);
+    appendDataProperty(properties, newLabel, der_data);
+    CFRelease(newLabel);
+}
+
+static void appendDateContentProperty(CFMutableArrayRef properties,
+    CFStringRef label, DERTag tag, const DERItem *dateContent) {
+       CFAbsoluteTime absTime;
+       if (!derDateContentGetAbsoluteTime(tag, dateContent, &absTime)) {
+               /* Date decode failure insert hex bytes instead. */
+               return appendInvalidProperty(properties, label, dateContent);
+       }
+    CFDateRef date = CFDateCreate(CFGetAllocator(properties), absTime);
+    appendProperty(properties, kSecPropertyTypeDate, label, date);
+    CFRelease(date);
+}
+
+static void appendDateProperty(CFMutableArrayRef properties,
+    CFStringRef label, CFAbsoluteTime absTime) {
+    CFDateRef date = CFDateCreate(CFGetAllocator(properties), absTime);
+    appendProperty(properties, kSecPropertyTypeDate, label, date);
+    CFRelease(date);
+}
+
+static void appendIPAddressContentProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *ip) {
+       CFStringRef value =
+               copyIPAddressContentDescription(CFGetAllocator(properties), ip);
+       if (value) {
+        appendProperty(properties, kSecPropertyTypeString, label, value);
+               CFRelease(value);
+       } else {
+               appendUnparsedProperty(properties, label, ip);
+       }
+}
+
+static void appendURLContentProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *urlContent) {
+    CFURLRef url = CFURLCreateWithBytes(CFGetAllocator(properties),
+        urlContent->data, urlContent->length, kCFStringEncodingASCII, NULL);
+    if (url) {
+        appendProperty(properties, kSecPropertyTypeURL, label, url);
+        CFRelease(url);
+    } else {
+               appendInvalidProperty(properties, label, urlContent);
+    }
+}
+
+static void appendURLProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *url) {
+       DERDecodedInfo decoded;
+       DERReturn drtn;
+
+       drtn = DERDecodeItem(url, &decoded);
+    if (drtn || decoded.tag != ASN1_IA5_STRING) {
+               appendInvalidProperty(properties, label, url);
+    } else {
+        appendURLContentProperty(properties, label, &decoded.content);
+    }
+}
+
+static void appendOIDProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *oid) {
+    CFStringRef oid_string = copyLocalizedOidDescription(CFGetAllocator(properties),
+        oid);
+    appendProperty(properties, kSecPropertyTypeString, label, oid_string);
+    CFRelease(oid_string);
+}
+
+static void appendAlgorithmProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERAlgorithmId *algorithm) {
+    CFMutableArrayRef alg_props =
+        CFArrayCreateMutable(CFGetAllocator(properties), 0,
+            &kCFTypeArrayCallBacks);
+    appendOIDProperty(alg_props, CFSTR("Algorithm"), &algorithm->oid);
+    if (algorithm->params.length) {
+        if (algorithm->params.length == 2 &&
+            algorithm->params.data[0] == ASN1_NULL &&
+            algorithm->params.data[1] == 0) {
+            /* @@@ Localize <NULL> or perhaps skip it? */
+            appendProperty(alg_props, kSecPropertyTypeString,
+                CFSTR("Parameters"), CFSTR("none"));
+        } else {
+            appendUnparsedProperty(alg_props, CFSTR("Parameters"),
+                               &algorithm->params);
+        }
+    }
+    appendProperty(properties, kSecPropertyTypeSection, label, alg_props);
+    CFRelease(alg_props);
+}
+
+static CFStringRef copyHexDescription(CFAllocatorRef allocator,
+    const DERItem *blob) {
+    CFIndex ix, length = blob->length /* < 24 ? blob->length : 24 */;
+    CFMutableStringRef string = CFStringCreateMutable(allocator,
+        blob->length * 3 - 1);
+    for (ix = 0; ix < length; ++ix)
+        if (ix == 0)
+            CFStringAppendFormat(string, NULL, CFSTR("%02X"), blob->data[ix]);
+        else
+            CFStringAppendFormat(string, NULL, CFSTR(" %02X"), blob->data[ix]);
+
+    return string;
+}
+
+static CFStringRef copyBlobString(CFAllocatorRef allocator,
+    CFStringRef blobType, CFStringRef quanta, const DERItem *blob) {
+    CFStringRef blobFormat = SecFrameworkCopyLocalizedString(
+        CFSTR("%@; %d %@; data = %@"), CFSTR("SecCertificate")
+        /*, "format string for encoded field data (e.g. Sequence; 128 bytes; "
+            "data = 00 00 ...)" */);
+    CFStringRef hex = copyHexDescription(allocator, blob);
+    CFStringRef result = CFStringCreateWithFormat(allocator, NULL,
+        blobFormat, blobType, blob->length, quanta, hex);
+    CFRelease(hex);
+    CFRelease(blobFormat);
+
+    return result;
+}
+
+static CFStringRef copyContentString(CFAllocatorRef allocator,
+       const DERItem *string, CFStringEncoding encoding,
+    bool printableOnly) {
+    /* Strip potential bogus trailing zero from printable strings. */
+    DERSize length = string->length;
+    if (length && string->data[length - 1] == 0) {
+        /* Don't mess with the length of UTF16 strings though. */
+        if (encoding != kCFStringEncodingUTF16)
+            length--;
+    }
+    /* A zero length string isn't considered printable. */
+    if (!length && printableOnly)
+        return NULL;
+
+    /* Passing true for the 5th paramater to CFStringCreateWithBytes() makes
+       it treat kCFStringEncodingUTF16 as big endian by default, whereas
+       passing false makes it treat it as native endian by default.  */
+    CFStringRef result = CFStringCreateWithBytes(allocator, string->data,
+        length, encoding, encoding == kCFStringEncodingUTF16);
+    if (result)
+        return result;
+
+    return printableOnly ? NULL : copyHexDescription(allocator, string);
+}
+
+/* From rfc3280 - Appendix B.  ASN.1 Notes
+
+   CAs MUST force the serialNumber to be a non-negative integer, that
+   is, the sign bit in the DER encoding of the INTEGER value MUST be
+   zero - this can be done by adding a leading (leftmost) `00'H octet if
+   necessary.  This removes a potential ambiguity in mapping between a
+   string of octets and an integer value.
+
+   As noted in section 4.1.2.2, serial numbers can be expected to
+   contain long integers.  Certificate users MUST be able to handle
+   serialNumber values up to 20 octets in length.  Conformant CAs MUST
+   NOT use serialNumber values longer than 20 octets.
+*/
+
+/* Return the given numeric data as a string: decimal up to 64 bits,
+   hex otherwise. */
+static CFStringRef copyIntegerContentDescription(CFAllocatorRef allocator,
+       const DERItem *integer) {
+       uint64_t value = 0;
+       CFIndex ix, length = integer->length;
+
+       if (length == 0 || length > 8)
+               return copyHexDescription(allocator, integer);
+
+       for(ix = 0; ix < length; ++ix) {
+               value <<= 8;
+               value += integer->data[ix];
+       }
+
+    return CFStringCreateWithFormat(allocator, NULL, CFSTR("%llu"), value);
+}
+
+static CFStringRef copyDERThingContentDescription(CFAllocatorRef allocator,
+       DERTag tag, const DERItem *derThing, bool printableOnly) {
+       switch(tag) {
+    case ASN1_INTEGER:
+    case ASN1_BOOLEAN:
+        return printableOnly ? NULL : copyIntegerContentDescription(allocator, derThing);
+    case ASN1_PRINTABLE_STRING:
+    case ASN1_IA5_STRING:
+        return copyContentString(allocator, derThing, kCFStringEncodingASCII, printableOnly);
+    case ASN1_UTF8_STRING:
+    case ASN1_GENERAL_STRING:
+    case ASN1_UNIVERSAL_STRING:
+        return copyContentString(allocator, derThing, kCFStringEncodingUTF8, printableOnly);
+    case ASN1_T61_STRING:              // 20, also BER_TAG_TELETEX_STRING
+    case ASN1_VIDEOTEX_STRING:   // 21
+    case ASN1_VISIBLE_STRING:          // 26
+        return copyContentString(allocator, derThing, kCFStringEncodingISOLatin1, printableOnly);
+    case ASN1_BMP_STRING:   // 30
+        return copyContentString(allocator, derThing, kCFStringEncodingUTF16, printableOnly);
+    case ASN1_OCTET_STRING:
+        return printableOnly ? NULL : copyBlobString(allocator, CFSTR("Byte string"), CFSTR("bytes"),
+            derThing);
+        //return copyBlobString(BYTE_STRING_STR, BYTES_STR, derThing);
+    case ASN1_BIT_STRING:
+        return printableOnly ? NULL : copyBlobString(allocator, CFSTR("Bit string"), CFSTR("bits"),
+            derThing);
+    case (DERByte)ASN1_CONSTR_SEQUENCE:
+        return printableOnly ? NULL : copyBlobString(allocator, CFSTR("Sequence"), CFSTR("bytes"),
+            derThing);
+    case (DERByte)ASN1_CONSTR_SET:
+        return printableOnly ? NULL : copyBlobString(allocator, CFSTR("Set"), CFSTR("bytes"),
+            derThing);
+    case ASN1_OBJECT_ID:
+        return printableOnly ? NULL : copyLocalizedOidDescription(allocator, derThing);
+    default:
+        /* @@@ Localize. */
+        /* "format string for undisplayed field data with a given DER tag" */
+        return printableOnly ? NULL : CFStringCreateWithFormat(allocator, NULL,
+            CFSTR("not displayed (tag = %d; length %d)"),
+            tag, (int)derThing->length);
+       }
+}
+
+static CFStringRef copyDERThingDescription(CFAllocatorRef allocator,
+       const DERItem *derThing, bool printableOnly) {
+       DERDecodedInfo decoded;
+       DERReturn drtn;
+
+       drtn = DERDecodeItem(derThing, &decoded);
+    if (drtn) {
+        return printableOnly ? NULL : copyHexDescription(allocator, derThing);
+    } else {
+        return copyDERThingContentDescription(allocator, decoded.tag,
+            &decoded.content, false);
+    }
+}
+
+static void appendDERThingProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *derThing) {
+    CFStringRef value = copyDERThingDescription(CFGetAllocator(properties),
+        derThing, false);
+    appendProperty(properties, kSecPropertyTypeString, label, value);
+    CFRelease(value);
+}
+
+static OSStatus appendRDNProperty(void *context, const DERItem *rdnType,
+       const DERItem *rdnValue, CFIndex rdnIX) {
+       CFMutableArrayRef properties = (CFMutableArrayRef)context;
+       if (rdnIX > 0) {
+               /* If there is more than one value pair we create a subsection for the
+                  second pair, and append things to the subsection for subsequent
+                  pairs. */
+               CFIndex lastIX = CFArrayGetCount(properties) - 1;
+               CFTypeRef lastValue = CFArrayGetValueAtIndex(properties, lastIX);
+               if (rdnIX == 1) {
+                       /* Since this is the second rdn pair for a given rdn, we setup a
+                          new subsection for this rdn.  We remove the first property
+                          from the properties array and make it the first element in the
+                          subsection instead. */
+                       CFMutableArrayRef rdn_props = CFArrayCreateMutable(
+                               CFGetAllocator(properties), 0, &kCFTypeArrayCallBacks);
+                       CFArrayAppendValue(rdn_props, lastValue);
+                       CFArrayRemoveValueAtIndex(properties, lastIX);
+                       appendProperty(properties, kSecPropertyTypeSection, NULL, rdn_props);
+                       properties = rdn_props;
+               } else {
+                       /* Since this is the third or later rdn pair we have already
+                          created a subsection in the top level properties array.  Instead
+                          of appending to that directly we append to the array inside the
+                          subsection. */
+                       properties = (CFMutableArrayRef)CFDictionaryGetValue(
+                               (CFDictionaryRef)lastValue, kSecPropertyKeyValue);
+               }
+       }
+
+       /* Finally we append the new rdn value to the property array. */
+       CFStringRef label = copyLocalizedOidDescription(CFGetAllocator(properties),
+               rdnType);
+       if (label) {
+               appendDERThingProperty(properties, label, rdnValue);
+               CFRelease(label);
+               return errSecSuccess;
+       } else {
+               return errSecInvalidCertificate;
+       }
+}
+
+static CFArrayRef createPropertiesForRDNContent(CFAllocatorRef allocator,
+       const DERItem *rdnSetContent) {
+       CFMutableArrayRef properties = CFArrayCreateMutable(allocator, 0,
+               &kCFTypeArrayCallBacks);
+       OSStatus status = parseRDNContent(rdnSetContent, properties,
+               appendRDNProperty);
+       if (status) {
+        CFArrayRemoveAllValues(properties);
+               appendInvalidProperty(properties, CFSTR("RDN"), rdnSetContent);
+       }
+
+       return properties;
+}
+
+/*
+    From rfc3739 - 3.1.2.  Subject
+
+    When parsing the subject here are some tips for a short name of the cert.
+      Choice   I:  commonName
+      Choice  II:  givenName
+      Choice III:  pseudonym
+
+      The commonName attribute value SHALL, when present, contain a name
+      of the subject.  This MAY be in the subject's preferred
+      presentation format, or a format preferred by the CA, or some
+      other format.  Pseudonyms, nicknames, and names with spelling
+      other than defined by the registered name MAY be used.  To
+      understand the nature of the name presented in commonName,
+      complying applications MAY have to examine present values of the
+      givenName and surname attributes, or the pseudonym attribute.
+
+*/
+static CFArrayRef createPropertiesForX501NameContent(CFAllocatorRef allocator,
+       const DERItem *x501NameContent) {
+       CFMutableArrayRef properties = CFArrayCreateMutable(allocator, 0,
+               &kCFTypeArrayCallBacks);
+       OSStatus status = parseX501NameContent(x501NameContent, properties,
+               appendRDNProperty);
+       if (status) {
+        CFArrayRemoveAllValues(properties);
+        appendInvalidProperty(properties, CFSTR("X.501 Name"), x501NameContent);
+       }
+
+       return properties;
+}
+
+static CFArrayRef createPropertiesForX501Name(CFAllocatorRef allocator,
+       const DERItem *x501Name) {
+       CFMutableArrayRef properties = CFArrayCreateMutable(allocator, 0,
+               &kCFTypeArrayCallBacks);
+       OSStatus status = parseX501Name(x501Name, properties, appendRDNProperty);
+       if (status) {
+        CFArrayRemoveAllValues(properties);
+        appendInvalidProperty(properties, CFSTR("X.501 Name"), x501Name);
+       }
+
+       return properties;
+}
+
+static void appendIntegerProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *integer) {
+    CFStringRef string = copyIntegerContentDescription(
+        CFGetAllocator(properties), integer);
+    appendProperty(properties, kSecPropertyTypeString, label, string);
+    CFRelease(string);
+}
+
+static void appendBoolProperty(CFMutableArrayRef properties,
+    CFStringRef label, bool boolean) {
+    appendProperty(properties, kSecPropertyTypeString,
+        label, boolean ? CFSTR("Yes") : CFSTR("No"));
+}
+
+static void appendBooleanProperty(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *boolean, bool defaultValue) {
+    bool result;
+    DERReturn drtn = DERParseBoolean(boolean, defaultValue, &result);
+    if (drtn) {
+        /* Couldn't parse boolean; dump the raw unparsed data as hex. */
+        appendInvalidProperty(properties, label, boolean);
+    } else {
+        appendBoolProperty(properties, label, result);
+    }
+}
+
+static void appendBitStringContentNames(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *bitStringContent,
+    const CFStringRef *names, CFIndex namesCount) {
+    DERSize len = bitStringContent->length - 1;
+    require_quiet(len == 1 || len == 2, badDER);
+    DERByte numUnusedBits = bitStringContent->data[0];
+    require_quiet(numUnusedBits < 8, badDER);
+    uint_fast16_t bits = 8 * len - numUnusedBits;
+    require_quiet(bits <= (uint_fast16_t)namesCount, badDER);
+    uint_fast16_t value = bitStringContent->data[1];
+    uint_fast16_t mask;
+    if (len > 1) {
+        value = (value << 8) + bitStringContent->data[2];
+        mask = 0x8000;
+    } else {
+        mask = 0x80;
+    }
+    uint_fast16_t ix;
+    bool didOne = false;
+    CFMutableStringRef string =
+        CFStringCreateMutable(CFGetAllocator(properties), 0);
+    for (ix = 0; ix < bits; ++ix) {
+        if (value & mask) {
+            if (didOne) {
+                CFStringAppend(string, CFSTR(", "));
+            } else {
+                didOne = true;
+            }
+            CFStringAppend(string, names[ix]);
+        }
+        mask >>= 1;
+    }
+    appendProperty(properties, kSecPropertyTypeString, label, string);
+    CFRelease(string);
+    return;
+badDER:
+    appendInvalidProperty(properties, label, bitStringContent);
+}
+
+static void appendBitStringNames(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *bitString,
+    const CFStringRef *names, CFIndex namesCount) {
+    DERDecodedInfo bitStringContent;
+    DERReturn drtn = DERDecodeItem(bitString, &bitStringContent);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(bitStringContent.tag == ASN1_BIT_STRING, badDER);
+    appendBitStringContentNames(properties, label, &bitStringContent.content,
+        names, namesCount);
+    return;
+badDER:
+    appendInvalidProperty(properties, label, bitString);
+}
+
+#if 0
+typedef uint16_t SecKeyUsage;
+
+#define kSecKeyUsageDigitalSignature    0x8000
+#define kSecKeyUsageNonRepudiation      0x4000
+#define kSecKeyUsageKeyEncipherment     0x2000
+#define kSecKeyUsageDataEncipherment    0x1000
+#define kSecKeyUsageKeyAgreement        0x0800
+#define kSecKeyUsageKeyCertSign         0x0400
+#define kSecKeyUsageCRLSign             0x0200
+#define kSecKeyUsageEncipherOnly        0x0100
+#define kSecKeyUsageDecipherOnly        0x0080
+
+/*
+      KeyUsage ::= BIT STRING {
+           digitalSignature        (0),
+           nonRepudiation          (1),
+           keyEncipherment         (2),
+           dataEncipherment        (3),
+           keyAgreement            (4),
+           keyCertSign             (5),
+           cRLSign                 (6),
+           encipherOnly            (7),
+           decipherOnly            (8) }
+ */
+static void appendKeyUsage(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    if ((extnValue->length != 4 && extnValue->length != 5)  ||
+        extnValue->data[0] !=  ASN1_BIT_STRING ||
+        extnValue->data[1] < 2 || extnValue->data[1] > 3 ||
+        extnValue->data[2] > 7) {
+        appendInvalidProperty(properties, CFSTR("KeyUsage Extension"),
+            extnValue);
+    } else {
+        CFMutableStringRef string =
+            CFStringCreateMutable(CFGetAllocator(properties), 0);
+        SecKeyUsage usage = (extnValue->data[3] << 8);
+        if (extnValue->length == 5)
+            usage += extnValue->data[4];
+        secdebug("keyusage", "keyusage: %04X", usage);
+        static const CFStringRef usageNames[] = {
+            CFSTR("Digital Signature"),
+            CFSTR("Non-Repudiation"),
+            CFSTR("Key Encipherment"),
+            CFSTR("Data Encipherment"),
+            CFSTR("Key Agreement"),
+            CFSTR("Cert Sign"),
+            CFSTR("CRL Sign"),
+            CFSTR("Encipher"),
+            CFSTR("Decipher"),
+        };
+        bool didOne = false;
+        SecKeyUsage mask = kSecKeyUsageDigitalSignature;
+        CFIndex ix, bits = (extnValue->data[1] - 1) * 8 - extnValue->data[2];
+        for (ix = 0; ix < bits; ++ix) {
+            if (usage & mask) {
+                if (didOne) {
+                    CFStringAppend(string, CFSTR(", "));
+                } else {
+                    didOne = true;
+                }
+                /* @@@ Localize usageNames[ix]. */
+                CFStringAppend(string, usageNames[ix]);
+            }
+            mask >>= 1;
+        }
+        appendProperty(properties, kSecPropertyTypeString, CFSTR("Usage"),
+            string);
+        CFRelease(string);
+    }
+}
+#else
+static void appendKeyUsage(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    static const CFStringRef usageNames[] = {
+        CFSTR("Digital Signature"),
+        CFSTR("Non-Repudiation"),
+        CFSTR("Key Encipherment"),
+        CFSTR("Data Encipherment"),
+        CFSTR("Key Agreement"),
+        CFSTR("Cert Sign"),
+        CFSTR("CRL Sign"),
+        CFSTR("Encipher Only"),
+        CFSTR("Decipher Only")
+    };
+    appendBitStringNames(properties, CFSTR("Usage"), extnValue,
+        usageNames, sizeof(usageNames) / sizeof(*usageNames));
+}
+#endif
+
+static void appendPrivateKeyUsagePeriod(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    DERPrivateKeyUsagePeriod pkup;
+       DERReturn drtn = DERParseSequence(extnValue,
+        DERNumPrivateKeyUsagePeriodItemSpecs, DERPrivateKeyUsagePeriodItemSpecs,
+        &pkup, sizeof(pkup));
+       require_noerr_quiet(drtn, badDER);
+    if (pkup.notBefore.length) {
+        appendDateContentProperty(properties, CFSTR("Not Valid Before"),
+            ASN1_GENERALIZED_TIME, &pkup.notBefore);
+    }
+    if (pkup.notAfter.length) {
+        appendDateContentProperty(properties, CFSTR("Not Valid After"),
+            ASN1_GENERALIZED_TIME, &pkup.notAfter);
+    }
+    return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Private Key Usage Period"),
+        extnValue);
+}
+
+static void appendStringContentProperty(CFMutableArrayRef properties,
+       CFStringRef label, const DERItem *stringContent,
+       CFStringEncoding encoding) {
+    CFStringRef string = CFStringCreateWithBytes(CFGetAllocator(properties),
+               stringContent->data, stringContent->length, encoding, FALSE);
+    if (string) {
+               appendProperty(properties, kSecPropertyTypeString, label, string);
+        CFRelease(string);
+       } else {
+               appendInvalidProperty(properties, label, stringContent);
+       }
+}
+
+/*
+      OtherName ::= SEQUENCE {
+           type-id    OBJECT IDENTIFIER,
+           value      [0] EXPLICIT ANY DEFINED BY type-id }
+*/
+static void appendOtherNameContentProperty(CFMutableArrayRef properties,
+       const DERItem *otherNameContent) {
+    DEROtherName on;
+       DERReturn drtn = DERParseSequenceContent(otherNameContent,
+        DERNumOtherNameItemSpecs, DEROtherNameItemSpecs,
+        &on, sizeof(on));
+       require_noerr_quiet(drtn, badDER);
+       CFAllocatorRef allocator = CFGetAllocator(properties);
+    CFStringRef oid_string = copyLocalizedOidDescription(allocator,
+               &on.typeIdentifier);
+       CFStringRef value_string = copyDERThingDescription(allocator, &on.value, false);
+       if (value_string)
+               appendProperty(properties, kSecPropertyTypeString, oid_string,
+                       value_string);
+       else
+        appendUnparsedProperty(properties, oid_string, &on.value);
+
+    return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Other Name"), otherNameContent);
+}
+
+/*
+      GeneralName ::= CHOICE {
+           otherName                       [0]     OtherName,
+           rfc822Name                      [1]     IA5String,
+           dNSName                         [2]     IA5String,
+           x400Address                     [3]     ORAddress,
+           directoryName                   [4]     Name,
+           ediPartyName                    [5]     EDIPartyName,
+           uniformResourceIdentifier       [6]     IA5String,
+           iPAddress                       [7]     OCTET STRING,
+           registeredID                    [8]     OBJECT IDENTIFIER}
+
+      EDIPartyName ::= SEQUENCE {
+           nameAssigner            [0]     DirectoryString OPTIONAL,
+           partyName               [1]     DirectoryString }
+ */
+static bool appendGeneralNameContentProperty(CFMutableArrayRef properties,
+    DERTag tag, const DERItem *generalName) {
+       switch (tag) {
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 0:
+               appendOtherNameContentProperty(properties, generalName);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 1:
+               /* IA5String. */
+               appendStringContentProperty(properties, CFSTR("Email Address"),
+                       generalName, kCFStringEncodingASCII);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 2:
+               /* IA5String. */
+               appendStringContentProperty(properties, CFSTR("DNS Name"), generalName,
+                       kCFStringEncodingASCII);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 3:
+               appendUnparsedProperty(properties, CFSTR("X.400 Address"),
+                       generalName);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 4:
+       {
+               CFArrayRef directory_plist =
+                       createPropertiesForX501Name(CFGetAllocator(properties),
+                               generalName);
+               appendProperty(properties, kSecPropertyTypeSection,
+                       CFSTR("Directory Name"), directory_plist);
+               CFRelease(directory_plist);
+               break;
+       }
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 5:
+               appendUnparsedProperty(properties, CFSTR("EDI Party Name"),
+                       generalName);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 6:
+               /* Technically I don't think this is valid, but there are certs out
+                  in the wild that use a constructed IA5String.   In particular the
+                  VeriSign Time Stamping Authority CA.cer does this.  */
+               appendURLProperty(properties, CFSTR("URI"), generalName);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 6:
+               appendURLContentProperty(properties, CFSTR("URI"), generalName);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 7:
+               appendIPAddressContentProperty(properties, CFSTR("IP Address"),
+                       generalName);
+               break;
+       case ASN1_CONTEXT_SPECIFIC | 8:
+               appendOIDProperty(properties, CFSTR("Registered ID"), generalName);
+               break;
+       default:
+               goto badDER;
+               break;
+       }
+       return true;
+badDER:
+       return false;
+}
+
+static void appendGeneralNameProperty(CFMutableArrayRef properties,
+    const DERItem *generalName) {
+    DERDecodedInfo generalNameContent;
+       DERReturn drtn = DERDecodeItem(generalName, &generalNameContent);
+       require_noerr_quiet(drtn, badDER);
+       if (appendGeneralNameContentProperty(properties, generalNameContent.tag,
+               &generalNameContent.content))
+               return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("General Name"), generalName);
+}
+
+
+/*
+      GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ */
+static void appendGeneralNamesContent(CFMutableArrayRef properties,
+    const DERItem *generalNamesContent) {
+    DERSequence gnSeq;
+    DERReturn drtn = DERDecodeSeqContentInit(generalNamesContent, &gnSeq);
+    require_noerr_quiet(drtn, badDER);
+    DERDecodedInfo generalNameContent;
+    while ((drtn = DERDecodeSeqNext(&gnSeq, &generalNameContent)) ==
+               DR_Success) {
+               if (!appendGeneralNameContentProperty(properties,
+                       generalNameContent.tag, &generalNameContent.content)) {
+                       goto badDER;
+               }
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+       return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("General Names"),
+        generalNamesContent);
+}
+
+static void appendGeneralNames(CFMutableArrayRef properties,
+    const DERItem *generalNames) {
+    DERDecodedInfo generalNamesContent;
+    DERReturn drtn = DERDecodeItem(generalNames, &generalNamesContent);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(generalNamesContent.tag == ASN1_CONSTR_SEQUENCE,
+        badDER);
+    appendGeneralNamesContent(properties, &generalNamesContent.content);
+    return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("General Names"), generalNames);
+}
+
+/*
+BasicConstraints ::= SEQUENCE {
+     cA                      BOOLEAN DEFAULT FALSE,
+     pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
+*/
+static void appendBasicConstraints(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+       DERBasicConstraints basicConstraints;
+       DERReturn drtn = DERParseSequence(extnValue,
+        DERNumBasicConstraintsItemSpecs, DERBasicConstraintsItemSpecs,
+        &basicConstraints, sizeof(basicConstraints));
+       require_noerr_quiet(drtn, badDER);
+
+    appendBooleanProperty(properties, CFSTR("Certificate Authority"),
+        &basicConstraints.cA, false);
+
+    if (basicConstraints.pathLenConstraint.length != 0) {
+        appendIntegerProperty(properties, CFSTR("Path Length Constraint"),
+            &basicConstraints.pathLenConstraint);
+    }
+    return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Basic Constraints"), extnValue);
+}
+
+/*
+   CRLDistPointsSyntax ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
+
+   DistributionPoint ::= SEQUENCE {
+        distributionPoint       [0]     DistributionPointName OPTIONAL,
+        reasons                 [1]     ReasonFlags OPTIONAL,
+        cRLIssuer               [2]     GeneralNames OPTIONAL }
+
+   DistributionPointName ::= CHOICE {
+        fullName                [0]     GeneralNames,
+        nameRelativeToCRLIssuer [1]     RelativeDistinguishedName }
+
+   ReasonFlags ::= BIT STRING {
+        unused                  (0),
+        keyCompromise           (1),
+        cACompromise            (2),
+        affiliationChanged      (3),
+        superseded              (4),
+        cessationOfOperation    (5),
+        certificateHold         (6),
+        privilegeWithdrawn      (7),
+        aACompromise            (8) }
+*/
+static void appendCrlDistributionPoints(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    CFAllocatorRef allocator = CFGetAllocator(properties);
+    DERTag tag;
+    DERSequence dpSeq;
+    DERReturn drtn = DERDecodeSeqInit(extnValue, &tag, &dpSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo dpSeqContent;
+    while ((drtn = DERDecodeSeqNext(&dpSeq, &dpSeqContent)) == DR_Success) {
+        require_quiet(dpSeqContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+        DERDistributionPoint dp;
+        drtn = DERParseSequenceContent(&dpSeqContent.content,
+            DERNumDistributionPointItemSpecs,
+            DERDistributionPointItemSpecs,
+            &dp, sizeof(dp));
+        require_noerr_quiet(drtn, badDER);
+        if (dp.distributionPoint.length) {
+            DERDecodedInfo distributionPointName;
+            drtn = DERDecodeItem(&dp.distributionPoint, &distributionPointName);
+            require_noerr_quiet(drtn, badDER);
+            if (distributionPointName.tag ==
+                (ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 0)) {
+                /* Full Name */
+                appendGeneralNamesContent(properties,
+                    &distributionPointName.content);
+            } else if (distributionPointName.tag ==
+                (ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 1)) {
+                               CFArrayRef rdn_props = createPropertiesForRDNContent(allocator,
+                                       &dp.reasons);
+                               appendProperty(properties, kSecPropertyTypeSection,
+                                       CFSTR("Name Relative To CRL Issuer"), rdn_props);
+                               CFRelease(rdn_props);
+            } else {
+                goto badDER;
+            }
+        }
+        if (dp.reasons.length) {
+            static const CFStringRef reasonNames[] = {
+                CFSTR("Unused"),
+                CFSTR("Key Compromise"),
+                CFSTR("CA Compromise"),
+                CFSTR("Affiliation Changed"),
+                CFSTR("Superseded"),
+                CFSTR("Cessation Of Operation"),
+                CFSTR("Certificate Hold"),
+                CFSTR("Priviledge Withdrawn"),
+                CFSTR("AA Compromise")
+            };
+            appendBitStringContentNames(properties, CFSTR("Reasons"),
+                &dp.reasons,
+                reasonNames, sizeof(reasonNames) / sizeof(*reasonNames));
+        }
+        if (dp.cRLIssuer.length) {
+            CFMutableArrayRef crlIssuer = CFArrayCreateMutable(allocator, 0,
+                &kCFTypeArrayCallBacks);
+            appendProperty(properties, kSecPropertyTypeSection,
+                CFSTR("CRL Issuer"), crlIssuer);
+            CFRelease(crlIssuer);
+            appendGeneralNames(crlIssuer, &dp.cRLIssuer);
+        }
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+       return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Crl Distribution Points"),
+               extnValue);
+}
+
+/* Decode a sequence of integers into a comma separated list of ints. */
+static void appendIntegerSequenceContent(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *intSequenceContent) {
+    CFAllocatorRef allocator = CFGetAllocator(properties);
+       DERSequence intSeq;
+       DERReturn drtn = DERDecodeSeqContentInit(intSequenceContent, &intSeq);
+       require_noerr_quiet(drtn, badDER);
+       DERDecodedInfo intContent;
+       CFMutableStringRef value = NULL;
+       while ((drtn = DERDecodeSeqNext(&intSeq, &intContent))
+               == DR_Success) {
+               require_quiet(intContent.tag == ASN1_INTEGER, badDER);
+               CFStringRef intDesc = copyIntegerContentDescription(
+                       allocator, &intContent.content);
+               if (value) {
+                       CFStringAppendFormat(value, NULL, CFSTR(", %@"), intDesc);
+               } else {
+                       value = CFStringCreateMutableCopy(allocator, 0, intDesc);
+               }
+               CFRelease(intDesc);
+       }
+       require_quiet(drtn == DR_EndOfSequence, badDER);
+       if (value) {
+               appendProperty(properties, kSecPropertyTypeString,
+                       CFSTR("Notice Numbers"), value);
+               CFRelease(value);
+               return;
+       }
+       /* DROPTHOUGH if !value. */
+badDER:
+       appendInvalidProperty(properties, label, intSequenceContent);
+}
+
+static void appendCertificatePolicies(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    CFAllocatorRef allocator = CFGetAllocator(properties);
+    DERTag tag;
+    DERSequence piSeq;
+    DERReturn drtn = DERDecodeSeqInit(extnValue, &tag, &piSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo piContent;
+    int pin = 1;
+    while ((drtn = DERDecodeSeqNext(&piSeq, &piContent)) == DR_Success) {
+        require_quiet(piContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+        DERPolicyInformation pi;
+        drtn = DERParseSequenceContent(&piContent.content,
+            DERNumPolicyInformationItemSpecs,
+            DERPolicyInformationItemSpecs,
+            &pi, sizeof(pi));
+        require_noerr_quiet(drtn, badDER);
+        CFStringRef piLabel = CFStringCreateWithFormat(allocator, NULL,
+            CFSTR("Policy Identifier #%d"), pin++);
+        appendOIDProperty(properties, piLabel, &pi.policyIdentifier);
+        CFRelease(piLabel);
+        if (pi.policyQualifiers.length == 0)
+            continue;
+
+        DERSequence pqSeq;
+        drtn = DERDecodeSeqContentInit(&pi.policyQualifiers, &pqSeq);
+        require_noerr_quiet(drtn, badDER);
+        DERDecodedInfo pqContent;
+        int pqn = 1;
+        while ((drtn = DERDecodeSeqNext(&pqSeq, &pqContent)) == DR_Success) {
+            DERPolicyQualifierInfo pqi;
+            drtn = DERParseSequenceContent(&pqContent.content,
+                DERNumPolicyQualifierInfoItemSpecs,
+                DERPolicyQualifierInfoItemSpecs,
+                &pqi, sizeof(pqi));
+            require_noerr_quiet(drtn, badDER);
+            DERDecodedInfo qualifierContent;
+            drtn = DERDecodeItem(&pqi.qualifier, &qualifierContent);
+            require_noerr_quiet(drtn, badDER);
+            CFStringRef pqLabel = CFStringCreateWithFormat(allocator, NULL,
+                CFSTR("Policy Qualifier #%d"), pqn++);
+            appendOIDProperty(properties, pqLabel, &pqi.policyQualifierID);
+            CFRelease(pqLabel);
+            if (DEROidCompare(&oidQtCps, &pqi.policyQualifierID)) {
+                require_quiet(qualifierContent.tag == ASN1_IA5_STRING, badDER);
+                appendURLContentProperty(properties,
+                    CFSTR("CPS URI"),
+                    &qualifierContent.content);
+            } else if (DEROidCompare(&oidQtUNotice, &pqi.policyQualifierID)) {
+                require_quiet(qualifierContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+                DERUserNotice un;
+                drtn = DERParseSequenceContent(&qualifierContent.content,
+                    DERNumUserNoticeItemSpecs,
+                    DERUserNoticeItemSpecs,
+                    &un, sizeof(un));
+                require_noerr_quiet(drtn, badDER);
+                if (un.noticeRef.length) {
+                    DERNoticeReference nr;
+                    drtn = DERParseSequenceContent(&un.noticeRef,
+                        DERNumNoticeReferenceItemSpecs,
+                        DERNoticeReferenceItemSpecs,
+                        &nr, sizeof(nr));
+                    require_noerr_quiet(drtn, badDER);
+                    appendDERThingProperty(properties,
+                        CFSTR("Organization"),
+                        &nr.organization);
+                                       appendIntegerSequenceContent(properties,
+                                               CFSTR("Notice Numbers"), &nr.noticeNumbers);
+                }
+                if (un.explicitText.length) {
+                    appendDERThingProperty(properties, CFSTR("Explicit Text"),
+                        &un.explicitText);
+                }
+            } else {
+                appendUnparsedProperty(properties, CFSTR("Qualifier"),
+                    &pqi.qualifier);
+            }
+        }
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+       return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Certificate Policies"),
+        extnValue);
+}
+
+static void appendSubjectKeyIdentifier(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+       DERReturn drtn;
+    DERDecodedInfo keyIdentifier;
+       drtn = DERDecodeItem(extnValue, &keyIdentifier);
+       require_noerr_quiet(drtn, badDER);
+       require_quiet(keyIdentifier.tag == ASN1_OCTET_STRING, badDER);
+       appendDataProperty(properties, CFSTR("Key Identifier"),
+               &keyIdentifier.content);
+
+       return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Invalid Subject Key Identifier"),
+        extnValue);
+}
+
+/*
+AuthorityKeyIdentifier ::= SEQUENCE {
+    keyIdentifier             [0] KeyIdentifier            OPTIONAL,
+    authorityCertIssuer       [1] GeneralNames             OPTIONAL,
+    authorityCertSerialNumber [2] CertificateSerialNumber  OPTIONAL }
+    -- authorityCertIssuer and authorityCertSerialNumber MUST both
+    -- be present or both be absent
+
+KeyIdentifier ::= OCTET STRING
+*/
+static void appendAuthorityKeyIdentifier(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+       DERAuthorityKeyIdentifier akid;
+       DERReturn drtn;
+       drtn = DERParseSequence(extnValue,
+               DERNumAuthorityKeyIdentifierItemSpecs,
+               DERAuthorityKeyIdentifierItemSpecs,
+               &akid, sizeof(akid));
+       require_noerr_quiet(drtn, badDER);
+       if (akid.keyIdentifier.length) {
+               appendDataProperty(properties, CFSTR("Key Identifier"),
+                       &akid.keyIdentifier);
+       }
+       if (akid.authorityCertIssuer.length ||
+               akid.authorityCertSerialNumber.length) {
+               require_quiet(akid.authorityCertIssuer.length &&
+                       akid.authorityCertSerialNumber.length, badDER);
+               /* Perhaps put in a subsection called Authority Certificate Issuer. */
+               appendGeneralNamesContent(properties,
+                       &akid.authorityCertIssuer);
+               appendIntegerProperty(properties,
+                       CFSTR("Authority Certificate Serial Number"),
+                       &akid.authorityCertSerialNumber);
+       }
+
+       return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Authority Key Identifier"),
+        extnValue);
+}
+
+/*
+   PolicyConstraints ::= SEQUENCE {
+        requireExplicitPolicy           [0] SkipCerts OPTIONAL,
+        inhibitPolicyMapping            [1] SkipCerts OPTIONAL }
+
+   SkipCerts ::= INTEGER (0..MAX)
+*/
+static void appendPolicyConstraints(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+       DERPolicyConstraints pc;
+       DERReturn drtn;
+       drtn = DERParseSequence(extnValue,
+               DERNumPolicyConstraintsItemSpecs,
+               DERPolicyConstraintsItemSpecs,
+               &pc, sizeof(pc));
+       require_noerr_quiet(drtn, badDER);
+       if (pc.requireExplicitPolicy.length) {
+               appendIntegerProperty(properties,
+                       CFSTR("Require Explicit Policy"), &pc.requireExplicitPolicy);
+       }
+       if (pc.inhibitPolicyMapping.length) {
+               appendIntegerProperty(properties,
+                       CFSTR("Inhibit Policy Mapping"), &pc.inhibitPolicyMapping);
+       }
+
+       return;
+
+badDER:
+       appendInvalidProperty(properties, CFSTR("Policy Constraints"), extnValue);
+}
+
+/*
+extendedKeyUsage EXTENSION ::= {
+        SYNTAX SEQUENCE SIZE (1..MAX) OF KeyPurposeId
+        IDENTIFIED BY id-ce-extKeyUsage }
+
+KeyPurposeId ::= OBJECT IDENTIFIER
+*/
+static void appendExtendedKeyUsage(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    DERTag tag;
+    DERSequence derSeq;
+    DERReturn drtn = DERDecodeSeqInit(extnValue, &tag, &derSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo currDecoded;
+    while ((drtn = DERDecodeSeqNext(&derSeq, &currDecoded)) == DR_Success) {
+        require_quiet(currDecoded.tag == ASN1_OBJECT_ID, badDER);
+        appendOIDProperty(properties, CFSTR("Purpose"),
+            &currDecoded.content);
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+       return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Extended Key Usage"), extnValue);
+}
+
+/*
+   id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
+
+   AuthorityInfoAccessSyntax  ::=
+           SEQUENCE SIZE (1..MAX) OF AccessDescription
+
+   AccessDescription  ::=  SEQUENCE {
+           accessMethod          OBJECT IDENTIFIER,
+           accessLocation        GeneralName  }
+
+   id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
+
+   id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
+
+   id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
+*/
+static void appendInfoAccess(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    DERTag tag;
+    DERSequence adSeq;
+    DERReturn drtn = DERDecodeSeqInit(extnValue, &tag, &adSeq);
+    require_noerr_quiet(drtn, badDER);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badDER);
+    DERDecodedInfo adContent;
+    while ((drtn = DERDecodeSeqNext(&adSeq, &adContent)) == DR_Success) {
+        require_quiet(adContent.tag == ASN1_CONSTR_SEQUENCE, badDER);
+               DERAccessDescription ad;
+               drtn = DERParseSequenceContent(&adContent.content,
+                       DERNumAccessDescriptionItemSpecs,
+                       DERAccessDescriptionItemSpecs,
+                       &ad, sizeof(ad));
+               require_noerr_quiet(drtn, badDER);
+        appendOIDProperty(properties, CFSTR("Access Method"),
+            &ad.accessMethod);
+               //CFSTR("Access Location");
+        appendGeneralNameProperty(properties, &ad.accessLocation);
+    }
+    require_quiet(drtn == DR_EndOfSequence, badDER);
+       return;
+badDER:
+    appendInvalidProperty(properties, CFSTR("Authority Information Access"),
+        extnValue);
+}
+
+static void appendNetscapeCertType(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+    static const CFStringRef certTypes[] = {
+        CFSTR("SSL client"),
+        CFSTR("SSL server"),
+        CFSTR("S/MIME"),
+        CFSTR("Object Signing"),
+        CFSTR("Reserved"),
+        CFSTR("SSL CA"),
+        CFSTR("S/MIME CA"),
+        CFSTR("Object Signing CA")
+    };
+    appendBitStringNames(properties, CFSTR("Usage"), extnValue,
+        certTypes, sizeof(certTypes) / sizeof(*certTypes));
+}
+
+#if 0
+static void appendEntrustVersInfo(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+}
+
+/*
+ * The list of Qualified Cert Statement statementIds we understand, even though
+ * we don't actually do anything with them; if these are found in a Qualified
+ * Cert Statement that's critical, we can truthfully say "yes we understand this".
+ */
+static const CSSM_OID_PTR knownQualifiedCertStatements[] =
+{
+    /* id-qcs := { id-pkix 11 } */
+       (const CSSM_OID_PTR)&CSSMOID_OID_QCS_SYNTAX_V1, /* id-qcs 1 */
+       (const CSSM_OID_PTR)&CSSMOID_OID_QCS_SYNTAX_V2, /* id-qcs 2 */
+       (const CSSM_OID_PTR)&CSSMOID_ETSI_QCS_QC_COMPLIANCE,
+       (const CSSM_OID_PTR)&CSSMOID_ETSI_QCS_QC_LIMIT_VALUE,
+       (const CSSM_OID_PTR)&CSSMOID_ETSI_QCS_QC_RETENTION,
+       (const CSSM_OID_PTR)&CSSMOID_ETSI_QCS_QC_SSCD
+};
+#define NUM_KNOWN_QUAL_CERT_STATEMENTS (sizeof(knownQualifiedCertStatements) / sizeof(CSSM_OID_PTR))
+*/
+static void appendQCCertStatements(CFMutableArrayRef properties,
+    const DERItem *extnValue) {
+}
+
+#endif
+
+static bool appendPrintableDERSequence(CFMutableArrayRef properties,
+    CFStringRef label, const DERItem *sequence) {
+    DERTag tag;
+    DERSequence derSeq;
+    DERReturn drtn = DERDecodeSeqInit(sequence, &tag, &derSeq);
+    require_noerr_quiet(drtn, badSequence);
+    require_quiet(tag == ASN1_CONSTR_SEQUENCE, badSequence);
+    DERDecodedInfo currDecoded;
+    bool appendedSomething = false;
+    while ((drtn = DERDecodeSeqNext(&derSeq, &currDecoded)) == DR_Success) {
+               switch (currDecoded.tag)
+               {
+                       case 0:                             // 0
+                       case ASN1_SEQUENCE:                 // 16
+                       case ASN1_SET:                      // 17
+                               // skip constructed object lengths
+                               break;
+
+                       case ASN1_UTF8_STRING:              // 12
+                       case ASN1_NUMERIC_STRING:           // 18
+                       case ASN1_PRINTABLE_STRING:         // 19
+                       case ASN1_T61_STRING:               // 20, also ASN1_TELETEX_STRING
+                       case ASN1_VIDEOTEX_STRING:          // 21
+                       case ASN1_IA5_STRING:               // 22
+                       case ASN1_GRAPHIC_STRING:           // 25
+                       case ASN1_VISIBLE_STRING:           // 26, also ASN1_ISO646_STRING
+                       case ASN1_GENERAL_STRING:           // 27
+                       case ASN1_UNIVERSAL_STRING:         // 28
+                       {
+                CFStringRef string =
+                    copyDERThingContentDescription(CFGetAllocator(properties),
+                        currDecoded.tag, &currDecoded.content, false);
+                //CFStringRef cleanString = copyStringRemovingPercentEscapes(string);
+
+                appendProperty(properties, kSecPropertyTypeString, label,
+                    string);
+                CFRelease(string);
+                               appendedSomething = true;
+                break;
+                       }
+                       default:
+                               break;
+               }
+    }
+    require_quiet(drtn == DR_EndOfSequence, badSequence);
+       return appendedSomething;
+badSequence:
+    return false;
+}
+
+static void appendExtension(CFMutableArrayRef parent,
+    const SecCertificateExtension *extn) {
+    CFAllocatorRef allocator = CFGetAllocator(parent);
+    CFMutableArrayRef properties = CFArrayCreateMutable(allocator, 0,
+        &kCFTypeArrayCallBacks);
+    const DERItem
+        *extnID = &extn->extnID,
+        *extnValue = &extn->extnValue;
+
+    appendBoolProperty(properties, CFSTR("Critical"), extn->critical);
+
+#if 1
+       bool handeled = true;
+       /* Extensions that we know how to handle ourselves... */
+       if (extnID->length == oidSubjectKeyIdentifier.length &&
+               !memcmp(extnID->data, oidSubjectKeyIdentifier.data, extnID->length - 1))
+       {
+               switch (extnID->data[extnID->length - 1]) {
+               case 14: /* SubjectKeyIdentifier     id-ce 14 */
+                       appendSubjectKeyIdentifier(properties, extnValue);
+                       break;
+               case 15: /* KeyUsage                 id-ce 15 */
+                       appendKeyUsage(properties, extnValue);
+                       break;
+               case 16: /* PrivateKeyUsagePeriod    id-ce 16 */
+                       appendPrivateKeyUsagePeriod(properties, extnValue);
+                       break;
+               case 17: /* SubjectAltName           id-ce 17 */
+               case 18: /* IssuerAltName            id-ce 18 */
+                       appendGeneralNames(properties, extnValue);
+                       break;
+               case 19: /* BasicConstraints         id-ce 19 */
+                       appendBasicConstraints(properties, extnValue);
+                       break;
+               case 30: /* NameConstraints          id-ce 30 */
+                       handeled = false;
+                       break;
+               case 31: /* CRLDistributionPoints    id-ce 31 */
+                       appendCrlDistributionPoints(properties, extnValue);
+                       break;
+               case 32: /* CertificatePolicies      id-ce 32 */
+                       appendCertificatePolicies(properties, extnValue);
+                       break;
+               case 33: /* PolicyMappings           id-ce 33 */
+                       handeled = false;
+                       break;
+               case 35: /* AuthorityKeyIdentifier   id-ce 35 */
+                       appendAuthorityKeyIdentifier(properties, extnValue);
+                       break;
+               case 36: /* PolicyConstraints        id-ce 36 */
+                       appendPolicyConstraints(properties, extnValue);
+                       break;
+               case 37: /* ExtKeyUsage              id-ce 37 */
+                       appendExtendedKeyUsage(properties, extnValue);
+                       break;
+               case 46: /* FreshestCRL              id-ce 46 */
+                       handeled = false;
+                       break;
+               case 54: /* InhibitAnyPolicy         id-ce 54 */
+                       handeled = false;
+                       break;
+               default:
+                       handeled = false;
+                       break;
+               }
+       } else if (extnID->length == oidAuthorityInfoAccess.length &&
+               !memcmp(extnID->data, oidAuthorityInfoAccess.data, extnID->length - 1))
+       {
+               switch (extnID->data[extnID->length - 1]) {
+               case  1: /* AuthorityInfoAccess      id-pe 1 */
+                       appendInfoAccess(properties, extnValue);
+                       break;
+               case  3: /* QCStatements             id-pe 3 */
+                       handeled = false;
+                       break;
+               case 11: /* SubjectInfoAccess        id-pe 11 */
+                       appendInfoAccess(properties, extnValue);
+                       break;
+               default:
+                       handeled = false;
+                       break;
+               }
+       } else if (DEROidCompare(extnID, &oidNetscapeCertType)) {
+               /* 2.16.840.1.113730.1.1 netscape 1 1 */
+               appendNetscapeCertType(properties, extnValue);
+       } else {
+               handeled = false;
+       }
+
+       if (!handeled) {
+               /* Try to parse and display printable string(s). */
+               if (appendPrintableDERSequence(properties, CFSTR("Data"), extnValue)) {
+                       /* Nothing to do here appendPrintableDERSequence did the work. */
+               } else {
+                       /* Couldn't parse extension; dump the raw unparsed data as hex. */
+                       appendUnparsedProperty(properties, CFSTR("Data"), extnValue);
+               }
+       }
+#else
+       /* Extensions that we know how to handle ourselves... */
+       if (DEROidCompare(extnID, &oidSubjectKeyIdentifier)) {
+               appendSubjectKeyIdentifier(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidKeyUsage)) {
+               appendKeyUsage(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidPrivateKeyUsagePeriod)) {
+               appendPrivateKeyUsagePeriod(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidSubjectAltName)) {
+               appendGeneralNames(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidIssuerAltName)) {
+               appendGeneralNames(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidBasicConstraints)) {
+               appendBasicConstraints(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidCrlDistributionPoints)) {
+               appendCrlDistributionPoints(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidCertificatePolicies)) {
+               appendCertificatePolicies(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidAuthorityKeyIdentifier)) {
+               appendAuthorityKeyIdentifier(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidPolicyConstraints)) {
+               appendPolicyConstraints(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidExtendedKeyUsage)) {
+               appendExtendedKeyUsage(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidAuthorityInfoAccess)) {
+               appendInfoAccess(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidSubjectInfoAccess)) {
+               appendInfoAccess(properties, extnValue);
+       } else if (DEROidCompare(extnID, &oidNetscapeCertType)) {
+               appendNetscapeCertType(properties, extnValue);
+#if 0
+       } else if (DEROidCompare(extnID, &oidEntrustVersInfo)) {
+               appendEntrustVersInfo(properties, extnValue);
+#endif
+       } else
+       /* Try to parse and display printable string(s). */
+    if (appendPrintableDERSequence(properties, CFSTR("Data"), extnValue)) {
+        /* Nothing to do here appendPrintableDERSequence did the work. */
+    } else {
+        /* Couldn't parse extension; dump the raw unparsed data as hex. */
+        appendUnparsedProperty(properties, CFSTR("Data"), extnValue);
+    }
+#endif
+    CFStringRef oid_string = copyLocalizedOidDescription(allocator, extnID);
+    appendProperty(parent, kSecPropertyTypeSection, oid_string, properties);
+    CFRelease(oid_string);
+    CFRelease(properties);
+}
+
+/* Different types of summary types from least desired to most desired. */
+enum SummaryType {
+    kSummaryTypeNone,
+    kSummaryTypePrintable,
+    kSummaryTypeOrganizationName,
+    kSummaryTypeOrganizationalUnitName,
+    kSummaryTypeCommonName,
+};
+
+struct Summary {
+    enum SummaryType type;
+    CFStringRef summary;
+    CFStringRef description;
+};
+
+static OSStatus obtainSummaryFromX501Name(void *context,
+       const DERItem *type, const DERItem *value, CFIndex rdnIX) {
+    struct Summary *summary = (struct Summary *)context;
+    enum SummaryType stype = kSummaryTypeNone;
+    CFStringRef string = NULL;
+    if (DEROidCompare(type, &oidCommonName)) {
+        /* We skip Common Names that have generic values. */
+        const char tfm[] = "Thawte Freemail Member";
+        if ((value->length == sizeof(tfm) + 1) &&
+              !memcmp(value->data + 2, tfm, sizeof(tfm) - 1)) {
+            return errSecSuccess;
+        }
+        stype = kSummaryTypeCommonName;
+    } else if (DEROidCompare(type, &oidOrganizationalUnitName)) {
+        stype = kSummaryTypeOrganizationalUnitName;
+    } else if (DEROidCompare(type, &oidOrganizationName)) {
+        stype = kSummaryTypeOrganizationName;
+    } else if (DEROidCompare(type, &oidDescription)) {
+        if (!summary->description) {
+            summary->description = string = copyDERThingDescription(kCFAllocatorDefault, value, true);
+            CFRetain(string);
+        }
+        stype = kSummaryTypePrintable;
+    } else {
+        stype = kSummaryTypePrintable;
+    }
+
+    /* Use the first field we encounter of the highest priority type. */
+    if (summary->type < stype) {
+        if (!string) {
+            string = copyDERThingDescription(kCFAllocatorDefault, value, true);
+        }
+
+        if (string) {
+            CFReleaseSafe(summary->summary);
+            summary->summary = string;
+            summary->type = stype;
+        }
+    } else {
+        CFReleaseSafe(string);
+    }
+
+       return errSecSuccess;
+}
+
+CFStringRef SecCertificateCopySubjectSummaryP(SecCertificateRefP certificate) {
+    struct Summary summary = {};
+       parseX501NameContent(&certificate->_subject, &summary, obtainSummaryFromX501Name);
+    /* If we found a description and a common name we change the summary to
+       CommonName (Description). */
+    if (summary.description) {
+        if (summary.type == kSummaryTypeCommonName) {
+            CFStringRef newSummary = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
+                CFSTR("%@ (%@)"), summary.summary, summary.description);
+            CFRelease(summary.summary);
+            summary.summary = newSummary;
+        }
+        CFRelease(summary.description);
+    }
+
+    if (!summary.summary) {
+        /* If we didn't find a suitable printable string in the subject at all, we try
+           the first email address in the certificate instead. */
+        CFArrayRef names = SecCertificateCopyRFC822Names(certificate);
+        if (!names) {
+            /* If we didn't find any email addresses in the certificate, we try finding
+               a DNS name instead. */
+            names = SecCertificateCopyDNSNamesP(certificate);
+        }
+        if (names) {
+            summary.summary = CFArrayGetValueAtIndex(names, 0);
+            CFRetain(summary.summary);
+            CFRelease(names);
+        }
+    }
+
+       return summary.summary;
+}
+
+CFStringRef SecCertificateCopyIssuerSummaryP(SecCertificateRefP certificate) {
+    struct Summary summary = {};
+       parseX501NameContent(&certificate->_issuer, &summary, obtainSummaryFromX501Name);
+    /* If we found a description and a common name we change the summary to
+       CommonName (Description). */
+    if (summary.description) {
+        if (summary.type == kSummaryTypeCommonName) {
+            CFStringRef newSummary = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
+                CFSTR("%@ (%@)"), summary.summary, summary.description);
+            CFRelease(summary.summary);
+            summary.summary = newSummary;
+        }
+        CFRelease(summary.description);
+    }
+
+       return summary.summary;
+}
+
+/* Return the earliest date on which all certificates in this chain are still
+   valid. */
+static CFAbsoluteTime SecCertificateGetChainsLastValidity(
+    SecCertificateRefP certificate) {
+    CFAbsoluteTime earliest = certificate->_notAfter;
+#if 0
+    while (certificate->_parent) {
+        certificate = certificate->_parent;
+        if (earliest > certificate->_notAfter)
+            earliest = certificate->_notAfter;
+    }
+#endif
+
+    return earliest;
+}
+
+/* Return the latest date on which all certificates in this chain will be
+   valid. */
+static CFAbsoluteTime SecCertificateGetChainsFirstValidity(
+    SecCertificateRefP certificate) {
+    CFAbsoluteTime latest = certificate->_notBefore;
+#if 0
+    while (certificate->_parent) {
+        certificate = certificate->_parent;
+        if (latest < certificate->_notBefore)
+            latest = certificate->_notBefore;
+    }
+#endif
+
+    return latest;
+}
+
+bool SecCertificateIsValidP(SecCertificateRefP certificate,
+       CFAbsoluteTime verifyTime) {
+       check(certificate);
+    return certificate->_notBefore <= verifyTime &&
+               verifyTime <= certificate->_notAfter;
+}
+
+CFIndex SecCertificateVersion(SecCertificateRefP certificate) {
+       return certificate->_version + 1;
+}
+
+CFAbsoluteTime SecCertificateNotValidBeforeP(SecCertificateRefP certificate) {
+       return certificate->_notBefore;
+}
+
+CFAbsoluteTime SecCertificateNotValidAfterP(SecCertificateRefP certificate) {
+       return certificate->_notAfter;
+}
+
+CFMutableArrayRef SecCertificateCopySummaryProperties(
+    SecCertificateRefP certificate, CFAbsoluteTime verifyTime) {
+    CFAllocatorRef allocator = CFGetAllocator(certificate);
+    CFMutableArrayRef summary = CFArrayCreateMutable(allocator, 0,
+        &kCFTypeArrayCallBacks);
+
+    /* First we put the subject summary name. */
+    CFStringRef ssummary = SecCertificateCopySubjectSummaryP(certificate);
+    if (ssummary) {
+        appendProperty(summary, kSecPropertyTypeTitle,
+            NULL, ssummary);
+        CFRelease(ssummary);
+    }
+#if 0
+    CFStringRef isummary = CFSTR("Issuer Summary");
+    appendProperty(summary, kSecPropertyTypeString,
+        CFSTR("Issued By"), isummary);
+    CFRelease(isummary);
+#endif
+
+    /* Let see if this certificate is currently valid. */
+    CFStringRef label;
+    CFAbsoluteTime when;
+    CFStringRef message;
+    CFStringRef ptype;
+    if (verifyTime > certificate->_notAfter) {
+        label = CFSTR("Expired");
+        when = certificate->_notAfter;
+        ptype = kSecPropertyTypeError;
+        message = CFSTR("This certificate has expired");
+    } else if (certificate->_notBefore > verifyTime) {
+        label = CFSTR("Valid from");
+        when = certificate->_notBefore;
+        ptype = kSecPropertyTypeError;
+        message = CFSTR("This certificate is not yet valid");
+    } else {
+        CFAbsoluteTime last = SecCertificateGetChainsLastValidity(certificate);
+        CFAbsoluteTime first = SecCertificateGetChainsFirstValidity(certificate);
+        if (verifyTime > last) {
+            label = CFSTR("Expired");
+            when = last;
+            ptype = kSecPropertyTypeError;
+            message = CFSTR("This certificate has an issuer that has expired");
+        } else if (verifyTime < first) {
+            label = CFSTR("Valid from");
+            when = first;
+            ptype = kSecPropertyTypeError;
+            message = CFSTR("This certificate has an issuer that is not yet valid");
+        } else {
+            label = CFSTR("Expires");
+            when = certificate->_notAfter;
+            ptype = kSecPropertyTypeSuccess;
+            message = CFSTR("This certificate is valid");
+        }
+    }
+
+    appendDateProperty(summary, label, when);
+    appendProperty(summary, ptype, NULL, message);
+
+       return summary;
+}
+
+CFArrayRef SecCertificateCopyProperties(SecCertificateRefP certificate) {
+       if (!certificate->_properties) {
+               CFAllocatorRef allocator = CFGetAllocator(certificate);
+               CFMutableArrayRef properties = CFArrayCreateMutable(allocator, 0,
+                       &kCFTypeArrayCallBacks);
+
+        /* First we put the Subject Name in the property list. */
+               CFArrayRef subject_plist = createPropertiesForX501NameContent(allocator,
+                &certificate->_subject);
+        appendProperty(properties, kSecPropertyTypeSection,
+            CFSTR("Subject Name"), subject_plist);
+               CFRelease(subject_plist);
+
+#if 0
+        /* Put Normalized subject in for testing. */
+               if (certificate->_normalizedSubject) {
+                       DERItem nsubject = {
+                               (DERByte *)CFDataGetBytePtr(certificate->_normalizedSubject),
+                               CFDataGetLength(certificate->_normalizedSubject)
+                       };
+                       CFArrayRef nsubject_plist = createPropertiesForX501NameContent(allocator,
+                                       &nsubject);
+                       appendProperty(properties, kSecPropertyTypeSection,
+                               CFSTR("Normalized Subject Name"), nsubject_plist);
+                       CFRelease(nsubject_plist);
+               }
+#endif
+
+               /* Next we put the Issuer Name in the property list. */
+               CFArrayRef issuer_plist = createPropertiesForX501NameContent(allocator,
+                       &certificate->_issuer);
+        appendProperty(properties, kSecPropertyTypeSection,
+            CFSTR("Issuer Name"), issuer_plist);
+               CFRelease(issuer_plist);
+
+#if 0
+        /* Certificate version/type. */
+        bool isRoot = false;
+        CFStringRef typeString = CFStringCreateWithFormat(allocator, NULL,
+            CFSTR("X.509 version %d %scertificate"),
+                certificate->_version + 1, isRoot ? "root " : "");
+        appendProperty(properties, kSecPropertyTypeString,
+            CFSTR("Certificate Type"), typeString);
+        CFRelease(typeString);
+#endif
+
+               /* Version */
+        CFStringRef versionString = CFStringCreateWithFormat(allocator,
+            NULL, CFSTR("%d"), certificate->_version + 1);
+        appendProperty(properties, kSecPropertyTypeString,
+            CFSTR("Version"), versionString);
+        CFRelease(versionString);
+
+               /* Serial Number */
+        if (certificate->_serialNum.length) {
+            appendIntegerProperty(properties, CFSTR("Serial Number"),
+                &certificate->_serialNum);
+        }
+
+        /* Signature algorithm. */
+#if 0
+        appendAlgorithmProperty(properties, CFSTR("Signature Algorithm"),
+            &certificate->_sigAlg);
+#endif
+        appendAlgorithmProperty(properties, CFSTR("Signature Algorithm"),
+            &certificate->_tbsSigAlg);
+
+
+        /* Validity dates. */
+        appendDateProperty(properties, CFSTR("Not Valid Before"),
+            certificate->_notBefore);
+        appendDateProperty(properties, CFSTR("Not Valid After"),
+            certificate->_notAfter);
+
+        if (certificate->_subjectUniqueID.length) {
+            appendDataProperty(properties, CFSTR("Subject Unique ID"),
+                &certificate->_subjectUniqueID);
+        }
+        if (certificate->_issuerUniqueID.length) {
+            appendDataProperty(properties, CFSTR("Issuer Unique ID"),
+                &certificate->_issuerUniqueID);
+        }
+
+        /* Public key algorithm. */
+        appendAlgorithmProperty(properties, CFSTR("Public Key Algorithm"),
+            &certificate->_algId);
+
+        /* Consider breaking down an RSA public key into modulus and
+           exponent? */
+        appendDataProperty(properties, CFSTR("Public Key Data"),
+            &certificate->_pubKeyDER);
+               /* @@@ Key Size. */
+               /* @@@ Key Usage. */
+
+        appendDataProperty(properties, CFSTR("Signature"),
+            &certificate->_signature);
+
+        CFIndex ix;
+        for (ix = 0; ix < certificate->_extensionCount; ++ix) {
+            appendExtension(properties, &certificate->_extensions[ix]);
+        }
+
+               /* @@@ Key Fingerprints. */
+
+               certificate->_properties = properties;
+       }
+
+    CFRetain(certificate->_properties);
+       return certificate->_properties;
+}
+
+CFDataRef SecCertificateCopySerialNumberP(
+    SecCertificateRefP certificate) {
+       if (certificate->_serialNumber) {
+               CFRetain(certificate->_serialNumber);
+       }
+    return certificate->_serialNumber;
+}
+
+/*
+ * Accessor for normalized issuer content
+ */
+CFDataRef SecCertificateGetNormalizedIssuerContent(
+    SecCertificateRefP certificate) {
+    return certificate->_normalizedIssuer;
+}
+
+/*
+ * Accessor for normalized subject content
+ */
+CFDataRef SecCertificateGetNormalizedSubjectContent(
+    SecCertificateRefP certificate) {
+    return certificate->_normalizedSubject;
+}
+
+/*
+ * Returns DER-encoded normalized issuer sequence
+ * for use with SecItemCopyMatching; caller must release
+ */
+CFDataRef SecCertificateCopyNormalizedIssuerSequence(
+    SecCertificateRefP certificate) {
+       DERItem tmpdi;
+       tmpdi.data = (DERByte *)CFDataGetBytePtr(certificate->_normalizedIssuer);
+       tmpdi.length = CFDataGetLength(certificate->_normalizedIssuer);
+
+    return SecDERItemCopySequence(&tmpdi);
+}
+
+/*
+ * Returns DER-encoded normalized subject sequence
+ * for use with SecItemCopyMatching; caller must release
+ */
+CFDataRef SecCertificateCopyNormalizedSubjectSequence(
+    SecCertificateRefP certificate) {
+       DERItem tmpdi;
+       tmpdi.data = (DERByte *)CFDataGetBytePtr(certificate->_normalizedSubject);
+       tmpdi.length = CFDataGetLength(certificate->_normalizedSubject);
+
+    return SecDERItemCopySequence(&tmpdi);
+}
+
+/* Verify that certificate was signed by issuerKey. */
+static
+OSStatus SecCertificateIsSignedByP(SecCertificateRefP certificate,
+       SecKeyRefP issuerKey) {
+    /* Setup algId in SecAsn1AlgId format. */
+    SecAsn1AlgId algId;
+    algId.algorithm.Length = certificate->_tbsSigAlg.oid.length;
+    algId.algorithm.Data = certificate->_tbsSigAlg.oid.data;
+    algId.parameters.Length = certificate->_tbsSigAlg.params.length;
+    algId.parameters.Data = certificate->_tbsSigAlg.params.data;
+
+#warning implementation empty
+#if 0
+    OSStatus status = SecKeyDigestAndVerify(issuerKey, &algId,
+        certificate->_tbs.data, certificate->_tbs.length,
+        certificate->_signature.data, certificate->_signature.length);
+       if (status) {
+               secdebug("verify", "signature verify failed: %d", status);
+               return errSecNotSigner;
+       }
+#endif
+
+    return errSecSuccess;
+}
+
+#if 0
+static OSStatus SecCertificateIsIssuedBy(SecCertificateRefP certificate,
+       SecCertificateRefP issuer, bool signatureCheckOnly) {
+    if (!signatureCheckOnly) {
+        /* It turns out we don't actually need to use normalized subject and
+           issuer according to rfc2459.  */
+
+        /* If present we should check issuerID against the issuer subjectID. */
+
+        /* If we have an AuthorityKeyIdentifier extension that has a keyIdentifier
+           then we should look for a SubjectKeyIdentifier in the issuer
+           certificate.
+           If we have a authorityCertSerialNumber we can use that for chaining.
+           If we have a authorityCertIssuer we can use that? (or not)  */
+
+        /* Verify that this cert was issued by issuer. Do so by chaining
+           either issuerID to subjectID or normalized issuer to normalized
+           subject. */
+        CFDataRef normalizedIssuer =
+            SecCertificateGetNormalizedIssuerContent(certificate);
+        CFDataRef normalizedIssuerSubject =
+            SecCertificateGetNormalizedSubjectContent(issuer);
+        if (normalizedIssuer && normalizedIssuerSubject &&
+            !CFEqual(normalizedIssuer, normalizedIssuerSubject))
+            return errSecIssuerMismatch;
+    }
+
+       /* Next verify that this cert was signed by issuer. */
+       SecKeyRef issuerKey = SecCertificateGetPublicKey(issuer);
+
+       /* Get the encodedDigestInfo from the digest of the subject's TBSCert */
+       /* FIXME: We sould cache this (or at least the digest) until we find
+          a suitable issuer. */
+       uint8_t signedData[DER_SHA1_DIGEST_INFO_LEN];
+       CFIndex signedDataLength;
+       CertVerifyReturn crtn;
+       if (DEROidCompare(&certificate->_tbsSigAlg.oid, &oidSha1Rsa)) {
+               signedDataLength = DER_SHA1_DIGEST_INFO_LEN;
+               crtn = sha1DigestInfo(&certificate->_tbs, signedData);
+       } else if(DEROidCompare(&certificate->_tbsSigAlg.oid, &oidMd5Rsa)) {
+               signedDataLength = DER_MD_DIGEST_INFO_LEN;
+               crtn = mdDigestInfo(WD_MD5, &certificate->_tbs, signedData);
+       } else if(DEROidCompare(&certificate->_tbsSigAlg.oid, &oidMd2Rsa)) {
+               signedDataLength = DER_MD_DIGEST_INFO_LEN;
+               crtn = mdDigestInfo(WD_MD2, &certificate->_tbs, signedData);
+       } else {
+               secdebug("verify", "unsupported algorithm");
+               return errSecUnsupportedAlgorithm;
+       }
+       if (crtn) {
+               secdebug("verify", "*DigestInfo returned: %d", crtn);
+        /* FIXME: Do proper error code translation. */
+               return errSecUnsupportedAlgorithm;
+       }
+
+       OSStatus status = SecKeyRawVerify(issuerKey, kSecPaddingPKCS1,
+               signedData, signedDataLength,
+               certificate->_signature.data, certificate->_signature.length);
+       if (status) {
+               secdebug("verify", "signature verify failed: %d", status);
+               return errSecNotSigner;
+       }
+
+    return errSecSuccess;
+}
+
+static OSStatus _SecCertificateSetParent(SecCertificateRefP certificate,
+       SecCertificateRefP issuer, bool signatureCheckOnly) {
+       check(issuer);
+    if (certificate->_parent) {
+               /* Setting a certificates issuer twice is only allowed if the new
+                  issuer is equal to the current one. */
+        return issuer && CFEqual(certificate->_parent, issuer);
+    }
+
+#if 0
+    OSStatus status = SecCertificateIsIssuedBy(certificate, issuer,
+        signatureCheckOnly);
+#else
+       OSStatus status = errSecSuccess;
+#endif
+    if (!status) {
+        if (CFEqual(certificate, issuer)) {
+            /* We don't retain ourselves cause that would be bad mojo,
+               however we do record that we are properly self signed. */
+            certificate->_isSelfSigned = kSecSelfSignedTrue;
+            secdebug("cert", "set self as parent");
+            return errSecSuccess;
+        }
+
+        CFRetain(issuer);
+        certificate->_parent = issuer;
+        certificate->_isSelfSigned = kSecSelfSignedFalse;
+    }
+
+    return status;
+}
+
+static bool SecCertificateIsSelfSigned(SecCertificateRefP certificate) {
+    if (certificate->_isSelfSigned == kSecSelfSignedUnknown) {
+        certificate->_isSelfSigned =
+            (SecCertificateIsIssuedBy(certificate, certificate, false) ?
+             kSecSelfSignedTrue : kSecSelfSignedFalse);
+    }
+
+    return certificate->_isSelfSigned == kSecSelfSignedTrue;
+}
+
+/* Return true iff we were able to set our own parent from one of the
+   certificates in other_certificates, return false otherwise.   If
+   signatureCheckOnly is true, we can skip the subject == issuer or
+   authorityKeyIdentifier tests. */
+static bool SecCertificateSetParentFrom(SecCertificateRefP certificate,
+    CFArrayRef other_certificates, bool signatureCheckOnly) {
+    CFIndex count = CFArrayGetCount(other_certificates);
+    CFIndex ix;
+    for (ix = 0; ix < count; ++ix) {
+        SecCertificateRefP candidate = (SecCertificateRefP)
+            CFArrayGetValueAtIndex(other_certificates, ix);
+        if (_SecCertificateSetParent(certificate, candidate,
+            signatureCheckOnly))
+            return true;
+    }
+    return false;
+}
+
+/* Lookup the parent of certificate in the keychain and set it. */
+static bool SecCertificateFindParent(SecCertificateRefP certificate) {
+    /* FIXME: Search for things other than just subject of our issuer if we
+       have a subjectID or authorityKeyIdentifier. */
+    CFDataRef normalizedIssuer =
+        SecCertificateGetNormalizedIssuerContent(certificate);
+    const void *keys[] = {
+        kSecClass,
+        kSecReturnRef,
+        kSecMatchLimit,
+        kSecAttrSubject
+    },
+    *values[] = {
+        kSecClassCertificate,
+        kCFBooleanTrue,
+        kSecMatchLimitAll,
+        normalizedIssuer
+    };
+    CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 4,
+        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+    CFTypeRef results;
+    OSStatus status = SecItemCopyMatching(query, &results);
+    CFRelease(query);
+    if (status) {
+               secdebug("cert", "SecCertificateFindParent: SecItemCopyMatching: %d",
+            status);
+        return false;
+    }
+    CFArrayRef certs = (CFArrayRef)results;
+    /* Since we already know the certificates we are providing as candidates
+       have been checked for subject matching, we can ask
+       SecCertificateSetParentFrom to skip everything except the signature
+       checks. */
+    bool result = SecCertificateSetParentFrom(certificate, certs, true);
+    CFRelease(certs);
+    return result;
+}
+
+OSStatus SecCertificateCompleteChain(SecCertificateRefP certificate,
+       CFArrayRef other_certificates) {
+    for (;;) {
+        if (certificate->_parent == NULL) {
+            if (SecCertificateIsSelfSigned(certificate))
+                return errSecSuccess;
+            if (!other_certificates ||
+                !SecCertificateSetParentFrom(certificate, other_certificates,\
+                    false)) {
+                if (!SecCertificateFindParent(certificate))
+                    return errSecIssuerNotFound;
+            }
+        }
+        certificate = certificate->_parent;
+    }
+}
+#endif
+
+static OSStatus appendIPAddressesFromGeneralNames(void *context,
+       SecCEGeneralNameType gnType, const DERItem *generalName) {
+       CFMutableArrayRef ipAddresses = (CFMutableArrayRef)context;
+       if (gnType == GNT_IPAddress) {
+               CFStringRef string = copyIPAddressContentDescription(
+                       kCFAllocatorDefault, generalName);
+               if (string) {
+                       CFArrayAppendValue(ipAddresses, string);
+                       CFRelease(string);
+               } else {
+                       return errSecInvalidCertificate;
+               }
+       }
+       return errSecSuccess;
+}
+
+CFArrayRef SecCertificateCopyIPAddresses(SecCertificateRefP certificate) {
+       /* These can only exist in the subject alt name. */
+       if (!certificate->_subjectAltName)
+               return NULL;
+
+       CFMutableArrayRef ipAddresses = CFArrayCreateMutable(kCFAllocatorDefault,
+               0, &kCFTypeArrayCallBacks);
+       OSStatus status = parseGeneralNames(&certificate->_subjectAltName->extnValue,
+               ipAddresses, appendIPAddressesFromGeneralNames);
+       if (status || CFArrayGetCount(ipAddresses) == 0) {
+               CFRelease(ipAddresses);
+               ipAddresses = NULL;
+       }
+       return ipAddresses;
+}
+
+static OSStatus appendDNSNamesFromGeneralNames(void *context, SecCEGeneralNameType gnType,
+       const DERItem *generalName) {
+       CFMutableArrayRef dnsNames = (CFMutableArrayRef)context;
+       if (gnType == GNT_DNSName) {
+               CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault,
+                       generalName->data, generalName->length,
+                       kCFStringEncodingUTF8, FALSE);
+               if (string) {
+                       CFArrayAppendValue(dnsNames, string);
+                       CFRelease(string);
+               } else {
+                       return errSecInvalidCertificate;
+               }
+       }
+       return errSecSuccess;
+}
+
+/* Return true if the passed in string matches the
+   Preferred name syntax from sections 2.3.1. in RFC 1035.
+   With the added check that we disallow empty dns names.
+   Also in order to support wildcard DNSNames we allow for the '*'
+   character anywhere in a dns component where we currently allow
+   a letter.
+
+       <domain> ::= <subdomain> | " "
+
+       <subdomain> ::= <label> | <subdomain> "." <label>
+
+       <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+
+       <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+
+       <let-dig-hyp> ::= <let-dig> | "-"
+
+       <let-dig> ::= <letter> | <digit>
+
+       <letter> ::= any one of the 52 alphabetic characters A through Z in
+       upper case and a through z in lower case
+
+       <digit> ::= any one of the ten digits 0 through 9
+   */
+static bool isDNSName(CFStringRef string) {
+       CFStringInlineBuffer buf;
+       CFIndex ix, labelLength = 0, length = CFStringGetLength(string);
+       /* From RFC 1035 2.3.4. Size limits:
+          labels          63 octets or less
+          names           255 octets or less */
+       require_quiet(length <= 255, notDNS);
+       CFRange range = { 0, length };
+       CFStringInitInlineBuffer(string, &buf, range);
+       enum {
+               kDNSStateInital,
+               kDNSStateAfterDot,
+               kDNSStateAfterAlpha,
+               kDNSStateAfterDigit,
+               kDNSStateAfterDash,
+       } state = kDNSStateInital;
+
+       bool nonAlpha = false;
+       for (ix = 0; ix < length; ++ix) {
+               UniChar ch = CFStringGetCharacterFromInlineBuffer(&buf, ix);
+               labelLength++;
+               if (ch == '.') {
+                       require_quiet(labelLength <= 64 &&
+                               (state == kDNSStateAfterAlpha || state == kDNSStateAfterDigit),
+                               notDNS);
+                       state = kDNSStateAfterDot;
+                       labelLength = 0;
+                       nonAlpha = false;
+               } else if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z')  ||
+                       ch == '*') {
+                       state = kDNSStateAfterAlpha;
+               } else if ('0' <= ch && ch <= '9') {
+#if 0
+                       /* The requirement for labels to start with a letter was
+                          dropped so we don't check this anymore.  */
+                       require_quiet(state == kDNSStateAfterAlpha ||
+                               state == kDNSStateAfterDigit ||
+                               state == kDNSStateAfterDash, notDNS);
+#endif
+                       state = kDNSStateAfterDigit;
+                       nonAlpha = true;
+               } else if (ch == '-') {
+                       require_quiet(state == kDNSStateAfterAlpha ||
+                               state == kDNSStateAfterDigit ||
+                               state == kDNSStateAfterDash, notDNS);
+                       state = kDNSStateAfterDash;
+                       nonAlpha = true;
+               } else {
+                       goto notDNS;
+               }
+       }
+
+       /* We don't allow a dns name to end in a dot, and we require the
+          final name component to only have alphanumeric chars.  */
+       require_quiet(!nonAlpha && labelLength <= 63 &&
+               (state == kDNSStateAfterAlpha || state == kDNSStateAfterDigit),
+               notDNS);
+
+       return true;
+notDNS:
+       return false;
+}
+
+static OSStatus appendDNSNamesFromX501Name(void *context, const DERItem *type,
+       const DERItem *value, CFIndex rdnIX) {
+       CFMutableArrayRef dnsNames = (CFMutableArrayRef)context;
+       if (DEROidCompare(type, &oidCommonName)) {
+               CFStringRef string = copyDERThingDescription(kCFAllocatorDefault,
+                       value, true);
+               if (string) {
+                       if (isDNSName(string)) {
+                               /* We found a common name that is formatted like a valid
+                                  dns name. */
+                               CFArrayAppendValue(dnsNames, string);
+                       }
+                       CFRelease(string);
+               } else {
+                       return errSecInvalidCertificate;
+               }
+       }
+       return errSecSuccess;
+}
+
+/* Not everything returned by this function is going to be a proper DNS name,
+   we also return the certificates common name entries from the subject,
+   assuming they look like dns names as specified in RFC 1035. */
+CFArrayRef SecCertificateCopyDNSNamesP(SecCertificateRefP certificate) {
+       /* These can exist in the subject alt name or in the subject. */
+       CFMutableArrayRef dnsNames = CFArrayCreateMutable(kCFAllocatorDefault,
+               0, &kCFTypeArrayCallBacks);
+       OSStatus status = errSecSuccess;
+       if (certificate->_subjectAltName) {
+               status = parseGeneralNames(&certificate->_subjectAltName->extnValue,
+                       dnsNames, appendDNSNamesFromGeneralNames);
+       }
+       /* RFC 2818 section 3.1.  Server Identity
+         [...]
+         If a subjectAltName extension of type dNSName is present, that MUST
+         be used as the identity. Otherwise, the (most specific) Common Name
+         field in the Subject field of the certificate MUST be used. Although
+         the use of the Common Name is existing practice, it is deprecated and
+         Certification Authorities are encouraged to use the dNSName instead.
+         [...]
+
+         This implies that if we found 1 or more DNSNames in the
+         subjectAltName, we should not use the Common Name of the subject as
+         a DNSName.
+       */
+       if (!status && CFArrayGetCount(dnsNames) == 0) {
+               status = parseX501NameContent(&certificate->_subject, dnsNames,
+                       appendDNSNamesFromX501Name);
+       }
+       if (status || CFArrayGetCount(dnsNames) == 0) {
+               CFRelease(dnsNames);
+               dnsNames = NULL;
+       }
+       return dnsNames;
+}
+
+static OSStatus appendRFC822NamesFromGeneralNames(void *context,
+       SecCEGeneralNameType gnType, const DERItem *generalName) {
+       CFMutableArrayRef dnsNames = (CFMutableArrayRef)context;
+       if (gnType == GNT_RFC822Name) {
+               CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault,
+                       generalName->data, generalName->length,
+                       kCFStringEncodingASCII, FALSE);
+               if (string) {
+                       CFArrayAppendValue(dnsNames, string);
+                       CFRelease(string);
+               } else {
+                       return errSecInvalidCertificate;
+               }
+       }
+       return errSecSuccess;
+}
+
+static OSStatus appendRFC822NamesFromX501Name(void *context, const DERItem *type,
+       const DERItem *value, CFIndex rdnIX) {
+       CFMutableArrayRef dnsNames = (CFMutableArrayRef)context;
+       if (DEROidCompare(type, &oidEmailAddress)) {
+               CFStringRef string = copyDERThingDescription(kCFAllocatorDefault,
+                       value, true);
+               if (string) {
+                       CFArrayAppendValue(dnsNames, string);
+                       CFRelease(string);
+               } else {
+                       return errSecInvalidCertificate;
+               }
+       }
+       return errSecSuccess;
+}
+
+CFArrayRef SecCertificateCopyRFC822Names(SecCertificateRefP certificate) {
+       /* These can exist in the subject alt name or in the subject. */
+       CFMutableArrayRef rfc822Names = CFArrayCreateMutable(kCFAllocatorDefault,
+               0, &kCFTypeArrayCallBacks);
+       OSStatus status = errSecSuccess;
+       if (certificate->_subjectAltName) {
+               status = parseGeneralNames(&certificate->_subjectAltName->extnValue,
+                       rfc822Names, appendRFC822NamesFromGeneralNames);
+       }
+       if (!status) {
+               status = parseX501NameContent(&certificate->_subject, rfc822Names,
+                       appendRFC822NamesFromX501Name);
+       }
+       if (status || CFArrayGetCount(rfc822Names) == 0) {
+               CFRelease(rfc822Names);
+               rfc822Names = NULL;
+       }
+       return rfc822Names;
+}
+
+static OSStatus appendCommonNamesFromX501Name(void *context,
+    const DERItem *type, const DERItem *value, CFIndex rdnIX) {
+       CFMutableArrayRef commonNames = (CFMutableArrayRef)context;
+       if (DEROidCompare(type, &oidCommonName)) {
+               CFStringRef string = copyDERThingDescription(kCFAllocatorDefault,
+                       value, true);
+               if (string) {
+            CFArrayAppendValue(commonNames, string);
+                       CFRelease(string);
+               } else {
+                       return errSecInvalidCertificate;
+               }
+       }
+       return errSecSuccess;
+}
+
+CFArrayRef SecCertificateCopyCommonNames(SecCertificateRefP certificate) {
+       CFMutableArrayRef commonNames = CFArrayCreateMutable(kCFAllocatorDefault,
+               0, &kCFTypeArrayCallBacks);
+       OSStatus status;
+    status = parseX501NameContent(&certificate->_subject, commonNames,
+        appendCommonNamesFromX501Name);
+       if (status || CFArrayGetCount(commonNames) == 0) {
+               CFRelease(commonNames);
+               commonNames = NULL;
+       }
+       return commonNames;
+}
+
+static OSStatus appendOrganizationFromX501Name(void *context,
+       const DERItem *type, const DERItem *value, CFIndex rdnIX) {
+       CFMutableArrayRef organization = (CFMutableArrayRef)context;
+       if (DEROidCompare(type, &oidOrganizationName)) {
+               CFStringRef string = copyDERThingDescription(kCFAllocatorDefault,
+                       value, true);
+               if (string) {
+                       CFArrayAppendValue(organization, string);
+                       CFRelease(string);
+               } else {
+                       return errSecInvalidCertificate;
+               }
+       }
+       return errSecSuccess;
+}
+
+CFArrayRef SecCertificateCopyOrganization(SecCertificateRefP certificate) {
+       CFMutableArrayRef organization = CFArrayCreateMutable(kCFAllocatorDefault,
+               0, &kCFTypeArrayCallBacks);
+       OSStatus status;
+       status = parseX501NameContent(&certificate->_subject, organization,
+        appendOrganizationFromX501Name);
+       if (status || CFArrayGetCount(organization) == 0) {
+               CFRelease(organization);
+               organization = NULL;
+       }
+       return organization;
+}
+
+const SecCEBasicConstraints *
+SecCertificateGetBasicConstraints(SecCertificateRefP certificate) {
+       if (certificate->_basicConstraints.present)
+               return &certificate->_basicConstraints;
+       else
+               return NULL;
+}
+
+const SecCEPolicyConstraints *
+SecCertificateGetPolicyConstraints(SecCertificateRefP certificate) {
+       if (certificate->_policyConstraints.present)
+               return &certificate->_policyConstraints;
+       else
+               return NULL;
+}
+
+CFDictionaryRef
+SecCertificateGetPolicyMappings(SecCertificateRefP certificate) {
+    return certificate->_policyMappings;
+}
+
+const SecCECertificatePolicies *
+SecCertificateGetCertificatePolicies(SecCertificateRefP certificate) {
+       if (certificate->_certificatePolicies.present)
+               return &certificate->_certificatePolicies;
+       else
+               return NULL;
+}
+
+uint32_t
+SecCertificateGetInhibitAnyPolicySkipCerts(SecCertificateRefP certificate) {
+    return certificate->_inhibitAnyPolicySkipCerts;
+}
+
+static OSStatus appendNTPrincipalNamesFromGeneralNames(void *context,
+       SecCEGeneralNameType gnType, const DERItem *generalName) {
+       CFMutableArrayRef ntPrincipalNames = (CFMutableArrayRef)context;
+       if (gnType == GNT_OtherName) {
+        DEROtherName on;
+        DERReturn drtn = DERParseSequenceContent(generalName,
+            DERNumOtherNameItemSpecs, DEROtherNameItemSpecs,
+            &on, sizeof(on));
+        require_noerr_quiet(drtn, badDER);
+        if (DEROidCompare(&on.typeIdentifier, &oidMSNTPrincipalName)) {
+            CFStringRef string;
+            require_quiet(string = copyDERThingDescription(kCFAllocatorDefault,
+                &on.value, true), badDER);
+            CFArrayAppendValue(ntPrincipalNames, string);
+            CFRelease(string);
+               }
+       }
+       return errSecSuccess;
+
+badDER:
+    return errSecInvalidCertificate;
+
+}
+
+CFArrayRef SecCertificateCopyNTPrincipalNames(SecCertificateRefP certificate) {
+       CFMutableArrayRef ntPrincipalNames = CFArrayCreateMutable(kCFAllocatorDefault,
+               0, &kCFTypeArrayCallBacks);
+       OSStatus status = errSecSuccess;
+       if (certificate->_subjectAltName) {
+               status = parseGeneralNames(&certificate->_subjectAltName->extnValue,
+                       ntPrincipalNames, appendNTPrincipalNamesFromGeneralNames);
+       }
+       if (status || CFArrayGetCount(ntPrincipalNames) == 0) {
+               CFRelease(ntPrincipalNames);
+               ntPrincipalNames = NULL;
+       }
+       return ntPrincipalNames;
+}
+
+static OSStatus appendToRFC2253String(void *context,
+       const DERItem *type, const DERItem *value, CFIndex rdnIX) {
+       CFMutableStringRef string = (CFMutableStringRef)context;
+    /*
+                    CN      commonName
+                    L       localityName
+                    ST      stateOrProvinceName
+                    O       organizationName
+                    OU      organizationalUnitName
+                    C       countryName
+                    STREET  streetAddress
+                    DC      domainComponent
+                    UID     userid
+    */
+    /* Prepend a + if this is not the first RDN in an RDN set.
+       Otherwise prepend a , if this is not the first RDN. */
+    if (rdnIX > 0)
+        CFStringAppend(string, CFSTR("+"));
+    else if (CFStringGetLength(string)) {
+        CFStringAppend(string, CFSTR(","));
+    }
+
+    CFStringRef label, oid = NULL;
+    /* @@@ Consider changing this to a dictionary lookup keyed by the
+       decimal representation. */
+#if 0 // represent all labels as oids
+       if (DEROidCompare(type, &oidCommonName)) {
+        label = CFSTR("CN");
+    } else if (DEROidCompare(type, &oidLocalityName)) {
+        label = CFSTR("L");
+    } else if (DEROidCompare(type, &oidStateOrProvinceName)) {
+        label = CFSTR("ST");
+    } else if (DEROidCompare(type, &oidOrganizationName)) {
+        label = CFSTR("O");
+    } else if (DEROidCompare(type, &oidOrganizationalUnitName)) {
+        label = CFSTR("OU");
+    } else if (DEROidCompare(type, &oidCountryName)) {
+        label = CFSTR("C");
+#if 0
+    } else if (DEROidCompare(type, &oidStreetAddress)) {
+        label = CFSTR("STREET");
+    } else if (DEROidCompare(type, &oidDomainComponent)) {
+        label = CFSTR("DC");
+    } else if (DEROidCompare(type, &oidUserID)) {
+        label = CFSTR("UID");
+#endif
+    } else
+#endif
+        {
+        label = oid = SecDERItemCopyOIDDecimalRepresentation(kCFAllocatorDefault, type);
+    }
+
+    CFStringAppend(string, label);
+    CFStringAppend(string, CFSTR("="));
+    CFStringRef raw = NULL;
+    if (!oid)
+        raw = copyDERThingDescription(kCFAllocatorDefault, value, true);
+
+    if (raw) {
+        /* Append raw to string while escaping:
+           a space or "#" character occurring at the beginning of the string
+           a space character occurring at the end of the string
+           one of the characters ",", "+", """, "\", "<", ">" or ";"
+        */
+        CFStringInlineBuffer buffer;
+        CFIndex ix, length = CFStringGetLength(raw);
+        CFRange range = { 0, length };
+        CFStringInitInlineBuffer(raw, &buffer, range);
+        for (ix = 0; ix < length; ++ix) {
+            UniChar ch = CFStringGetCharacterFromInlineBuffer(&buffer, ix);
+            if (ch < 0x20) {
+                CFStringAppendFormat(string, NULL, CFSTR("\\%02X"), ch);
+            } else if (ch == ',' || ch == '+' || ch == '"' || ch == '\\' ||
+                ch == '<' || ch == '>' || ch == ';' ||
+                (ch == ' ' && (ix == 0 || ix == length - 1)) ||
+                (ch == '#' && ix == 0)) {
+                UniChar chars[] = { '\\', ch };
+                CFStringAppendCharacters(string, chars, 2);
+            } else {
+                CFStringAppendCharacters(string, &ch, 1);
+            }
+        }
+        CFRelease(raw);
+    } else {
+        /* Append the value in hex. */
+        CFStringAppend(string, CFSTR("#"));
+        DERSize ix;
+        for (ix = 0; ix < value->length; ++ix)
+            CFStringAppendFormat(string, NULL, CFSTR("%02X"), value->data[ix]);
+    }
+
+    CFReleaseSafe(oid);
+
+       return errSecSuccess;
+}
+
+CFStringRef SecCertificateCopySubjectString(SecCertificateRefP certificate) {
+       CFMutableStringRef string = CFStringCreateMutable(kCFAllocatorDefault, 0);
+       OSStatus status = parseX501NameContent(&certificate->_subject, string, appendToRFC2253String);
+       if (status || CFStringGetLength(string) == 0) {
+               CFRelease(string);
+               string = NULL;
+       }
+       return string;
+}
+
+static OSStatus appendToCompanyNameString(void *context,
+       const DERItem *type, const DERItem *value, CFIndex rdnIX) {
+       CFMutableStringRef string = (CFMutableStringRef)context;
+    if (CFStringGetLength(string) != 0)
+        return errSecSuccess;
+
+    if (!DEROidCompare(type, &oidOrganizationName))
+        return errSecSuccess;
+
+    CFStringRef raw;
+    raw = copyDERThingDescription(kCFAllocatorDefault, value, true);
+    if (!raw)
+        return errSecSuccess;
+    CFStringAppend(string, raw);
+    CFRelease(raw);
+
+       return errSecSuccess;
+}
+
+CFStringRef SecCertificateCopyCompanyName(SecCertificateRefP certificate) {
+       CFMutableStringRef string = CFStringCreateMutable(kCFAllocatorDefault, 0);
+       OSStatus status = parseX501NameContent(&certificate->_subject, string,
+        appendToCompanyNameString);
+       if (status || CFStringGetLength(string) == 0) {
+               CFRelease(string);
+               string = NULL;
+       }
+       return string;
+}
+
+CFDataRef SecDERItemCopySequence(DERItem *content) {
+    DERSize seq_len_length = DERLengthOfLength(content->length);
+    size_t sequence_length = 1 + seq_len_length + content->length;
+       CFMutableDataRef sequence = CFDataCreateMutable(kCFAllocatorDefault,
+        sequence_length);
+       CFDataSetLength(sequence, sequence_length);
+       uint8_t *sequence_ptr = CFDataGetMutableBytePtr(sequence);
+    *sequence_ptr++ = 0x30; /* ASN1_CONSTR_SEQUENCE */
+    require_noerr_quiet(DEREncodeLength(content->length,
+        sequence_ptr, &seq_len_length), out);
+    sequence_ptr += seq_len_length;
+    memcpy(sequence_ptr, content->data, content->length);
+       return sequence;
+out:
+    CFReleaseSafe(sequence);
+    return NULL;
+}
+
+CFDataRef SecCertificateCopyIssuerSequenceP(
+    SecCertificateRefP certificate) {
+    return SecDERItemCopySequence(&certificate->_issuer);
+}
+
+CFDataRef SecCertificateCopySubjectSequenceP(
+    SecCertificateRefP certificate) {
+    return SecDERItemCopySequence(&certificate->_subject);
+}
+
+const DERAlgorithmId *SecCertificateGetPublicKeyAlgorithm(
+       SecCertificateRefP certificate) {
+       return &certificate->_algId;
+}
+
+const DERItem *SecCertificateGetPublicKeyData(SecCertificateRefP certificate) {
+       return &certificate->_pubKeyDER;
+}
+
+SecKeyRefP SecCertificateCopyPublicKeyP(SecCertificateRefP certificate) {
+       SecKeyRefP publicKey = NULL;
+#warning implementation empty
+#if 0
+       const DERAlgorithmId *algId =
+               SecCertificateGetPublicKeyAlgorithm(certificate);
+       const DERItem *keyData = SecCertificateGetPublicKeyData(certificate);
+       if (DEROidCompare(&algId->oid, &oidRsa)) {
+               publicKey = SecKeyCreateRSAPublicKey(kCFAllocatorDefault,
+                       keyData->data, keyData->length, kSecKeyEncodingPkcs1);
+    } else {
+               secdebug("cert", "Unsupported algorithm oid");
+       }
+#endif
+
+    return publicKey;
+}
+
+CFDataRef SecCertificateGetSHA1DigestP(SecCertificateRefP certificate) {
+    if (!certificate->_sha1Digest) {
+               certificate->_sha1Digest =
+                       SecSHA1DigestCreate(CFGetAllocator(certificate),
+                               certificate->_der.data, certificate->_der.length);
+       }
+
+       return certificate->_sha1Digest;
+}
+
+CFDataRef SecCertificateCopyIssuerSHA1Digest(SecCertificateRefP certificate) {
+    CFDataRef digest = NULL;
+    CFDataRef issuer = SecCertificateCopyIssuerSequenceP(certificate);
+    if (issuer) {
+        digest = SecSHA1DigestCreate(kCFAllocatorDefault,
+            CFDataGetBytePtr(issuer), CFDataGetLength(issuer));
+        CFRelease(issuer);
+    }
+       return digest;
+}
+
+CFDataRef SecCertificateCopyPublicKeySHA1Digest(SecCertificateRefP certificate) {
+    return SecSHA1DigestCreate(CFGetAllocator(certificate),
+        certificate->_pubKeyDER.data, certificate->_pubKeyDER.length);
+}
+
+CFDataRef SecCertificateCopyPublicKeySHA1DigestFromCertificateData(CFAllocatorRef allocator,
+       CFDataRef der_certificate)
+{
+       CFDataRef result = NULL;
+       SecCertificateRefP iosCertRef = SecCertificateCreateWithDataP(allocator, der_certificate);
+       if (NULL == iosCertRef)
+       {
+               return result;
+       }
+       
+       result = SecCertificateCopyPublicKeySHA1Digest(iosCertRef);
+       CFRelease(iosCertRef);
+       return result;  
+}
+
+CFDataRef SecCertificateGetAuthorityKeyID(SecCertificateRefP certificate) {
+       if (!certificate->_authorityKeyID &&
+               certificate->_authorityKeyIdentifier.length) {
+               certificate->_authorityKeyID = CFDataCreate(kCFAllocatorDefault,
+                       certificate->_authorityKeyIdentifier.data,
+                       certificate->_authorityKeyIdentifier.length);
+       }
+
+    return certificate->_authorityKeyID;
+}
+
+CFDataRef SecCertificateGetSubjectKeyID(SecCertificateRefP certificate) {
+       if (!certificate->_subjectKeyID &&
+               certificate->_subjectKeyIdentifier.length) {
+               certificate->_subjectKeyID = CFDataCreate(kCFAllocatorDefault,
+                       certificate->_subjectKeyIdentifier.data,
+                       certificate->_subjectKeyIdentifier.length);
+       }
+
+    return certificate->_subjectKeyID;
+}
+
+CFArrayRef SecCertificateGetCRLDistributionPoints(SecCertificateRefP certificate) {
+    return certificate->_crlDistributionPoints;
+}
+
+CFArrayRef SecCertificateGetOCSPResponders(SecCertificateRefP certificate) {
+    return certificate->_ocspResponders;
+}
+
+CFArrayRef SecCertificateGetCAIssuers(SecCertificateRefP certificate) {
+    return certificate->_caIssuers;
+}
+
+bool SecCertificateHasCriticalSubjectAltName(SecCertificateRefP certificate) {
+       return certificate->_subjectAltName &&
+               certificate->_subjectAltName->critical;
+}
+
+bool SecCertificateHasSubject(SecCertificateRefP certificate) {
+       /* Since the _subject field is the content of the subject and not the
+          whole thing, we can simply check for a 0 length subject here. */
+       return certificate->_subject.length != 0;
+}
+
+bool SecCertificateHasUnknownCriticalExtension(SecCertificateRefP certificate) {
+       return certificate->_foundUnknownCriticalExtension;
+}
+
+/* Private API functions. */
+void SecCertificateShow(SecCertificateRefP certificate) {
+       check(certificate);
+       fprintf(stderr, "SecCertificate instance %p:\n", certificate);
+               fprintf(stderr, "\n");
+}
+
+CFDictionaryRef SecCertificateCopyAttributeDictionary(
+       SecCertificateRefP certificate) {
+       CFAllocatorRef allocator = CFGetAllocator(certificate);
+       CFNumberRef certificateType, certificateEncoding;
+       CFStringRef label, alias;
+       CFDataRef skid, pubKeyDigest, certData;
+       CFDictionaryRef dict = NULL;
+
+       DICT_DECLARE(11);
+
+       /* CSSM_CERT_X_509v1, CSSM_CERT_X_509v2 or CSSM_CERT_X_509v3 */
+       SInt32 ctv = certificate->_version + 1;
+       SInt32 cev = 3; /* CSSM_CERT_ENCODING_DER */
+       certificateType = CFNumberCreate(allocator, kCFNumberSInt32Type, &ctv);
+       certificateEncoding = CFNumberCreate(allocator, kCFNumberSInt32Type, &cev);
+       certData = SecCertificateCopyDataP(certificate);
+       skid = SecCertificateGetSubjectKeyID(certificate);
+       pubKeyDigest = SecSHA1DigestCreate(allocator, certificate->_pubKeyDER.data,
+               certificate->_pubKeyDER.length);
+#if 0
+       /* We still need to figure out how to deal with multi valued attributes. */
+       alias = SecCertificateCopyRFC822Names(certificate);
+       label = SecCertificateCopySubjectSummary(certificate);
+#else
+       alias = NULL;
+       label = NULL;
+#endif
+
+       DICT_ADDPAIR(kSecClass, kSecClassCertificate);
+       DICT_ADDPAIR(kSecAttrCertificateType, certificateType);
+       DICT_ADDPAIR(kSecAttrCertificateEncoding, certificateEncoding);
+       if (label)
+               DICT_ADDPAIR(kSecAttrLabel, label);
+       if (alias)
+               DICT_ADDPAIR(kSecAttrAlias, alias);
+       DICT_ADDPAIR(kSecAttrSubject, certificate->_normalizedSubject);
+       DICT_ADDPAIR(kSecAttrIssuer, certificate->_normalizedIssuer);
+       DICT_ADDPAIR(kSecAttrSerialNumber, certificate->_serialNumber);
+       if (skid)
+               DICT_ADDPAIR(kSecAttrSubjectKeyID, skid);
+       DICT_ADDPAIR(kSecAttrPublicKeyHash, pubKeyDigest);
+       DICT_ADDPAIR(kSecValueData, certData);
+    dict = DICT_CREATE(allocator);
+
+       CFReleaseSafe(label);
+       CFReleaseSafe(pubKeyDigest);
+       CFReleaseSafe(certData);
+       CFReleaseSafe(certificateEncoding);
+       CFReleaseSafe(certificateType);
+
+       return dict;
+}
+
+SecCertificateRefP SecCertificateCreateFromAttributeDictionary(
+       CFDictionaryRef refAttributes) {
+       /* @@@ Support having an allocator in refAttributes. */
+       CFAllocatorRef allocator = NULL;
+       CFDataRef data = CFDictionaryGetValue(refAttributes, kSecValueData);
+       return SecCertificateCreateWithDataP(allocator, data);
+}
+
+bool SecCertificateIsSelfSignedCA(SecCertificateRefP certificate) {
+    bool result = false;
+    SecKeyRefP publicKey;
+    require(publicKey = SecCertificateCopyPublicKeyP(certificate), out);
+    CFDataRef normalizedIssuer =
+        SecCertificateGetNormalizedIssuerContent(certificate);
+    CFDataRef normalizedSubject =
+        SecCertificateGetNormalizedSubjectContent(certificate);
+    require_quiet(normalizedIssuer && normalizedSubject &&
+        CFEqual(normalizedIssuer, normalizedSubject), out);
+
+    CFDataRef authorityKeyID = SecCertificateGetAuthorityKeyID(certificate);
+    CFDataRef subjectKeyID = SecCertificateGetSubjectKeyID(certificate);
+    if (authorityKeyID) {
+        require_quiet(subjectKeyID && CFEqual(subjectKeyID, authorityKeyID), out);
+    }
+
+    if (SecCertificateVersion(certificate) >= 3) {
+        const SecCEBasicConstraints *basicConstraints = SecCertificateGetBasicConstraints(certificate);
+        require_quiet(basicConstraints && basicConstraints->isCA, out);
+        require_noerr_quiet(SecCertificateIsSignedByP(certificate, publicKey), out);
+    }
+
+    result = true;
+out:
+    CFReleaseSafe(publicKey);
+    return result;
+}
+
+SecKeyUsage SecCertificateGetKeyUsage(SecCertificateRefP certificate) {
+    return certificate->_keyUsage;
+}
+
+CFArrayRef SecCertificateCopyExtendedKeyUsage(SecCertificateRefP certificate)
+{
+    CFMutableArrayRef extended_key_usage_oids =
+        CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+    require_quiet(extended_key_usage_oids, out);
+    int ix;
+    for (ix = 0; ix < certificate->_extensionCount; ++ix) {
+        const SecCertificateExtension *extn = &certificate->_extensions[ix];
+        if (extn->extnID.length == oidExtendedKeyUsage.length &&
+            !memcmp(extn->extnID.data, oidExtendedKeyUsage.data, extn->extnID.length)) {
+            DERTag tag;
+            DERSequence derSeq;
+            DERReturn drtn = DERDecodeSeqInit(&extn->extnValue, &tag, &derSeq);
+            require_noerr_quiet(drtn, out);
+            require_quiet(tag == ASN1_CONSTR_SEQUENCE, out);
+            DERDecodedInfo currDecoded;
+
+            while ((drtn = DERDecodeSeqNext(&derSeq, &currDecoded)) == DR_Success) {
+                require_quiet(currDecoded.tag == ASN1_OBJECT_ID, out);
+                CFDataRef oid = CFDataCreate(kCFAllocatorDefault,
+                    currDecoded.content.data, currDecoded.content.length);
+                if (oid) {
+                    CFArrayAppendValue(extended_key_usage_oids, oid);
+                    CFRelease(oid);
+                }
+            }
+            require_quiet(drtn == DR_EndOfSequence, out);
+            return extended_key_usage_oids;
+        }
+    }
+out:
+    CFReleaseSafe(extended_key_usage_oids);
+    return NULL;
+}
+
+SecCertificateRefP SecCertificateCreateWithPEM(CFAllocatorRef allocator,
+       CFDataRef pem_certificate)
+{
+    static const char begin_cert[] = "-----BEGIN CERTIFICATE-----\n";
+    static const char end_cert[] = "-----END CERTIFICATE-----\n";
+    uint8_t *base64_data = NULL;
+    SecCertificateRefP cert = NULL;
+    const unsigned char *data = CFDataGetBytePtr(pem_certificate);
+    //const size_t length = CFDataGetLength(pem_certificate);
+    char *begin = strstr((const char *)data, begin_cert);
+    char *end = strstr((const char *)data, end_cert);
+    if (!begin || !end)
+        return NULL;
+    begin += sizeof(begin_cert) - 1;
+    size_t base64_length = SecBase64Decode(begin, end - begin, NULL, 0);
+    if (base64_length) {
+        require_quiet(base64_data = calloc(1, base64_length), out);
+        require_quiet(base64_length = SecBase64Decode(begin, end - begin, base64_data, base64_length), out);
+        cert = SecCertificateCreateWithBytesP(kCFAllocatorDefault, base64_data, base64_length);
+        free(base64_data);
+    }
+out:
+    return cert;
+}
+
+static void convertCertificateToCFData(const void *value, void *context) {
+    CFMutableArrayRef result = (CFMutableArrayRef)context;
+    SecCertificateRefP certificate = (SecCertificateRefP)value;
+    CFDataRef data = SecCertificateCopyDataP(certificate);
+    CFArrayAppendValue(result, data);
+    CFRelease(data);
+}
+
+/* Return an array of CFDataRefs from an array of SecCertificateRefPs. */
+CFArrayRef SecCertificateArrayCopyDataArray(CFArrayRef certificates) {
+    CFIndex count = CFArrayGetCount(certificates);
+    CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, count, &kCFTypeArrayCallBacks);
+    CFRange all_certs = { 0, count };
+    CFArrayApplyFunction(certificates, all_certs, convertCertificateToCFData, result);
+    return result;
+}
+
+/* AUDIT[securityd](done):
+   value (ok) is an element in a caller provided array.
+ */
+static void convertCFDataToCertificate(const void *value, void *context) {
+    CFMutableArrayRef result = (CFMutableArrayRef)context;
+    CFDataRef data = (CFDataRef)value;
+    if (data && CFGetTypeID(data) == CFDataGetTypeID()) {
+        SecCertificateRefP certificate = SecCertificateCreateWithDataP(kCFAllocatorDefault, data);
+        if (certificate) {
+            CFArrayAppendValue(result, certificate);
+            CFRelease(certificate);
+        }
+    }
+}
+
+/* AUDIT[securityd](done):
+   certificates (ok) is a caller provided array, only its cf type has
+   been checked.
+ */
+CFArrayRef SecCertificateDataArrayCopyArray(CFArrayRef certificates) {
+    CFIndex count = CFArrayGetCount(certificates);
+    CFMutableArrayRef result = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks);
+    CFRange all_certs = { 0, count };
+    CFArrayApplyFunction(certificates, all_certs, convertCFDataToCertificate, result);
+    return result;
+}