X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/libsecurity_apple_x509_tp/lib/tpPolicies.cpp diff --git a/OSX/libsecurity_apple_x509_tp/lib/tpPolicies.cpp b/OSX/libsecurity_apple_x509_tp/lib/tpPolicies.cpp new file mode 100644 index 00000000..761dc806 --- /dev/null +++ b/OSX/libsecurity_apple_x509_tp/lib/tpPolicies.cpp @@ -0,0 +1,3366 @@ +/* + * 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 +#include +#include "tpPolicies.h" +#include +#include "tpdebugging.h" +#include "certGroupUtils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; iclHand(), + 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; rdnDexnumberOfRDNs; rdnDex++) { + rdnp = &x509name->RelativeDistinguishedName[rdnDex]; + for(pairDex=0; pairDexnumberOfPairs; 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; dexLength; dex++) { + /* cp points to start of current string digit */ + /* find next dot */ + const char *lastChar = cp + strLen; + nextDot = cp + 1; + for( ; nextDotLength - 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; dexnumNames; 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; ivalueType) { + 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; inumPurposes; 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; inumPolicies; 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; rdnDexnumberOfRDNs; rdnDex++) { + rdnp = &x509name->RelativeDistinguishedName[rdnDex]; + for(pairDex=0; pairDexnumberOfPairs; 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;idxaddStatusCode(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; inumPurposes; 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; inumPurposes; 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; inumPurposes; 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;ixnumPurposes;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;ixnumPurposes;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; certDexcertAtIndex(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; certDexcertAtIndex(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 + */ + 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; qdexqcStatements[qdex].statementId; + bool ok = false; + for(unsigned kdex=0; kdexaddStatusCode(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; certDexData == 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