--- /dev/null
+/*
+ * Copyright (c) 2000-2014 Apple Inc. All Rights Reserved.
+ *
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ *
+ * This 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.
+ */
+
+
+/*
+ policies.cpp - TP module policy implementation
+*/
+
+#include <Security/cssmtype.h>
+#include <Security/cssmapi.h>
+#include "tpPolicies.h"
+#include <Security/cssmerr.h>
+#include "tpdebugging.h"
+#include "certGroupUtils.h"
+#include <Security/x509defs.h>
+#include <Security/oidsalg.h>
+#include <Security/oidsattr.h>
+#include <Security/oidscert.h>
+#include <Security/certextensions.h>
+#include <Security/cssmapple.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecCertificatePriv.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <CoreFoundation/CFString.h>
+#include <CommonCrypto/CommonDigest.h>
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-const-variable"
+
+/*
+ * Our private per-extension info. One of these per (understood) extension per
+ * cert.
+ */
+typedef struct {
+ CSSM_BOOL present;
+ CSSM_BOOL critical;
+ CE_Data *extnData; // mallocd by CL
+ CSSM_DATA *valToFree; // the data we pass to freeField()
+} iSignExtenInfo;
+
+/*
+ * Struct to keep track of info pertinent to one cert.
+ */
+typedef struct {
+
+ /* extensions we're interested in */
+ iSignExtenInfo authorityId;
+ iSignExtenInfo subjectId;
+ iSignExtenInfo keyUsage;
+ iSignExtenInfo extendKeyUsage;
+ iSignExtenInfo basicConstraints;
+ iSignExtenInfo netscapeCertType;
+ iSignExtenInfo subjectAltName;
+ iSignExtenInfo certPolicies;
+ iSignExtenInfo qualCertStatements;
+ iSignExtenInfo nameConstraints;
+ iSignExtenInfo policyMappings;
+ iSignExtenInfo policyConstraints;
+ iSignExtenInfo inhibitAnyPolicy;
+ iSignExtenInfo certificatePolicies;
+
+ /* flag indicating presence of CSSMOID_APPLE_EXTENSION_PASSBOOK_SIGNING */
+ CSSM_BOOL foundPassbookSigning;
+ /* flag indicating presence of CSSMOID_APPLE_EXTENSION_SYSINT2_INTERMEDIATE */
+ CSSM_BOOL foundAppleSysInt2Marker;
+ /* flag indicating presence of CSSMOID_APPLE_EXTENSION_SERVER_AUTHENTICATION */
+ CSSM_BOOL foundAppleServerAuthMarker;
+ /* flag indicating presence of CSSMOID_APPLE_EXTENSION_ESCROW_SERVICE */
+ CSSM_BOOL foundEscrowServiceMarker;
+ /* flag indicating presence of a critical extension we don't understand */
+ CSSM_BOOL foundUnknownCritical;
+ /* flag indicating that this certificate was signed with a known-broken algorithm */
+ CSSM_BOOL untrustedSigAlg;
+
+} iSignCertInfo;
+
+/*
+ * 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[] =
+{
+ (const CSSM_OID_PTR)&CSSMOID_OID_QCS_SYNTAX_V1,
+ (const CSSM_OID_PTR)&CSSMOID_OID_QCS_SYNTAX_V2,
+ (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 CSSM_RETURN tp_verifyMacAppStoreReceiptOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, const iSignCertInfo *certInfo);
+
+static CSSM_RETURN tp_verifyPassbookSigningOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, const iSignCertInfo *certInfo);
+
+bool certificatePoliciesContainsOID(const CE_CertPolicies *certPolicies, const CSSM_OID *oidToFind);
+
+#define kSecPolicySHA1Size 20
+static const UInt8 kAppleCASHA1[kSecPolicySHA1Size] = {
+ 0x61, 0x1E, 0x5B, 0x66, 0x2C, 0x59, 0x3A, 0x08, 0xFF, 0x58,
+ 0xD1, 0x4A, 0xE2, 0x24, 0x52, 0xD1, 0x98, 0xDF, 0x6C, 0x60
+};
+
+static const UInt8 kMobileRootSHA1[kSecPolicySHA1Size] = {
+ 0xBD, 0xD6, 0x7C, 0x34, 0xD0, 0xB2, 0x68, 0x5D, 0x31, 0x82,
+ 0xCD, 0x32, 0xCB, 0xF4, 0x54, 0x69, 0xA1, 0xF1, 0x6B, 0x09
+};
+
+static const UInt8 kAppleCorpCASHA1[kSecPolicySHA1Size] = {
+ 0xA1, 0x71, 0xDC, 0xDE, 0xE0, 0x8B, 0x1B, 0xAE, 0x30, 0xA1,
+ 0xAE, 0x6C, 0xC6, 0xD4, 0x03, 0x3B, 0xFD, 0xEF, 0x91, 0xCE
+};
+
+/*
+ * Certificate policy OIDs
+ */
+
+/* 2.5.29.32.0 */
+#define ANY_POLICY_OID OID_EXTENSION, 0x32, 0x00
+#define ANY_POLICY_OID_LEN OID_EXTENSION_LENGTH + 2
+
+/* 2.5.29.54 */
+#define INHIBIT_ANY_POLICY_OID OID_EXTENSION, 0x54
+#define INHIBIT_ANY_POLICY_OID_LEN OID_EXTENSION_LENGTH + 1
+
+/* 2.16.840.1.101.2.1 */
+#define US_DOD_INFOSEC 0x60, 0x86, 0x48, 0x01, 0x65, 0x02, 0x01
+#define US_DOD_INFOSEC_LEN 7
+
+/* 2.16.840.1.101.2.1.11.10 */
+#define PIV_AUTH_OID US_DOD_INFOSEC, 0x0B, 0x0A
+#define PIV_AUTH_OID_LEN US_DOD_INFOSEC_LEN + 2
+
+/* 2.16.840.1.101.2.1.11.20 */
+#define PIV_AUTH_2048_OID US_DOD_INFOSEC, 0x0B, 0x14
+#define PIV_AUTH_2048_OID_LEN US_DOD_INFOSEC_LEN + 2
+
+static const uint8 OID_ANY_POLICY[] = {ANY_POLICY_OID};
+const CSSM_OID CSSMOID_ANY_POLICY = {ANY_POLICY_OID_LEN, (uint8 *)OID_ANY_POLICY};
+static const uint8 OID_INHIBIT_ANY_POLICY[] = {INHIBIT_ANY_POLICY_OID};
+const CSSM_OID CSSMOID_INHIBIT_ANY_POLICY = {INHIBIT_ANY_POLICY_OID_LEN, (uint8 *)OID_INHIBIT_ANY_POLICY};
+static const uint8 OID_PIV_AUTH[] = {PIV_AUTH_OID};
+const CSSM_OID CSSMOID_PIV_AUTH = {PIV_AUTH_OID_LEN, (uint8 *)OID_PIV_AUTH};
+static const uint8 OID_PIV_AUTH_2048[] = {PIV_AUTH_2048_OID};
+const CSSM_OID CSSMOID_PIV_AUTH_2048 = {PIV_AUTH_2048_OID_LEN, (uint8 *)OID_PIV_AUTH_2048};
+
+static CSSM_RETURN tp_verifyAppleIDSharingOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, // optional Common Name
+ const iSignCertInfo *certInfo);
+/*
+ * Setup a single iSignExtenInfo. Called once per known extension
+ * per cert.
+ */
+static CSSM_RETURN tpSetupExtension(
+ Allocator &alloc,
+ CSSM_DATA *extnData,
+ iSignExtenInfo *extnInfo) // which component of certInfo
+{
+ if(extnData->Length != sizeof(CSSM_X509_EXTENSION)) {
+ tpPolicyError("tpSetupExtension: malformed CSSM_FIELD");
+ return CSSMERR_TP_UNKNOWN_FORMAT;
+ }
+ CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)extnData->Data;
+ extnInfo->present = CSSM_TRUE;
+ extnInfo->critical = cssmExt->critical;
+ extnInfo->extnData = (CE_Data *)cssmExt->value.parsedValue;
+ extnInfo->valToFree = extnData;
+ return CSSM_OK;
+}
+
+/*
+ * Fetch a known extension, set up associated iSignExtenInfo if present.
+ */
+static CSSM_RETURN iSignFetchExtension(
+ Allocator &alloc,
+ TPCertInfo *tpCert,
+ const CSSM_OID *fieldOid, // which extension to fetch
+ iSignExtenInfo *extnInfo) // where the info goes
+{
+ CSSM_DATA_PTR fieldValue; // mallocd by CL
+ CSSM_RETURN crtn;
+
+ crtn = tpCert->fetchField(fieldOid, &fieldValue);
+ switch(crtn) {
+ case CSSM_OK:
+ break;
+ case CSSMERR_CL_NO_FIELD_VALUES:
+ /* field not present, OK */
+ return CSSM_OK;
+ default:
+ return crtn;
+ }
+ return tpSetupExtension(alloc,
+ fieldValue,
+ extnInfo);
+}
+
+/*
+ * This function performs a check of an extension marked 'critical'
+ * to see if it's one we understand. Returns CSSM_OK if the extension
+ * is acceptable, CSSMERR_APPLETP_UNKNOWN_CRITICAL_EXTEN if unknown.
+ */
+static CSSM_RETURN iSignVerifyCriticalExtension(
+ CSSM_X509_EXTENSION *cssmExt)
+{
+ if (!cssmExt || !cssmExt->extnId.Data)
+ return CSSMERR_TP_INVALID_FIELD_POINTER;
+
+ if (!cssmExt->critical)
+ return CSSM_OK;
+
+ /* FIXME: remove when policyConstraints NSS template is fixed */
+ if (!memcmp(cssmExt->extnId.Data, CSSMOID_PolicyConstraints.Data, CSSMOID_PolicyConstraints.Length))
+ return CSSM_OK;
+
+ if (cssmExt->extnId.Length > APPLE_EXTENSION_OID_LENGTH &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION.Data, APPLE_EXTENSION_OID_LENGTH)) {
+ /* This extension's OID is under the appleCertificateExtensions arc */
+ return CSSM_OK;
+
+ }
+ return CSSMERR_APPLETP_UNKNOWN_CRITICAL_EXTEN;
+}
+
+/*
+ * Search for all unknown extensions. If we find one which is flagged critical,
+ * flag certInfo->foundUnknownCritical. Only returns error on gross errors.
+ */
+static CSSM_RETURN iSignSearchUnknownExtensions(
+ TPCertInfo *tpCert,
+ iSignCertInfo *certInfo)
+{
+ CSSM_RETURN crtn;
+ CSSM_DATA_PTR fieldValue = NULL;
+ CSSM_HANDLE searchHand = CSSM_INVALID_HANDLE;
+ uint32 numFields = 0;
+
+ certInfo->foundPassbookSigning = CSSM_FALSE;
+ certInfo->foundAppleSysInt2Marker = CSSM_FALSE;
+ certInfo->foundEscrowServiceMarker = CSSM_FALSE;
+
+ crtn = CSSM_CL_CertGetFirstCachedFieldValue(tpCert->clHand(),
+ tpCert->cacheHand(),
+ &CSSMOID_X509V3CertificateExtensionCStruct,
+ &searchHand,
+ &numFields,
+ &fieldValue);
+ switch(crtn) {
+ case CSSM_OK:
+ /* found one, proceed */
+ break;
+ case CSSMERR_CL_NO_FIELD_VALUES:
+ /* no unknown extensions present, OK */
+ return CSSM_OK;
+ default:
+ return crtn;
+ }
+
+ if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
+ tpPolicyError("iSignSearchUnknownExtensions: malformed CSSM_FIELD");
+ return CSSMERR_TP_UNKNOWN_FORMAT;
+ }
+
+ CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
+ if (cssmExt->extnId.Length == APPLE_EXTENSION_CODE_SIGNING_LENGTH+1 &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION_PASSBOOK_SIGNING.Data,
+ APPLE_EXTENSION_CODE_SIGNING_LENGTH+1)) {
+ /* this is the Passbook Signing extension */
+ certInfo->foundPassbookSigning = CSSM_TRUE;
+ }
+ if (cssmExt->extnId.Length == APPLE_EXTENSION_SYSINT2_INTERMEDIATE_LENGTH &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION_SYSINT2_INTERMEDIATE.Data,
+ APPLE_EXTENSION_SYSINT2_INTERMEDIATE_LENGTH)) {
+ /* this is the Apple System Integration 2 Signing extension */
+ certInfo->foundAppleSysInt2Marker = CSSM_TRUE;
+ }
+ if (cssmExt->extnId.Length == APPLE_EXTENSION_ESCROW_SERVICE_LENGTH &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION_ESCROW_SERVICE.Data,
+ APPLE_EXTENSION_ESCROW_SERVICE_LENGTH)) {
+ /* this is the Escrow Service Signing extension */
+ certInfo->foundEscrowServiceMarker = CSSM_TRUE;
+ }
+
+ if(iSignVerifyCriticalExtension(cssmExt) != CSSM_OK) {
+ /* BRRZAPP! Found an unknown extension marked critical */
+ certInfo->foundUnknownCritical = CSSM_TRUE;
+ goto fini;
+ }
+ CSSM_CL_FreeFieldValue(tpCert->clHand(),
+ &CSSMOID_X509V3CertificateExtensionCStruct,
+ fieldValue);
+ fieldValue = NULL;
+
+ /* process remaining unknown extensions */
+ for(unsigned i=1; i<numFields; i++) {
+ crtn = CSSM_CL_CertGetNextCachedFieldValue(tpCert->clHand(),
+ searchHand,
+ &fieldValue);
+ if(crtn) {
+ /* should never happen */
+ tpPolicyError("searchUnknownExtensions: GetNextCachedFieldValue"
+ "error");
+ break;
+ }
+ if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
+ tpPolicyError("iSignSearchUnknownExtensions: "
+ "malformed CSSM_FIELD");
+ crtn = CSSMERR_TP_UNKNOWN_FORMAT;
+ break;
+ }
+
+ CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
+ if (cssmExt->extnId.Length == APPLE_EXTENSION_CODE_SIGNING_LENGTH+1 &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION_PASSBOOK_SIGNING.Data,
+ APPLE_EXTENSION_CODE_SIGNING_LENGTH+1)) {
+ /* this is the Passbook Signing extension */
+ certInfo->foundPassbookSigning = CSSM_TRUE;
+ }
+ if (cssmExt->extnId.Length == APPLE_EXTENSION_SYSINT2_INTERMEDIATE_LENGTH &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION_SYSINT2_INTERMEDIATE.Data,
+ APPLE_EXTENSION_SYSINT2_INTERMEDIATE_LENGTH)) {
+ /* this is the Apple System Integration 2 Signing extension */
+ certInfo->foundAppleSysInt2Marker = CSSM_TRUE;
+ }
+ if (cssmExt->extnId.Length == APPLE_EXTENSION_SERVER_AUTHENTICATION_LENGTH &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION_SERVER_AUTHENTICATION.Data,
+ APPLE_EXTENSION_SERVER_AUTHENTICATION_LENGTH)) {
+ /* this is the Apple Server Authentication extension */
+ certInfo->foundAppleServerAuthMarker = CSSM_TRUE;
+ }
+ if (cssmExt->extnId.Length == APPLE_EXTENSION_ESCROW_SERVICE_LENGTH &&
+ !memcmp(cssmExt->extnId.Data, CSSMOID_APPLE_EXTENSION_ESCROW_SERVICE.Data,
+ APPLE_EXTENSION_ESCROW_SERVICE_LENGTH)) {
+ /* this is the Escrow Service Signing extension */
+ certInfo->foundEscrowServiceMarker = CSSM_TRUE;
+ }
+
+ if(iSignVerifyCriticalExtension(cssmExt) != CSSM_OK) {
+ /* BRRZAPP! Found an unknown extension marked critical */
+ certInfo->foundUnknownCritical = CSSM_TRUE;
+ break;
+ }
+ CSSM_CL_FreeFieldValue(tpCert->clHand(),
+ &CSSMOID_X509V3CertificateExtensionCStruct,
+ fieldValue);
+ fieldValue = NULL;
+ } /* for additional fields */
+
+fini:
+ if(fieldValue) {
+ CSSM_CL_FreeFieldValue(tpCert->clHand(),
+ &CSSMOID_X509V3CertificateExtensionCStruct,
+ fieldValue);
+ }
+ if(searchHand != CSSM_INVALID_HANDLE) {
+ CSSM_CL_CertAbortQuery(tpCert->clHand(), searchHand);
+ }
+ return crtn;
+}
+
+/*
+ * Check the signature algorithm. If it's known to be untrusted,
+ * flag certInfo->untrustedSigAlg.
+ */
+static void iSignCheckSignatureAlgorithm(
+ TPCertInfo *tpCert,
+ iSignCertInfo *certInfo)
+{
+ CSSM_X509_ALGORITHM_IDENTIFIER *algId = NULL;
+ CSSM_DATA_PTR valueToFree = NULL;
+
+ algId = tp_CertGetAlgId(tpCert, &valueToFree);
+ if(!algId ||
+ tpCompareCssmData(&algId->algorithm, &CSSMOID_MD2) ||
+ tpCompareCssmData(&algId->algorithm, &CSSMOID_MD2WithRSA) ||
+ tpCompareCssmData(&algId->algorithm, &CSSMOID_MD5) ||
+ tpCompareCssmData(&algId->algorithm, &CSSMOID_MD5WithRSA) ) {
+ certInfo->untrustedSigAlg = CSSM_TRUE;
+ } else {
+ certInfo->untrustedSigAlg = CSSM_FALSE;
+ }
+
+ if (valueToFree) {
+ tp_CertFreeAlgId(tpCert->clHand(), valueToFree);
+ }
+}
+
+/*
+ * Given a TPCertInfo, fetch the associated iSignCertInfo fields.
+ * Returns CSSM_FAIL on error.
+ */
+static CSSM_RETURN iSignGetCertInfo(
+ Allocator &alloc,
+ TPCertInfo *tpCert,
+ iSignCertInfo *certInfo)
+{
+ CSSM_RETURN crtn;
+
+ /* first grind thru the extensions we're interested in */
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_AuthorityKeyIdentifier,
+ &certInfo->authorityId);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_SubjectKeyIdentifier,
+ &certInfo->subjectId);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_KeyUsage,
+ &certInfo->keyUsage);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_ExtendedKeyUsage,
+ &certInfo->extendKeyUsage);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_BasicConstraints,
+ &certInfo->basicConstraints);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_NetscapeCertType,
+ &certInfo->netscapeCertType);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_SubjectAltName,
+ &certInfo->subjectAltName);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_CertificatePolicies,
+ &certInfo->certPolicies);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_QC_Statements,
+ &certInfo->qualCertStatements);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_NameConstraints,
+ &certInfo->nameConstraints);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_PolicyMappings,
+ &certInfo->policyMappings);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_PolicyConstraints,
+ &certInfo->policyConstraints);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_InhibitAnyPolicy,
+ &certInfo->inhibitAnyPolicy);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = iSignFetchExtension(alloc,
+ tpCert,
+ &CSSMOID_CertificatePolicies,
+ &certInfo->certificatePolicies);
+ if(crtn) {
+ return crtn;
+ }
+
+ /* check signature algorithm field */
+ iSignCheckSignatureAlgorithm(tpCert, certInfo);
+
+ /* now look for extensions we don't understand - the only thing we're interested
+ * in is the critical flag. */
+ return iSignSearchUnknownExtensions(tpCert, certInfo);
+}
+
+/*
+ * Free (via CL) the fields allocated in iSignGetCertInfo().
+ */
+static void iSignFreeCertInfo(
+ CSSM_CL_HANDLE clHand,
+ iSignCertInfo *certInfo)
+{
+ if(certInfo->authorityId.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_AuthorityKeyIdentifier,
+ certInfo->authorityId.valToFree);
+ }
+ if(certInfo->subjectId.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_SubjectKeyIdentifier,
+ certInfo->subjectId.valToFree);
+ }
+ if(certInfo->keyUsage.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_KeyUsage,
+ certInfo->keyUsage.valToFree);
+ }
+ if(certInfo->extendKeyUsage.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_ExtendedKeyUsage,
+ certInfo->extendKeyUsage.valToFree);
+ }
+ if(certInfo->basicConstraints.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_BasicConstraints,
+ certInfo->basicConstraints.valToFree);
+ }
+ if(certInfo->netscapeCertType.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_NetscapeCertType,
+ certInfo->netscapeCertType.valToFree);
+ }
+ if(certInfo->subjectAltName.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_SubjectAltName,
+ certInfo->subjectAltName.valToFree);
+ }
+ if(certInfo->certPolicies.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_CertificatePolicies,
+ certInfo->certPolicies.valToFree);
+ }
+// if(certInfo->policyConstraints.present) {
+// CSSM_CL_FreeFieldValue(clHand, &CSSMOID_PolicyConstraints,
+// certInfo->policyConstraints.valToFree);
+// }
+ if(certInfo->qualCertStatements.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_QC_Statements,
+ certInfo->qualCertStatements.valToFree);
+ }
+ if(certInfo->certificatePolicies.present) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_CertificatePolicies,
+ certInfo->certificatePolicies.valToFree);
+ }
+}
+
+/*
+ * See if cert's Subject.{commonName,EmailAddress} matches caller-specified
+ * string. Returns CSSM_TRUE if match, else returns CSSM_FALSE.
+ * Also indicates whether *any* of the specified fields were found, regardless
+ * of match state.
+ */
+typedef enum {
+ SN_CommonName, // CSSMOID_CommonName, host name format
+ SN_Email, // CSSMOID_EmailAddress
+ SN_UserID, // CSSMOID_UserID
+ SN_OrgUnit // CSSMOID_OrganizationalUnitName
+} SubjSubjNameSearchType;
+
+static CSSM_BOOL tpCompareSubjectName(
+ TPCertInfo &cert,
+ SubjSubjNameSearchType searchType,
+ bool normalizeAll, // for SN_Email case: lower-case all of
+ // the cert's value, not just the portion
+ // after the '@'
+ const char *callerStr, // already tpToLower'd
+ uint32 callerStrLen,
+ bool &fieldFound)
+{
+ char *certName = NULL; // from cert's subject name
+ uint32 certNameLen = 0;
+ CSSM_DATA_PTR subjNameData = NULL;
+ CSSM_RETURN crtn;
+ CSSM_BOOL ourRtn = CSSM_FALSE;
+ const CSSM_OID *oidSrch;
+
+ const char x500_userid_oid[] = { 0x09,0x92,0x26,0x89,0x93,0xF2,0x2C,0x64,0x01,0x01 };
+ CSSM_OID X500_UserID_OID = { sizeof(x500_userid_oid), (uint8*)x500_userid_oid };
+
+ fieldFound = false;
+ switch(searchType) {
+ case SN_CommonName:
+ oidSrch = &CSSMOID_CommonName;
+ break;
+ case SN_Email:
+ oidSrch = &CSSMOID_EmailAddress;
+ break;
+ case SN_UserID:
+ oidSrch = &X500_UserID_OID;
+ break;
+ case SN_OrgUnit:
+ oidSrch = &CSSMOID_OrganizationalUnitName;
+ break;
+ default:
+ assert(0);
+ return CSSM_FALSE;
+ }
+ crtn = cert.fetchField(&CSSMOID_X509V1SubjectNameCStruct, &subjNameData);
+ if(crtn) {
+ /* should never happen, we shouldn't be here if there is no subject */
+ tpPolicyError("tpCompareSubjectName: error retrieving subject name");
+ return CSSM_FALSE;
+ }
+ CSSM_X509_NAME_PTR x509name = (CSSM_X509_NAME_PTR)subjNameData->Data;
+ if((x509name == NULL) || (subjNameData->Length != sizeof(CSSM_X509_NAME))) {
+ tpPolicyError("tpCompareSubjectName: malformed CSSM_X509_NAME");
+ cert.freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
+ return CSSM_FALSE;
+ }
+
+ /* Now grunge thru the X509 name looking for a common name */
+ CSSM_X509_TYPE_VALUE_PAIR *ptvp;
+ CSSM_X509_RDN_PTR rdnp;
+ unsigned rdnDex;
+ unsigned pairDex;
+
+ for(rdnDex=0; rdnDex<x509name->numberOfRDNs; rdnDex++) {
+ rdnp = &x509name->RelativeDistinguishedName[rdnDex];
+ for(pairDex=0; pairDex<rdnp->numberOfPairs; pairDex++) {
+ ptvp = &rdnp->AttributeTypeAndValue[pairDex];
+ if(tpCompareOids(&ptvp->type, oidSrch)) {
+ fieldFound = true;
+ certName = (char *)ptvp->value.Data;
+ certNameLen = (uint32)ptvp->value.Length;
+ switch(searchType) {
+ case SN_CommonName:
+ {
+ /* handle odd encodings that we need to convert to 8-bit */
+ CFStringBuiltInEncodings encoding;
+ CFDataRef cfd = NULL;
+ bool doConvert = false;
+ switch(ptvp->valueType) {
+ case BER_TAG_T61_STRING:
+ /* a.k.a. Teletex */
+ encoding = kCFStringEncodingISOLatin1;
+ doConvert = true;
+ break;
+ case BER_TAG_PKIX_BMP_STRING:
+ encoding = kCFStringEncodingUnicode;
+ doConvert = true;
+ break;
+ /*
+ * All others - either take as is, or let it fail due to
+ * illegal/incomprehensible format
+ */
+ default:
+ break;
+ }
+ if(doConvert) {
+ /* raw data ==> CFString */
+ cfd = CFDataCreate(NULL, (UInt8 *)certName, certNameLen);
+ if(cfd == NULL) {
+ /* try next component */
+ break;
+ }
+ CFStringRef cfStr = CFStringCreateFromExternalRepresentation(
+ NULL, cfd, encoding);
+ CFRelease(cfd);
+ if(cfStr == NULL) {
+ tpPolicyError("tpCompareSubjectName: bad str (1)");
+ break;
+ }
+
+ /* CFString ==> straight ASCII */
+ cfd = CFStringCreateExternalRepresentation(NULL,
+ cfStr, kCFStringEncodingASCII, 0);
+ CFRelease(cfStr);
+ if(cfd == NULL) {
+ tpPolicyError("tpCompareSubjectName: bad str (2)");
+ break;
+ }
+ certNameLen = (uint32)CFDataGetLength(cfd);
+ certName = (char *)CFDataGetBytePtr(cfd);
+ }
+ ourRtn = tpCompareHostNames(callerStr, callerStrLen,
+ certName, certNameLen);
+ if(doConvert) {
+ assert(cfd != NULL);
+ CFRelease(cfd);
+ }
+ break;
+ }
+ case SN_Email:
+ ourRtn = tpCompareEmailAddr(callerStr, callerStrLen,
+ certName, certNameLen, normalizeAll);
+ break;
+ case SN_UserID:
+ case SN_OrgUnit:
+ /* exact match only here, for now */
+ ourRtn = ((callerStrLen == certNameLen) &&
+ !memcmp(callerStr, certName, certNameLen)) ?
+ CSSM_TRUE : CSSM_FALSE;
+ break;
+ }
+ if(ourRtn) {
+ /* success */
+ break;
+ }
+ /* else keep going, maybe there's another common name */
+ }
+ }
+ if(ourRtn) {
+ break;
+ }
+ }
+ cert.freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
+ return ourRtn;
+}
+
+/*
+ * Compare ASCII form of an IP address to a CSSM_DATA containing
+ * the IP address's numeric components. Returns true on match.
+ */
+static CSSM_BOOL tpCompIpAddrStr(
+ const char *str,
+ unsigned strLen,
+ const CSSM_DATA *numeric)
+{
+ const char *cp = str;
+ const char *nextDot;
+ char buf[100];
+
+ if((numeric == NULL) || (numeric->Length == 0) || (str == NULL)) {
+ return CSSM_FALSE;
+ }
+ if(cp[strLen - 1] == '\0') {
+ /* ignore NULL terminator */
+ strLen--;
+ }
+ for(unsigned dex=0; dex<numeric->Length; dex++) {
+ /* cp points to start of current string digit */
+ /* find next dot */
+ const char *lastChar = cp + strLen;
+ nextDot = cp + 1;
+ for( ; nextDot<lastChar; nextDot++) {
+ if(*nextDot == '.') {
+ break;
+ }
+ }
+ if(nextDot == lastChar) {
+ /* legal and required on last digit */
+ if(dex != (numeric->Length - 1)) {
+ return CSSM_FALSE;
+ }
+ }
+ else if(dex == (numeric->Length - 1)) {
+ return CSSM_FALSE;
+ }
+ ptrdiff_t digLen = nextDot - cp;
+ if(digLen >= sizeof(buf)) {
+ /* preposterous */
+ return CSSM_FALSE;
+ }
+ memmove(buf, cp, digLen);
+ buf[digLen] = '\0';
+ /* incr digLen to include the next dot */
+ digLen++;
+ cp += digLen;
+ strLen -= digLen;
+ int digVal = atoi(buf);
+ if(digVal != numeric->Data[dex]) {
+ return CSSM_FALSE;
+ }
+ }
+ return CSSM_TRUE;
+}
+
+/*
+ * See if cert's subjectAltName contains an element matching caller-specified
+ * string, hostname, in the following forms:
+ *
+ * SAN_HostName : dnsName, iPAddress
+ * SAN_Email : RFC822Name
+ *
+ * Returns CSSM_TRUE if match, else returns CSSM_FALSE.
+ *
+ * Also indicates whether or not a dnsName (search type HostName) or
+ * RFC822Name (search type SAM_Email) was found, regardless of result
+ * of comparison.
+ *
+ * The appStr/appStrLen args are optional - if NULL/0, only the
+ * search for dnsName/RFC822Name is done.
+ */
+typedef enum {
+ SAN_HostName,
+ SAN_Email
+} SubjAltNameSearchType;
+
+static CSSM_BOOL tpCompareSubjectAltName(
+ const iSignExtenInfo &subjAltNameInfo,
+ const char *appStr, // caller has lower-cased as appropriate
+ uint32 appStrLen,
+ SubjAltNameSearchType searchType,
+ bool normalizeAll, // for SAN_Email case: lower-case all of
+ // the cert's value, not just the portion
+ // after the '@'
+ bool &dnsNameFound, // RETURNED, SAN_HostName case
+ bool &emailFound) // RETURNED, SAN_Email case
+{
+ dnsNameFound = false;
+ emailFound = false;
+ if(!subjAltNameInfo.present) {
+ /* common failure, no subjectAltName found */
+ return CSSM_FALSE;
+ }
+
+ CE_GeneralNames *names = &subjAltNameInfo.extnData->subjectAltName;
+ CSSM_BOOL ourRtn = CSSM_FALSE;
+ char *certName;
+ uint32 certNameLen;
+
+ /* Search thru the CE_GeneralNames looking for the appropriate attribute */
+ for(unsigned dex=0; dex<names->numNames; dex++) {
+ CE_GeneralName *name = &names->generalName[dex];
+ switch(searchType) {
+ case SAN_HostName:
+ switch(name->nameType) {
+ case GNT_IPAddress:
+ if(appStr == NULL) {
+ /* nothing to do here */
+ break;
+ }
+ ourRtn = tpCompIpAddrStr(appStr, appStrLen, &name->name);
+ break;
+
+ case GNT_DNSName:
+ if(name->berEncoded) {
+ tpErrorLog("tpCompareSubjectAltName: malformed "
+ "CE_GeneralName (1)\n");
+ break;
+ }
+ certName = (char *)name->name.Data;
+ if(certName == NULL) {
+ tpErrorLog("tpCompareSubjectAltName: malformed "
+ "CE_GeneralName (2)\n");
+ break;
+ }
+ certNameLen = (uint32)(name->name.Length);
+ dnsNameFound = true;
+ if(appStr != NULL) {
+ /* skip if caller passed in NULL */
+ ourRtn = tpCompareHostNames(appStr, appStrLen,
+ certName, certNameLen);
+ }
+ break;
+
+ default:
+ /* not interested, proceed to next name */
+ break;
+ }
+ break; /* from case HostName */
+
+ case SAN_Email:
+ if(name->nameType != GNT_RFC822Name) {
+ /* not interested */
+ break;
+ }
+ certName = (char *)name->name.Data;
+ if(certName == NULL) {
+ tpErrorLog("tpCompareSubjectAltName: malformed "
+ "GNT_RFC822Name\n");
+ break;
+ }
+ certNameLen = (uint32)(name->name.Length);
+ emailFound = true;
+ if(appStr != NULL) {
+ ourRtn = tpCompareEmailAddr(appStr, appStrLen, certName,
+ certNameLen, normalizeAll);
+ }
+ break;
+ }
+ if(ourRtn) {
+ /* success */
+ break;
+ }
+ }
+ return ourRtn;
+}
+
+/* is host name in the form of a.b.c.d, where a,b,c, and d are digits? */
+static CSSM_BOOL tpIsNumeric(
+ const char *hostName,
+ unsigned hostNameLen)
+{
+ if(hostName[hostNameLen - 1] == '\0') {
+ /* ignore NULL terminator */
+ hostNameLen--;
+ }
+ for(unsigned i=0; i<hostNameLen; i++) {
+ char c = *hostName++;
+ if(isdigit(c)) {
+ continue;
+ }
+ if(c != '.') {
+ return CSSM_FALSE;
+ }
+ }
+ return CSSM_TRUE;
+}
+
+/*
+ * Convert a typed string represented by a CSSM_X509_TYPE_VALUE_PAIR to a
+ * CFStringRef. Caller owns and must release the result. NULL return means
+ * unconvertible input "string".
+ */
+static CFStringRef tpTvpToCfString(
+ const CSSM_X509_TYPE_VALUE_PAIR *tvp)
+{
+ CFStringBuiltInEncodings encoding;
+ switch(tvp->valueType) {
+ case BER_TAG_T61_STRING:
+ /* a.k.a. Teletex */
+ encoding = kCFStringEncodingISOLatin1;
+ break;
+ case BER_TAG_PKIX_BMP_STRING:
+ encoding = kCFStringEncodingUnicode;
+ break;
+ case BER_TAG_PRINTABLE_STRING:
+ case BER_TAG_IA5_STRING:
+ case BER_TAG_PKIX_UTF8_STRING:
+ encoding = kCFStringEncodingUTF8;
+ break;
+ default:
+ return NULL;
+ }
+
+ /* raw data ==> CFString */
+ CFDataRef cfd = CFDataCreate(NULL, tvp->value.Data, tvp->value.Length);
+ if(cfd == NULL) {
+ return NULL;
+ }
+ CFStringRef cfStr = CFStringCreateFromExternalRepresentation(NULL, cfd, encoding);
+ CFRelease(cfd);
+ return cfStr;
+}
+
+/*
+ * Compare a CFString and a string represented by a CSSM_X509_TYPE_VALUE_PAIR.
+ * Returns CSSM_TRUE if they are equal.
+ */
+static bool tpCompareTvpToCfString(
+ const CSSM_X509_TYPE_VALUE_PAIR *tvp,
+ CFStringRef refStr,
+ CFOptionFlags flags) // e.g., kCFCompareCaseInsensitive
+{
+ CFStringRef cfStr = tpTvpToCfString(tvp);
+ if(cfStr == NULL) {
+ return false;
+ }
+ CFComparisonResult res = CFStringCompare(refStr, cfStr, flags);
+ CFRelease(cfStr);
+ if(res == kCFCompareEqualTo) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+/*
+ * Given one iSignCertInfo, determine whether or not the specified
+ * EKU OID, or - optionally - CSSMOID_ExtendedKeyUsageAny - is present.
+ * Returns true if so, else false.
+ */
+static bool tpVerifyEKU(
+ const iSignCertInfo &certInfo,
+ const CSSM_OID &ekuOid,
+ bool ekuAnyOK) // if true, CSSMOID_ExtendedKeyUsageAny counts as "found"
+{
+ if(!certInfo.extendKeyUsage.present) {
+ return false;
+ }
+ CE_ExtendedKeyUsage *eku = &certInfo.extendKeyUsage.extnData->extendedKeyUsage;
+ assert(eku != NULL);
+
+ for(unsigned i=0; i<eku->numPurposes; i++) {
+ const CSSM_OID *foundEku = &eku->purposes[i];
+ if(tpCompareOids(foundEku, &ekuOid)) {
+ return true;
+ }
+ if(ekuAnyOK && tpCompareOids(foundEku, &CSSMOID_ExtendedKeyUsageAny)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Given one iSignCertInfo, determine whether or not the specified
+ * Certificate Policy OID, or - optionally - CSSMOID_ANY_POLICY - is present.
+ * Returns true if so, else false.
+ */
+static bool tpVerifyCPE(
+ const iSignCertInfo &certInfo,
+ const CSSM_OID &cpOid,
+ bool anyPolicyOK) // if true, CSSMOID_ANY_POLICY counts as "found"
+{
+ if(!certInfo.certPolicies.present) {
+ return false;
+ }
+ CE_CertPolicies *cp = &certInfo.certPolicies.extnData->certPolicies;
+ assert(cp != NULL);
+
+ for(unsigned i=0; i<cp->numPolicies; i++) {
+ const CE_PolicyInformation *foundPolicy = &cp->policies[i];
+ if(tpCompareOids(&foundPolicy->certPolicyId, &cpOid)) {
+ return true;
+ }
+ if(anyPolicyOK && tpCompareOids(&foundPolicy->certPolicyId, &CSSMOID_ANY_POLICY)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Verify iChat handle. We search for a matching (case-insensitive) string
+ * comprised of:
+ *
+ * -- name component ("dmitch") from subject name's CommonName
+ * -- implicit '@'
+ * -- domain name from subject name's organizationalUnit
+ *
+ * Plus we require an Organization component of "Apple Computer, Inc." or "Apple Inc."
+ */
+static bool tpCompareIChatHandleName(
+ TPCertInfo &cert,
+ const char *iChatHandle, // UTF8
+ uint32 iChatHandleLen)
+{
+ CSSM_DATA_PTR subjNameData = NULL; // from fetchField
+ CSSM_RETURN crtn;
+ bool ourRtn = false;
+ CSSM_X509_NAME_PTR x509name;
+ CSSM_X509_TYPE_VALUE_PAIR *ptvp;
+ CSSM_X509_RDN_PTR rdnp;
+ unsigned rdnDex;
+ unsigned pairDex;
+
+ /* search until all of these are true */
+ CSSM_BOOL commonNameMatch = CSSM_FALSE; // name before '@'
+ CSSM_BOOL orgUnitMatch = CSSM_FALSE; // domain after '@
+ CSSM_BOOL orgMatch = CSSM_FALSE; // Apple Computer, Inc. (or Apple Inc.)
+
+ /*
+ * incoming UTF8 handle ==> two components.
+ * First convert to CFString.
+ */
+ if(iChatHandle[iChatHandleLen - 1] == '\0') {
+ /* avoid NULL when creating CFStrings */
+ iChatHandleLen--;
+ }
+ CFDataRef cfd = CFDataCreate(NULL, (const UInt8 *)iChatHandle, iChatHandleLen);
+ if(cfd == NULL) {
+ return false;
+ }
+ CFStringRef handleStr = CFStringCreateFromExternalRepresentation(NULL, cfd,
+ kCFStringEncodingUTF8);
+ CFRelease(cfd);
+ if(handleStr == NULL) {
+ tpPolicyError("tpCompareIChatHandleName: bad incoming handle (1)");
+ return false;
+ }
+
+ /*
+ * Find the '@' delimiter
+ */
+ CFRange whereIsAt;
+ whereIsAt = CFStringFind(handleStr, CFSTR("@"), 0);
+ if(whereIsAt.length == 0) {
+ tpPolicyError("tpCompareIChatHandleName: bad incoming handle: no @");
+ CFRelease(handleStr);
+ return false;
+ }
+
+ /*
+ * Two components, before and after delimiter
+ */
+ CFRange r = {0, whereIsAt.location};
+ CFStringRef iChatName = CFStringCreateWithSubstring(NULL, handleStr, r);
+ if(iChatName == NULL) {
+ tpPolicyError("tpCompareIChatHandleName: bad incoming handle (2)");
+ CFRelease(handleStr);
+ return false;
+ }
+ r.location = whereIsAt.location + 1; // after the '@'
+ r.length = CFStringGetLength(handleStr) - r.location;
+ CFStringRef iChatDomain = CFStringCreateWithSubstring(NULL, handleStr, r);
+ CFRelease(handleStr);
+ if(iChatDomain == NULL) {
+ tpPolicyError("tpCompareIChatHandleName: bad incoming handle (3)");
+ CFRelease(iChatName);
+ return false;
+ }
+ /* subsequent errors to errOut: */
+
+ /* get subject name in CSSM form, all subsequent ops work on that */
+ crtn = cert.fetchField(&CSSMOID_X509V1SubjectNameCStruct, &subjNameData);
+ if(crtn) {
+ /* should never happen, we shouldn't be here if there is no subject */
+ tpPolicyError("tpCompareIChatHandleName: error retrieving subject name");
+ goto errOut;
+ }
+
+ x509name = (CSSM_X509_NAME_PTR)subjNameData->Data;
+ if((x509name == NULL) || (subjNameData->Length != sizeof(CSSM_X509_NAME))) {
+ tpPolicyError("tpCompareIChatHandleName: malformed CSSM_X509_NAME");
+ goto errOut;
+ }
+
+ /* Now grunge thru the X509 name looking for three fields */
+
+ for(rdnDex=0; rdnDex<x509name->numberOfRDNs; rdnDex++) {
+ rdnp = &x509name->RelativeDistinguishedName[rdnDex];
+ for(pairDex=0; pairDex<rdnp->numberOfPairs; pairDex++) {
+ ptvp = &rdnp->AttributeTypeAndValue[pairDex];
+ if(!commonNameMatch &&
+ tpCompareOids(&ptvp->type, &CSSMOID_CommonName) &&
+ tpCompareTvpToCfString(ptvp, iChatName, kCFCompareCaseInsensitive)) {
+ commonNameMatch = CSSM_TRUE;
+ }
+
+ if(!orgUnitMatch &&
+ tpCompareOids(&ptvp->type, &CSSMOID_OrganizationalUnitName) &&
+ tpCompareTvpToCfString(ptvp, iChatDomain, kCFCompareCaseInsensitive)) {
+ orgUnitMatch = CSSM_TRUE;
+ }
+
+ if(!orgMatch &&
+ tpCompareOids(&ptvp->type, &CSSMOID_OrganizationName) &&
+ /* this one is case sensitive */
+ (tpCompareTvpToCfString(ptvp, CFSTR("Apple Computer, Inc."), 0) ||
+ tpCompareTvpToCfString(ptvp, CFSTR("Apple Inc."), 0))) {
+ orgMatch = CSSM_TRUE;
+ }
+
+ if(commonNameMatch && orgUnitMatch && orgMatch) {
+ /* TA DA */
+ ourRtn = true;
+ goto errOut;
+ }
+ }
+ }
+errOut:
+ cert.freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
+ CFRelease(iChatName);
+ CFRelease(iChatDomain);
+ return ourRtn;
+}
+
+/*
+ * Verify SSL options. Currently this just consists of matching the
+ * leaf cert's subject common name against the caller's (optional)
+ * server name.
+ */
+static CSSM_RETURN tp_verifySslOpts(
+ TPPolicy policy,
+ TPCertGroup &certGroup,
+ const CSSM_DATA *sslFieldOpts,
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ const iSignCertInfo &leafCertInfo = certInfo[0];
+ CSSM_APPLE_TP_SSL_OPTIONS *sslOpts = NULL;
+ unsigned hostNameLen = 0;
+ const char *serverName = NULL;
+ TPCertInfo *leaf = certGroup.certAtIndex(0);
+ assert(leaf != NULL);
+
+ /* CSSM_APPLE_TP_SSL_OPTIONS is optional */
+ if((sslFieldOpts != NULL) && (sslFieldOpts->Data != NULL)) {
+ sslOpts = (CSSM_APPLE_TP_SSL_OPTIONS *)sslFieldOpts->Data;
+ switch(sslOpts->Version) {
+ case CSSM_APPLE_TP_SSL_OPTS_VERSION:
+ if(sslFieldOpts->Length != sizeof(CSSM_APPLE_TP_SSL_OPTIONS)) {
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ }
+ break;
+ /* handle backwards compatibility here if necessary */
+ default:
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ }
+ hostNameLen = sslOpts->ServerNameLen;
+ serverName = sslOpts->ServerName;
+ }
+
+ /* host name check is optional */
+ if(hostNameLen != 0) {
+ if(serverName == NULL) {
+ return CSSMERR_TP_INVALID_POINTER;
+ }
+
+ /* convert caller's hostname string to lower case */
+ char *hostName = (char *)certGroup.alloc().malloc(hostNameLen);
+ memmove(hostName, serverName, hostNameLen);
+ tpToLower(hostName, hostNameLen);
+
+ CSSM_BOOL match = CSSM_FALSE;
+
+ /* First check subjectAltName... */
+ bool dnsNameFound = false;
+ bool dummy;
+ match = tpCompareSubjectAltName(leafCertInfo.subjectAltName,
+ hostName, hostNameLen,
+ SAN_HostName, false, dnsNameFound, dummy);
+
+ /*
+ * Then common name, if
+ * -- no match from subjectAltName, AND
+ * -- dnsName was NOT found, AND
+ * -- hostName is not strictly numeric form (1.2.3.4)
+ */
+ if(!match && !dnsNameFound && !tpIsNumeric(hostName, hostNameLen)) {
+ bool fieldFound;
+ match = tpCompareSubjectName(*leaf, SN_CommonName, false, hostName, hostNameLen,
+ fieldFound);
+ }
+
+ /*
+ * Limit allowed domains for specific anchors
+ */
+ CSSM_BOOL domainMatch = CSSM_TRUE;
+ if(match) {
+ TPCertInfo *tpCert = certGroup.lastCert();
+ if (tpCert) {
+ const CSSM_DATA *certData = tpCert->itemData();
+ unsigned char digest[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(certData->Data, (CC_LONG)certData->Length, digest);
+ if (!memcmp(digest, kAppleCorpCASHA1, sizeof(digest))) {
+ const char *dnlist[] = { "apple.com", "icloud.com" };
+ unsigned int idx, dncount=2;
+ domainMatch = CSSM_FALSE;
+ for(idx=0;idx<dncount;idx++) {
+ uint32 len=(uint32)strlen(dnlist[idx]);
+ char *domainName=(char*)certGroup.alloc().malloc(len);
+ memmove(domainName, (char*)dnlist[idx], len);
+ if(tpCompareDomainSuffix(hostName, hostNameLen,
+ domainName, len)) {
+ domainMatch = CSSM_TRUE;
+ }
+ certGroup.alloc().free(domainName);
+ if (domainMatch) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ certGroup.alloc().free(hostName);
+ if(!match) {
+ if(leaf->addStatusCode(CSSMERR_APPLETP_HOSTNAME_MISMATCH)) {
+ return CSSMERR_APPLETP_HOSTNAME_MISMATCH;
+ }
+ }
+ if(!domainMatch) {
+ if(leaf->addStatusCode(CSSMERR_APPLETP_CA_PIN_MISMATCH)) {
+ return CSSMERR_APPLETP_CA_PIN_MISMATCH;
+ }
+ }
+ }
+
+ /*
+ * Ensure that, if an extendedKeyUsage extension is present in the
+ * leaf, that either anyExtendedKeyUsage or the appropriate
+ * CSSMOID_{Server,Client}Auth, or a SeverGatedCrypto usage is present.
+ */
+ const iSignExtenInfo &ekuInfo = leafCertInfo.extendKeyUsage;
+ if(ekuInfo.present) {
+ bool foundGoodEku = false;
+ bool isServer = true;
+ CE_ExtendedKeyUsage *eku = (CE_ExtendedKeyUsage *)ekuInfo.extnData;
+ assert(eku != NULL);
+
+ /*
+ * Determine appropriate extended key usage; default is SSL server
+ */
+ const CSSM_OID *extUse = &CSSMOID_ServerAuth;
+ if((sslOpts != NULL) && /* optional, default server side */
+ (sslOpts->Version > 0) && /* this was added in struct version 1 */
+ (sslOpts->Flags & CSSM_APPLE_TP_SSL_CLIENT)) {
+ extUse = &CSSMOID_ClientAuth;
+ isServer = false;
+ }
+
+ /* search for that one or for "any" indicator */
+ for(unsigned i=0; i<eku->numPurposes; i++) {
+ const CSSM_OID *purpose = &eku->purposes[i];
+ if(tpCompareOids(purpose, extUse)) {
+ foundGoodEku = true;
+ break;
+ }
+ if(tpCompareOids(purpose, &CSSMOID_ExtendedKeyUsageAny)) {
+ foundGoodEku = true;
+ break;
+ }
+ if((policy == kTP_IPSec) && (tpCompareOids(purpose, &CSSMOID_EKU_IPSec))) {
+ foundGoodEku = true;
+ break;
+ }
+ if(isServer) {
+ /* server gated crypto: server side only */
+ if(tpCompareOids(purpose, &CSSMOID_NetscapeSGC)) {
+ foundGoodEku = true;
+ break;
+ }
+ if(tpCompareOids(purpose, &CSSMOID_MicrosoftSGC)) {
+ foundGoodEku = true;
+ break;
+ }
+ }
+ }
+ if(!foundGoodEku) {
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SSL_BAD_EXT_KEY_USE)) {
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+ }
+
+ /*
+ * Check for additional options flag (2nd lowest bit set) which indicates
+ * we must be issued by an Apple intermediate with a particular extension.
+ * (This flag is set by SecPolicyCreateAppleSSLService in SecPolicy.cpp.)
+ */
+ if((sslOpts != NULL) &&
+ (sslOpts->Version > 0) && /* this was added in struct version 1 */
+ (sslOpts->Flags & 0x00000002)) {
+
+ if (certGroup.numCerts() > 1) {
+ const iSignCertInfo *isCertInfo = &certInfo[1];
+ if (!(isCertInfo->foundAppleServerAuthMarker == CSSM_TRUE)) {
+ TPCertInfo *tpCert = certGroup.certAtIndex(1);
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+ }
+ else {
+ /* we only have the leaf? */
+ if(leaf->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION)) {
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+ }
+ }
+ return CSSM_OK;
+}
+
+/*
+ * Verify SMIME and iChat options.
+ * This deals with both S/MIME and iChat policies; within the iChat domain it
+ * deals with Apple-specific .mac certs as well as what we call "generic AIM"
+ * certs, as used in the Windows AIM client.
+ */
+#define CE_CIPHER_MASK (~(CE_KU_EncipherOnly | CE_KU_DecipherOnly))
+
+static CSSM_RETURN tp_verifySmimeOpts(
+ TPPolicy policy,
+ TPCertGroup &certGroup,
+ const CSSM_DATA *smimeFieldOpts,
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ const iSignCertInfo &leafCertInfo = certInfo[0];
+ bool iChat = (policy == kTP_iChat) ? true : false;
+ /*
+ * The CSSM_APPLE_TP_SMIME_OPTIONS pointer is optional as is everything in it.
+ */
+ CSSM_APPLE_TP_SMIME_OPTIONS *smimeOpts = NULL;
+ if(smimeFieldOpts != NULL) {
+ smimeOpts = (CSSM_APPLE_TP_SMIME_OPTIONS *)smimeFieldOpts->Data;
+ }
+ if(smimeOpts != NULL) {
+ switch(smimeOpts->Version) {
+ case CSSM_APPLE_TP_SMIME_OPTS_VERSION:
+ if(smimeFieldOpts->Length !=
+ sizeof(CSSM_APPLE_TP_SMIME_OPTIONS)) {
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ }
+ break;
+ /* handle backwards compatibility here if necessary */
+ default:
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ }
+ }
+
+ TPCertInfo *leaf = certGroup.certAtIndex(0);
+ assert(leaf != NULL);
+
+ /* Verify optional email address, a.k.a. handle for iChat policy */
+ unsigned emailLen = 0;
+ if(smimeOpts != NULL) {
+ emailLen = smimeOpts->SenderEmailLen;
+ }
+
+ bool match = false;
+ bool emailFoundInSAN = false;
+ bool iChatHandleFound = false; /* indicates a genuine Apple iChat cert */
+ bool emailFoundInDN = false;
+ if(emailLen != 0) {
+ if(smimeOpts->SenderEmail == NULL) {
+ return CSSMERR_TP_INVALID_POINTER;
+ }
+
+ /* iChat - first try the Apple custom format */
+ if(iChat) {
+ iChatHandleFound = tpCompareIChatHandleName(*leaf, smimeOpts->SenderEmail,
+ emailLen);
+ if(iChatHandleFound) {
+ match = true;
+ }
+
+ }
+
+ if(!match) {
+ /*
+ * normalize caller's email string
+ * SMIME - lowercase only the portion after '@'
+ * iChat - lowercase all of it
+ */
+ char *email = (char *)certGroup.alloc().malloc(emailLen);
+ memmove(email, smimeOpts->SenderEmail, emailLen);
+ tpNormalizeAddrSpec(email, emailLen, iChat);
+
+
+ /*
+ * First check subjectAltName. The emailFound bool indicates
+ * that *some* email address was found, regardless of a match
+ * condition.
+ */
+ bool dummy;
+ match = tpCompareSubjectAltName(leafCertInfo.subjectAltName,
+ email, emailLen,
+ SAN_Email, iChat, dummy, emailFoundInSAN);
+
+ /*
+ * Then subject DN, CSSMOID_EmailAddress, if no match from
+ * subjectAltName. In this case the whole email address is
+ * case insensitive (RFC 3280, section 4.1.2.6), so
+ * renormalize.
+ */
+ if(!match) {
+ tpNormalizeAddrSpec(email, emailLen, true);
+ match = tpCompareSubjectName(*leaf, SN_Email, true, email, emailLen,
+ emailFoundInDN);
+ }
+ certGroup.alloc().free(email);
+
+ /*
+ * Error here if no match found but there was indeed *some*
+ * email address in the cert.
+ */
+ if(!match && (emailFoundInSAN || emailFoundInDN)) {
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND)) {
+ tpPolicyError("SMIME email addrs in cert but no match");
+ return CSSMERR_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND;
+ }
+ }
+ }
+
+ /*
+ * iChat only: error if app specified email address but there was
+ * none in the cert.
+ */
+ if(iChat && !emailFoundInSAN && !emailFoundInDN && !iChatHandleFound) {
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS)) {
+ tpPolicyError("iChat: no email address or handle in cert");
+ return CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS;
+ }
+ }
+ }
+
+ /*
+ * Going by the letter of the law, here's what RFC 2632 has to say
+ * about the legality of an empty Subject Name:
+ *
+ * ...the subject DN in a user's (i.e. end-entity) certificate MAY
+ * be an empty SEQUENCE in which case the subjectAltName extension
+ * will include the subject's identifier and MUST be marked as
+ * critical.
+ *
+ * OK, first examine the leaf cert's subject name.
+ */
+ CSSM_RETURN crtn;
+ CSSM_DATA_PTR subjNameData = NULL;
+ const iSignExtenInfo &kuInfo = leafCertInfo.keyUsage;
+ const iSignExtenInfo &ekuInfo = leafCertInfo.extendKeyUsage;
+ const CSSM_X509_NAME *x509Name = NULL;
+
+ if(iChat) {
+ /* empty subject name processing is S/MIME only */
+ goto checkEku;
+ }
+
+ crtn = leaf->fetchField(&CSSMOID_X509V1SubjectNameCStruct, &subjNameData);
+ if(crtn) {
+ /* This should really never happen */
+ tpPolicyError("SMIME policy: error fetching subjectName");
+ leaf->addStatusCode(CSSMERR_TP_INVALID_CERTIFICATE);
+ return CSSMERR_TP_INVALID_CERTIFICATE;
+ }
+ /* must do a leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct on exit */
+
+ x509Name = (const CSSM_X509_NAME *)subjNameData->Data;
+ if(x509Name->numberOfRDNs == 0) {
+ /*
+ * Empty subject name. If we haven't already seen a valid
+ * email address in the subject alternate name (by looking
+ * for a specific address specified by app), try to find
+ * one now.
+ */
+ if(!emailFoundInSAN && // haven't found one, and
+ (emailLen == 0)) { // didn't even look yet
+ bool dummy;
+ tpCompareSubjectAltName(leafCertInfo.subjectAltName,
+ NULL, 0, // email, emailLen,
+ SAN_Email, false, dummy,
+ emailFoundInSAN); // the variable we're updating
+ }
+ if(!emailFoundInSAN) {
+ tpPolicyError("SMIME policy fail: empty subject name and "
+ "no Email Addrs in SubjectAltName");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS)) {
+ leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ else {
+ /* have to skip the next block */
+ goto postSAN;
+ }
+ }
+
+ /*
+ * One more thing: this leaf must indeed have a subjAltName
+ * extension and it must be critical. We would not have gotten this
+ * far if the subjAltName extension was not actually present....
+ */
+ assert(leafCertInfo.subjectAltName.present);
+ if(!leafCertInfo.subjectAltName.critical) {
+ tpPolicyError("SMIME policy fail: empty subject name and "
+ "no Email Addrs in SubjectAltName");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_SUBJ_ALT_NAME_NOT_CRIT)) {
+ leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+ }
+postSAN:
+ leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
+
+ /*
+ * Enforce the usage of the key associated with the leaf cert.
+ * Cert's KeyUsage must be a superset of what the app is trying to do.
+ * Note the {en,de}cipherOnly flags are handled separately....
+ */
+ if(kuInfo.present && (smimeOpts != NULL)) {
+ CE_KeyUsage certKu = *((CE_KeyUsage *)kuInfo.extnData);
+ CE_KeyUsage appKu = smimeOpts->IntendedUsage;
+ CE_KeyUsage intersection = certKu & appKu;
+ if((intersection & CE_CIPHER_MASK) != (appKu & CE_CIPHER_MASK)) {
+ tpPolicyError("SMIME KeyUsage err: appKu 0x%x certKu 0x%x",
+ appKu, certKu);
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE)) {
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+
+ /* Now the en/de cipher only bits - for keyAgreement only */
+ if(appKu & CE_KU_KeyAgreement) {
+ /*
+ * 1. App wants to use this for key agreement; it must
+ * say what it wants to do with the derived key.
+ * In this context, the app's XXXonly bit means that
+ * it wants to use the key for that op - not necessarliy
+ * "only".
+ */
+ if((appKu & (CE_KU_EncipherOnly | CE_KU_DecipherOnly)) == 0) {
+ tpPolicyError("SMIME KeyUsage err: KeyAgreement with "
+ "no Encipher or Decipher");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE)) {
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+
+ /*
+ * 2. If cert restricts to encipher only make sure the
+ * app isn't trying to decipher.
+ */
+ if((certKu & CE_KU_EncipherOnly) &&
+ (appKu & CE_KU_DecipherOnly)) {
+ tpPolicyError("SMIME KeyUsage err: cert EncipherOnly, "
+ "app wants to decipher");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE)) {
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+
+ /*
+ * 3. If cert restricts to decipher only make sure the
+ * app isn't trying to encipher.
+ */
+ if((certKu & CE_KU_DecipherOnly) &&
+ (appKu & CE_KU_EncipherOnly)) {
+ tpPolicyError("SMIME KeyUsage err: cert DecipherOnly, "
+ "app wants to encipher");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE)) {
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+ }
+ }
+
+ /*
+ * Extended Key Use verification, which is different for the two policies.
+ */
+checkEku:
+ if(iChat && !ekuInfo.present) {
+ /*
+ * iChat: whether generic AIM cert or Apple .mac/iChat cert, we must have an
+ * extended key use extension.
+ */
+ tpPolicyError("iChat: No extended Key Use");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE)) {
+ return CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE;
+ }
+ }
+
+ if(!iChatHandleFound) {
+ /*
+ * S/MIME and generic AIM certs when evaluating iChat policy.
+ * Look for either emailProtection or anyExtendedKeyUsage usages.
+ *
+ * S/MIME : the whole extension is optional.
+ * iChat : extension must be there (which we've already covered, above)
+ * and we must find one of those extensions.
+ */
+ if(ekuInfo.present) {
+ bool foundGoodEku = false;
+ CE_ExtendedKeyUsage *eku = (CE_ExtendedKeyUsage *)ekuInfo.extnData;
+ assert(eku != NULL);
+ for(unsigned i=0; i<eku->numPurposes; i++) {
+ if(tpCompareOids(&eku->purposes[i], &CSSMOID_EmailProtection)) {
+ foundGoodEku = true;
+ break;
+ }
+ if(tpCompareOids(&eku->purposes[i], &CSSMOID_ExtendedKeyUsageAny)) {
+ foundGoodEku = true;
+ break;
+ }
+ }
+ if(!foundGoodEku) {
+ tpPolicyError("iChat/SMIME: No appropriate extended Key Use");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE)) {
+ return CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE;
+ }
+ }
+ }
+ }
+ else {
+ /*
+ * Apple iChat cert. Look for anyExtendedKeyUsage, iChatSigning,
+ * ichatEncrypting - the latter of two which can optionally be
+ * required by app.
+ */
+ assert(iChat); /* or we could not have even looked for an iChat style handle */
+ assert(ekuInfo.present); /* checked above */
+ bool foundAnyEku = false;
+ bool foundIChatSign = false;
+ bool foundISignEncrypt = false;
+ CE_ExtendedKeyUsage *eku = (CE_ExtendedKeyUsage *)ekuInfo.extnData;
+ assert(eku != NULL);
+
+ for(unsigned i=0; i<eku->numPurposes; i++) {
+ if(tpCompareOids(&eku->purposes[i],
+ &CSSMOID_APPLE_EKU_ICHAT_SIGNING)) {
+ foundIChatSign = true;
+ }
+ else if(tpCompareOids(&eku->purposes[i],
+ &CSSMOID_APPLE_EKU_ICHAT_ENCRYPTION)) {
+ foundISignEncrypt = true;
+ }
+ else if(tpCompareOids(&eku->purposes[i], &CSSMOID_ExtendedKeyUsageAny)) {
+ foundAnyEku = true;
+ }
+ }
+
+ if(!foundAnyEku && !foundISignEncrypt && !foundIChatSign) {
+ /* No go - no acceptable uses found */
+ tpPolicyError("iChat: No valid extended Key Uses found");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE)) {
+ return CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE;
+ }
+ }
+
+ /* check for specifically required uses */
+ if((smimeOpts != NULL) && (smimeOpts->IntendedUsage != 0)) {
+ if(smimeOpts->IntendedUsage & CE_KU_DigitalSignature) {
+ if(!foundIChatSign) {
+ tpPolicyError("iChat: ICHAT_SIGNING required, but missing");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE)) {
+ return CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE;
+ }
+ }
+ }
+ if(smimeOpts->IntendedUsage & CE_KU_DataEncipherment) {
+ if(!foundISignEncrypt) {
+ tpPolicyError("iChat: ICHAT_ENCRYPT required, but missing");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE)) {
+ return CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE;
+ }
+ }
+ }
+ } /* checking IntendedUsage */
+ } /* iChat cert format */
+
+ return CSSM_OK;
+}
+
+/*
+ * Verify Apple SW Update signing (was Apple Code Signing, pre-Leopard) options.
+ *
+ * -- Must have one intermediate cert
+ * -- intermediate must have basic constraints with path length 0
+ * -- intermediate has CSSMOID_APPLE_EKU_CODE_SIGNING EKU
+ * -- leaf cert has either CODE_SIGNING or CODE_SIGN_DEVELOPMENT EKU (the latter of
+ * which triggers a CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT error)
+ */
+static CSSM_RETURN tp_verifySWUpdateSigningOpts(
+ TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, // currently unused
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ unsigned numCerts = certGroup.numCerts();
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+// const CE_BasicConstraints *bc; // currently unused
+ CE_ExtendedKeyUsage *eku;
+ CSSM_RETURN crtn = CSSM_OK;
+
+ if(numCerts != 3) {
+ if(!certGroup.isAllowedError(CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH)) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: numCerts %u", numCerts);
+ return CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ }
+ else if(numCerts < 3) {
+ /* this error allowed, but no intermediate...check leaf */
+ goto checkLeaf;
+ }
+ }
+
+ /* verify intermediate cert */
+ isCertInfo = &certInfo[1];
+ tpCert = certGroup.certAtIndex(1);
+
+ if(!isCertInfo->basicConstraints.present) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: no basicConstraints in intermediate");
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS)) {
+ return CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS;
+ }
+ }
+
+ /* ExtendedKeyUse required, one legal value */
+ if(!isCertInfo->extendKeyUsage.present) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: no extendedKeyUse in intermediate");
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE)) {
+ return CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE;
+ }
+ else {
+ goto checkLeaf;
+ }
+ }
+
+ eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage;
+ assert(eku != NULL);
+ if(eku->numPurposes != 1) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: bad eku->numPurposes in intermediate (%lu)",
+ (unsigned long)eku->numPurposes);
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ return CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ else if(eku->numPurposes == 0) {
+ /* ignore that error but no EKU - skip EKU check */
+ goto checkLeaf;
+ }
+ /* else ignore error and we have an intermediate EKU; proceed */
+ }
+
+ if(!tpCompareOids(&eku->purposes[0], &CSSMOID_APPLE_EKU_CODE_SIGNING)) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: bad EKU");
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ }
+
+checkLeaf:
+
+ /* verify leaf cert */
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+ if(!isCertInfo->extendKeyUsage.present) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: no extendedKeyUse in leaf");
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE)) {
+ return crtn ? crtn : CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE;
+ }
+ else {
+ /* have to skip remainder */
+ return CSSM_OK;
+ }
+ }
+
+ eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage;
+ assert(eku != NULL);
+ if(eku->numPurposes != 1) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: bad eku->numPurposes (%lu)",
+ (unsigned long)eku->numPurposes);
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ if(crtn == CSSM_OK) {
+ crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ }
+ return crtn;
+ }
+ if(!tpCompareOids(&eku->purposes[0], &CSSMOID_APPLE_EKU_CODE_SIGNING)) {
+ if(tpCompareOids(&eku->purposes[0], &CSSMOID_APPLE_EKU_CODE_SIGNING_DEV)) {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: DEVELOPMENT cert");
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT)) {
+ if(crtn == CSSM_OK) {
+ crtn = CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT;
+ }
+ }
+ }
+ else {
+ tpPolicyError("tp_verifySWUpdateSigningOpts: bad EKU in leaf");
+ if(tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ if(crtn == CSSM_OK) {
+ crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ }
+ }
+ }
+
+ return crtn;
+}
+
+/*
+ * Verify Apple Resource Signing options.
+ *
+ * -- leaf cert must have CSSMOID_APPLE_EKU_RESOURCE_SIGNING EKU
+ * -- chain length must be >= 2
+ * -- mainline code already verified that leaf KeyUsage = digitalSignature (only)
+ */
+static CSSM_RETURN tp_verifyResourceSigningOpts(
+ TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, // currently unused
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ unsigned numCerts = certGroup.numCerts();
+ if(numCerts < 2) {
+ if(!certGroup.isAllowedError(CSSMERR_APPLETP_RS_BAD_CERT_CHAIN_LENGTH)) {
+ tpPolicyError("tp_verifyResourceSigningOpts: numCerts %u", numCerts);
+ return CSSMERR_APPLETP_RS_BAD_CERT_CHAIN_LENGTH;
+ }
+ }
+ const iSignCertInfo &leafCert = certInfo[0];
+ TPCertInfo *leaf = certGroup.certAtIndex(0);
+
+ /* leaf ExtendedKeyUse required, one legal value */
+ if(!tpVerifyEKU(leafCert, CSSMOID_APPLE_EKU_RESOURCE_SIGNING, false)) {
+ tpPolicyError("tp_verifyResourceSigningOpts: no RESOURCE_SIGNING EKU");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ return CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ }
+
+ return CSSM_OK;
+}
+
+/*
+ * Common code for Apple Code Signing and Apple Package Signing.
+ * For now we just require an RFC3280-style CodeSigning EKU in the leaf
+ * for both policies.
+ */
+static CSSM_RETURN tp_verifyCodePkgSignOpts(
+ TPPolicy policy,
+ TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, // currently unused
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ const iSignCertInfo &leafCert = certInfo[0];
+
+ /* leaf ExtendedKeyUse required, one legal value */
+ if(!tpVerifyEKU(leafCert, CSSMOID_ExtendedUseCodeSigning, false)) {
+ TPCertInfo *leaf = certGroup.certAtIndex(0);
+ tpPolicyError("tp_verifyCodePkgSignOpts: no CodeSigning EKU");
+ if(leaf->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ return CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ }
+
+ return CSSM_OK;
+
+}
+
+/*
+ * Verify MacAppStore receipt verification policy options.
+ *
+ * -- Must have one intermediate cert
+ * -- intermediate must be the FairPlay intermediate
+ * -- leaf cert has the CSSMOID_APPLE_EXTENSION_MACAPPSTORE_RECEIPT marker extension
+ */
+static CSSM_RETURN tp_verifyMacAppStoreReceiptOpts(
+ TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, // currently unused
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ unsigned numCerts = certGroup.numCerts();
+ if (numCerts < 3)
+ {
+ if (!certGroup.isAllowedError(CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH))
+ {
+ tpPolicyError("tp_verifyMacAppStoreReceiptOpts: numCerts %u", numCerts);
+ return CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ }
+ }
+
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+
+ /* verify intermediate cert */
+ isCertInfo = &certInfo[1];
+ tpCert = certGroup.certAtIndex(1);
+
+ if (!isCertInfo->basicConstraints.present)
+ {
+ tpPolicyError("tp_verifyAppleIDSharingOpts: no basicConstraints in intermediate");
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS))
+ return CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS;
+ }
+
+ // Now check the leaf
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+ if (certInfo->certificatePolicies.present)
+ {
+ // syslog(LOG_ERR, "tp_verifyMacAppStoreReceiptOpts: found certificatePolicies");
+ const CE_CertPolicies *certPolicies =
+ &isCertInfo->certificatePolicies.extnData->certPolicies;
+ if (!certificatePoliciesContainsOID(certPolicies, &CSSMOID_MACAPPSTORE_RECEIPT_CERT_POLICY))
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION))
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+ else
+ {
+ // syslog(LOG_ERR, "tp_verifyMacAppStoreReceiptOpts: no certificatePolicies present"); // DEBUG
+ tpPolicyError("tp_verifyMacAppStoreReceiptOpts: no certificatePolicies present in leaf");
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION))
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+
+ return CSSM_OK;
+}
+
+bool certificatePoliciesContainsOID(const CE_CertPolicies *certPolicies, const CSSM_OID *oidToFind)
+{
+ // returns true if the given OID is present in the cert policies
+
+ if (!certPolicies || !oidToFind)
+ return false;
+
+ const uint32 maxIndex = 100; // sanity check
+ for (uint32 policyIndex = 0; policyIndex < certPolicies->numPolicies && policyIndex < maxIndex; policyIndex++)
+ {
+ CE_PolicyInformation *certPolicyInfo = &certPolicies->policies[policyIndex];
+ CSSM_OID_PTR oid = &certPolicyInfo->certPolicyId;
+ if (oid && tpCompareOids(oid, oidToFind)) // found it
+ return true;
+ }
+
+ return false;
+}
+
+
+/*
+ * Verify Apple ID Sharing options.
+ *
+ * -- Do basic cert validation (OCSP-based certs)
+ * -- Validate that the cert is an Apple ID sharing cert:
+ * has a custom extension: OID: Apple ID Sharing Certificate ( 1 2 840 113635 100 4 7 )
+ * (CSSMOID_APPLE_EXTENSION_APPLEID_SHARING)
+ * EKU should have both client and server authentication
+ * chains to the "Apple Application Integration Certification Authority" intermediate
+ * -- optionally has a client-specified common name, which is the Apple ID account's UUID.
+
+ * -- Must have one intermediate cert ("Apple Application Integration Certification Authority")
+ * -- intermediate must have basic constraints with path length 0
+ * -- intermediate has CSSMOID_APPLE_EXTENSION_AAI_INTERMEDIATE extension (OID 1 2 840 113635 100 6 2 3)
+ OR APPLE_EXTENSION_AAI_INTERMEDIATE_2
+ */
+
+static CSSM_RETURN tp_verifyAppleIDSharingOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, // optional Common Name
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ unsigned numCerts = certGroup.numCerts();
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+ // const CE_BasicConstraints *bc; // currently unused
+ CE_ExtendedKeyUsage *eku;
+ CSSM_RETURN crtn = CSSM_OK;
+ unsigned int serverNameLen = 0;
+ const char *serverName = NULL;
+
+ // The CSSM_APPLE_TP_SMIME_OPTIONS pointer is optional as is everything in it.
+ if (fieldOpts && fieldOpts->Data)
+ {
+ CSSM_APPLE_TP_SSL_OPTIONS *sslOpts = (CSSM_APPLE_TP_SSL_OPTIONS *)fieldOpts->Data;
+ switch (sslOpts->Version)
+ {
+ case CSSM_APPLE_TP_SSL_OPTS_VERSION:
+ if (fieldOpts->Length != sizeof(CSSM_APPLE_TP_SSL_OPTIONS))
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ break;
+ /* handle backwards compatibility here if necessary */
+ default:
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ }
+ serverNameLen = sslOpts->ServerNameLen;
+ serverName = sslOpts->ServerName;
+ }
+
+ //------------------------------------------------------------------------
+
+ if (numCerts != 3)
+ {
+ if (!certGroup.isAllowedError(CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH))
+ {
+ tpPolicyError("tp_verifyAppleIDSharingOpts: numCerts %u", numCerts);
+ return CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ }
+ else
+ if (numCerts < 3)
+ {
+ /* this error allowed, but no intermediate...check leaf */
+ goto checkLeaf;
+ }
+ }
+
+ /* verify intermediate cert */
+ isCertInfo = &certInfo[1];
+ tpCert = certGroup.certAtIndex(1);
+
+ if (!isCertInfo->basicConstraints.present)
+ {
+ tpPolicyError("tp_verifyAppleIDSharingOpts: no basicConstraints in intermediate");
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS))
+ return CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS;
+ }
+
+checkLeaf:
+
+ /* verify leaf cert */
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+
+ /* host name check is optional */
+ if (serverNameLen != 0)
+ {
+ if (serverName == NULL)
+ return CSSMERR_TP_INVALID_POINTER;
+
+ /* convert caller's hostname string to lower case */
+ char *hostName = (char *)certGroup.alloc().malloc(serverNameLen);
+ memmove(hostName, serverName, serverNameLen);
+ tpToLower(hostName, serverNameLen);
+
+ /* Check common name... */
+
+ bool fieldFound;
+ CSSM_BOOL match = tpCompareSubjectName(*tpCert, SN_CommonName, false, hostName,
+ serverNameLen, fieldFound);
+
+ certGroup.alloc().free(hostName);
+ if (!match && tpCert->addStatusCode(CSSMERR_APPLETP_HOSTNAME_MISMATCH))
+ return CSSMERR_APPLETP_HOSTNAME_MISMATCH;
+ }
+
+ if (certInfo->certificatePolicies.present)
+ {
+ const CE_CertPolicies *certPolicies =
+ &isCertInfo->certificatePolicies.extnData->certPolicies;
+ if (!certificatePoliciesContainsOID(certPolicies, &CSSMOID_APPLEID_SHARING_CERT_POLICY))
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION))
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+ else
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION))
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+
+ if (!isCertInfo->extendKeyUsage.present)
+ {
+ tpPolicyError("tp_verifyAppleIDSharingOpts: no extendedKeyUse in leaf");
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE))
+ return crtn ? crtn : CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE;
+
+ /* have to skip remainder */
+ return CSSM_OK;
+ }
+
+ // Check that certificate can do Client and Server Authentication (EKU)
+ eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage;
+ assert(eku != NULL);
+ if(eku->numPurposes != 2)
+ {
+ tpPolicyError("tp_verifyAppleIDSharingOpts: bad eku->numPurposes (%lu)",
+ (unsigned long)eku->numPurposes);
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE))
+ {
+ if (crtn == CSSM_OK)
+ crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ return crtn;
+ }
+ bool canDoClientAuth = false, canDoServerAuth = false, ekuError = false;
+ for (int ix=0;ix<2;ix++)
+ {
+ if (tpCompareOids(&eku->purposes[ix], &CSSMOID_ClientAuth))
+ canDoClientAuth = true;
+ else
+ if (tpCompareOids(&eku->purposes[ix], &CSSMOID_ServerAuth))
+ canDoServerAuth = true;
+ else
+ {
+ ekuError = true;
+ break;
+ }
+ }
+
+ if (!(canDoClientAuth && canDoServerAuth))
+ ekuError = true;
+ if (ekuError)
+ {
+ tpPolicyError("tp_verifyAppleIDSharingOpts: bad EKU in leaf");
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE))
+ {
+ if (crtn == CSSM_OK)
+ crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+ }
+
+ return crtn;
+}
+
+/*
+ * Verify Time Stamping (RFC3161) policy options.
+ *
+ * -- Leaf must contain Extended Key Usage (EKU), marked critical
+ * -- The EKU must contain the id-kp-timeStamping purpose and no other
+ */
+static CSSM_RETURN tp_verifyTimeStampingOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts, // currently unused
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ //unsigned numCerts = certGroup.numCerts();
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+ CE_ExtendedKeyUsage *eku;
+
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+
+ if (!isCertInfo->extendKeyUsage.present)
+ {
+ tpPolicyError("tp_verifyTimeStampingOpts: no extendedKeyUse in leaf");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+
+ if(!isCertInfo->extendKeyUsage.critical)
+ {
+ tpPolicyError("tp_verifyTimeStampingOpts: extended key usage !critical");
+ tpCert->addStatusCode(CSSMERR_APPLETP_EXT_KEYUSAGE_NOT_CRITICAL);
+ return CSSMERR_APPLETP_EXT_KEYUSAGE_NOT_CRITICAL;
+ }
+
+ eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage;
+ assert(eku != NULL);
+
+ if(eku->numPurposes != 1)
+ {
+ tpPolicyError("tp_verifyTimeStampingOpts: bad eku->numPurposes (%lu)",
+ (unsigned long)eku->numPurposes);
+ tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE);
+ return CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+
+ if(!tpCompareOids(&eku->purposes[0], &CSSMOID_TimeStamping))
+ {
+ tpPolicyError("tp_verifyTimeStampingOpts: TimeStamping purpose not found");
+ tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE);
+ return CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ }
+
+ return CSSM_OK;
+}
+
+/*
+ * Verify Passbook Signing policy options.
+ *
+ * -- Do basic cert validation (OCSP-based certs)
+ * -- Chains to the Apple root CA
+ * -- Has custom marker extension (1.2.840.113635.100.6.1.16)
+ * (CSSMOID_APPLE_EXTENSION_PASSBOOK_SIGNING)
+ * -- EKU contains Passbook Signing purpose (1.2.840.113635.100.4.14)
+ * (CSSMOID_APPLE_EKU_PASSBOOK_SIGNING)
+ * -- UID field of Subject must contain provided card signer string
+ * -- OU field of Subject must contain provided team identifier string
+ */
+static CSSM_RETURN tp_verifyPassbookSigningOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts,
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ unsigned numCerts = certGroup.numCerts();
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+ CE_ExtendedKeyUsage *eku;
+ CSSM_RETURN crtn = CSSM_OK;
+ unsigned int nameLen = 0;
+ const char *name = NULL;
+ char *p, *signerName = NULL, *teamIdentifier = NULL;
+ bool found;
+
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+
+ /* The CSSM_APPLE_TP_SMIME_OPTIONS pointer is required. */
+ if (!fieldOpts || !fieldOpts->Data)
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ else {
+ CSSM_APPLE_TP_SMIME_OPTIONS *opts = (CSSM_APPLE_TP_SMIME_OPTIONS *)fieldOpts->Data;
+ switch (opts->Version)
+ {
+ case CSSM_APPLE_TP_SMIME_OPTS_VERSION:
+ if (fieldOpts->Length != sizeof(CSSM_APPLE_TP_SMIME_OPTIONS))
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ break;
+ /* handle backwards compatibility here if necessary */
+ default:
+ return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
+ }
+ nameLen = opts->SenderEmailLen;
+ name = opts->SenderEmail;
+ if (!name || !nameLen)
+ return CSSMERR_APPLETP_IDENTIFIER_MISSING;
+ }
+
+ /* Split the provided name into signer name and team identifier
+ * (allocates memory, which must be freed at end) */
+ signerName = (char *)certGroup.alloc().malloc(nameLen);
+ teamIdentifier = (char *)certGroup.alloc().malloc(nameLen);
+ memmove(signerName, name, nameLen);
+ teamIdentifier[0] = '\0';
+ if ((p = strchr(signerName, '\t')) != NULL) {
+ *p++ = '\0';
+ memmove(teamIdentifier, p, strlen(p)+1);
+ }
+
+ /* Check signer name in UID field */
+ if (CSSM_FALSE == tpCompareSubjectName(*tpCert,
+ SN_UserID, false, signerName, (unsigned int)strlen(signerName), found)) {
+ tpPolicyError("tp_verifyPassbookSigningOpts: signer name not in subject UID field");
+ tpCert->addStatusCode(CSSMERR_APPLETP_IDENTIFIER_MISSING);
+ crtn = CSSMERR_APPLETP_IDENTIFIER_MISSING;
+ goto cleanup;
+ }
+
+ /* Check team identifier in OU field */
+ if (CSSM_FALSE == tpCompareSubjectName(*tpCert,
+ SN_OrgUnit, false, teamIdentifier, (unsigned int)strlen(teamIdentifier), found)) {
+ tpPolicyError("tp_verifyPassbookSigningOpts: team identifier not in subject OU field");
+ tpCert->addStatusCode(CSSMERR_APPLETP_IDENTIFIER_MISSING);
+ crtn = CSSMERR_APPLETP_IDENTIFIER_MISSING;
+ goto cleanup;
+ }
+
+ /* Check that EKU extension is present */
+ if (!isCertInfo->extendKeyUsage.present) {
+ tpPolicyError("tp_verifyPassbookSigningOpts: no extendedKeyUse in leaf");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ crtn = CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ goto cleanup;
+ }
+
+ /* Check that EKU contains Passbook Signing purpose */
+ eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage;
+ assert(eku != NULL);
+ found = false;
+ for (int ix=0;ix<eku->numPurposes;ix++) {
+ if (tpCompareOids(&eku->purposes[ix], &CSSMOID_APPLE_EKU_PASSBOOK_SIGNING)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ tpPolicyError("tp_verifyPassbookSigningOpts: Passbook Signing purpose not found");
+ tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE);
+ crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ goto cleanup;
+ }
+
+ /* Check that Passbook Signing marker extension is present */
+ if (!(isCertInfo->foundPassbookSigning == CSSM_TRUE)) {
+ tpPolicyError("tp_verifyPassbookSigningOpts: no Passbook Signing extension in leaf");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ crtn = CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ goto cleanup;
+ }
+
+ /* Check that cert chain is anchored by the Apple Root CA */
+ if (numCerts < 3) {
+ tpPolicyError("tp_verifyPassbookSigningOpts: numCerts %u", numCerts);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+ else {
+ tpCert = certGroup.certAtIndex(numCerts-1);
+ const CSSM_DATA *certData = tpCert->itemData();
+ unsigned char digest[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(certData->Data, (CC_LONG)certData->Length, digest);
+ if (memcmp(digest, kAppleCASHA1, sizeof(digest))) {
+ tpPolicyError("tp_verifyPassbookSigningOpts: invalid anchor for policy");
+ tpCert->addStatusCode(CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ if (signerName)
+ certGroup.alloc().free(signerName);
+ if (teamIdentifier)
+ certGroup.alloc().free(teamIdentifier);
+
+ return crtn;
+}
+
+/*
+ * Verify Mobile Store policy options.
+ *
+ * -- Do basic cert validation.
+ * -- Chain length must be exactly 3.
+ * -- Must chain to known Mobile Store root.
+ * -- Intermediate must have CSSMOID_APPLE_EXTENSION_SYSINT2_INTERMEDIATE marker
+ * (1.2.840.113635.100.6.2.10)
+ * -- Key usage in leaf certificate must be Digital Signature.
+ * -- Leaf has certificatePolicies extension with appropriate policy:
+ * (1.2.840.113635.100.5.12) if testPolicy is false
+ * (1.2.840.113635.100.5.12.1) if testPolicy is true
+ */
+static CSSM_RETURN tp_verifyMobileStoreSigningOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts,
+ const iSignCertInfo *certInfo, // all certs, size certGroup.numCerts()
+ bool testPolicy)
+{
+ unsigned numCerts = certGroup.numCerts();
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+ CE_KeyUsage ku;
+ CSSM_RETURN crtn = CSSM_OK;
+
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+
+ /* Check that KU extension is present */
+ if (!isCertInfo->keyUsage.present) {
+ tpPolicyError("tp_verifyMobileStoreSigningOpts: no keyUsage in leaf");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ crtn = CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ goto cleanup;
+ }
+
+ /* Check that KU contains Digital Signature usage */
+ ku = isCertInfo->keyUsage.extnData->keyUsage;
+ if (!(ku & CE_KU_DigitalSignature)) {
+ tpPolicyError("tp_verifyMobileStoreSigningOpts: DigitalSignature usage not found");
+ tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE);
+ crtn = CSSMERR_APPLETP_INVALID_KEY_USAGE;
+ goto cleanup;
+ }
+
+ /* Check that Mobile Store Signing certicate policy is present in leaf */
+ if (isCertInfo->certificatePolicies.present)
+ {
+ const CE_CertPolicies *certPolicies =
+ &isCertInfo->certificatePolicies.extnData->certPolicies;
+ const CSSM_OID *policyOID = (testPolicy) ?
+ &CSSMOID_TEST_MOBILE_STORE_SIGNING_POLICY :
+ &CSSMOID_MOBILE_STORE_SIGNING_POLICY;
+ if (!certificatePoliciesContainsOID(certPolicies, policyOID))
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION))
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+ else
+ {
+ tpPolicyError("tp_verifyMobileStoreSigningOpts: no certificatePolicies present in leaf");
+ if (tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION))
+ return CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ }
+
+ /* Check that cert chain length is 3 */
+ if (numCerts != 3) {
+ tpPolicyError("tp_verifyMobileStoreSigningOpts: numCerts %u", numCerts);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+
+ /* Check that cert chain is anchored by a known root */
+ {
+ tpCert = certGroup.certAtIndex(numCerts-1);
+ const CSSM_DATA *certData = tpCert->itemData();
+ unsigned char digest[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(certData->Data, (CC_LONG)certData->Length, digest);
+ if (memcmp(digest, kMobileRootSHA1, sizeof(digest))) {
+ tpPolicyError("tp_verifyMobileStoreSigningOpts: invalid anchor for policy");
+ tpCert->addStatusCode(CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+ }
+
+ /* Check that Apple System Integration 2 marker extension is present in intermediate */
+ isCertInfo = &certInfo[1];
+ tpCert = certGroup.certAtIndex(1);
+ if (!(isCertInfo->foundAppleSysInt2Marker == CSSM_TRUE)) {
+ tpPolicyError("tp_verifyMobileStoreSigningOpts: intermediate marker extension not found");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ crtn = CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ goto cleanup;
+ }
+
+cleanup:
+ return crtn;
+}
+
+/*
+ * Verify Escrow Service policy options.
+ *
+ * -- Chain length must be exactly 2.
+ * -- Must be issued by known escrow root.
+ * -- Key usage in leaf certificate must be Key Encipherment.
+ * -- Leaf has CSSMOID_APPLE_EXTENSION_ESCROW_SERVICE_MARKER extension
+ * (1.2.840.113635.100.6.23.1)
+ */
+static CSSM_RETURN tp_verifyEscrowServiceCommon(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts,
+ const iSignCertInfo *certInfo, // all certs, size certGroup.numCerts()
+ SecCertificateEscrowRootType rootType)
+{
+ unsigned numCerts = certGroup.numCerts();
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+ CE_KeyUsage ku;
+ CSSM_RETURN crtn = CSSM_OK;
+
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+
+ /* Check that KU extension is present */
+ if (!isCertInfo->keyUsage.present) {
+ tpPolicyError("tp_verifyEscrowServiceCommon: no keyUsage in leaf");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ crtn = CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ goto cleanup;
+ }
+
+ /* Check that KU contains Key Encipherment usage */
+ ku = isCertInfo->keyUsage.extnData->keyUsage;
+ if (!(ku & CE_KU_KeyEncipherment)) {
+ tpPolicyError("tp_verifyEscrowServiceCommon: KeyEncipherment usage not found");
+ tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE);
+ crtn = CSSMERR_APPLETP_INVALID_KEY_USAGE;
+ goto cleanup;
+ }
+
+ /* Check that Escrow Service marker extension is present */
+ if (!(isCertInfo->foundEscrowServiceMarker == CSSM_TRUE)) {
+ tpPolicyError("tp_verifyEscrowServiceCommon: no Escrow Service extension in leaf");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ crtn = CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ goto cleanup;
+ }
+
+ /* Check that cert chain length is 2 */
+ if (numCerts != 2) {
+ tpPolicyError("tp_verifyEscrowServiceCommon: numCerts %u", numCerts);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+
+ /* Check that cert chain is anchored by a known root */
+ {
+ tpCert = certGroup.certAtIndex(numCerts-1);
+ const CSSM_DATA *certData = tpCert->itemData();
+ bool anchorMatch = false;
+ SecCertificateRef anchor = NULL;
+ OSStatus status = SecCertificateCreateFromData(certData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &anchor);
+ if (!status) {
+ CFArrayRef anchors = SecCertificateCopyEscrowRoots(rootType);
+ CFIndex idx, count = (anchors) ? CFArrayGetCount(anchors) : 0;
+ for (idx = 0; idx < count; idx++) {
+ SecCertificateRef cert = (SecCertificateRef) CFArrayGetValueAtIndex(anchors, idx);
+ if (cert && CFEqual(cert, anchor)) {
+ anchorMatch = true;
+ break;
+ }
+ }
+ if (anchors)
+ CFRelease(anchors);
+ }
+ if (anchor)
+ CFRelease(anchor);
+
+ if (!anchorMatch) {
+ tpPolicyError("tp_verifyEscrowServiceCommon: invalid anchor for policy");
+ tpCert->addStatusCode(CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ return crtn;
+}
+
+static CSSM_RETURN tp_verifyEscrowServiceSigningOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts,
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ return tp_verifyEscrowServiceCommon(certGroup, fieldOpts, certInfo, kSecCertificateProductionEscrowRoot);
+}
+
+static CSSM_RETURN tp_verifyPCSEscrowServiceSigningOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts,
+ const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts()
+{
+ return tp_verifyEscrowServiceCommon(certGroup, fieldOpts, certInfo, kSecCertificateProductionPCSEscrowRoot);
+}
+
+/*
+ * Verify Configuration Profile Signing policy options.
+ *
+ * -- Do basic cert validation (OCSP-based certs)
+ * -- Chains to the Apple root CA
+ * -- Leaf has EKU extension with appropriate purpose:
+ * (1.2.840.113635.100.4.16) if testPolicy is false
+ * (1.2.840.113635.100.4.17) if testPolicy is true
+ */
+static CSSM_RETURN tp_verifyProfileSigningOpts(TPCertGroup &certGroup,
+ const CSSM_DATA *fieldOpts,
+ const iSignCertInfo *certInfo, // all certs, size certGroup.numCerts()
+ bool testPolicy)
+{
+ unsigned numCerts = certGroup.numCerts();
+ const iSignCertInfo *isCertInfo;
+ TPCertInfo *tpCert;
+ CE_ExtendedKeyUsage *eku;
+ CSSM_RETURN crtn = CSSM_OK;
+ bool found;
+
+ isCertInfo = &certInfo[0];
+ tpCert = certGroup.certAtIndex(0);
+
+ /* Check that EKU extension is present */
+ if (!isCertInfo->extendKeyUsage.present) {
+ tpPolicyError("tp_verifyProfileSigningOpts: no extendedKeyUse in leaf");
+ tpCert->addStatusCode(CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION);
+ crtn = CSSMERR_APPLETP_MISSING_REQUIRED_EXTENSION;
+ goto cleanup;
+ }
+
+ /* Check that EKU contains appropriate Profile Signing purpose */
+ eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage;
+ assert(eku != NULL);
+ found = false;
+ for (int ix=0;ix<eku->numPurposes;ix++) {
+ if (tpCompareOids(&eku->purposes[ix], (testPolicy) ?
+ &CSSMOID_APPLE_EKU_QA_PROFILE_SIGNING :
+ &CSSMOID_APPLE_EKU_PROFILE_SIGNING)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ tpPolicyError("tp_verifyProfileSigningOpts: Profile Signing purpose not found");
+ tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE);
+ crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE;
+ goto cleanup;
+ }
+
+ /* Check that cert chain is anchored by the Apple Root CA */
+ if (numCerts < 3) {
+ tpPolicyError("tp_verifyProfileSigningOpts: numCerts %u", numCerts);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+ else {
+ tpCert = certGroup.certAtIndex(numCerts-1);
+ const CSSM_DATA *certData = tpCert->itemData();
+ unsigned char digest[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(certData->Data, (CC_LONG)certData->Length, digest);
+ if (memcmp(digest, kAppleCASHA1, sizeof(digest))) {
+ tpPolicyError("tp_verifyProfileSigningOpts: invalid anchor for policy");
+ tpCert->addStatusCode(CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH);
+ crtn = CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ return crtn;
+}
+
+/*
+ * RFC2459 says basicConstraints must be flagged critical for
+ * CA certs, but Verisign doesn't work that way.
+ */
+#define BASIC_CONSTRAINTS_MUST_BE_CRITICAL 0
+
+/*
+ * TP iSign spec says Extended Key Usage required for leaf certs,
+ * but Verisign doesn't work that way.
+ */
+#define EXTENDED_KEY_USAGE_REQUIRED_FOR_LEAF 0
+
+/*
+ * TP iSign spec says Subject Alternate Name required for leaf certs,
+ * but Verisign doesn't work that way.
+ */
+#define SUBJECT_ALT_NAME_REQUIRED_FOR_LEAF 0
+
+/*
+ * TP iSign spec originally required KeyUsage for all certs, but
+ * Verisign doesn't have that in their roots.
+ */
+#define KEY_USAGE_REQUIRED_FOR_ROOT 0
+
+/*
+ * RFC 2632, "S/MIME Version 3 Certificate Handling", section
+ * 4.4.2, says that KeyUsage extensions MUST be flagged critical,
+ * but Thawte's intermediate cert (common name "Thawte Personal
+ * Freemail Issuing CA") does not meet this requirement.
+ */
+#define SMIME_KEY_USAGE_MUST_BE_CRITICAL 0
+
+/*
+ * Public routine to perform TP verification on a constructed
+ * cert group.
+ * Returns CSSM_OK on success.
+ * Assumes the chain has passed basic subject/issuer verification. First cert of
+ * incoming certGroup is end-entity (leaf).
+ *
+ * Per-policy details:
+ * iSign: Assumes that last cert in incoming certGroup is a root cert.
+ * Also assumes a cert group of more than one cert.
+ * kTPx509Basic: CertGroup of length one allowed.
+ */
+CSSM_RETURN tp_policyVerify(
+ TPPolicy policy,
+ Allocator &alloc,
+ CSSM_CL_HANDLE clHand,
+ CSSM_CSP_HANDLE cspHand,
+ TPCertGroup *certGroup,
+ CSSM_BOOL verifiedToRoot, // last cert is good root
+ CSSM_BOOL verifiedViaTrustSetting, // last cert verified via
+ // user trust
+ CSSM_APPLE_TP_ACTION_FLAGS actionFlags,
+ const CSSM_DATA *policyFieldData, // optional
+ void *policyOpts) // future options
+{
+ iSignCertInfo *certInfo = NULL;
+ uint32 numCerts;
+ iSignCertInfo *thisCertInfo;
+ uint16 expUsage;
+ uint16 actUsage;
+ unsigned certDex;
+ CSSM_BOOL cA = CSSM_FALSE; // init for compiler warning
+ bool isLeaf; // end entity
+ bool isRoot; // root cert
+ CE_ExtendedKeyUsage *extendUsage;
+ CE_AuthorityKeyID *authorityId;
+ CSSM_KEY_PTR pubKey;
+ CSSM_RETURN outErr = CSSM_OK; // for gross, non-policy errors
+ CSSM_BOOL policyFail = CSSM_FALSE;// generic CSSMERR_TP_VERIFY_ACTION_FAILED
+ CSSM_RETURN policyError = CSSM_OK; // policy-specific failure
+
+ /* First, kTPDefault is a nop here */
+ if(policy == kTPDefault) {
+ return CSSM_OK;
+ }
+
+ if(certGroup == NULL) {
+ return CSSMERR_TP_INVALID_CERTGROUP;
+ }
+ numCerts = certGroup->numCerts();
+ if(numCerts == 0) {
+ return CSSMERR_TP_INVALID_CERTGROUP;
+ }
+ if(policy == kTPiSign) {
+ if(!verifiedToRoot) {
+ /* no way, this requires a root cert */
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ if(numCerts <= 1) {
+ /* nope, not for iSign */
+ return CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+
+ /* cook up an iSignCertInfo array */
+ certInfo = (iSignCertInfo *)tpCalloc(alloc, numCerts, sizeof(iSignCertInfo));
+ /* subsequent errors to errOut: */
+
+ /* fill it with interesting info from parsed certs */
+ for(certDex=0; certDex<numCerts; certDex++) {
+ if(iSignGetCertInfo(alloc,
+ certGroup->certAtIndex(certDex),
+ &certInfo[certDex])) {
+ (certGroup->certAtIndex(certDex))->addStatusCode(
+ CSSMERR_TP_INVALID_CERTIFICATE);
+ /* this one is fatal (and can't ignore) */
+ outErr = CSSMERR_TP_INVALID_CERTIFICATE;
+ goto errOut;
+ }
+ }
+
+ /*
+ * OK, the heart of TP enforcement.
+ */
+ for(certDex=0; certDex<numCerts; certDex++) {
+ thisCertInfo = &certInfo[certDex];
+ TPCertInfo *thisTpCertInfo = certGroup->certAtIndex(certDex);
+
+ /*
+ * First check for presence of required extensions and
+ * critical extensions we don't understand.
+ */
+ if(thisCertInfo->foundUnknownCritical) {
+ /* illegal for all policies */
+ tpPolicyError("tp_policyVerify: critical flag in unknown extension");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_UNKNOWN_CRITICAL_EXTEN)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+
+ /*
+ * Check for unsupported key length, per <rdar://6892837>
+ */
+ if((pubKey=thisTpCertInfo->pubKey()) != NULL) {
+ CSSM_KEYHEADER *keyHdr = &pubKey->KeyHeader;
+ if(keyHdr->AlgorithmId == CSSM_ALGID_RSA && keyHdr->LogicalKeySizeInBits < 1024) {
+ tpPolicyError("tp_policyVerify: RSA key size too small");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_CSP_UNSUPPORTED_KEY_SIZE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /*
+ * Note it's possible for both of these to be true, for a chain
+ * of length one (kTPx509Basic, kCrlPolicy only!)
+ * FIXME: should this code work if the last cert in the chain is NOT a root?
+ */
+ isLeaf = thisTpCertInfo->isLeaf();
+ isRoot = thisTpCertInfo->isSelfSigned(true);
+
+ /*
+ * BasicConstraints.cA
+ * iSign: required in all but leaf and root,
+ * for which it is optional (with default values of false
+ * for leaf and true for root).
+ * all others: always optional, default of false for leaf and
+ * true for others
+ * All: cA must be false for leaf, true for others
+ */
+ if(!thisCertInfo->basicConstraints.present) {
+ /*
+ * No basicConstraints present; infer a cA value if appropriate.
+ */
+ if(isLeaf) {
+ /* cool, use default; note that kTPx509Basic with
+ * certGroup length of one may take this case */
+ cA = CSSM_FALSE;
+ }
+ else if(isRoot) {
+ /* cool, use default */
+ cA = CSSM_TRUE;
+ }
+ else {
+ switch(policy) {
+ default:
+ /*
+ * not present, not leaf, not root....
+ * ....RFC2459 says this can not be a CA
+ */
+ cA = CSSM_FALSE;
+ break;
+ case kTPiSign:
+ /* required for iSign in this position */
+ tpPolicyError("tp_policyVerify: no "
+ "basicConstraints");
+ if(thisTpCertInfo->addStatusCode(
+ CSSMERR_APPLETP_NO_BASIC_CONSTRAINTS)) {
+ policyFail = CSSM_TRUE;
+ }
+ break;
+ }
+ }
+ } /* inferred a default value */
+ else {
+ /* basicConstraints present */
+ #if BASIC_CONSTRAINTS_MUST_BE_CRITICAL
+ /* disabled for verisign compatibility */
+ if(!thisCertInfo->basicConstraints.critical) {
+ /* per RFC 2459 */
+ tpPolicyError("tp_policyVerify: basicConstraints marked "
+ "not critical");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_TP_VERIFY_ACTION_FAILED)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ #endif /* BASIC_CONSTRAINTS_MUST_BE_CRITICAL */
+
+ const CE_BasicConstraints *bcp =
+ &thisCertInfo->basicConstraints.extnData->basicConstraints;
+
+ cA = bcp->cA;
+
+ /* Verify pathLenConstraint if present */
+ if(!isLeaf && // leaf, certDex=0, don't care
+ cA && // p.l.c. only valid for CAs
+ bcp->pathLenConstraintPresent) { // present?
+ /*
+ * pathLenConstraint=0 legal for certDex 1 only
+ * pathLenConstraint=1 legal for certDex {1,2}
+ * etc.
+ */
+ if(certDex > (bcp->pathLenConstraint + 1)) {
+ tpPolicyError("tp_policyVerify: pathLenConstraint "
+ "exceeded");
+ if(thisTpCertInfo->addStatusCode(
+ CSSMERR_APPLETP_PATH_LEN_CONSTRAINT)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+ }
+
+ if(isLeaf) {
+ /*
+ * Special cases to allow a chain of length 1, leaf and root
+ * both true, and for caller to override the "leaf can't be a CA"
+ * requirement when a CA cert is explicitly being evaluated as the
+ * leaf.
+ */
+ if(cA && !isRoot &&
+ !(actionFlags & CSSM_TP_ACTION_LEAF_IS_CA)) {
+ tpPolicyError("tp_policyVerify: cA true for leaf");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_CA)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ } else if(!cA) {
+ tpPolicyError("tp_policyVerify: cA false for non-leaf");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_CA)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+
+ /*
+ * Authority Key Identifier optional
+ * iSign : only allowed in !root.
+ * If present, must not be critical.
+ * all others : ignored (though used later for chain verification)
+ */
+ if((policy == kTPiSign) && thisCertInfo->authorityId.present) {
+ if(isRoot) {
+ tpPolicyError("tp_policyVerify: authorityId in root");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ if(thisCertInfo->authorityId.critical) {
+ /* illegal per RFC 2459 */
+ tpPolicyError("tp_policyVerify: authorityId marked "
+ "critical");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /*
+ * Subject Key Identifier optional
+ * iSign : can't be critical.
+ * all others : ignored (though used later for chain verification)
+ */
+ if(thisCertInfo->subjectId.present) {
+ if((policy == kTPiSign) && thisCertInfo->subjectId.critical) {
+ tpPolicyError("tp_policyVerify: subjectId marked critical");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_SUBJECT_ID)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /*
+ * Key Usage optional except required as noted
+ * iSign : required for non-root/non-leaf
+ * Leaf cert : if present, usage = digitalSignature
+ * Exception : if leaf, and keyUsage not present,
+ * netscape-cert-type must be present, with
+ * Object Signing bit set
+ * kCrlPolicy : Leaf: usage = CRLSign
+ * kTP_SMIME : if present, must be critical
+ * kTP_SWUpdateSign, kTP_ResourceSign, kTP_CodeSigning, kTP_PackageSigning : Leaf :
+ usage = digitalSignature
+ * all others : non-leaf : usage = keyCertSign
+ * Leaf : don't care
+ */
+ if(thisCertInfo->keyUsage.present) {
+ /*
+ * Leaf cert:
+ * iSign and *Signing: usage = digitalSignature
+ * all others : don't care
+ * Others: usage = keyCertSign
+ * We only require that one bit to be set, we ignore others.
+ */
+ if(isLeaf) {
+ switch(policy) {
+ case kTPiSign:
+ case kTP_SWUpdateSign:
+ case kTP_ResourceSign:
+ case kTP_CodeSigning:
+ case kTP_PackageSigning:
+ expUsage = CE_KU_DigitalSignature;
+ break;
+ case kCrlPolicy:
+ /* if present, this bit must be set */
+ expUsage = CE_KU_CRLSign;
+ break;
+ default:
+ /* accept whatever's there */
+ expUsage = thisCertInfo->keyUsage.extnData->keyUsage;
+ break;
+ }
+ }
+ else {
+ /* !leaf: this is true for all policies */
+ expUsage = CE_KU_KeyCertSign;
+ }
+ actUsage = thisCertInfo->keyUsage.extnData->keyUsage;
+ if(!(actUsage & expUsage)) {
+ tpPolicyError("tp_policyVerify: bad keyUsage (leaf %s; "
+ "usage 0x%x)",
+ (certDex == 0) ? "TRUE" : "FALSE", actUsage);
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+
+ #if 0
+ /*
+ * Radar 3523221 renders this whole check obsolete, but I'm leaving
+ * the code here to document its conspicuous functional absence.
+ */
+ if((policy == kTP_SMIME) && !thisCertInfo->keyUsage.critical) {
+ /*
+ * Per Radar 3410245, allow this for intermediate certs.
+ */
+ if(SMIME_KEY_USAGE_MUST_BE_CRITICAL || isLeaf || isRoot) {
+ tpPolicyError("tp_policyVerify: key usage, !critical, SMIME");
+ if(thisTpCertInfo->addStatusCode(
+ CSSMERR_APPLETP_SMIME_KEYUSAGE_NOT_CRITICAL)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+ #endif
+ }
+ else if(policy == kTPiSign) {
+ /*
+ * iSign requires keyUsage present for non root OR
+ * netscape-cert-type/ObjectSigning for leaf
+ */
+ if(isLeaf && thisCertInfo->netscapeCertType.present) {
+ CE_NetscapeCertType ct =
+ thisCertInfo->netscapeCertType.extnData->netscapeCertType;
+
+ if(!(ct & CE_NCT_ObjSign)) {
+ tpPolicyError("tp_policyVerify: netscape-cert-type, "
+ "!ObjectSign");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+ else if(!isRoot) {
+ tpPolicyError("tp_policyVerify: !isRoot, no keyUsage, "
+ "!(leaf and netscapeCertType)");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /*
+ * RFC 3280, 4.1.2.6, says that an empty subject name can only appear in a
+ * leaf cert, and only if subjectAltName is present and marked critical.
+ */
+ if(isLeaf && thisTpCertInfo->hasEmptySubjectName()) {
+ bool badEmptySubject = false;
+ if(actionFlags & CSSM_TP_ACTION_LEAF_IS_CA) {
+ /*
+ * True when evaluating a CA cert as well as when
+ * evaluating a CRL's cert chain. Note the odd case of a CRL's
+ * signer having an empty subject matching an empty issuer
+ * in the CRL. That'll be caught here.
+ */
+ badEmptySubject = true;
+ }
+ else if(!thisCertInfo->subjectAltName.present || /* no subjectAltName */
+ !thisCertInfo->subjectAltName.critical) { /* not critical */
+ badEmptySubject = true;
+ }
+ if(badEmptySubject) {
+ tpPolicyError("tp_policyVerify: bad empty subject");
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_EMPTY_SUBJECT)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /*
+ * RFC 3739: if this cert has a Qualified Cert Statements extension, and
+ * it's Critical, make sure we understand all of the extension's statementIds.
+ */
+ if(thisCertInfo->qualCertStatements.present &&
+ thisCertInfo->qualCertStatements.critical) {
+ CE_QC_Statements *qcss =
+ &thisCertInfo->qualCertStatements.extnData->qualifiedCertStatements;
+ uint32 numQcs = qcss->numQCStatements;
+ for(unsigned qdex=0; qdex<numQcs; qdex++) {
+ CSSM_OID_PTR qid = &qcss->qcStatements[qdex].statementId;
+ bool ok = false;
+ for(unsigned kdex=0; kdex<NUM_KNOWN_QUAL_CERT_STATEMENTS; kdex++) {
+ if(tpCompareCssmData(qid, knownQualifiedCertStatements[kdex])) {
+ ok = true;
+ break;
+ }
+ }
+ if(!ok) {
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_UNKNOWN_QUAL_CERT_STATEMENT)) {
+ policyFail = CSSM_TRUE;
+ break;
+ }
+ }
+ }
+ } /* critical Qualified Cert Statement */
+
+ /*
+ * Certificate Policies extension validation, per section 1.2 of:
+ * http://iase.disa.mil/pki/dod_cp_v10_final_2_mar_09_signed.pdf
+ */
+ if (tpVerifyCPE(*thisCertInfo, CSSMOID_PIV_AUTH, false) ||
+ tpVerifyCPE(*thisCertInfo, CSSMOID_PIV_AUTH_2048, false)) {
+ /*
+ * Certificate asserts one of the PIV-Auth Certificate Policy OIDs;
+ * check the required Key Usage extension for compliance.
+ *
+ * Leaf cert:
+ * usage = digitalSignature (only; no other bits asserted)
+ * Others:
+ * usage = keyCertSign (required; other bits ignored)
+ */
+ if(thisCertInfo->keyUsage.present) {
+ actUsage = thisCertInfo->keyUsage.extnData->keyUsage;
+ } else {
+ /* No key usage! Policy fail. */
+ actUsage = 0;
+ }
+ if(!(actionFlags & CSSM_TP_ACTION_LEAF_IS_CA) && (certDex == 0)) {
+ expUsage = CE_KU_DigitalSignature;
+ } else {
+ expUsage = actUsage | CE_KU_KeyCertSign;
+ }
+ if(!(actUsage == expUsage)) {
+ tpPolicyError("tp_policyVerify: bad keyUsage for PIV-Auth policy (leaf %s; "
+ "usage 0x%x)",
+ (certDex == 0) ? "TRUE" : "FALSE", actUsage);
+ if(thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ } /* Certificate Policies */
+
+
+ } /* for certDex, checking presence of extensions */
+
+ /*
+ * Special case checking for leaf (end entity) cert
+ *
+ * iSign only: Extended key usage, optional for leaf,
+ * value CSSMOID_ExtendedUseCodeSigning
+ */
+ if((policy == kTPiSign) && certInfo[0].extendKeyUsage.present) {
+ extendUsage = &certInfo[0].extendKeyUsage.extnData->extendedKeyUsage;
+ if(extendUsage->numPurposes != 1) {
+ tpPolicyError("tp_policyVerify: bad extendUsage->numPurposes "
+ "(%d)",
+ (int)extendUsage->numPurposes);
+ if((certGroup->certAtIndex(0))->addStatusCode(
+ CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ if(!tpCompareOids(extendUsage->purposes,
+ &CSSMOID_ExtendedUseCodeSigning)) {
+ tpPolicyError("tp_policyVerify: bad extendKeyUsage");
+ if((certGroup->certAtIndex(0))->addStatusCode(
+ CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /*
+ * Verify authorityId-->subjectId linkage.
+ * All optional - skip if needed fields not present.
+ * Also, always skip last (root) cert.
+ */
+ for(certDex=0; certDex<(numCerts-1); certDex++) {
+ if(!certInfo[certDex].authorityId.present ||
+ !certInfo[certDex+1].subjectId.present) {
+ continue;
+ }
+ authorityId = &certInfo[certDex].authorityId.extnData->authorityKeyID;
+ if(!authorityId->keyIdentifierPresent) {
+ /* we only know how to compare keyIdentifier */
+ continue;
+ }
+ if(!tpCompareCssmData(&authorityId->keyIdentifier,
+ &certInfo[certDex+1].subjectId.extnData->subjectKeyID)) {
+ tpPolicyError("tp_policyVerify: bad key ID linkage");
+ if((certGroup->certAtIndex(certDex))->addStatusCode(
+ CSSMERR_APPLETP_INVALID_ID_LINKAGE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /*
+ * Check signature algorithm on all non-root certs,
+ * reject if known to be untrusted
+ */
+ for(certDex=0; certDex<(numCerts-1); certDex++) {
+ if(certInfo[certDex].untrustedSigAlg) {
+ tpPolicyError("tp_policyVerify: untrusted signature algorithm");
+ if((certGroup->certAtIndex(certDex))->addStatusCode(
+ CSSMERR_TP_INVALID_CERTIFICATE)) {
+ policyFail = CSSM_TRUE;
+ }
+ }
+ }
+
+ /* specific per-policy checking */
+ switch(policy) {
+ case kTP_SSL:
+ case kTP_EAP:
+ case kTP_IPSec:
+ /*
+ * SSL, EAP, IPSec: optionally verify common name; all are identical
+ * other than their names.
+ * FIXME - should this be before or after the root cert test? How can
+ * we return both errors?
+ */
+ policyError = tp_verifySslOpts(policy, *certGroup, policyFieldData, certInfo);
+ break;
+
+ case kTP_iChat:
+ tpDebug("iChat policy");
+ /* fall thru */
+ case kTP_SMIME:
+ policyError = tp_verifySmimeOpts(policy, *certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_SWUpdateSign:
+ policyError = tp_verifySWUpdateSigningOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_ResourceSign:
+ policyError = tp_verifyResourceSigningOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_CodeSigning:
+ case kTP_PackageSigning:
+ policyError = tp_verifyCodePkgSignOpts(policy, *certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_MacAppStoreRec:
+ policyError = tp_verifyMacAppStoreReceiptOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_AppleIDSharing:
+ policyError = tp_verifyAppleIDSharingOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_TimeStamping:
+ policyError = tp_verifyTimeStampingOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_PassbookSigning:
+ policyError = tp_verifyPassbookSigningOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_MobileStore:
+ policyError = tp_verifyMobileStoreSigningOpts(*certGroup, policyFieldData, certInfo, false);
+ break;
+ case kTP_TestMobileStore:
+ policyError = tp_verifyMobileStoreSigningOpts(*certGroup, policyFieldData, certInfo, true);
+ break;
+ case kTP_EscrowService:
+ policyError = tp_verifyEscrowServiceSigningOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTP_ProfileSigning:
+ policyError = tp_verifyProfileSigningOpts(*certGroup, policyFieldData, certInfo, false);
+ break;
+ case kTP_QAProfileSigning:
+ policyError = tp_verifyProfileSigningOpts(*certGroup, policyFieldData, certInfo, true);
+ break;
+ case kTP_PCSEscrowService:
+ policyError = tp_verifyPCSEscrowServiceSigningOpts(*certGroup, policyFieldData, certInfo);
+ break;
+ case kTPx509Basic:
+ case kTPiSign:
+ case kCrlPolicy:
+ case kTP_PKINIT_Client:
+ default:
+ break;
+
+ }
+
+ if(outErr == CSSM_OK) {
+ /* policy-specific error takes precedence here */
+ if(policyError != CSSM_OK) {
+ outErr = policyError;
+ }
+ else if(policyFail) {
+ /* plain vanilla error return from this module */
+ outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
+ }
+ }
+errOut:
+ /* free resources */
+ for(certDex=0; certDex<numCerts; certDex++) {
+ thisCertInfo = &certInfo[certDex];
+ iSignFreeCertInfo(clHand, thisCertInfo);
+ }
+ tpFree(alloc, certInfo);
+ return outErr;
+}
+
+/*
+ * Obtain policy-specific User Trust parameters
+ */
+void tp_policyTrustSettingParams(
+ TPPolicy policy,
+ const CSSM_DATA *policyData, // optional
+ /* returned values - not mallocd */
+ const char **policyStr,
+ uint32 *policyStrLen,
+ SecTrustSettingsKeyUsage *keyUse)
+{
+ /* default values */
+ *policyStr = NULL;
+ *keyUse = kSecTrustSettingsKeyUseAny;
+
+ if((policyData == NULL) || (policyData->Data == NULL)) {
+ /* currently, no further action possible */
+ return;
+ }
+ switch(policy) {
+ case kTP_SSL:
+ case kTP_EAP:
+ case kTP_IPSec:
+ {
+ if(policyData->Length != sizeof(CSSM_APPLE_TP_SSL_OPTIONS)) {
+ /* this error will be caught later */
+ return;
+ }
+ CSSM_APPLE_TP_SSL_OPTIONS *sslOpts =
+ (CSSM_APPLE_TP_SSL_OPTIONS *)policyData->Data;
+ *policyStr = sslOpts->ServerName;
+ *policyStrLen = sslOpts->ServerNameLen;
+ if(sslOpts->Flags & CSSM_APPLE_TP_SSL_CLIENT) {
+ /*
+ * Client signs with its priv key. Server end,
+ * which (also) verifies the client cert, verifies.
+ */
+ *keyUse = kSecTrustSettingsKeyUseSignature;
+ }
+ else {
+ /* server decrypts */
+ *keyUse = kSecTrustSettingsKeyUseEnDecryptKey;
+ }
+ return;
+ }
+
+ case kTP_iChat:
+ case kTP_SMIME:
+ {
+ if(policyData->Length != sizeof(CSSM_APPLE_TP_SMIME_OPTIONS)) {
+ /* this error will be caught later */
+ return;
+ }
+ CSSM_APPLE_TP_SMIME_OPTIONS *smimeOpts =
+ (CSSM_APPLE_TP_SMIME_OPTIONS *)policyData->Data;
+ *policyStr = smimeOpts->SenderEmail;
+ *policyStrLen = smimeOpts->SenderEmailLen;
+ SecTrustSettingsKeyUsage ku = 0;
+ CE_KeyUsage smimeKu = smimeOpts->IntendedUsage;
+ if(smimeKu & (CE_KU_DigitalSignature | CE_KU_KeyCertSign | CE_KU_CRLSign)) {
+ ku |= kSecTrustSettingsKeyUseSignature;
+ }
+ if(smimeKu & (CE_KU_KeyEncipherment | CE_KU_DataEncipherment)) {
+ ku |= kSecTrustSettingsKeyUseEnDecryptKey;
+ }
+ *keyUse = ku;
+ return;
+ }
+
+ default:
+ /* no other options */
+ return;
+ }
+}
+
+#pragma clang diagnostic pop