예제 #1
0
/* 
 * Returns true if we are to allow/trust the specified
 * cert as a PKINIT-only anchor.
 */
static bool tpCheckPkinitServerCert(
	TPCertGroup &certGroup)
{
	/* 
	 * Basic requirement: exactly one cert, self-signed.
	 * The numCerts == 1 requirement might change...
	 */
	unsigned numCerts = certGroup.numCerts();
	if(numCerts != 1) {
		tpDebug("tpCheckPkinitServerCert: too many certs");
		return false;
	}
	/* end of chain... */
	TPCertInfo *theCert = certGroup.certAtIndex(numCerts - 1);
	if(!theCert->isSelfSigned()) {
		tpDebug("tpCheckPkinitServerCert: 1 cert, not self-signed");
		return false;
	}
	const CSSM_DATA *subjectName = theCert->subjectName();
	
	/* 
	 * Open the magic keychain.
	 * We're going up and over the Sec layer here, not generally 
	 * kosher, but this is a temp hack.
	 */
	OSStatus ortn;
	SecKeychainRef kcRef = NULL;
	string fullPathName;
	const char *homeDir = getenv("HOME");
	if (homeDir == NULL)
	{
		// If $HOME is unset get the current user's home directory
		// from the passwd file.
		uid_t uid = geteuid();
		if (!uid) uid = getuid();
		struct passwd *pw = getpwuid(uid);
		if (!pw) {
			return false;
		}
		homeDir = pw->pw_dir;
	}
	fullPathName = homeDir;
	fullPathName += "/Library/Application Support/PKINIT/TrustedServers.keychain";
	ortn = SecKeychainOpen(fullPathName.c_str(), &kcRef);
	if(ortn) {
		tpDebug("tpCheckPkinitServerCert: keychain not found (1)");
		return false;
	}
	/* subsequent errors to errOut: */
	
	bool ourRtn = false;
	SecKeychainStatus kcStatus;
	CSSM_DATA_PTR subjSerial = NULL;
	CSSM_RETURN crtn;
	SecKeychainSearchRef		srchRef = NULL;
	SecKeychainAttributeList	attrList;
	SecKeychainAttribute		attrs[2];
	SecKeychainItemRef			foundItem = NULL;
	
	ortn = SecKeychainGetStatus(kcRef, &kcStatus);
	if(ortn) {
		tpDebug("tpCheckPkinitServerCert: keychain not found (2)");
		goto errOut;
	}
	
	/*
	 * We already have this cert's normalized name; get its
	 * serial number.
	 */
	crtn = theCert->fetchField(&CSSMOID_X509V1SerialNumber, &subjSerial);
	if(crtn) {
		/* should never happen */
		tpDebug("tpCheckPkinitServerCert: error fetching serial number");
		goto errOut;
	}
	
	attrs[0].tag    = kSecSubjectItemAttr;
	attrs[0].length = subjectName->Length;
	attrs[0].data   = subjectName->Data;
	attrs[1].tag    = kSecSerialNumberItemAttr;
	attrs[1].length = subjSerial->Length;
	attrs[1].data   = subjSerial->Data;
	attrList.count  = 2;
	attrList.attr   = attrs;
	
	ortn = SecKeychainSearchCreateFromAttributes(kcRef,
		kSecCertificateItemClass,
		&attrList,
		&srchRef);
	if(ortn) {
		tpDebug("tpCheckPkinitServerCert: search failure");
		goto errOut;
	}
	for(;;) {
		ortn = SecKeychainSearchCopyNext(srchRef, &foundItem);
		if(ortn) {
			tpDebug("tpCheckPkinitServerCert: end search");
			break;
		}
		
		/* found a matching cert; do byte-for-byte compare */
		CSSM_DATA certData;
		ortn = SecCertificateGetData((SecCertificateRef)foundItem, &certData);
		if(ortn) {
			tpDebug("tpCheckPkinitServerCert: SecCertificateGetData failure");
			continue;
		}
		if(tpCompareCssmData(&certData, theCert->itemData())){
			tpDebug("tpCheckPkinitServerCert: FOUND CERT");
			ourRtn = true;
			break;
		}
		tpDebug("tpCheckPkinitServerCert: skipping matching cert");
		CFRelease(foundItem);
		foundItem = NULL;
	}
errOut:
	CFRELEASE(kcRef);
	CFRELEASE(srchRef);
	CFRELEASE(foundItem);
	if(subjSerial != NULL) {
		theCert->freeField(&CSSMOID_X509V1SerialNumber, subjSerial);
	}
	return ourRtn;
}
/*
 * Perform CRL verification on a cert group.
 * The cert group has already passed basic issuer/subject and signature
 * verification. The status of the incoming CRLs is completely unknown. 
 * 
 * FIXME - No mechanism to get CRLs from net with non-NULL verifyTime.
 * How are we supposed to get the CRL which was valid at a specified 
 * time in the past?
 */
CSSM_RETURN tpVerifyCertGroupWithCrls(
	TPVerifyContext					&vfyCtx,
	TPCertGroup 					&certGroup)		// to be verified 
{
	CSSM_RETURN 	crtn;
	CSSM_RETURN		ourRtn = CSSM_OK;

	assert(vfyCtx.clHand != 0);
	assert(vfyCtx.policy == kRevokeCrlBasic);
	tpCrlDebug("tpVerifyCertGroupWithCrls numCerts %u", certGroup.numCerts());
	CSSM_DATA issuers = { 0, NULL };
	CSSM_APPLE_TP_CRL_OPT_FLAGS optFlags = 0;
	if(vfyCtx.crlOpts != NULL) {
		optFlags = vfyCtx.crlOpts->CrlFlags;
	}
	
	/* found & verified CRLs we need to release */
	TPCrlGroup foundCrls(vfyCtx.alloc, TGO_Caller);
	
	try {
		
		unsigned certDex;
		TPCrlInfo *crl = NULL;
		
		/* get issuers as PEM-encoded data blob; we need to release */
		certGroup.encodeIssuers(issuers);

		/* main loop, verify each cert */
		for(certDex=0; certDex<certGroup.numCerts(); certDex++) {
			TPCertInfo *cert = certGroup.certAtIndex(certDex);

			tpCrlDebug("...verifying %s cert %u", 
				cert->isAnchor() ? "anchor " : "", cert->index());
			if(cert->isSelfSigned() || cert->trustSettingsFound()) {
				/* CRL meaningless for a root or trusted cert */
				continue;
			}
			if(cert->revokeCheckComplete()) {
				/* Another revocation policy claimed that this cert is good to go */
				tpCrlDebug("   ...cert at index %u revokeCheckComplete; skipping", 
					cert->index());
				continue;
			}
			crl = NULL;
			do {
				/* first, see if we have CRL status available for this cert */
				crtn = tpGetCrlStatusForCert(*cert, issuers);
				tpCrlDebug("...tpGetCrlStatusForCert: %u", crtn);
				if(crtn == CSSM_OK) {
					tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u verified by local .crl\n",
								cert->index());
					cert->revokeCheckGood(true);
					if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
						/* no more revocation checking necessary for this cert */
						cert->revokeCheckComplete(true);
					}
					break;
				}
				if(crtn == CSSMERR_TP_CERT_REVOKED) {
					tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u revoked in local .crl\n",
								cert->index());
					cert->addStatusCode(crtn);
					break;
				}
				if(crtn == CSSMERR_APPLETP_NETWORK_FAILURE) {
					/* crl is being fetched from net, but we don't have it yet */
					if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) &&
								tpCertHasCrlDistPt(*cert)) {
						/* crl is required; we don't have it yet, so we fail */
						tpCrlDebug("   ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
								cert->index());
						break;
					}
					/* "Best Attempt" case, so give the cert a pass for now */
					tpCrlDebug("   ...cert %u: no CRL; tolerating", cert->index());
					crtn = CSSM_OK;
					break;
				}
				/* all other CRL status results: try to fetch the CRL */

				/* find a CRL for this cert by hook or crook */
				crtn = tpFindCrlForCert(*cert, crl, vfyCtx);
				if(crtn) {
					/* tpFindCrlForCert may have simply caused ocspd to start
					 * downloading a CRL asynchronously; depending on the speed
					 * of the network and the CRL size, this may return 0 bytes
					 * of data with a CSSMERR_APPLETP_NETWORK_FAILURE result.
					 * We won't know the actual revocation result until the
					 * next time we call tpGetCrlStatusForCert after the full
					 * CRL has been downloaded successfully.
					 */
					if(optFlags & CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT) {
						tpCrlDebug("   ...cert %u: REQUIRE_CRL_PER_CERT abort",
								cert->index());
						break;
					}
					if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) && 
								tpCertHasCrlDistPt(*cert)) {
						tpCrlDebug("   ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
								cert->index());
						break;
					}
					/* 
					 * This is the only place where "Best Attempt" tolerates an error
					 */
					tpCrlDebug("   ...cert %u: no CRL; tolerating", cert->index());
					crtn = CSSM_OK;
					assert(crl == NULL);
					break;
				}
				
				/* Keep track; we'll release all when done. */
				assert(crl != NULL);
				foundCrls.appendCrl(*crl);
				
				/* revoked? */
				crtn = crl->isCertRevoked(*cert, vfyCtx.verifyTime);
				if(crtn) {
					break;
				}
				tpCrlDebug("   ...cert %u VERIFIED by CRL", cert->index());
				cert->revokeCheckGood(true);
				if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
					/* no more revocation checking necessary for this cert */
					cert->revokeCheckComplete(true);
				}
			} while(0);
			
			/* done processing one cert */
			if(crtn) {
				tpCrlDebug("   ...cert at index %u FAILED crl vfy", 
					cert->index());
				if(ourRtn == CSSM_OK) {
					ourRtn = crtn;
				}
				/* continue on to next cert */
			}	/* error on one cert */
		}		/* for each cert */
	}
	catch(const CssmError &cerr) {
		if(ourRtn == CSSM_OK) {
			ourRtn = cerr.error;
		}
	}
	/* other exceptions fatal */

	/* release all found CRLs */
	for(unsigned dex=0; dex<foundCrls.numCrls(); dex++) {
		TPCrlInfo *crl = foundCrls.crlAtIndex(dex);
		assert(crl != NULL);
		tpDisposeCrl(*crl, vfyCtx);
	}
	/* release issuers */
	if(issuers.Data) {
		free(issuers.Data);
	}
	return ourRtn;
}
/*
 * Search a list of DBs for a cert which verifies specified subject item.
 * Just a boolean return - we found it, or not. If we did, we return
 * TPCertInfo associated with the raw cert.
 * A true partialIssuerKey on return indicates that caller must deal
 * with partial public key processing later.
 * If verifyCurrent is true, we will not return a cert which is not
 * temporally valid; else we may well do so.
 */
TPCertInfo *tpDbFindIssuerCert(
	Allocator				&alloc,
	CSSM_CL_HANDLE			clHand,
	CSSM_CSP_HANDLE			cspHand,
	const TPClItemInfo		*subjectItem,
	const CSSM_DL_DB_LIST	*dbList,
	const char 				*verifyTime,		// may be NULL
	bool					&partialIssuerKey)	// RETURNED
{
	StLock<Mutex> _(SecTrustKeychainsGetMutex());

	uint32						dbDex;
	CSSM_HANDLE					resultHand;
	CSSM_DATA					cert;
	CSSM_DL_DB_HANDLE			dlDb;
	CSSM_DB_UNIQUE_RECORD_PTR	record;
	TPCertInfo 					*issuerCert = NULL;
	bool 						foundIt;
	TPCertInfo					*expiredIssuer = NULL;
	TPCertInfo					*nonRootIssuer = NULL;

	partialIssuerKey = false;
	if(dbList == NULL) {
		return NULL;
	}
	for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
		dlDb = dbList->DLDBHandle[dbDex];
		cert.Data = NULL;
		cert.Length = 0;
		resultHand = 0;
		record = tpCertLookup(dlDb,
			subjectItem->issuerName(),
			&resultHand,
			&cert);
		/* remember we have to:
		 * -- abort this query regardless, and
		 * -- free the CSSM_DATA cert regardless, and
		 * -- free the unique record if we don't use it
		 *    (by placing it in issuerCert)...
		 */
		if(record != NULL) {
			/* Found one */
			assert(cert.Data != NULL);
			tpDbDebug("tpDbFindIssuerCert: found cert record (1) %p", record);
			issuerCert = NULL;
			CSSM_RETURN crtn = CSSM_OK;
			try {
				issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, verifyTime);
			}
			catch(...) {
				crtn = CSSMERR_TP_INVALID_CERTIFICATE;
			}

			/* we're done with raw cert data */
			tpFreePluginMemory(dlDb.DLHandle, cert.Data);
			cert.Data = NULL;
			cert.Length = 0;

			/* Does it verify the subject cert? */
			if(crtn == CSSM_OK) {
				crtn = subjectItem->verifyWithIssuer(issuerCert);
			}

			/*
			 * Handle temporal invalidity - if so and this is the first one
			 * we've seen, hold on to it while we search for better one.
			 */
			if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
				if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
					/*
					 * Exact value not important here, this just uniquely identifies
					 * this situation in the switch below.
					 */
					tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)");
					crtn = CSSM_CERT_STATUS_EXPIRED;
					expiredIssuer = issuerCert;
					expiredIssuer->dlDbHandle(dlDb);
					expiredIssuer->uniqueRecord(record);
				}
			}
			/*
			 * Prefer a root over an intermediate issuer if we can get one
			 * (in case a cross-signed intermediate and root are both available)
			 */
			if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
				if(!issuerCert->isSelfSigned()) {
					/*
					 * Exact value not important here, this just uniquely identifies
					 * this situation in the switch below.
					 */
					tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)");
					crtn = CSSM_CERT_STATUS_IS_ROOT;
					nonRootIssuer = issuerCert;
					nonRootIssuer->dlDbHandle(dlDb);
					nonRootIssuer->uniqueRecord(record);
				}
			}
			switch(crtn) {
				case CSSM_OK:
					break;
				case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
					partialIssuerKey = true;
					break;
				default:
					if(issuerCert != NULL) {
						/* either holding onto this cert, or done with it. */
						if(crtn != CSSM_CERT_STATUS_EXPIRED &&
						   crtn != CSSM_CERT_STATUS_IS_ROOT) {
							delete issuerCert;
							CSSM_DL_FreeUniqueRecord(dlDb, record);
						}
						issuerCert = NULL;
					}

					/*
					 * Continue searching this DB. Break on finding the holy
					 * grail or no more records found.
					 */
					for(;;) {
						cert.Data = NULL;
						cert.Length = 0;
						record = NULL;
						CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb,
							resultHand,
							NULL,		// no attrs
							&cert,
							&record);
						if(crtn) {
							/* no more, done with this DB */
							assert(cert.Data == NULL);
							break;
						}
						assert(cert.Data != NULL);
						tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record);

						/* found one - does it verify subject? */
						try {
							issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData,
									verifyTime);
						}
						catch(...) {
							crtn = CSSMERR_TP_INVALID_CERTIFICATE;
						}
						/* we're done with raw cert data */
						tpFreePluginMemory(dlDb.DLHandle, cert.Data);
						cert.Data = NULL;
						cert.Length = 0;

						if(crtn == CSSM_OK) {
							crtn = subjectItem->verifyWithIssuer(issuerCert);
						}

						/* temporal validity check, again */
						if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
							if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
								tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)");
								crtn = CSSM_CERT_STATUS_EXPIRED;
								expiredIssuer = issuerCert;
								expiredIssuer->dlDbHandle(dlDb);
								expiredIssuer->uniqueRecord(record);
							}
						}
						/* self-signed check, again */
						if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
							if(!issuerCert->isSelfSigned()) {
								tpDbDebug("tpDbFindIssuerCert: holding non-root cert (2)");
								crtn = CSSM_CERT_STATUS_IS_ROOT;
								nonRootIssuer = issuerCert;
								nonRootIssuer->dlDbHandle(dlDb);
								nonRootIssuer->uniqueRecord(record);
							}
						}

						foundIt = false;
						switch(crtn) {
							case CSSM_OK:
								foundIt = true;
								break;
							case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
								partialIssuerKey = true;
								foundIt = true;
								break;
							default:
								break;
						}
						if(foundIt) {
							/* yes! */
							break;
						}
						if(issuerCert != NULL) {
							/* either holding onto this cert, or done with it. */
							if(crtn != CSSM_CERT_STATUS_EXPIRED &&
							   crtn != CSSM_CERT_STATUS_IS_ROOT) {
								delete issuerCert;
								CSSM_DL_FreeUniqueRecord(dlDb, record);
							}
							issuerCert = NULL;
						}
					} /* searching subsequent records */
			}	/* switch verify */

			if(record != NULL) {
				/* NULL record --> end of search --> DB auto-aborted */
				crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand);
				assert(crtn == CSSM_OK);
			}
			if(issuerCert != NULL) {
				/* successful return */
				tpDbDebug("tpDbFindIssuer: returning record %p", record);
				issuerCert->dlDbHandle(dlDb);
				issuerCert->uniqueRecord(record);
				if(expiredIssuer != NULL) {
					/* We found a replacement */
					tpDbDebug("tpDbFindIssuer: discarding expired cert");
					expiredIssuer->freeUniqueRecord();
					delete expiredIssuer;
				}
				/* Avoid deleting the non-root cert if same as expired cert */
				if(nonRootIssuer != NULL && nonRootIssuer != expiredIssuer) {
					/* We found a replacement */
					tpDbDebug("tpDbFindIssuer: discarding non-root cert");
					nonRootIssuer->freeUniqueRecord();
					delete nonRootIssuer;
				}
				return issuerCert;
			}
		}	/* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
		else {
			assert(cert.Data == NULL);
			assert(resultHand == 0);
		}
	}	/* main loop searching dbList */

	if(nonRootIssuer != NULL) {
		/* didn't find root issuer, so use this one */
		tpDbDebug("tpDbFindIssuer: taking non-root issuer cert, record %p",
			nonRootIssuer->uniqueRecord());
		if(expiredIssuer != NULL && expiredIssuer != nonRootIssuer) {
			expiredIssuer->freeUniqueRecord();
			delete expiredIssuer;
		}
		return nonRootIssuer;
	}

	if(expiredIssuer != NULL) {
		/* OK, we'll take this one */
		tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p",
			expiredIssuer->uniqueRecord());
		return expiredIssuer;
	}
	/* issuer not found */
	return NULL;
}